0% found this document useful (0 votes)
2 views56 pages

MITS Python FullStack Ebook v2

MITS Academy offers a comprehensive course on Python Full Stack Development, focusing on Django, React, and PostgreSQL. The course covers the architecture, frameworks, and the HTTP request-response lifecycle, emphasizing the importance of understanding full-stack concepts. Additionally, it includes practical exercises and object-oriented programming principles to enhance students' skills in Django development.

Uploaded by

bhumikatalwar19
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views56 pages

MITS Python FullStack Ebook v2

MITS Academy offers a comprehensive course on Python Full Stack Development, focusing on Django, React, and PostgreSQL. The course covers the architecture, frameworks, and the HTTP request-response lifecycle, emphasizing the importance of understanding full-stack concepts. Additionally, it includes practical exercises and object-oriented programming principles to enhance students' skills in Django development.

Uploaded by

bhumikatalwar19
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

MITS Academy — Python Full Stack Development

MITS ACADEMY
Python Full Stack Development

Django + React + PostgreSQL — Complete Course


[Link]

Page 1 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

Chapter 1: Python Full Stack Overview


Full-stack development with Python means commanding every tier of a modern web application:
PostgreSQL for durable, relational data storage; Django for the server-side business logic,
authentication, and REST API; and React for the interactive, component-driven user interface
running in the browser. Each tier communicates through well-defined contracts — SQL queries
between Django and PostgreSQL, and JSON over HTTP between Django REST Framework
and React — so each layer can be developed, tested, and scaled independently of the others.
Python has become the dominant language for web backends because of its readable syntax,
its enormous package ecosystem, and its strength in adjacent domains such as data science
and machine learning. When your web API needs to call a machine-learning model or run a
data-processing pipeline, Python lets you do that in the same codebase without switching
languages. The Django framework adds batteries-included features — an ORM, an admin
panel, form handling, and security middleware — so teams can ship production-quality software
rapidly.
The HTTP request-response cycle is the heartbeat of every web application. When a user clicks
a link or submits a form, the browser constructs an HTTP request — containing a method (GET,
POST, PUT, DELETE), a URL, headers, and optionally a body — and sends it across the
network to the server. Django receives the request, routes it to the correct view function based
on [Link], the view queries the database through the ORM, constructs a response (HTML page
or JSON payload), and sends it back. The browser renders the response, completing the cycle.
Understanding this cycle deeply is the foundation of debugging any full-stack problem.
Choosing the right Python web framework matters. Django is the full-featured, opinionated
choice: it includes an ORM, admin, auth, and templating out of the box, making it ideal for
content-heavy platforms and enterprise systems. Flask is a micro-framework — minimal by
design — giving you full control at the cost of assembling authentication, ORM, and other pieces
yourself; it suits small APIs and microservices. FastAPI is the newest contender, built on Python
type hints and async I/O, delivering very high throughput and automatic OpenAPI
documentation; it is the best choice when raw API performance and modern async patterns are
priorities. This course focuses on Django because its breadth maps best to teaching full-stack
concepts end-to-end.

1.1 Framework Comparison


• Django — Full-featured, batteries-included, ORM + Admin + Auth built-in, best for large
apps
• Flask — Micro-framework, minimal overhead, assemble your own stack, best for small
APIs
• FastAPI — Async-first, type-hint-driven, auto OpenAPI docs, best for high-performance
APIs

1.2 MTV Architecture (Django)


• Model — Python class mapped to a database table; defines fields and relationships
• Template — HTML files with Django Template Language; handles presentation
• View — Python function/class that receives a request, queries Model, returns
Template or JSON
• URLs — [Link] routes incoming requests to the correct View

Page 2 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

• Settings — [Link] centralises database, static files, installed apps, middleware


config

1.3 Full Stack Technology Stack


• Backend : Python 3.11+, Django 5.x, Django REST Framework, Celery, Redis
• Frontend : React 18, Vite, React Router v6, Axios, Context API
• Database : PostgreSQL 15+, psycopg2 driver, Django ORM migrations
• Auth : djangorestframework-simplejwt, JWT access + refresh tokens
• Deploy : Ubuntu VPS, Gunicorn, Nginx, python-decouple, Whitenoise, Let's Encrypt

1.4 Comprehensive Example — HTTP Request Lifecycle in Django


# ── FILE: myproject/[Link] ──────────────────────────────────────────────────
# This is the ROOT URL configuration. Django reads this file first on every
# request to determine which view should handle the incoming URL.
# urlpatterns is a list Django iterates top-to-bottom until a pattern matches.

from [Link] import admin


from [Link] import path, include

urlpatterns = [
path('admin/', [Link]), # built-in Django admin panel
path('api/', include('[Link]')), # delegate /api/* to the courses
app
]

# ── FILE: courses/[Link] ────────────────────────────────────────────────────


# App-level URL config. Django finds this via the include() above.
# Each path() maps a URL pattern to a specific view function.

from [Link] import path


from . import views

urlpatterns = [
# GET /api/courses/ → list all courses
# POST /api/courses/ → create a new course
path('courses/', views.course_list, name='course-list'),

# GET /api/courses/<id>/ → retrieve one course


# PUT /api/courses/<id>/ → update one course
# DELETE /api/courses/<id>/→ delete one course
path('courses/<int:pk>/', views.course_detail, name='course-detail'),
]

# ── FILE: courses/[Link] ──────────────────────────────────────────────────


# A Model class is a Python class that maps to a database table.
# Django's ORM translates Python operations into SQL automatically.

from [Link] import models

class Course([Link]):
# CharField maps to VARCHAR; max_length is required for VARCHAR columns
title = [Link](max_length=200)
description = [Link]() # TEXT column — unlimited
length
price = [Link](max_digits=8, decimal_places=2)
created_at = [Link](auto_now_add=True) # set once on INSERT

Page 3 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

updated_at = [Link](auto_now=True) # updated on every


SAVE

def __str__(self):
# This controls how the object appears in the admin panel and shell
return [Link]

# ── FILE: courses/[Link] ───────────────────────────────────────────────────


# A view is a Python function that receives an HttpRequest and returns
# an HttpResponse. This is the core of Django's MTV architecture.

import json
from [Link] import JsonResponse
from [Link] import csrf_exempt
from .models import Course

@csrf_exempt # disable CSRF for JSON API endpoints (use DRF auth instead)
def course_list(request):
if [Link] == 'GET':
# .values() returns a QuerySet of dicts — easy to serialise to JSON
courses = list([Link]('id', 'title', 'price'))
return JsonResponse(courses, safe=False) # safe=False allows list at
root

elif [Link] == 'POST':


data = [Link]([Link]) # parse JSON request body
course = [Link](
title=data['title'],
description=data['description'],
price=data['price'],
)
return JsonResponse({'id': [Link], 'title': [Link]},
status=201)

1.5 Common Mistakes


• Forgetting to run migrations after changing models — always run makemigrations then
migrate
• Using bare except: clauses — always catch specific exceptions for predictable error
handling
• Hardcoding SECRET_KEY or database passwords in [Link] — use python-decouple
and .env files
• Serving Django with runserver in production — runserver is single-threaded and not
secure; use Gunicorn

1.6 Practice Exercises


• Draw the full request-response cycle for a POST /api/courses/ request with a JSON body
• List three differences between Django and FastAPI and explain when you would choose
each
• Create a simple Django project with one model and verify it appears in the admin panel

Page 4 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

Chapter 1B: Object-Oriented Programming in Python


Django — the backbone of Python Full Stack development — is built entirely on object-oriented
principles. Every Django Model is a class. Every View can be a class (CBV). Every Form is a
class. Understanding OOP deeply makes you not just a Django user, but a Django expert who
can read, extend, and customise the framework itself. This chapter covers all OOP pillars with
code examples before we dive into Django.

Types of Inheritance
Python supports five forms of inheritance: Single (one child, one parent), Multiple (one child,
many parents — Python resolves method calls via MRO), Multilevel (A → B → C chain),
Hierarchical (many children, one parent), and Hybrid (combination). The Method Resolution
Order (MRO) computed by C3 linearisation determines which method is invoked when there is
ambiguity in multiple or hybrid inheritance. Call super() consistently — it follows the MRO rather
than hardcoding a parent class name.
from abc import ABC, abstractmethod

# ── Single Inheritance ─────────────────────────────────────────


class Model:
"""Django-style base model — single inheritance"""
def __init__(self, pk=None):
[Link] = pk

def save(self):
print(f"Saving {self.__class__.__name__} (pk={[Link]})")

def delete(self):
print(f"Deleting {self.__class__.__name__} (pk={[Link]})")

class Course(Model): # single: Course → Model


def __init__(self, pk, title, price):
super().__init__(pk)
[Link] = title
[Link] = price

def __str__(self):
return f"Course({[Link]}: {[Link]}, ₹{[Link]})"

# ── Multilevel Inheritance ──────────────────────────────────────


class BaseView:
def dispatch(self, request):
print(f"BaseView dispatching {request}")

class ListView(BaseView): # multilevel level 1


def get_queryset(self):
return []

class CourseListView(ListView): # multilevel level 2


def get_queryset(self):
return [Course(1,"Python",4999), Course(2,"Django",5999)]

def render(self):
for item in self.get_queryset():
print(f" {item}")

# ── Hierarchical Inheritance ────────────────────────────────────


class Notification(ABC):

Page 5 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

@abstractmethod
def send(self, message): pass # every child must implement

class EmailNotif(Notification): # hierarchical — same parent


def send(self, message):
print(f"[EMAIL] {message}")

class SMSNotif(Notification): # hierarchical — same parent


def send(self, message):
print(f"[SMS] {message}")

class PushNotif(Notification): # hierarchical — same parent


def send(self, message):
print(f"[PUSH] {message}")

# ── Multiple Inheritance ────────────────────────────────────────


class JSONMixin:
def to_json(self):
import json
return [Link](self.__dict__)

class TimestampMixin:
def created_at(self):
from datetime import datetime
return [Link]().isoformat()

class Enrollment(JSONMixin, TimestampMixin, Model): # multiple inheritance


def __init__(self, pk, student, course):
super().__init__(pk)
[Link] = student
[Link] = course

# ── Testing ─────────────────────────────────────────────────────
c = Course(1, "Python Full Stack", 8999)
[Link]()
print(c)

view = CourseListView()
[Link]()

for notif in [EmailNotif(), SMSNotif(), PushNotif()]:


[Link]("Your enrollment is confirmed!")

e = Enrollment(1, "Ravi", "Python Full Stack")


print(e.to_json())
print("Created at:", e.created_at())
print("MRO:", [cls.__name__ for cls in Enrollment.__mro__])

Abstraction with ABC


In Django, abstraction is everywhere: Model is an abstract concept that all your models
implement. Abstract base classes (ABC module) enforce that every subclass implements
required methods. Mark a class abstract with class MyClass(ABC), and mark methods with
@abstractmethod. Trying to instantiate an abstract class raises TypeError — Python enforces
the contract at runtime.
from abc import ABC, abstractmethod

class BaseRepository(ABC):
"""Abstract repository — defines the DATA ACCESS CONTRACT.

Page 6 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

All data sources (DB, CSV, API) must implement these methods."""

@abstractmethod
def get_all(self) -> list:
pass

@abstractmethod
def get_by_id(self, pk: int):
pass

@abstractmethod
def create(self, data: dict):
pass

@abstractmethod
def update(self, pk: int, data: dict):
pass

@abstractmethod
def delete(self, pk: int) -> bool:
pass

# Concrete method — available to all subclasses for free


def exists(self, pk: int) -> bool:
return self.get_by_id(pk) is not None

class InMemoryStudentRepo(BaseRepository):
"""Concrete: stores students in a dict (for testing)"""
def __init__(self):
self._store = {}
self._next_id = 1

def get_all(self):
return list(self._store.values())

def get_by_id(self, pk):


return self._store.get(pk)

def create(self, data):


data['id'] = self._next_id
self._store[self._next_id] = data
self._next_id += 1
return data

def update(self, pk, data):


if pk in self._store:
self._store[pk].update(data)
return self._store[pk]
return None

def delete(self, pk):


return self._store.pop(pk, None) is not None

repo = InMemoryStudentRepo()
[Link]({"name": "Alice", "dept": "CS"})
[Link]({"name": "Bob", "dept": "IT"})
print("All:", repo.get_all())
print("Exists id=1:", [Link](1))
print("Exists id=99:", [Link](99))

Page 7 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

[Link](1, {"dept": "AI"})


print("After update:", repo.get_by_id(1))
[Link](2)
print("After delete:", repo.get_all())

Polymorphism, Overriding, and Overloading


Polymorphism in Python is primarily achieved through method overriding and duck typing. When
a Django view calls form.is_valid(), it does not know — or care — whether form is a
ContactForm, RegistrationForm, or PaymentForm. Each has its own is_valid() that runs its own
validation logic. That is runtime polymorphism. Python also supports compile-time
polymorphism simulation via default arguments and @singledispatch.
from functools import singledispatch

# ── METHOD OVERRIDING (Runtime Polymorphism) ───────────────────


class BaseProcessor:
def process(self, data):
print(f"[Base] processing: {data}")
return data

def validate(self, data):


return bool(data) # override in subclasses

class CourseEnrollmentProcessor(BaseProcessor):
def process(self, data): # OVERRIDE
print(f"[Enrollment] Student={data['student']},
Course={data['course']}")
if not [Link](data):
raise ValueError("Invalid enrollment data")
return {"status": "enrolled", **data}

def validate(self, data): # OVERRIDE


return "student" in data and "course" in data

class PaymentProcessor(BaseProcessor):
def process(self, data): # OVERRIDE
print(f"[Payment] ₹{data['amount']} via {data['method']}")
return {"status": "paid", "txn_id": "TXN123", **data}

def validate(self, data): # OVERRIDE


return [Link]("amount", 0) > 0

# Polymorphic call — caller doesn't know which processor it has


def run_pipeline(processor: BaseProcessor, data: dict):
if [Link](data):
result = [Link](data) # correct version called at runtime
print(f" Result: {result}")
else:
print(" Validation failed!")

processors = [
(CourseEnrollmentProcessor(), {"student": "Ravi", "course": "Django"}),
(PaymentProcessor(), {"amount": 5999, "method": "UPI"}),
(PaymentProcessor(), {"amount": -100, "method": "Card"}), #
invalid
]
for proc, data in processors:
run_pipeline(proc, data)
print()

Page 8 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

# ── FUNCTION OVERLOADING SIMULATION ────────────────────────────


@singledispatch
def serialize(obj):
return str(obj)

@[Link](dict)
def _(obj):
import json
return [Link](obj)

@[Link](list)
def _(obj):
return ", ".join(str(x) for x in obj)

@[Link](int)
def _(obj):
return f"{obj:,}" # with thousands separator

print(serialize({"name": "Alice", "score": 92}))


print(serialize([10, 20, 30, 40]))
print(serialize(1000000))

• Practice 1: Build a PaymentGateway abstract class with process_payment(), refund(), and


get_status() abstract methods. Implement RazorpayGateway and StripeGateway.
• Practice 2: Create a Django-style Form class hierarchy: BaseForm → LoginForm,
RegistrationForm, ContactForm. Each overrides validate() with its own rules.
• Practice 3: Use @singledispatch to write a to_response() function that converts dict →
JSON response, list → JSON array, str → text response, int → numeric response.

Chapter 2: Django Setup & Project Structure


Before writing a single line of application code, a professional Python developer sets up an
isolated virtual environment. A virtual environment is a self-contained directory that holds a
specific Python interpreter and a private set of installed packages. Without it, every project on
your machine shares the same global Python installation, leading to version conflicts — project
A needs Django 4.x while project B needs Django 5.x. The venv module, bundled with Python
3.3+, makes creating isolated environments trivial: python -m venv venv creates the
environment and source venv/bin/activate activates it. On Windows the activation command is
venv\Scripts\activate. Once activated, pip install only modifies the local environment, leaving the
system Python untouched.
The django-admin startproject command generates the skeleton of a Django project. It creates a
[Link] script at the root — your command-line interface for every Django management task
— and a package directory with the same name as your project containing [Link], [Link],
[Link], and [Link]. The [Link] file is the nerve centre: it configures the database
connection, lists installed applications, defines middleware, sets the template directories, and
controls dozens of other behaviours. The [Link] at the project level is the master router that
delegates URL prefixes to individual application URL configs.
Django applications are self-contained modules of functionality created with [Link]
startapp. Each app contains its own [Link] (data layer), [Link] (logic layer), [Link]
(routing), [Link] (admin panel registration), and [Link]. A well-structured Django project is a

Page 9 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

collection of small, focused apps rather than one monolithic app. You must register each new
app in the INSTALLED_APPS list inside [Link] — Django will not discover or run
migrations for unregistered apps. Common app names include: users, courses, payments,
notifications.
The [Link] script is the Swiss Army knife of Django development. [Link] runserver
starts the development server. [Link] makemigrations inspects your models and generates
migration files describing database schema changes. [Link] migrate applies pending
migration files to the database. [Link] createsuperuser creates an admin account.
[Link] shell opens a Python REPL with the Django environment pre-loaded so you can
interact with models directly. Memorising these commands and understanding when to use
each is essential for productive Django development.

2.1 Setup Commands


• python -m venv venv — create virtual environment
• source venv/bin/activate — activate (Linux/Mac)
• venv\Scripts\activate — activate (Windows)
• pip install django djangorestframework psycopg2-binary python-decouple pillow
• django-admin startproject myproject . — create project in current directory
• python [Link] startapp courses — create a new application
• python [Link] runserver — start development server on port 8000
• python [Link] makemigrations — generate migration files from model changes
• python [Link] migrate — apply migrations to the database
• python [Link] createsuperuser — create admin user

2.2 Project Directory Layout


• myproject/ — root directory (also called the project root)
• [Link] — CLI entry point for all Django commands
• .env — environment variables (never commit to git)
• [Link] — pinned package list for reproducible installs
• myproject/ — project package (same name as project)
• [Link] — master configuration file
• [Link] — root URL dispatcher
• [Link] — WSGI entry point for Gunicorn/Apache
• [Link] — ASGI entry point for async servers
• courses/ — a Django application
• [Link] — ORM model definitions
• [Link] — view functions / class-based views
• [Link] — app-level URL patterns
• [Link] — DRF serializers
• [Link] — admin panel registrations
• [Link] — unit and integration tests
• migrations/ — auto-generated database migration files

2.3 Comprehensive Example — Full Project Bootstrap

Page 10 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

# ── TERMINAL COMMANDS ────────────────────────────────────────────────────────


# Run these commands once to bootstrap a production-ready Django project.
#
# 1. Create and activate a virtual environment
# python -m venv venv
# source venv/bin/activate # Linux/Mac
# venv\Scripts\activate # Windows
#
# 2. Install dependencies
# pip install django djangorestframework psycopg2-binary python-decouple
pillow
# pip freeze > [Link] # lock versions for reproducibility
#
# 3. Create the project (the trailing dot puts [Link] in current dir)
# django-admin startproject myproject .
#
# 4. Create the first application
# python [Link] startapp courses

# ── FILE: .env ───────────────────────────────────────────────────────────────


# Store ALL secrets here. Add .env to .gitignore immediately.
# python-decouple reads this file via config().
SECRET_KEY=your-very-secret-key-here
DEBUG=True
ALLOWED_HOSTS=localhost,[Link]
DB_NAME=mits_db
DB_USER=postgres
DB_PASSWORD=yourpassword
DB_HOST=localhost
DB_PORT=5432

# ── FILE: myproject/[Link] ──────────────────────────────────────────────


# [Link] is imported by Django at startup. Every configuration option
# lives here. We use python-decouple to read from .env so secrets never
# appear in source code.

from pathlib import Path


from decouple import config # pip install python-decouple

BASE_DIR = Path(__file__).resolve().[Link] # project root directory

# SECURITY: load SECRET_KEY from .env — never hardcode this value


SECRET_KEY = config('SECRET_KEY')

# DEBUG=True enables detailed error pages; ALWAYS False in production


DEBUG = config('DEBUG', default=False, cast=bool)

# ALLOWED_HOSTS restricts which domain names Django will serve


ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='').split(',')

# INSTALLED_APPS: every app Django should load must be listed here.


# Omitting an app means its models won't be migrated and its URLs won't load.
INSTALLED_APPS = [
'[Link]', # admin panel at /admin/
'[Link]', # built-in user model and authentication
'[Link]', # required by auth and admin
'[Link]', # session framework
'[Link]', # one-time flash messages
'[Link]', # static file management
'rest_framework', # Django REST Framework
'corsheaders', # allow React frontend to call this API

Page 11 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

'courses', # our custom application


]

MIDDLEWARE = [
'[Link]', # must be first for CORS
headers
'[Link]',
'[Link]',
'[Link]',
'[Link]',
'[Link]',
'[Link]',
'[Link]',
]

ROOT_URLCONF = '[Link]' # tells Django where the master [Link] lives

# DATABASE: connect to PostgreSQL using values from .env


DATABASES = {
'default': {
'ENGINE': '[Link]', # use psycopg2 under the
hood
'NAME': config('DB_NAME'),
'USER': config('DB_USER'),
'PASSWORD': config('DB_PASSWORD'),
'HOST': config('DB_HOST', default='localhost'),
'PORT': config('DB_PORT', default='5432'),
}
}

# Static files (CSS, JavaScript, images served by Django/Whitenoise)


STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles' # collectstatic copies files here

# Media files (user uploads — profile pictures, course thumbnails)


MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'

# Django REST Framework global defaults


REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.[Link]',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.[Link]',
],
}

# CORS: allow the React dev server to call our Django API
CORS_ALLOWED_ORIGINS = [
'[Link] # Vite dev server default port
'[Link] # Create React App default port
]

2.4 Common Mistakes


• Forgetting to add new apps to INSTALLED_APPS — results in migrations never running
• Running [Link] commands outside the virtual environment — installs go to wrong
Python

Page 12 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

• Committing .env or settings with hardcoded secrets to git — use .gitignore and python-
decouple
• Using [Link] runserver in production — it is single-threaded and not hardened for
public traffic

2.5 Practice Exercises


• Bootstrap a fresh Django project with a courses app and verify the admin panel loads
• Move all sensitive settings to a .env file and confirm python-decouple reads them correctly
• Create a [Link] and verify a colleague can reproduce your environment with pip
install -r

Chapter 3: Django Models & ORM


A Django model is a Python class that subclasses [Link]. Each class
represents a database table, and each class attribute represents a column in that table.
Django's ORM (Object-Relational Mapper) translates your Python-level operations — creating
an instance, calling .save(), calling .filter() — into the correct SQL statements for your database
engine. This abstraction means you write Python and the ORM generates INSERT, SELECT,
UPDATE, DELETE statements, handling differences between PostgreSQL, MySQL, and SQLite
transparently.
Field types in Django models map directly to database column types. CharField maps to
VARCHAR and requires a max_length argument. TextField maps to TEXT for unlimited-length
strings. IntegerField maps to INTEGER, DecimalField to NUMERIC, BooleanField to
BOOLEAN, DateTimeField to TIMESTAMP. The auto_now_add=True option sets the timestamp
once at insert time; auto_now=True updates it on every save — use these for created_at and
updated_at audit columns respectively. ForeignKey creates a many-to-one relationship by
adding a foreign-key column; ManyToManyField creates a hidden junction table managed
entirely by Django.
Migrations are Django's version control for the database schema. When you add, remove, or
modify a field in [Link], you run [Link] makemigrations to generate a migration file — a
Python script describing the change — and then [Link] migrate to apply it. Migration files
should be committed to git; they document every schema change and allow any developer to
reconstruct the database from scratch by running migrate on a clean database. Never manually
edit the database schema; always go through migrations so the schema stays in sync with the
codebase.
The QuerySet API is the heart of the ORM. [Link]() returns every row.
[Link](price__lt=500) adds a WHERE clause. .get(id=1) retrieves exactly one
object and raises DoesNotExist if not found. .create() runs an INSERT. .update() runs a bulk
UPDATE without loading objects into memory. .delete() removes rows. QuerySets are lazy —
they do not hit the database until you iterate, call len(), or force evaluation with list(). This
laziness allows you to chain filters and build complex queries before any SQL is executed. Use
select_related() for ForeignKey fields and prefetch_related() for ManyToMany to avoid the N+1
query problem.

3.1 Field Type Quick Reference


• CharField(max_length=N) — VARCHAR(N)

Page 13 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

• TextField() — TEXT, unlimited length


• IntegerField() — INTEGER
• DecimalField(max_digits, decimal_places) — NUMERIC, use for money
• BooleanField() — BOOLEAN
• DateField() — DATE
• DateTimeField() — TIMESTAMP
• ImageField(upload_to='path/') — VARCHAR storing file path, file saved to
MEDIA_ROOT
• ForeignKey(Model, on_delete=CASCADE) — many-to-one relationship
• ManyToManyField(Model) — many-to-many, Django manages junction table

3.2 Comprehensive Example — Models, Migrations, and QuerySet API


# ── FILE: courses/[Link] ──────────────────────────────────────────────────
# This file defines ALL database tables for the courses application.
# Every class here will become a table after running makemigrations + migrate.

from [Link] import models


from [Link] import User # built-in User table

class Category([Link]):
# Simple lookup table for course categories (e.g. "Web Development")
name = [Link](max_length=100, unique=True) # unique=True adds a
UNIQUE constraint

class Meta:
verbose_name_plural = 'categories' # fixes "categorys" in admin panel

def __str__(self):
return [Link]

class Course([Link]):
LEVEL_CHOICES = [ # database stores 'BG'/'IN'/'AD', admin shows
readable label
('BG', 'Beginner'),
('IN', 'Intermediate'),
('AD', 'Advanced'),
]

title = [Link](max_length=200)
description = [Link]()
price = [Link](max_digits=8, decimal_places=2)
level = [Link](max_length=2, choices=LEVEL_CHOICES,
default='BG')
thumbnail = [Link](upload_to='thumbnails/', blank=True,
null=True)
is_published = [Link](default=False)
created_at = [Link](auto_now_add=True)
updated_at = [Link](auto_now=True)

# ForeignKey: many Courses belong to one Category


# on_delete=SET_NULL means if the Category is deleted, [Link]
becomes NULL
category = [Link](
Category,

Page 14 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='courses', # allows [Link]() reverse
lookup
)

# ManyToManyField: many Students can enrol in many Courses


# Django creates a hidden table: courses_course_students(course_id,
user_id)
students = [Link](
User,
through='Enrollment', # use explicit through model to store extra
data
related_name='enrolled_courses',
blank=True,
)

class Meta:
ordering = ['-created_at'] # newest courses appear first by default

def __str__(self):
return f"{[Link]} ({self.get_level_display()})"

class Enrollment([Link]):
# Explicit through model for the Course <-> User ManyToMany relationship.
# Using an explicit through model lets us store extra columns like
enrolled_at.
student = [Link](User, on_delete=[Link])
course = [Link](Course, on_delete=[Link])
enrolled_at = [Link](auto_now_add=True)
completed = [Link](default=False)
progress = [Link](default=0) # 0-100 percent

class Meta:
# Prevent a student from enrolling in the same course twice
unique_together = ('student', 'course')

def __str__(self):
return f"{[Link]} → {[Link]}"

# ── FILE: courses/management/commands/[Link] ────────────────────────────────


# Custom management command: python [Link] seed
# Demonstrates the full QuerySet API: create, filter, get, update, delete

from [Link] import BaseCommand


from [Link] import Category, Course

class Command(BaseCommand):
help = 'Seed the database with sample data'

def handle(self, *args, **kwargs):


# CREATE — INSERT INTO category (name) VALUES ('Web Development')
web, created = [Link].get_or_create(name='Web Development')
# get_or_create returns (object, created_bool) — avoids duplicate rows

# BULK CREATE — more efficient than calling .create() in a loop


[Link].bulk_create([

Page 15 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

Course(title='Python Basics', price=299, level='BG',


category=web),
Course(title='Django REST API', price=499, level='IN',
category=web),
Course(title='React + Django', price=699, level='AD',
category=web),
])

# FILTER — SELECT * FROM course WHERE price < 500


cheap = [Link](price__lt=500)
[Link](f"Cheap courses: {[Link]()}")

# GET — raises [Link] if not found,


MultipleObjectsReturned if >1
try:
django_course = [Link](title='Django REST API')
except [Link]:
[Link]('Not found')

# UPDATE — runs a single SQL UPDATE; does NOT call model's save()
method
[Link](level='BG').update(is_published=True)

# CHAINING — QuerySets are lazy; SQL runs only when evaluated


published_web = (
[Link]
.filter(is_published=True)
.filter(category=web)
.order_by('price')
.values('title', 'price') # SELECT only these columns
)
for c in published_web: # SQL executes here
[Link](f" {c['title']} — Rs.{c['price']}")

# SELECT_RELATED — JOIN to avoid N+1 queries on ForeignKey


# Without select_related, accessing [Link] inside a loop
# would fire one extra SELECT per course — the N+1 problem.
courses_with_category = [Link].select_related('category').all()

[Link]([Link]('Database seeded successfully'))

3.3 Admin Registration


# ── FILE: courses/[Link] ───────────────────────────────────────────────────
# Registering models here makes them appear in the Django admin panel at
/admin/
# The admin panel is a full CRUD interface generated automatically from your
models.

from [Link] import admin


from .models import Category, Course, Enrollment

@[Link](Category)
class CategoryAdmin([Link]):
list_display = ('name',) # columns shown in the list view
search_fields = ('name',) # adds a search box filtering by name

@[Link](Course)
class CourseAdmin([Link]):

Page 16 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

list_display = ('title', 'category', 'price', 'level', 'is_published',


'created_at')
list_filter = ('level', 'is_published', 'category') # sidebar filters
search_fields = ('title', 'description')
list_editable = ('is_published',) # allows toggling published directly
in list
date_hierarchy = 'created_at' # drill-down navigation by date

@[Link](Enrollment)
class EnrollmentAdmin([Link]):
list_display = ('student', 'course', 'enrolled_at', 'progress',
'completed')
list_filter = ('completed',)

3.4 Common Mistakes


• Calling .get() when multiple objects may match — use .filter().first() instead
• Forgetting select_related/prefetch_related in loops — causes N+1 queries and slow pages
• Using .update() when you need model signals or custom save() logic — .update()
bypasses both
• Not adding null=True AND blank=True together when making a field optional

3.5 Practice Exercises


• Add a Lesson model with a ForeignKey to Course and fields: title, video_url, order,
duration_minutes
• Write a query that returns all courses enrolled by a specific user using prefetch_related
• Register all models in [Link] and customize list_display for each

Chapter 4: Django Views & Templates


A Django view is the layer that bridges the Model and the Template — it receives an
HttpRequest object, performs business logic (often querying the database), and returns an
HttpResponse object. The simplest view is a plain Python function decorated with no special
decorator; it receives the request and returns a response. Django also provides class-based
views (CBVs), which encapsulate common patterns — listing objects, creating forms, updating
records — into reusable classes that you extend and configure. Understanding when to use
function-based views versus class-based views is a key architectural skill.
Function-based views (FBVs) are explicit and easy to trace: every step of request handling is
visible in the function body. They are ideal for views with unusual logic that does not fit the
mould of standard CRUD operations. Class-based views (CBVs) reduce boilerplate for standard
patterns: ListView, DetailView, CreateView, UpdateView, DeleteView are generic CBVs that
handle the full CRUD cycle with minimal code. The tradeoff is that CBVs require understanding
Django's Method Resolution Order (MRO) and the mixin system, which can make debugging
harder for beginners. A good rule of thumb: start with FBVs to understand Django's flow, then
migrate repetitive views to CBVs.
Django's template language (DTL) is a lightweight, intentionally restricted language for
expressing presentation logic in HTML files. Variables are rendered with {{ variable_name }}.
Filters modify output: {{ price|floatformat:2 }} or {{ title|upper }}. Tags add logic: {% if %}, {% for

Page 17 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

%}, {% url %}, {% csrf_token %}, {% block %}, {% extends %}. Template inheritance is the most
powerful DTL feature: a [Link] template defines the overall page layout with {% block %}
placeholders, and every child template uses {% extends "[Link]" %} and fills in those
placeholders — eliminating duplication across dozens of pages.
The render() shortcut is the most commonly used response helper: it takes the request, a
template name, and a context dictionary, renders the template with the context, and returns an
HttpResponse. The redirect() function returns an HttpResponseRedirect, sending the browser to
a new URL — essential for Post/Redirect/Get patterns that prevent form resubmission on page
refresh. For JSON APIs you can return JsonResponse directly from a view without any
template, setting the foundation for REST endpoints that React will consume.

4.1 View Types Comparison


• Function-Based View (FBV) — explicit, flexible, best for custom logic
• Class-Based View (CBV) — reusable, DRY, best for standard CRUD patterns
• Generic CBV (ListView etc)— maximum DRY, minimal code, requires understanding
mixins
• APIView (DRF) — best for JSON REST endpoints (Chapter 5)

4.2 Comprehensive Example — FBVs, CBVs, and Template


Inheritance
# ── FILE: courses/[Link] ───────────────────────────────────────────────────
# This file demonstrates both function-based and class-based views working
# together. FBVs are used for custom logic; CBVs for standard CRUD.

from [Link] import render, get_object_or_404, redirect


from [Link] import ListView, DetailView, CreateView
from [Link] import login_required
from [Link] import LoginRequiredMixin
from [Link] import reverse_lazy
from [Link] import JsonResponse
from .models import Course, Enrollment
from .forms import CourseForm

# ── FUNCTION-BASED VIEW ──────────────────────────────────────────────────────


def course_list(request):
"""
List all published courses. Supports optional search via ?q=keyword.
This is a FBV: explicit, readable, easy to customise.
"""
# [Link] is a QueryDict — like a dict but supports multiple values per
key
query = [Link]('q', '') # get ?q= param, default to empty
string

# Chained QuerySet — lazy until evaluated in the template


courses =
[Link](is_published=True).select_related('category')

if query:
# __icontains: case-insensitive substring match (ILIKE in PostgreSQL)
courses = [Link](title__icontains=query)

# render() looks up the template in templates/courses/course_list.html

Page 18 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

# The context dict makes Python variables available inside the template
return render(request, 'courses/course_list.html', {
'courses': courses,
'query': query,
})

def course_detail(request, pk):


"""
Show a single course. get_object_or_404 returns 404 if [Link].
This is safer than [Link](pk=pk) which would raise an unhandled
exception.
"""
course = get_object_or_404(Course, pk=pk, is_published=True)

# Check if the current user is enrolled


is_enrolled = False
if [Link].is_authenticated:
is_enrolled = [Link](
student=[Link], course=course
).exists() # .exists() is more efficient than .count() > 0

return render(request, 'courses/course_detail.html', {


'course': course,
'is_enrolled': is_enrolled,
})

@login_required # redirects to LOGIN_URL if user is not authenticated


def enroll(request, pk):
"""
Handle POST requests to enrol the logged-in user in a course.
Post/Redirect/Get pattern: after POST, redirect to avoid resubmission.
"""
if [Link] != 'POST':
return redirect('course-detail', pk=pk) # reject non-POST requests

course = get_object_or_404(Course, pk=pk)


# get_or_create prevents double-enrolment; created=False means already
enrolled
enrollment, created = [Link].get_or_create(
student=[Link],
course=course,
)
return redirect('course-detail', pk=pk) # PRG: redirect after POST

# ── CLASS-BASED VIEW ─────────────────────────────────────────────────────────


class CourseCreateView(LoginRequiredMixin, CreateView):
"""
Generic CreateView handles GET (show empty form) and POST (validate +
save).
LoginRequiredMixin redirects unauthenticated users before any logic runs.
"""
model = Course
form_class = CourseForm
template_name = 'courses/course_form.html'
success_url = reverse_lazy('course-list') # redirect here after
successful save
# reverse_lazy is used instead of reverse() because URLconf may not be
loaded yet

Page 19 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

# ── FILE: courses/[Link] ───────────────────────────────────────────────────


from django import forms
from .models import Course

class CourseForm([Link]):
class Meta:
model = Course
fields = ['title', 'description', 'price', 'level', 'category',
'thumbnail']
widgets = {
# Override default widget to add Bootstrap CSS classes
'title': [Link](attrs={'class': 'form-control'}),
'description': [Link](attrs={'class': 'form-control',
'rows': 4}),
'price': [Link](attrs={'class': 'form-control'}),
}

def clean_price(self):
# Custom validation: price must be positive
price = self.cleaned_data['price']
if price <= 0:
raise [Link]('Price must be greater than zero.')
return price

4.3 Template Inheritance Example


<!-- FILE: templates/[Link] ─────────────────────────────────────────────-->
<!-- [Link] is the master layout. Every page extends this file. -->
<!-- {% block %} tags are named placeholders that child templates fill in. -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}MITS E-Learning{% endblock %}</title>
<!-- Load static files using the {% static %} tag -->
{% load static %}
<link rel="stylesheet" href="{% static 'css/[Link]' %}">
</head>
<body>
<nav class="navbar navbar-dark bg-dark">
<a class="navbar-brand" href="{% url 'course-list' %}">MITS</a>
{% if user.is_authenticated %}
<span class="text-white">{{ [Link] }}</span>
<a href="{% url 'logout' %}" class="btn btn-sm btn-outline-
light">Logout</a>
{% else %}
<a href="{% url 'login' %}" class="btn btn-sm
btn-outline-light">Login</a>
{% endif %}
</nav>

<div class="container mt-4">


{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ [Link] }}">{{ message }}</div>
{% endfor %}
{% endif %}

Page 20 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

{% block content %}{% endblock %} <!-- child templates inject content here
-->
</div>

<script src="{% static 'js/[Link]' %}"></script>


{% block extra_js %}{% endblock %} <!-- child templates can add extra
scripts -->
</body>
</html>

<!-- FILE: templates/courses/course_list.html ──────────────────────────────-->


<!-- This child template extends [Link] and fills in the 'content' block. --
>
{% extends "[Link]" %}

{% block title %}All Courses{% endblock %}

{% block content %}
<h2>Available Courses</h2>

<!-- Search form — GET method appends ?q=... to the URL -->
<form method="get" class="mb-3">
<input type="text" name="q" value="{{ query }}" placeholder="Search
courses...">
<button type="submit">Search</button>
</form>

<!-- {% for %} tag iterates over the QuerySet passed in context -->
{% for course in courses %}
<div class="card mb-2">
<div class="card-body">
<h5>{{ [Link] }}</h5>
<!-- |floatformat:2 renders decimal with 2 decimal places -->
<p>Rs. {{ [Link]|floatformat:2 }}</p>
<!-- {% url %} generates the URL for the named URL pattern -->
<a href="{% url 'course-detail' [Link] %}">View Details</a>
</div>
</div>
{% empty %}
<!-- {% empty %} block renders when the loop list is empty -->
<p>No courses found.</p>
{% endfor %}
{% endblock %}

4.4 Common Mistakes


• Using [Link]() in views without try/except — use get_object_or_404 instead
• Forgetting {% csrf_token %} inside POST forms — Django rejects the request with 403
Forbidden
• Putting business logic inside templates — templates should only display data, not
compute it
• Not using the Post/Redirect/Get pattern — causes duplicate form submissions on browser
refresh

Page 21 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

4.5 Practice Exercises


• Create a view that shows courses filtered by category, using a URL pattern like
/courses/category/<slug>/
• Build a template that extends [Link] and uses {% for %} with {% empty %} to list
enrolled courses
• Convert the course_list FBV to a ListView CBV and verify identical behaviour

Chapter 5: Django REST Framework


Django REST Framework (DRF) is the standard library for building JSON APIs on top of
Django. It adds a serialisation layer that converts complex Python objects (QuerySets, model
instances) into JSON and validates incoming JSON back into Python objects. DRF also
provides class-based API views with HTTP method dispatch, a browsable API interface for
testing in the browser, and a robust permission and authentication system. Installing DRF
requires only pip install djangorestframework and adding rest_framework to INSTALLED_APPS
— after that, your entire Django project gains API capabilities.
Serializers are the core abstraction in DRF. A ModelSerializer inspects a Django model and
automatically generates fields matching each model column, just as ModelForm does for HTML
forms. The serializer's is_valid() method runs validation — including field-level validators and
cross-field validate() methods — and validated_data contains the cleaned Python dict ready to
be saved. The serializer's to_representation() method controls how an object is converted to a
dict for the JSON response. You can override any field, add computed fields with
SerializerMethodField, and nest serializers to represent related objects as embedded objects
rather than just foreign-key IDs.
DRF provides a hierarchy of view classes. The lowest level is APIView, a class-based view
where you implement get(), post(), put(), and delete() methods explicitly — full control, more
code. GenericAPIView adds queryset and serializer_class attributes plus mixins
(ListModelMixin, CreateModelMixin, etc.) that implement the common patterns. ViewSets go
one step further: a ModelViewSet combines list, create, retrieve, update, and destroy actions in
a single class. Routers automatically generate the URL patterns for ViewSets — a single
[Link]() call creates /courses/ and /courses/{pk}/ URLs covering all five actions.
Authentication and permissions are orthogonal in DRF. Authentication determines WHO is
making the request (TokenAuthentication, JWTAuthentication, SessionAuthentication).
Permissions determine WHAT they are allowed to do (IsAuthenticated, IsAdminUser,
IsAuthenticatedOrReadOnly, custom permissions). You can set defaults in
REST_FRAMEWORK settings and override per-view using the authentication_classes and
permission_classes attributes. A common pattern: public GET endpoints use
IsAuthenticatedOrReadOnly, while POST/PUT/DELETE require IsAuthenticated plus an
ownership check.

5.1 DRF Component Summary


• Serializer — converts Model instances to/from Python dicts for JSON
• ModelSerializer — auto-generates fields from a Model class
• APIView — base class with get()/post() methods, full control
• GenericAPIView — adds queryset + serializer_class + mixins

Page 22 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

• ModelViewSet — full CRUD in one class (list/create/retrieve/update/destroy)


• Router — auto-generates URL patterns for ViewSets
• Authentication — WHO is making the request (JWT, Token, Session)
• Permissions — WHAT they are allowed to do (IsAuthenticated etc.)
• Pagination — PageNumberPagination, LimitOffsetPagination
• Filtering — django-filter integration for ?field=value query params

5.2 Comprehensive Example — Serializers, ViewSets, and Router


# ── FILE: courses/[Link] ─────────────────────────────────────────────
# Serializers are the translation layer between Django model instances and
JSON.

from rest_framework import serializers


from [Link] import User
from .models import Course, Category, Enrollment

class CategorySerializer([Link]):
class Meta:
model = Category
fields = ['id', 'name']

class CourseSerializer([Link]):
# SerializerMethodField: computed field not present in the model
student_count = [Link]()

# Nested serializer: embed full category object in GET responses


category = CategorySerializer(read_only=True)

# Write-only field so clients can set the category by ID on POST/PUT


category_id = [Link](
queryset=[Link](),
source='category',
write_only=True,
)

class Meta:
model = Course
fields = [
'id', 'title', 'description', 'price', 'level',
'is_published', 'thumbnail', 'category', 'category_id',
'student_count', 'created_at',
]
read_only_fields = ['id', 'created_at']

def get_student_count(self, obj):


return [Link]()

def validate_price(self, value):


# Field-level validator runs automatically during is_valid()
if value < 0:
raise [Link]('Price cannot be negative.')
return value

# ── FILE: courses/[Link] ───────────────────────────────────────────────────


from rest_framework import viewsets, permissions, filters

Page 23 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

from rest_framework.decorators import action


from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from .models import Course, Enrollment
from .serializers import CourseSerializer

class CourseViewSet([Link]):
"""
ModelViewSet automatically provides all CRUD endpoints:
GET /api/courses/ list
POST /api/courses/ create
GET /api/courses/{pk}/ retrieve
PUT /api/courses/{pk}/ update
PATCH /api/courses/{pk}/ partial_update
DELETE /api/courses/{pk}/ destroy
"""
serializer_class = CourseSerializer
permission_classes = [[Link]]

filter_backends = [DjangoFilterBackend, [Link],


[Link]]
filterset_fields = ['level', 'is_published', 'category']
search_fields = ['title', 'description']
ordering_fields = ['price', 'created_at']

def get_queryset(self):
# select_related for ForeignKey, prefetch_related for ManyToMany
qs =
[Link].select_related('category').prefetch_related('students')
if not [Link].is_staff:
qs = [Link](is_published=True)
return qs

@action(detail=True, methods=['post'],
permission_classes=[[Link]])
def enroll(self, request, pk=None):
"""Custom action: POST /api/courses/{pk}/enroll/"""
course = self.get_object()
from .models import Enrollment
enrollment, created = [Link].get_or_create(
student=[Link],
course=course,
)
status_code = 201 if created else 200
return Response({'status': 'enrolled' if created else 'already
enrolled'}, status=status_code)

# ── FILE: courses/[Link] ────────────────────────────────────────────────────


from rest_framework.routers import DefaultRouter
from [Link] import path, include
from . import views

router = DefaultRouter()
[Link]('courses', [Link], basename='course')

urlpatterns = [path('', include([Link]))]

Page 24 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

5.3 Pagination and Custom Permissions


# ── FILE: myproject/[Link] — REST_FRAMEWORK config ─────────────────────
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.[Link]',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.[Link]',
],
'DEFAULT_PAGINATION_CLASS':
'rest_framework.[Link]',
'PAGE_SIZE': 10,
}

# ── FILE: courses/[Link] ─────────────────────────────────────────────


from rest_framework.permissions import BasePermission, SAFE_METHODS

class IsAdminOrReadOnly(BasePermission):
"""Allow read to everyone; restrict write to admin users."""
def has_permission(self, request, view):
if [Link] in SAFE_METHODS:
return True
return [Link] and [Link].is_staff

5.4 Common Mistakes


• Returning Model instances directly from views instead of using serializers
• Forgetting read_only_fields — allowing clients to overwrite created_at or id fields
• Not setting permission_classes — leaving endpoints open to anonymous users
• Using APIView for standard CRUD when ModelViewSet would cut the code in half

5.5 Practice Exercises


• Build an EnrollmentViewSet with a custom action GET /enrollments/my/ returning only the
current user's enrollments
• Add LimitOffsetPagination to the CourseViewSet and test with ?limit=5&offset=10
• Write a custom permission IsOwnerOrReadOnly that allows only the object owner to edit it

Chapter 6: React Frontend Basics


React is a JavaScript library for building user interfaces through a component-based model.
Instead of manipulating the DOM directly, you describe WHAT the UI should look like for a given
state, and React efficiently updates the DOM to match. Every piece of the UI is a component —
a JavaScript function that accepts props (inputs) and returns JSX (a syntax extension that looks
like HTML but compiles to [Link]() calls). Components compose hierarchically: a
Page component contains a Header, a CourseList, and a Footer; CourseList contains many
CourseCard components. This composability makes React UIs modular, testable, and easy to
reason about.

Page 25 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

JSX is not HTML — it is syntactic sugar for [Link]() calls that JavaScript
compilers (Babel/Vite) transform into plain JavaScript. JSX differences from HTML: class
becomes className, for becomes htmlFor, self-closing tags need a slash (e.g. <br />),
JavaScript expressions are embedded with curly braces (e.g. {[Link]}), and event handlers
are camelCase (onClick, onChange, onSubmit). Understanding that JSX is just function calls
explains why you can only return a single root element (or a Fragment <>...</>) from a
component — a function can only return one value.
useState is the fundamental React hook for managing local component state. It returns a pair:
the current state value and a setter function. Calling the setter with a new value triggers a re-
render — React calls your component function again, JSX is re-evaluated, and the DOM is
updated where it changed. Never mutate state directly (e.g. [Link]()) — always call the
setter with a new value or a new array/object, because React uses reference equality to detect
changes. useEffect is the hook for side effects: data fetching, DOM manipulation, subscriptions.
An empty dependency array [] means run once after the first render, making it the right place to
fetch initial data.
Calling the Django REST API from React requires fetch() or the Axios library. Axios is preferred
because it automatically serialises/deserialises JSON, throws errors for non-2xx status codes,
and supports request interceptors for attaching JWT tokens. Cross-Origin Resource Sharing
(CORS) is the browser mechanism that blocks JavaScript from calling an API on a different
origin. Because React runs on localhost:5173 and Django runs on localhost:8000, CORS
headers are required. The django-cors-headers package adds the necessary Access-Control-
Allow-Origin headers to Django responses, and CORS_ALLOWED_ORIGINS in [Link]
whitelists the React dev server.

6.1 React Hook Summary


• useState(initial) — local state; returns [value, setter]; setter triggers re-render
• useEffect(fn, deps) — side effects after render; [] = once, [x] = when x changes
• useContext(ctx) — read from Context without prop drilling
• useRef(initial) — mutable ref that does NOT trigger re-render; used for DOM refs
• useMemo(fn, deps) — memoize expensive computations
• useCallback(fn, deps)— memoize callbacks to prevent unnecessary child re-renders

6.2 Comprehensive Example — Course Listing Page with API Call


// ── FILE: frontend/src/api/[Link]
────────────────────────────────────
import axios from 'axios';

const api = [Link]({


baseURL: '[Link]
timeout: 10000,
headers: { 'Content-Type': 'application/json' },
});

// Attach JWT token to every request automatically


[Link]((config) => {
const token = [Link]('access_token');
if (token) [Link] = `Bearer ${token}`;
return config;
});

Page 26 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

export default api;

// ── FILE: frontend/src/components/[Link]
─────────────────────────────
import React from 'react';

function CourseCard({ course, onEnroll }) {


return (
<div className="card mb-3 shadow-sm">
{[Link] && (
<img
src={[Link]}
alt={[Link]}
className="card-img-top"
style={{ height: '180px', objectFit: 'cover' }}
/>
)}
<div className="card-body">
<h5 className="card-title">{[Link]}</h5>
<p className="card-text text-muted">{[Link](0,
100)}...</p>
<div className="d-flex justify-content-between align-items-center">
<span className="badge bg-primary">{[Link]}</span>
<strong>Rs. {parseFloat([Link]).toFixed(2)}</strong>
</div>
</div>
<div className="card-footer">
<button className="btn btn-success w-100" onClick={() =>
onEnroll([Link])}>
Enrol Now
</button>
</div>
</div>
);
}

export default CourseCard;

// ── FILE: frontend/src/pages/[Link]
─────────────────────────────────
import React, { useState, useEffect } from 'react';
import CourseCard from '../components/CourseCard';
import api from '../api/axiosConfig';

function CoursesPage() {
const [courses, setCourses] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [search, setSearch] = useState('');

// Dependency array [search] means: re-run this effect whenever search


changes
useEffect(() => {
const fetchCourses = async () => {
try {
setLoading(true);
setError(null);
// axios params object is serialised as URL query string: ?
search=<value>

Page 27 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

const response = await [Link]('/courses/', { params: { search } });


// DRF pagination wraps results in { count, next, previous, results }
setCourses([Link] ?? [Link]);
} catch (err) {
setError([Link]?.data?.detail ?? 'Failed to load courses.');
} finally {
setLoading(false);
}
};
fetchCourses();
}, [search]);

const handleEnroll = async (courseId) => {


try {
await [Link](`/courses/${courseId}/enroll/`);
alert('Enrolled successfully!');
} catch (err) {
alert([Link]?.data?.detail ?? 'Enrolment failed.');
}
};

if (loading) return <div className="text-center mt-5"><div


className="spinner-border"/></div>;
if (error) return <div className="alert alert-danger">{error}</div>;

return (
<div className="container mt-4">
<h2>All Courses</h2>
{/* Controlled input: React state drives the input value */}
<input
type="text"
className="form-control mb-3"
placeholder="Search courses..."
value={search}
onChange={(e) => setSearch([Link])}
/>
<div className="row">
{[Link]((course) => (
// key must be stable and unique — use database ID, never array index
<div key={[Link]} className="col-md-4">
<CourseCard course={course} onEnroll={handleEnroll} />
</div>
))}
</div>
</div>
);
}

export default CoursesPage;

6.3 Common Mistakes


• Mutating state directly (e.g. [Link]()) instead of calling setState with a new array
• Missing the key prop on list items — causes React reconciliation bugs
• Calling setState inside useEffect without a dependency array — causes infinite render
loops
• Not handling loading and error states — leaves users staring at a blank page

Page 28 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

• Forgetting CORS configuration in Django — browser silently blocks all cross-origin API
calls

6.4 Practice Exercises


• Build a CourseDetailPage that fetches /api/courses/{id}/ and displays full course
information
• Add a debounced search input — wait 400ms after typing stops before calling the API
• Create a reusable LoadingSpinner component and use it across all data-fetching pages

Chapter 7: React Advanced


React Router v6 enables client-side navigation — switching between views without a full page
reload — by synchronising the URL bar with the component tree. The BrowserRouter provider
wraps the entire app and listens for URL changes. Routes and Route elements define the
mapping from URL patterns to components. The useNavigate hook returns a function for
programmatic navigation (e.g. after a form submission). useParams extracts dynamic segments
from the URL (e.g. /courses/:id). useLocation accesses the current URL object. Client-side
routing makes React apps feel as fast as native applications because only the changed
components re-render.
The Context API solves prop drilling — the anti-pattern of passing props through many
intermediate components that do not use them. createContext() creates a context object. A
Provider component wraps part of the tree and supplies a value. Any descendant can read the
value with useContext() without any intermediate component needing to pass props. A common
pattern is an AuthContext that stores the current user object and a logout function, making them
available to any component in the app — a Navbar, a ProtectedRoute, a user profile page —
without prop chains.
Custom hooks are plain JavaScript functions whose names start with "use" and that call built-in
React hooks internally. They extract stateful logic from components into reusable units. A
useFetch(url) custom hook encapsulates the useState + useEffect + error-handling pattern for
API calls, returning { data, loading, error }. A useAuth() hook reads from AuthContext. A
useLocalStorage(key) hook syncs state with localStorage. Custom hooks promote the Single
Responsibility Principle: components handle rendering; hooks handle logic.
Form handling in React can be done with controlled components (state-driven) or with React
Hook Form (ref-driven). React Hook Form is preferred for complex forms because it avoids re-
rendering the entire form on every keystroke — it uses uncontrolled inputs and only validates on
submit or blur. The register() function connects an input to the form. handleSubmit wraps your
submit handler and runs validation first. [Link] contains validation error messages per
field. The Controller component integrates React Hook Form with third-party UI inputs.

7.1 Comprehensive Example — Router, Context, Custom Hooks, and


Forms
// ── FILE: frontend/src/context/[Link]
───────────────────────────────
import React, { createContext, useState, useContext, useEffect } from 'react';
import api from '../api/axiosConfig';

Page 29 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

const AuthContext = createContext(null);

export function AuthProvider({ children }) {


const [user, setUser] = useState(null);
const [token, setToken] = useState(() =>
[Link]('access_token'));

useEffect(() => {
if (token) {
[Link]('/auth/me/')
.then(res => setUser([Link]))
.catch(() => logout());
}
}, []);

const login = async (username, password) => {


const res = await [Link]('/auth/login/', { username, password });
const { access, refresh } = [Link];
[Link]('access_token', access);
[Link]('refresh_token', refresh);
setToken(access);
const meRes = await [Link]('/auth/me/');
setUser([Link]);
};

const logout = () => {


[Link]('access_token');
[Link]('refresh_token');
setToken(null);
setUser(null);
};

return (
<[Link] value={{ user, token, login, logout, isAuthenticated:
!!token }}>
{children}
</[Link]>
);
}

export function useAuth() {


return useContext(AuthContext);
}

// ── FILE: frontend/src/hooks/[Link]
─────────────────────────────────────
import { useState, useEffect } from 'react';
import api from '../api/axiosConfig';

function useFetch(url, params = {}) {


const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
let cancelled = false; // prevent state updates after unmount

const fetchData = async () => {


try {
setLoading(true);

Page 30 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

const res = await [Link](url, { params });


if (!cancelled) setData([Link]);
} catch (err) {
if (!cancelled) setError([Link]?.data ?? [Link]);
} finally {
if (!cancelled) setLoading(false);
}
};

fetchData();
return () => { cancelled = true; }; // cleanup on unmount
}, [url]);

return { data, loading, error };


}

export default useFetch;

// ── FILE: frontend/src/components/[Link]
─────────────────────────
import React from 'react';
import { Navigate, useLocation } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';

function ProtectedRoute({ children }) {


const { isAuthenticated } = useAuth();
const location = useLocation();

if (!isAuthenticated) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}

export default ProtectedRoute;

// ── FILE: frontend/src/[Link]
────────────────────────────────────────────────
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { AuthProvider } from './context/AuthContext';
import ProtectedRoute from './components/ProtectedRoute';
import CoursesPage from './pages/CoursesPage';
import CourseDetail from './pages/CourseDetail';
import LoginPage from './pages/LoginPage';
import Dashboard from './pages/Dashboard';
import Navbar from './components/Navbar';

function App() {
return (
<BrowserRouter>
<AuthProvider>
<Navbar />
<Routes>
<Route path="/" element={<CoursesPage />} />
<Route path="/courses/:id" element={<CourseDetail />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/dashboard" element={<ProtectedRoute><Dashboard
/></ProtectedRoute>} />

Page 31 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

</Routes>
</AuthProvider>
</BrowserRouter>
);
}

export default App;

// ── FILE: frontend/src/pages/[Link]
───────────────────────────────────
import React from 'react';
import { useForm } from 'react-hook-form';
import { useNavigate, useLocation } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';

function LoginPage() {
const { register, handleSubmit, formState: { errors, isSubmitting } } =
useForm();
const { login } = useAuth();
const navigate = useNavigate();
const location = useLocation();
const from = [Link]?.from?.pathname ?? '/dashboard';

const onSubmit = async (data) => {


try {
await login([Link], [Link]);
navigate(from, { replace: true });
} catch {
alert('Invalid credentials');
}
};

return (
<div className="container mt-5" style={{ maxWidth: 400 }}>
<h3>Login</h3>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="mb-3">
<input className="form-control" placeholder="Username"
{...register('username', { required: 'Username is required' })} />
{[Link] && <small className="text-
danger">{[Link]}</small>}
</div>
<div className="mb-3">
<input type="password" className="form-control"
placeholder="Password"
{...register('password', {
required: 'Password is required',
minLength: { value: 6, message: 'Min 6 characters' },
})} />
{[Link] && <small className="text-
danger">{[Link]}</small>}
</div>
<button type="submit" className="btn btn-primary w-100"
disabled={isSubmitting}>
{isSubmitting ? 'Logging in...' : 'Login'}
</button>
</form>
</div>
);
}

Page 32 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

export default LoginPage;

7.2 Common Mistakes


• Placing AuthProvider outside BrowserRouter — causes useNavigate to fail inside
AuthProvider
• Creating context value objects inline in Provider render — causes all consumers to re-
render on every parent render; memoize with useMemo
• Using array index as key in list renders — causes bugs when list order changes
• Forgetting the cleanup function in useEffect — causes state updates on unmounted
components

7.3 Practice Exercises


• Build a useEnrollments() custom hook that fetches the current user's enrolled courses
• Add a RegistrationPage with React Hook Form that validates email format and password
confirmation match
• Create an error boundary class component that catches JavaScript errors and shows a
friendly fallback UI

Chapter 8: PostgreSQL & Django


PostgreSQL is the production database of choice for Django applications. Unlike SQLite (the
default), PostgreSQL supports concurrent writes, row-level locking, full-text search, JSONB
columns, window functions, and horizontal read scaling through replication. The psycopg2
library (install psycopg2-binary for development, the compiled psycopg2 package for production)
is the Python adapter that Django uses to communicate with PostgreSQL. Connecting Django to
PostgreSQL requires a running PostgreSQL server, a database, a user, and the connection
parameters set in [Link] DATABASES.
Django migrations are PostgreSQL-aware: they generate DDL statements (CREATE TABLE,
ALTER TABLE, ADD COLUMN) that PostgreSQL executes. Once a migration is applied, the
corresponding migration file should never be deleted or manually edited, because Django tracks
which migrations have been applied in the django_migrations table. To reverse a migration, run
[Link] migrate app_name 0002 (the previous migration number) — Django runs the
migration's database_backwards() method. For complex schema changes on live databases,
consider creating data migrations ([Link] makemigrations --empty app_name) that
transform data between schema versions.
Performance in Django-PostgreSQL applications is almost always about query optimisation.
The most common issue is the N+1 query problem: a loop that fires one SELECT per iteration.
Use select_related() for ForeignKey/OneToOne fields (generates a SQL JOIN) and
prefetch_related() for ManyToMany/reverse ForeignKey (generates two queries and joins in
Python). Use .values() or .values_list() when you only need specific columns rather than full
model instances. Add database indexes with db_index=True on fields used in WHERE clauses
or ORDER BY. Use .explain() in the Django shell to view the PostgreSQL query plan and
identify sequential scans.

Page 33 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

Django's ORM covers 95% of use cases, but sometimes raw SQL is the right tool — for
complex aggregations, window functions, or database-specific features. [Link]() runs
raw SQL and returns a cursor. [Link] shows all SQL executed in the current
request (only in DEBUG mode). The .raw() method runs a raw SELECT and returns model
instances. For performance-critical reporting queries that do not map cleanly to the ORM, write
raw SQL in a dedicated data access module, not scattered across views.

8.1 PostgreSQL Setup Commands


• sudo apt install postgresql postgresql-contrib — install on Ubuntu
• sudo -u postgres psql — open PostgreSQL shell
• CREATE DATABASE mits_db;
• CREATE USER mits_user WITH PASSWORD 'pass123';
• GRANT ALL PRIVILEGES ON DATABASE mits_db TO mits_user;
• pip install psycopg2-binary — install Python adapter

8.2 Comprehensive Example — PostgreSQL Config, Indexes, Raw


SQL
# ── FILE: myproject/[Link] — PostgreSQL DATABASE config ─────────────────
from decouple import config

DATABASES = {
'default': {
'ENGINE': '[Link]',
'NAME': config('DB_NAME'),
'USER': config('DB_USER'),
'PASSWORD': config('DB_PASSWORD'),
'HOST': config('DB_HOST', default='localhost'),
'PORT': config('DB_PORT', default='5432'),
'CONN_MAX_AGE': 60, # reuse connections for up to 60 seconds
}
}

# ── FILE: courses/[Link] — Adding Indexes ─────────────────────────────────


from [Link] import models

class Course([Link]):
title = [Link](max_length=200, db_index=True)
price = [Link](max_digits=8, decimal_places=2)
is_published = [Link](default=False, db_index=True)
created_at = [Link](auto_now_add=True)

class Meta:
# Composite index for the common query:
# [Link](is_published=True).order_by('-created_at')
indexes = [
[Link](fields=['is_published', '-created_at'],
name='published_date_idx'),
]

# ── FILE: courses/[Link] — Raw SQL and Query Optimisation ────────────────


from [Link] import connection
from [Link] import Count, Avg

Page 34 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

def get_enrollment_stats():
"""
Report: enrollment counts per course using raw SQL.
Always use parameterised queries (%s placeholder) to prevent SQL injection.
"""
with [Link]() as cursor:
[Link]("""
SELECT
[Link],
[Link],
COUNT([Link]) AS enrollment_count,
AVG([Link]) AS avg_progress,
SUM(CASE WHEN [Link] THEN 1 ELSE 0 END) AS completions
FROM courses_course AS c
LEFT JOIN courses_enrollment AS e ON e.course_id = [Link]
WHERE c.is_published = TRUE
GROUP BY [Link], [Link]
ORDER BY enrollment_count DESC
LIMIT 10
""")
columns = [col[0] for col in [Link]]
return [dict(zip(columns, row)) for row in [Link]()]

def optimised_course_list():
"""
select_related + annotate eliminates N+1 queries.
Without optimisation (100 courses): 201 queries.
With this implementation: 1 query with JOINs and aggregations.
"""
from .models import Course
return (
[Link]
.filter(is_published=True)
.select_related('category')
.annotate(
student_count=Count('students'),
avg_progress=Avg('enrollment__progress'),
)
.order_by('-student_count')
)

def explain_query():
"""
Print the PostgreSQL query plan to find missing indexes.
'Seq Scan' means no index used. 'Index Scan' is optimal.
"""
from .models import Course
qs = [Link](is_published=True).order_by('-created_at')
print([Link](verbose=True, analyze=True))

8.3 Common Mistakes


• Using SQLite in development but PostgreSQL in production — SQL behaves differently;
develop on PostgreSQL from day one
• Deleting migration files to clean up — breaks [Link] migrate on fresh databases; use
squashmigrations instead
• Not using parameterised queries in raw SQL — creates SQL injection vulnerabilities

Page 35 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

• Running [Link] migrate without a backup on production — always pg_dump before


schema changes

8.4 Practice Exercises


• Write a query using annotate() that returns each Category with the count of its published
courses
• Use .explain() to compare query plans for [Link]() vs
[Link](is_published=True)
• Write a data migration that sets is_published=True for all courses created before a specific
date

Chapter 9: Authentication — JWT


JSON Web Tokens (JWT) are the standard mechanism for stateless authentication in REST
APIs. When a user logs in, the server generates two tokens: a short-lived access token
(typically 15–60 minutes) and a longer-lived refresh token (typically 7–30 days). The client
stores both in localStorage (or httpOnly cookies for higher security) and sends the access token
in the Authorization header as "Bearer <token>" on every API request. Because the token is
self-contained — it carries the user ID, expiry time, and a cryptographic signature — the server
can verify the token without hitting the database on every request, enabling horizontal scaling.
The djangorestframework-simplejwt package integrates JWT authentication into Django REST
Framework with minimal configuration. After installing it and adding the token URLs to [Link],
the /api/token/ endpoint accepts username and password and returns access and refresh
tokens. The /api/token/refresh/ endpoint accepts a refresh token and returns a new access
token — this is how the client stays logged in without re-entering credentials. The SIMPLE_JWT
settings dict in [Link] controls token lifetimes, signing algorithm, and the claims included in
the token payload.
On the React side, token management follows a clear lifecycle. After a successful login POST,
store both tokens in localStorage. The Axios request interceptor reads the access token and
attaches it to every outgoing request. When a request returns 401 Unauthorized, an Axios
response interceptor calls /api/token/refresh/ with the refresh token to get a new access token,
stores the new token, and retries the original request — all transparently without the user seeing
an error. If the refresh token has also expired, redirect to the login page. This pattern is called
silent token refresh and is the gold standard for JWT-based SPAs.
Protected routes in React are implemented with a ProtectedRoute component (shown in
Chapter 7) that reads the authentication state from AuthContext and redirects unauthenticated
users to /login. On the Django side, DRF's IsAuthenticated permission class rejects requests
without a valid Bearer token with a 401 response. Custom views can also access [Link]
to get the authenticated user object for ownership checks and personalized data queries.

9.1 JWT Flow Summary


• 1. POST /api/token/ with username + password → receive { access, refresh }
• 2. Store access_token and refresh_token in localStorage
• 3. Add Authorization: Bearer <access> header to every API request
• 4. When access token expires → POST /api/token/refresh/ with refresh token

Page 36 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

• 5. Store new access token; retry failed request


• 6. When refresh token expires → redirect to login page

9.2 Comprehensive Example — simplejwt Setup and React Token


Management
# ── INSTALL ──────────────────────────────────────────────────────────────────
# pip install djangorestframework-simplejwt

# ── FILE: myproject/[Link] additions ────────────────────────────────────


from datetime import timedelta

SIMPLE_JWT = {
# ACCESS_TOKEN_LIFETIME: token is valid for 60 minutes
# After expiry, the client must use the refresh token to get a new access
token
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),

# REFRESH_TOKEN_LIFETIME: client can get new access tokens for 7 days


# After 7 days, the user must log in again
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),

# ROTATE_REFRESH_TOKENS: issue a new refresh token on every refresh call


# Prevents refresh token replay attacks
'ROTATE_REFRESH_TOKENS': True,

# BLACKLIST_AFTER_ROTATION: old refresh tokens become invalid after


rotation
# Requires adding 'rest_framework_simplejwt.token_blacklist' to
INSTALLED_APPS
'BLACKLIST_AFTER_ROTATION': True,

# AUTH_HEADER_TYPES: the prefix before the token in the Authorization


header
# Standard is 'Bearer': Authorization: Bearer <token>
'AUTH_HEADER_TYPES': ('Bearer',),

'ALGORITHM': 'HS256', # HMAC-SHA256 signing algorithm


'SIGNING_KEY': SECRET_KEY, # uses Django's SECRET_KEY to sign
tokens
}

REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.[Link]',
],
}

# ── FILE: myproject/[Link] ──────────────────────────────────────────────────


from [Link] import path, include
from rest_framework_simplejwt.views import TokenObtainPairView,
TokenRefreshView

urlpatterns = [
# POST /api/token/ → { "access": "...", "refresh": "..." }
path('api/token/', TokenObtainPairView.as_view(),
name='token_obtain_pair'),
# POST /api/token/refresh/ with body { "refresh": "..." } → { "access":
"..." }

Page 37 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

path('api/token/refresh/', TokenRefreshView.as_view(),
name='token_refresh'),
path('api/', include('[Link]')),
]

# ── FILE: users/[Link] — custom /api/auth/me/ endpoint ────────────────────


from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated

class MeView(APIView):
"""
GET /api/auth/me/
Returns the profile of the currently authenticated user.
The JWTAuthentication class decodes the Bearer token and sets [Link].
"""
permission_classes = [IsAuthenticated]

def get(self, request):


user = [Link]
return Response({
'id': [Link],
'username': [Link],
'email': [Link],
'is_staff': user.is_staff,
'first_name': user.first_name,
'last_name': user.last_name,
})

9.3 React Silent Token Refresh


// ── FILE: frontend/src/api/[Link] — with silent token refresh
────────
// This pattern transparently refreshes the access token when it expires,
// retrying the original failed request without any user interaction.

import axios from 'axios';

const api = [Link]({


baseURL: '[Link]
timeout: 10000,
});

// REQUEST interceptor: attach current access token to every request


[Link]((config) => {
const token = [Link]('access_token');
if (token) [Link] = `Bearer ${token}`;
return config;
});

// Track whether a refresh is already in progress to prevent multiple


// simultaneous refresh calls (race condition when many requests expire at
once)
let isRefreshing = false;
let refreshSubscribers = []; // queue of callbacks waiting for new token

const subscribeTokenRefresh = (cb) => [Link](cb);


const onTokenRefreshed = (token) => [Link](cb =>
cb(token));

Page 38 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

// RESPONSE interceptor: handle 401 errors by refreshing the token


[Link](
(response) => response, // pass through successful responses unchanged

async (error) => {


const originalRequest = [Link];

// Only attempt refresh for 401 errors that haven't already been retried
if ([Link]?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true; // mark to prevent infinite retry loop

if (isRefreshing) {
// Another request is already refreshing — queue this request
return new Promise((resolve) => {
subscribeTokenRefresh((token) => {
[Link] = `Bearer ${token}`;
resolve(api(originalRequest));
});
});
}

isRefreshing = true;
const refreshToken = [Link]('refresh_token');

try {
const res = await
[Link]('[Link] {
refresh: refreshToken,
});

const newAccessToken = [Link];


[Link]('access_token', newAccessToken);

// Notify all queued requests with the new token


onTokenRefreshed(newAccessToken);
refreshSubscribers = [];
isRefreshing = false;

// Retry the original request with the new token


[Link] = `Bearer ${newAccessToken}`;
return api(originalRequest);

} catch (refreshError) {
// Refresh token also expired — force logout
isRefreshing = false;
[Link]('access_token');
[Link]('refresh_token');
[Link] = '/login'; // hard redirect to clear React
state
return [Link](refreshError);
}
}

return [Link](error);
}
);

export default api;

Page 39 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

9.4 Common Mistakes


• Storing tokens in localStorage when XSS is a concern — for high-security apps, use
httpOnly cookies
• Not rotating refresh tokens — allows replay attacks if a refresh token is stolen
• Setting ACCESS_TOKEN_LIFETIME too long (e.g. 24 hours) — defeats the purpose of
short-lived tokens
• Not handling 401 in the Axios interceptor — users see cryptic "Unauthorized" errors
instead of auto-refresh

9.5 Practice Exercises


• Implement a registration endpoint using Django's [Link].create_user() and return
JWT tokens immediately after registration
• Add a logout endpoint that blacklists the refresh token using simplejwt's BlacklistMixin
• Test the silent refresh: set ACCESS_TOKEN_LIFETIME to 30 seconds, log in, wait 30
seconds, and verify API calls still work

Chapter 10: File Uploads, Email, and Celery


File uploads in Django are handled through the MEDIA_ROOT and MEDIA_URL settings. When
a user uploads a file, Django saves it to the filesystem under MEDIA_ROOT and stores the
relative path in the ImageField or FileField database column. The Pillow library is required for
ImageField — it validates that the uploaded file is a valid image and can resize it. In views and
serializers, [Link] contains uploaded files separately from [Link] data. For DRF
serializers, the ImageField serializer automatically handles multipart form data. In production,
you should store uploaded files on object storage (AWS S3, DigitalOcean Spaces) rather than
the local filesystem to avoid losing files when the server is rebuilt.
Django's email framework provides a consistent API regardless of the backend (SMTP,
SendGrid, Amazon SES, console output for development). The EMAIL_BACKEND,
EMAIL_HOST, EMAIL_PORT, EMAIL_HOST_USER, and EMAIL_HOST_PASSWORD settings
configure the connection. The send_mail() function is the simplest way to send a single email.
EmailMultiAlternatives allows sending both a plain-text and an HTML version in the same email.
For transactional emails like password resets and enrollment confirmations, always include a
plain-text fallback because some email clients do not render HTML.
Celery is a distributed task queue that runs Python functions asynchronously in background
worker processes. It is essential for any task that takes more than a few hundred milliseconds
— sending emails, processing uploaded images, generating reports, or calling third-party APIs.
Without Celery, these tasks run synchronously inside the HTTP request-response cycle, making
the user wait. With Celery, the view creates a task (a database record in Redis or RabbitMQ),
returns an immediate 202 Accepted response, and a worker process picks up and executes the
task in the background. Redis is the most common message broker for Celery in Django
projects.
A Celery task is a regular Python function decorated with @shared_task (for reusable apps) or
@[Link]. The .delay() method enqueues the task asynchronously. The .apply_async()
method provides more control: you can specify countdown (delay in seconds), eta (exact
execution time), retry logic, and routing to specific queues. Celery Beat is a scheduler that

Page 40 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

triggers periodic tasks on a cron-like schedule — useful for daily report emails, nightly database
cleanup, or recurring data synchronisation with third-party services.

10.1 Comprehensive Example — File Upload, Email, and Celery Task


# ── FILE: courses/[Link] — ImageField for course thumbnail ────────────────
from [Link] import models

class Course([Link]):
title = [Link](max_length=200)
# ImageField requires Pillow: pip install pillow
# upload_to: relative path inside MEDIA_ROOT where files are saved
# Files are saved to: MEDIA_ROOT/thumbnails/2024/01/[Link]
thumbnail = [Link](
upload_to='thumbnails/%Y/%m/', # %Y/%m/ creates year/month
subdirectories
blank=True,
null=True,
)

# ── FILE: courses/[Link] — handling file uploads in DRF ──────────────


from rest_framework import serializers
from .models import Course

class CourseUploadSerializer([Link]):
class Meta:
model = Course
fields = ['id', 'title', 'thumbnail']

def validate_thumbnail(self, value):


# Validate file size: reject images larger than 5 MB
max_size = 5 * 1024 * 1024 # 5 MB in bytes
if [Link] > max_size:
raise [Link]('Image must be smaller than 5
MB.')
return value

# ── FILE: courses/[Link] — file upload endpoint ────────────────────────────


from rest_framework.parsers import MultiPartParser, FormParser
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import permissions
from .serializers import CourseUploadSerializer

class CourseUploadView(APIView):
# MultiPartParser handles multipart/form-data (file uploads)
# FormParser handles application/x-www-form-urlencoded
parser_classes = [MultiPartParser, FormParser]
permission_classes = [[Link]]

def post(self, request, *args, **kwargs):


# [Link] contains the uploaded file objects
# [Link] contains both text fields and file objects when using
MultiPartParser
serializer = CourseUploadSerializer(data=[Link])
if serializer.is_valid():
course = [Link]()
return Response(CourseUploadSerializer(course).data, status=201)

Page 41 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

return Response([Link], status=400)

# ── FILE: myproject/[Link] — media file serving ────────────────────────


import os
MEDIA_URL = '/media/'
MEDIA_ROOT = [Link](BASE_DIR, 'media')

# ── FILE: myproject/[Link] — serve media files in development ───────────────


from [Link] import settings
from [Link] import static

urlpatterns = [
# ... your URL patterns ...
]
# Django's development server does NOT serve media files automatically.
# This appends media URL patterns; in production, Nginx serves /media/
directly.
if [Link]:
urlpatterns += static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)

# ── FILE: notifications/[Link] — Celery tasks ──────────────────────────────


# pip install celery redis
# Run workers: celery -A myproject worker -l info

from celery import shared_task


from [Link] import EmailMultiAlternatives
from [Link] import render_to_string

@shared_task(bind=True, max_retries=3)
def send_enrollment_email(self, student_email, student_name, course_title):
"""
Celery task: send enrollment confirmation email in the background.
bind=True gives access to self for retry logic.
max_retries=3: retry up to 3 times if the SMTP server is down.
"""
try:
subject = f'You are enrolled in {course_title}'
# Render HTML and plain-text versions from templates
html_content = render_to_string('emails/[Link]', {
'student_name': student_name,
'course_title': course_title,
})
text_content = f'Hi {student_name}, you have enrolled in
{course_title}.'

# EmailMultiAlternatives: send both plain-text and HTML


email = EmailMultiAlternatives(
subject = subject,
body = text_content, # plain-text body (required
fallback)
from_email= 'noreply@[Link]',
to = [student_email],
)
email.attach_alternative(html_content, 'text/html') # attach HTML
version
[Link]()

except Exception as exc:

Page 42 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

# Retry with exponential backoff: 60s, 120s, 240s


raise [Link](exc=exc, countdown=60 * (2 ** [Link]))

# ── FILE: courses/[Link] — enroll view triggering Celery task ──────────────


from [Link] import send_enrollment_email

def enroll_in_course(request, pk):


course = get_object_or_404(Course, pk=pk)
enrollment, created = [Link].get_or_create(
student=[Link], course=course
)
if created:
# .delay() enqueues the task in Redis; returns immediately
# The view does NOT wait for the email to be sent
send_enrollment_email.delay(
student_email=[Link],
student_name=[Link].get_full_name(),
course_title=[Link],
)
return JsonResponse({'enrolled': created})

# ── FILE: myproject/[Link] — Celery app configuration ─────────────────────


import os
from celery import Celery

# Set the default Django settings module for the celery command-line program
[Link]('DJANGO_SETTINGS_MODULE', '[Link]')

app = Celery('myproject')

# Load task modules from all registered Django apps


# CELERY_BROKER_URL in [Link] points to Redis: 'redis://localhost:6379/0'
app.config_from_object('[Link]:settings', namespace='CELERY')
app.autodiscover_tasks() # finds [Link] in every INSTALLED_APP

10.2 Email and Celery Settings


# ── FILE: .env additions ─────────────────────────────────────────────────────
EMAIL_HOST=[Link]
EMAIL_PORT=587
EMAIL_HOST_USER=your@[Link]
EMAIL_HOST_PASSWORD=your-app-password
CELERY_BROKER_URL=redis://localhost:6379/0

# ── FILE: myproject/[Link] additions ────────────────────────────────────


EMAIL_BACKEND = '[Link]'
EMAIL_HOST = config('EMAIL_HOST')
EMAIL_PORT = config('EMAIL_PORT', cast=int)
EMAIL_HOST_USER = config('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD')
EMAIL_USE_TLS = True
DEFAULT_FROM_EMAIL = config('EMAIL_HOST_USER')

# Celery: use Redis as the message broker


CELERY_BROKER_URL = config('CELERY_BROKER_URL',
default='redis://localhost:6379/0')
CELERY_RESULT_BACKEND = CELERY_BROKER_URL

Page 43 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Asia/Kolkata'

10.3 Common Mistakes


• Sending emails synchronously in views — blocks the response for seconds; always use
Celery
• Storing uploaded files on the local server filesystem in production — files are lost on
redeploy; use S3
• Not setting max_retries on Celery tasks — a permanent SMTP failure retries forever
• Forgetting to start the Celery worker — tasks are queued but never executed

10.4 Practice Exercises


• Create a Celery task that resizes uploaded course thumbnails to 800x450 pixels using
Pillow
• Set up Celery Beat to send a weekly "new courses" digest email to all registered users
• Add a progress field to tasks and use the Celery task state to show upload progress to the
user

Chapter 11: Deployment


Deploying a Django + React + PostgreSQL application to a production Ubuntu VPS involves
coordinating several components: Gunicorn serves Django as a WSGI application server
handling multiple worker processes; Nginx acts as a reverse proxy sitting in front of Gunicorn,
serving static files and SSL termination; PostgreSQL runs as a database server; Celery and
Redis run as background task workers; and the React frontend is built into static files and
served by Nginx. Each component runs as a systemd service so it starts automatically on server
reboot and can be managed with systemctl start/stop/restart.
Environment variables are the correct mechanism for separating configuration from code. The
python-decouple library reads variables from a .env file in development and from the operating
system environment in production. On the VPS, you set environment variables in the systemd
service files or in a /etc/environment file. Never commit .env files or hardcoded secrets to git —
use a .[Link] file with placeholder values to document required variables. Whitenoise is
the easiest way to serve Django's static files in production: after running [Link]
collectstatic, Whitenoise serves the files from the STATIC_ROOT directory with proper caching
headers.
SSL/TLS encryption is non-negotiable for production applications. Certbot (the Let's Encrypt
client) obtains and automatically renews free SSL certificates. Once installed, certbot --nginx -d
[Link] modifies the Nginx configuration to redirect HTTP to HTTPS and serve the
certificate. Let's Encrypt certificates expire after 90 days; Certbot installs a systemd timer that
automatically renews them before expiry. After SSL is configured, set Django's
SECURE_SSL_REDIRECT = True, SECURE_HSTS_SECONDS = 31536000, and
SESSION_COOKIE_SECURE = CSRF_COOKIE_SECURE = True for hardened HTTPS
security.

Page 44 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

Docker is the modern approach to deployment that solves the "works on my machine" problem
by packaging the application and all its dependencies into isolated containers. A Dockerfile
defines the image for the Django application. [Link] orchestrates multiple
containers — Django, PostgreSQL, Redis, Celery, Nginx — as a single unit. docker-compose
up starts the entire stack with one command. Docker images are portable: build once, deploy to
any server running Docker. In production, Docker Compose is appropriate for single-server
deployments; Kubernetes manages containerised applications across multiple servers for high
availability.

11.1 Deployment Checklist


• Set DEBUG=False and ALLOWED_HOSTS to your domain in production
• Use python-decouple for all secrets; set environment variables on the server
• Run [Link] collectstatic to gather static files into STATIC_ROOT
• Configure Whitenoise in MIDDLEWARE to serve static files
• Install Gunicorn: pip install gunicorn
• Create a Gunicorn systemd service file
• Install and configure Nginx as reverse proxy
• Run certbot --nginx to get SSL certificate
• Set SECURE_SSL_REDIRECT, HSTS, and secure cookie flags
• Set up Celery and Redis as systemd services

11.2 Comprehensive Example — Gunicorn, Nginx, systemd, and


Docker
# ── GUNICORN: systemd service file ───────────────────────────────────────────
# FILE: /etc/systemd/system/[Link]
#
# Gunicorn is a production WSGI server. It runs multiple worker processes
# to handle concurrent requests. [Link] runserver is NOT safe for
production.
#
# [Unit] — metadata and dependencies
# [Service]— how to start/stop the process
# [Install]— when to auto-start (WantedBy=[Link] = on boot)

# [Unit]
# Description=Gunicorn daemon for Django
# After=[Link]
#
# [Service]
# User=ubuntu
# Group=www-data
# WorkingDirectory=/home/ubuntu/myproject
# # Load environment variables from .env file
# EnvironmentFile=/home/ubuntu/myproject/.env
# # --workers: 2*CPU+1 is the recommended formula for CPU-bound apps
# # --bind: listen on Unix socket (faster than TCP for local Nginx connections)
# ExecStart=/home/ubuntu/myproject/venv/bin/gunicorn # --workers 3 # --
bind unix:/run/[Link] # --access-logfile
/var/log/gunicorn/[Link] # --error-logfile /var/log/gunicorn/[Link]
# [Link]:application
# Restart=on-failure
#

Page 45 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

# [Install]
# WantedBy=[Link]

# ── NGINX: virtual host configuration ────────────────────────────────────────


# FILE: /etc/nginx/sites-available/myproject
#
# Nginx handles: SSL termination, serving static/media files, proxying
# API requests to Gunicorn. This keeps Django focused on Python, not file
serving.
#
# server {
# listen 80;
# server_name [Link];
# # Certbot will replace this block with an HTTPS redirect after: certbot
--nginx
# return 301 [Link]
# }
#
# server {
# listen 443 ssl;
# server_name [Link];
#
# # SSL certificate paths (filled in by Certbot)
# ssl_certificate /etc/letsencrypt/live/[Link]/[Link];
# ssl_certificate_key /etc/letsencrypt/live/[Link]/[Link];
#
# # Serve React build output as static files from /var/www/frontend/
# root /var/www/frontend;
# index [Link];
#
# # All non-API routes served by React (client-side routing)
# location / {
# try_files $uri $uri/ /[Link];
# }
#
# # Django static files (collected by [Link] collectstatic)
# location /static/ {
# alias /home/ubuntu/myproject/staticfiles/;
# expires 1y;
# add_header Cache-Control "public, immutable";
# }
#
# # Django media files (user uploads)
# location /media/ {
# alias /home/ubuntu/myproject/media/;
# }
#
# # Proxy API requests to Gunicorn via Unix socket
# location /api/ {
# proxy_pass [Link]
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header X-Forwarded-Proto $scheme;
# }
# }

# ── DOCKER: Dockerfile for Django ────────────────────────────────────────────


# FILE: Dockerfile
#

Page 46 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

# FROM python:3.11-slim — start from official Python image (small


variant)
# WORKDIR /app — all subsequent commands run in /app
# COPY [Link] .
# RUN pip install --no-cache-dir -r [Link]
# COPY . .
# RUN python [Link] collectstatic --noinput
# EXPOSE 8000
# CMD ["gunicorn", "--workers", "3", "--bind", "[Link]:8000",
"[Link]:application"]

# ── DOCKER COMPOSE: [Link]


────────────────────────────────────────
# version: '3.9'
# services:
# db:
# image: postgres:15
# environment:
# POSTGRES_DB: mits_db
# POSTGRES_USER: mits_user
# POSTGRES_PASSWORD: securepass
# volumes:
# - postgres_data:/var/lib/postgresql/data
#
# redis:
# image: redis:7-alpine
#
# web:
# build: .
# command: gunicorn --bind [Link]:8000 [Link]:application
# volumes:
# - .:/app
# ports:
# - "8000:8000"
# env_file:
# - .env
# depends_on:
# - db
# - redis
#
# celery:
# build: .
# command: celery -A myproject worker -l info
# env_file:
# - .env
# depends_on:
# - db
# - redis
#
# volumes:
# postgres_data:

# ── DJANGO: production settings additions ────────────────────────────────────


ALLOWED_HOSTS = config('ALLOWED_HOSTS').split(',')

# Force HTTPS redirects and set security headers


SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000 # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SESSION_COOKIE_SECURE = True

Page 47 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

CSRF_COOKIE_SECURE = True

# Whitenoise: serve static files with compression and long-term caching


# Add to MIDDLEWARE immediately after SecurityMiddleware
# '[Link]'
STATICFILES_STORAGE = '[Link]'

11.3 Common Mistakes


• Leaving DEBUG=True in production — exposes full stack traces and settings to attackers
• Not running collectstatic before deployment — Django cannot find static files
• Using runserver in production — it is single-threaded, not hardened, and not meant for
public traffic
• Forgetting to renew SSL certificates — Let's Encrypt certificates expire after 90 days; use
Certbot auto-renew
• Not configuring ALLOWED_HOSTS — leaves the application vulnerable to HTTP Host
header injection

11.4 Practice Exercises


• Deploy the application to a VPS using Gunicorn + Nginx and verify it serves HTTPS traffic
• Set up a Celery worker as a systemd service so it restarts automatically on server reboot
• Build a Docker Compose stack with Django, PostgreSQL, Redis, and Celery and verify all
services start correctly

Chapter 12: Capstone — MITS E-Learning Platform


The capstone project brings together every concept from the previous eleven chapters into a
production-quality, end-to-end web application: the MITS E-Learning Platform. The platform
allows instructors to create courses with lessons and quizzes; students can browse and enrol in
courses, track their progress through lessons, take quizzes, and receive certificates on
completion. The system is secured with JWT authentication, backed by PostgreSQL, served by
Django REST Framework, and visualised through a React frontend with React Router and
Context API.
The Django backend is organised into four applications: users (registration, login, profiles),
courses (courses, lessons, categories), enrollments (enrollment records, progress tracking), and
quizzes (questions, answers, attempts). Each app exposes a RESTful API through DRF
ViewSets and a Router. The User model extends Django's AbstractUser to add a profile picture,
bio, and role field distinguishing students from instructors. Signals fire after enrollment creation
to send the welcome email via Celery. The admin panel provides full CRUD for content
management without building a separate CMS.
The React frontend is structured with a clear separation between pages (route-level
components), components (reusable UI pieces), hooks (data-fetching and business logic),
context (global state), and api (Axios instance and endpoint functions). The Dashboard page
shows enrolled courses with progress bars. The CoursePlayer page shows the video lesson
and marks lessons as complete via API calls. The Quiz page renders questions and submits
answers, receiving a score from the backend. All authenticated routes are wrapped in
ProtectedRoute. The Navbar reads from AuthContext to show login/logout and the username.

Page 48 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

The complete application is deployed on an Ubuntu VPS: Nginx serves the React build and
proxies API calls to Gunicorn; PostgreSQL stores all data; Redis + Celery handle background
email sending and certificate generation; Certbot provides HTTPS. Environment variables are
managed with python-decouple on the backend. The React build uses Vite environment
variables (VITE_ prefix) for the API base URL, so the same build artefact can target different
backend URLs without code changes.

12.1 Complete Backend Architecture


# ── FILE: users/[Link] ────────────────────────────────────────────────────
# Extending AbstractUser adds custom fields without replacing Django's auth
system.
# Always extend AbstractUser from the start — migrating later is painful.

from [Link] import AbstractUser


from [Link] import models

class User(AbstractUser):
ROLE_CHOICES = [('ST', 'Student'), ('IN', 'Instructor'), ('AD', 'Admin')]
role = [Link](max_length=2, choices=ROLE_CHOICES,
default='ST')
bio = [Link](blank=True)
avatar = [Link](upload_to='avatars/', blank=True, null=True)

def is_instructor(self):
return [Link] == 'IN'

# ── FILE: myproject/[Link] ─────────────────────────────────────────────


# Tell Django to use our custom User model instead of the built-in one
AUTH_USER_MODEL = '[Link]'

# ── FILE: courses/[Link] ──────────────────────────────────────────────────


from [Link] import models
from [Link] import User

class Category([Link]):
name = [Link](max_length=100, unique=True)
slug = [Link](unique=True)

def __str__(self): return [Link]

class Course([Link]):
instructor = [Link](User, on_delete=[Link],
related_name='courses')
category = [Link](Category, on_delete=models.SET_NULL,
null=True)
title = [Link](max_length=200)
description = [Link]()
thumbnail = [Link](upload_to='thumbnails/', blank=True)
price = [Link](max_digits=8, decimal_places=2,
default=0)
is_published = [Link](default=False)
created_at = [Link](auto_now_add=True)

class Meta:
ordering = ['-created_at']

def __str__(self): return [Link]

Page 49 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

class Lesson([Link]):
course = [Link](Course, on_delete=[Link],
related_name='lessons')
title = [Link](max_length=200)
video_url = [Link](blank=True)
content = [Link](blank=True)
order = [Link](default=0)
duration = [Link](default=0, help_text='Duration in
minutes')

class Meta:
ordering = ['order']

def __str__(self): return f'{[Link]} — Lesson {[Link]}:


{[Link]}'

class Enrollment([Link]):
student = [Link](User, on_delete=[Link],
related_name='enrollments')
course = [Link](Course, on_delete=[Link],
related_name='enrollments')
enrolled_at = [Link](auto_now_add=True)
completed = [Link](default=False)

class Meta:
unique_together = ('student', 'course')

class LessonProgress([Link]):
enrollment = [Link](Enrollment, on_delete=[Link],
related_name='progress')
lesson = [Link](Lesson, on_delete=[Link])
watched_at = [Link](auto_now_add=True)

class Meta:
unique_together = ('enrollment', 'lesson')

12.2 Complete API ViewSets


# ── FILE: courses/[Link] ─────────────────────────────────────────────
from rest_framework import serializers
from .models import Course, Lesson, Enrollment, LessonProgress, Category

class LessonSerializer([Link]):
class Meta:
model = Lesson
fields = ['id', 'title', 'video_url', 'content', 'order', 'duration']

class CourseListSerializer([Link]):
"""Lightweight serializer for list endpoints — avoids loading all
lessons."""
lesson_count = [Link]()
instructor = [Link]()
category_name = [Link](source='[Link]',
read_only=True)

class Meta:
model = Course
fields = ['id', 'title', 'thumbnail', 'price', 'instructor',
'category_name', 'lesson_count', 'created_at']

Page 50 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

def get_lesson_count(self, obj):


return [Link]()

class CourseDetailSerializer([Link]):
"""Full serializer with nested lessons for the detail endpoint."""
lessons = LessonSerializer(many=True, read_only=True)
instructor = [Link]()

class Meta:
model = Course
fields = ['id', 'title', 'description', 'thumbnail', 'price',
'instructor', 'is_published', 'lessons', 'created_at']

class EnrollmentSerializer([Link]):
course = CourseListSerializer(read_only=True)
progress_percent = [Link]()

class Meta:
model = Enrollment
fields = ['id', 'course', 'enrolled_at', 'completed',
'progress_percent']

def get_progress_percent(self, obj):


total = [Link]()
if total == 0: return 0
done = [Link]()
return round((done / total) * 100)

# ── FILE: courses/[Link] ───────────────────────────────────────────────────


from rest_framework import viewsets, permissions, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters
from .models import Course, Lesson, Enrollment, LessonProgress
from .serializers import (
CourseListSerializer, CourseDetailSerializer,
LessonSerializer, EnrollmentSerializer,
)
from [Link] import send_enrollment_email

class CourseViewSet([Link]):
permission_classes = [[Link]]
filter_backends = [DjangoFilterBackend, [Link]]
filterset_fields = ['category', 'is_published']
search_fields = ['title', 'description']

def get_queryset(self):
return [Link](is_published=True).select_related(
'instructor', 'category'
).prefetch_related('lessons')

def get_serializer_class(self):
# Use lightweight serializer for list; full serializer for detail
if [Link] == 'list':
return CourseListSerializer
return CourseDetailSerializer

Page 51 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

@action(detail=True, methods=['post'],
permission_classes=[[Link]])
def enroll(self, request, pk=None):
"""POST /api/courses/{pk}/enroll/"""
course = self.get_object()
enrollment, created = [Link].get_or_create(
student=[Link], course=course
)
if created:
# Fire background email — does NOT block the response
send_enrollment_email.delay(
[Link],
[Link].get_full_name() or [Link],
[Link],
)
serializer = EnrollmentSerializer(enrollment)
return Response([Link], status=201 if created else 200)

@action(detail=False, methods=['get'],
permission_classes=[[Link]])
def my_courses(self, request):
"""GET /api/courses/my_courses/ — return enrolled courses with
progress"""
enrollments = [Link](
student=[Link]
).select_related('course__instructor',
'course__category').prefetch_related(
'course__lessons', 'progress'
)
serializer = EnrollmentSerializer(enrollments, many=True)
return Response([Link])

class LessonProgressViewSet([Link]):
permission_classes = [[Link]]

@action(detail=True, methods=['post'], url_path='complete')


def mark_complete(self, request, pk=None):
"""POST /api/lessons/{pk}/complete/ — mark a lesson as watched"""
lesson = get_object_or_404(Lesson, pk=pk)
enrollment = get_object_or_404(
Enrollment, student=[Link], course=[Link]
)
[Link].get_or_create(enrollment=enrollment,
lesson=lesson)

# Check if all lessons in the course are now complete


total = [Link]()
done = [Link]()
if done >= total:
[Link] = True
[Link]()

return Response({'lesson_id': [Link], 'completed':


[Link]})

12.3 Complete React Frontend

Page 52 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

// ── FILE: frontend/src/pages/[Link]
───────────────────────────────────
// Student dashboard: shows enrolled courses with progress bars.

import React from 'react';


import { Link } from 'react-router-dom';
import useFetch from '../hooks/useFetch';

function ProgressBar({ percent }) {


return (
<div className="progress" style={{ height: '8px' }}>
<div
className="progress-bar bg-success"
style={{ width: `${percent}%` }}
role="progressbar"
aria-valuenow={percent}
aria-valuemin="0"
aria-valuemax="100"
/>
</div>
);
}

function Dashboard() {
// useFetch custom hook encapsulates loading/error state
const { data, loading, error } = useFetch('/courses/my_courses/');

if (loading) return <div className="spinner-border mt-5" />;


if (error) return <div className="alert alert-danger">Failed to load
dashboard.</div>;

const enrollments = data ?? [];

return (
<div className="container mt-4">
<h2>My Courses</h2>
{[Link] === 0 ? (
<div className="alert alert-info">
You have not enrolled in any courses yet.{' '}
<Link to="/">Browse courses</Link>
</div>
) : (
<div className="row">
{[Link](({ id, course, progress_percent, completed }) => (
<div key={id} className="col-md-4 mb-4">
<div className="card h-100">
{[Link] && (
<img src={[Link]} alt={[Link]}
className="card-img-top" style={{ height: '160px',
objectFit: 'cover' }} />
)}
<div className="card-body d-flex flex-column">
<h5 className="card-title">{[Link]}</h5>
<p className="text-muted small">
{course.lesson_count} lessons
</p>
<div className="mt-auto">
<div className="d-flex justify-content-between mb-1">
<small>Progress</small>
<small>{progress_percent}%</small>
</div>

Page 53 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

<ProgressBar percent={progress_percent} />


{completed && (
<span className="badge bg-success mt-2">Completed</span>
)}
<Link to={`/courses/${[Link]}/learn`}
className="btn btn-primary btn-sm mt-2 w-100">
Continue Learning
</Link>
</div>
</div>
</div>
</div>
))}
</div>
)}
</div>
);
}

export default Dashboard;

// ── FILE: frontend/src/pages/[Link]
────────────────────────────────
// Lesson player: shows video/content and marks lessons as complete.

import React, { useState } from 'react';


import { useParams } from 'react-router-dom';
import useFetch from '../hooks/useFetch';
import api from '../api/axiosConfig';

function CoursePlayer() {
const { id } = useParams();
const { data: course, loading } = useFetch(`/courses/${id}/`);
const [currentLesson, setCurrentLesson] = useState(null);
const [completedIds, setCompletedIds] = useState(new Set());

const selectLesson = (lesson) => setCurrentLesson(lesson);

const markComplete = async (lessonId) => {


try {
const res = await [Link](`/lessons/${lessonId}/complete/`);
setCompletedIds(prev => new Set([...prev, lessonId]));
if ([Link]) {
alert('Congratulations! You have completed this course!');
}
} catch (err) {
[Link]('Failed to mark lesson complete', err);
}
};

if (loading) return <div className="spinner-border mt-5" />;


if (!course) return <div className="alert alert-danger">Course not
found.</div>;

const lessons = [Link] ?? [];


const active = currentLesson ?? lessons[0];

return (
<div className="container-fluid mt-3">
<div className="row">

Page 54 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

{/* Lesson list sidebar */}


<div className="col-md-3 border-end">
<h6 className="fw-bold mb-3">{[Link]}</h6>
<ul className="list-group list-group-flush">
{[Link]((lesson) => (
<li
key={[Link]}
className={`list-group-item list-group-item-action d-flex
justify-content-between
${active?.id === [Link] ? 'active' : ''}
${[Link]([Link]) ? 'text-success' : ''}`}
onClick={() => selectLesson(lesson)}
style={{ cursor: 'pointer' }}
>
<span>{[Link]}. {[Link]}</span>
{[Link]([Link]) && <span>&#10003;</span>}
</li>
))}
</ul>
</div>

{/* Video and content area */}


<div className="col-md-9">
{active ? (
<>
<h4>{[Link]}</h4>
{active.video_url && (
<div className="ratio ratio-16x9 mb-3">
<iframe src={active.video_url} allowFullScreen
title={[Link]} />
</div>
)}
<p>{[Link]}</p>
<button
className="btn btn-success"
onClick={() => markComplete([Link])}
disabled={[Link]([Link])}
>
{[Link]([Link]) ? 'Completed' : 'Mark as
Complete'}
</button>
</>
) : (
<p>Select a lesson to begin.</p>
)}
</div>
</div>
</div>
);
}

export default CoursePlayer;

12.4 Deployment for Capstone


• Backend: Django 5 + DRF + simplejwt on Gunicorn (3 workers), served behind Nginx
• Frontend: Vite build output in /var/www/frontend — served directly by Nginx
• Database: PostgreSQL 15 with optimised indexes on is_published, created_at,
student+course

Page 55 | MITS Academy | [Link]


MITS Academy — Python Full Stack Development

• Background: Celery + Redis for enrollment emails and certificate generation


• SSL: Let's Encrypt via Certbot — auto-renewed by systemd timer
• Env vars: python-decouple (.env on server), VITE_API_URL for React build

12.5 Final Practice Exercises


• Add a Quiz model with Question and Answer sub-models; expose a POST
/api/quizzes/{id}/submit/ endpoint that calculates the score
• Build a certificate generation endpoint using ReportLab (pip install reportlab) that creates
a PDF certificate when a course is completed
• Implement a search endpoint that queries course titles, descriptions, and instructor names
with PostgreSQL full-text search using SearchVector
• Add real-time lesson completion notifications using Django Channels (WebSocket) so the
dashboard updates without page refresh
• Write a comprehensive test suite using pytest-django covering all ViewSet actions with
authenticated and unauthenticated requests

Page 56 | MITS Academy | [Link]

You might also like