Python Django Full Stack Simply Explained (with Diagrams and Real Code)
Python Django Fullstack: The Essentials in One Article — Real Code, Diagrams and Concrete Steps, Excerpts from a 24-Lesson Course.
A guide that gets straight to the point: Python Django Fullstack broken down with diagrams, concrete examples and tested commands. Everything comes from a structured 8-chapter course — here are the highlights.
tl;dr
- Why Django
- Models and migrations
- Views and URLs
- Templates and forms
- Admin
~$ cat ./parcours.md # Python Django Fullstack — 8 chapters
01
Why Django
→ Django vs Flask vs FastAPI→ Installation et premier projet+ 1 more lessons
02
Models and migrations
→ Models et fields→ Relations+ 1 more lessons
03
Views and URLs
→ Function-based views→ Class-based views (CBV)+ 1 more lessons
04
Templates and forms
→ Template language→ Forms et ModelForms+ 1 more lessons
05
Admin
→ Customizer ModelAdmin→ Actions et filters custom+ 1 more lessons
06
Authentication
→ User model et auth views→ Custom User et Profile+ 1 more lessons
07
Deployment
→ Settings prod/dev→ Gunicorn + Nginx+ 1 more lessons
08
Final project blog ecom
→ Chapter 07 – Final project : Models and admin→ Chapter 07 – Final project : Views and templates+ 1 more lessons
🏁
Final project
→ You leave with a concrete and demonstrable project
Migrations
Basic cycle
# 1. Modify a model class Article(models.Model): title = models.CharField(max_length=200) summary = models.TextField(blank=True) # NEW # 2. Generate the migration python manage.py makemigrations # Migrations for 'blog': # blog/migrations/0002_article_summary.py # 3. Apply python manage.py migrate # 4. Verify python manage.py showmigrations
View the generated SQL
python manage.py sqlmigrate blog 0002 # Output : # BEGIN; # ALTER TABLE blog_article ADD COLUMN summary TEXT NOT NULL DEFAULT ''; # COMMIT;
Revert a migration
python manage.py migrate blog 0001 # Revert to 0001 (undoes 0002) python manage.py migrate blog zero # Cancel everything for the blog app
Data migration
python manage.py makemigrations --empty --name backfill_slugs blog # Edit the generated file: from django.db import migrations from django.utils.text import slugify def backfill_slugs(apps, schema_editor): Article = apps.get_model("blog", "Article") for a in Article.objects.all(): if not a.slug: a.slug = slugify(a.title) a.save() def reverse_func(apps, schema_editor): pass class Migration(migrations.Migration): dependencies = [("blog", "0001_initial")] operations = [migrations.RunPython(backfill_slugs, reverse_func)]
Squashing migrations
# After many migrations, compact them: python manage.py squashmigrations blog 0001 0020 # Creates 0001_squashed_0020 that replaces the 20 files # New developers avoid running 20 migrations
Migrations without downtime
NOTETo add a NOT NULL column without breaking production:
- Deploy 1: Add nullable column
- Deploy 2: Backfill via data migration
- Deploy 3: Update code to use/set the column
- Deploy 4: ALTER NOT NULL migration
Rename a model
# If you rename Article -> Post, Django will suggest # creating a new model + deleting the old one (data loss!) # Solution: edit the generated migration operations = [ migrations.RenameModel("Article", "Post") ] # Same for fields operations = [ migrations.RenameField("Post", "summary", "excerpt") ]
Django vs Flask vs FastAPI
NOTEGoal — Understand Django's philosophy and when to choose it.
Quick comparison
| Criterion | Django | Flask | FastAPI |
|---|---|---|---|
| Philosophy | Batteries included | Micro framework | API first, async |
| ORM | Built-in | External SQLAlchemy | External SQLAlchemy |
| Admin | Auto-generated | Must be coded | Must be coded |
| Auth | Complete | Extensions | JWT must be coded |
| Templates | Django templates | Jinja2 | Jinja2 optional |
| Native async | Partial (4.x) | No | Yes |
| Learning curve | Longer | Very simple | Medium |
When to choose Django
When NOT to choose Django
MTV architecture
+----------+ request +------+ query +-------+
| Browser | -----------> | View | --------> | Model |
+----------+ +------+ +-------+
^ | |
| v v
| +----------+ +-------+
+----------------- | Template |<--------| DB |
+----------+ +-------+
# M = Model (ORM, database)
# T = Template (HTML)
# V = View (control logic, equivalent to Controller in MVC)Django ecosystem
Versions and LTS
NOTEDjango releases:
- Django 5.0+ : current
- Django 4.2 LTS : supported until 2026
- Cycle: new version every 8 months
- LTS every 2 years, supported for 3 years
Companies using Django
Summary
NOTEKey takeaways
- Django = batteries included for complex web apps
- MTV architecture (Model, Template, View)
- Ideal for: SaaS, CMS, e-commerce, dashboards
- Pure API: prefer DRF or FastAPI
Chapter 07 – Final project: Models and admin
Requirements
NOTEGoal — Build a minimalist online store with: catalog, cart, orders, business admin.
Project structure
myshop/
manage.py
myshop/
settings/
urls.py
wsgi.py
accounts/
models.py # Custom User
views.py
urls.py
catalog/
models.py # Category, Product
views.py
admin.py
cart/
cart.py # Cart class (session)
views.py
orders/
models.py # Order, OrderItem
views.py
admin.py
templates/
static/
media/accounts/models.py
from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): phone = models.CharField(max_length=20, blank=True) address = models.TextField(blank=True) city = models.CharField(max_length=100, blank=True) postal_code = models.CharField(max_length=10, blank=True) country = models.CharField(max_length=2, default="CA") class Meta: ordering = ["-date_joined"] def __str__(self): return self.username
catalog/models.py
from django.db import models from django.urls import reverse from django.utils.text import slugify class Category(models.Model): name = models.CharField(max_length=100, unique=True) slug = models.SlugField(max_length=120, unique=True) description = models.TextField(blank=True) parent = models.ForeignKey("self", null=True, blank=True, on_delete=models.CASCADE, related_name="children") class Meta: verbose_name_plural = "Categories" ordering = ["name"] def __str__(self): return self.name def get_absolute_url(self): return reverse("catalog:category", kwargs={"slug": self.slug}) class Product(models.Model): category = models.ForeignKey(Category, on_delete=models.PROTECT, related_name="products") name = models.CharField(max_length=200) slug = models.SlugField(max_length=220, unique=True) description = models.TextField() price = models.DecimalField(max_digits=10, decimal_places=2) image = models.ImageField(upload_to="products/%Y/%m/") stock = models.PositiveIntegerField(default=0) available = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ["-created_at"] indexes = [ models.Index(fields=["slug"]), models.Index(fields=["available", "-created_at"]), ] def __str__(self): return self.name def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.name) super().save(*args, **kwargs) def get_absolute_url(self): return reverse("catalog:product", kwargs={"slug": self.slug}) @property def in_stock(self): return self.available and self.stock > 0
orders/models.py
class Order(models.Model): class Status(models.TextChoices): PENDING = "pending", "Pending" PAID = "paid", "Paid" SHIPPED = "shipped", "Shipped" DELIVERED = "delivered", "Delivered" CANCELED = "canceled", "Canceled" user = models.ForeignKey("accounts.User", on_delete=models.PROTECT, related_name="orders") status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING) address = models.TextField() city = models.CharField(max_length=100) postal_code = models.CharField(max_length=10) country = models.CharField(max_length=2) total = models.DecimalField(max_digits=12, decimal_places=2, default=0) paid_at = models.DateTimeField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ["-created_at"] def __str__(self): return f"Order #{self.id}" class OrderItem(models.Model): order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name="items") product = models.ForeignKey("catalog.Product", on_delete=models.PROTECT) price = models.DecimalField(max_digits=10, decimal_places=2) quantity = models.PositiveIntegerField(default=1) @property def subtotal(self): return self.price * self.quantity
catalog/admin.py
@admin.register(Category) class CategoryAdmin(admin.ModelAdmin): list_display = ["name", "parent", "product_count"] prepopulated_fields = {"slug": ("name",)} search_fields = ["name"] def product_count(self, obj): return obj.products.count() @admin.register(Product) class ProductAdmin(admin.ModelAdmin): list_display = ["name", "category", "price", "stock", "available"] list_filter = ["available", "category", "created_at"] list_editable = ["price", "stock", "available"] search_fields = ["name", "description"] prepopulated_fields = {"slug": ("name",)} list_per_page = 50
orders/admin.py
class OrderItemInline(admin.TabularInline): model = OrderItem extra = 0 readonly_fields = ["product", "price", "quantity", "subtotal"] can_delete = False @admin.register(Order) class OrderAdmin(admin.ModelAdmin): list_display = ["id", "user", "status", "total", "created_at", "paid_at"] list_filter = ["status", "created_at"] search_fields = ["id", "user__username", "user__email"] inlines = [OrderItemInline] actions = ["mark_shipped"] date_hierarchy = "created_at" @admin.action(description="Mark as shipped") def mark_shipped(self, request, queryset): queryset.update(status=Order.Status.SHIPPED)
go-further
This article covers the most useful excerpts — the complete Python Django Fullstack course (8 chapters, 24 lessons, corrected exercises and final project) takes you all the way.
./access-the-full-course free course: Vibe CodingFAQ
How long does it take to learn Python Django Fullstack?
With a structured progression (8 chapters, 24 short practical lessons), you reach an operational level in a few weeks at 30–60 minutes per day. The key is to practice each concept immediately.
Are there any prerequisites?
Basic computer knowledge is enough. If you can use a terminal and read simple code, you're ready.
Where to start concretely?
Reproduce the commands in this article, then follow the complete Python Django Fullstack course: it chains the 24 lessons in order, with exercises and a final project.
./also-read
→ Get started with Portfolio IA SEO Vercel: your first concrete step today→ IA Stripe GitHub SaaS in practice: the code and commands that really matter→ Python Requests APIs explained simply (with diagrams and real code)📬 Want to receive this type of guide every week? Subscribe for free — real code, zero fluff.