Full Web Application0%

Full Web Application

Beginner12 min read•Updated: Jul 11, 2026
Study MaterialsCheatsheet

Django Capstone: Full Web Application

Welcome to the Django for Backend Development Capstone Project. In this three-part capstone, you will architect, build, and secure an enterprise multi-tenant software-as-a-service (SaaS) web platform: "NexusFlow Enterprise". Part 1 focuses on domain data modeling, multi-tenancy architecture, custom user management, and relational database schema design.


1. System Requirements & Domain Model

NexusFlow is an enterprise collaboration platform where organizations (Tenants) manage workspaces, publish documentation articles, and assign staff roles.

Output
┌─────────────────────────────────────────────────────────────┐
│ NEXUSFLOW DOMAIN │
├─────────────────────────────────────────────────────────────┤
│ Tenant (Organization) ◄─── User (CustomUser with Role) │
│ │ │
│ ├──► Workspace (Department / Team Project) │
│ │ │ │
│ │ └──► Document / Article (Rich Text Content) │
│ │ │ │
│ │ └──► AuditLog (Telemetry Trail) │
└─────────────────────────────────────────────────────────────┘

2. Multi-Tenant Architecture & Database Models

Python
# nexus/models.py
import uuid
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.utils.text import slugify
 
# 1. Tenant (Organization)
class Organization(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=150, unique=True)
slug = models.SlugField(max_length=150, unique=True)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
 
def __str__(self):
return self.name
 
# 2. Custom User Model with Multi-Tenancy & Roles
class CustomUser(AbstractUser):
organization = models.ForeignKey(
Organization,
on_delete=models.CASCADE,
related_name="members",
null=True,
blank=True
)
ROLE_CHOICES = [
("admin", "Organization Administrator"),
("editor", "Technical Editor"),
("viewer", "Auditor / Viewer"),
]
role = models.CharField(max_length=20, choices=ROLE_CHOICES, default="viewer")
email = models.EmailField(unique=True)
 
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["username"]
 
def __str__(self):
return f"{self.email} ({self.role})"
 
# 3. Workspace
class Workspace(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
organization = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name="workspaces")
name = models.CharField(max_length=100)
slug = models.SlugField(max_length=100)
description = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
 
class Meta:
unique_together = ("organization", "slug")
 
def __str__(self):
return f"{self.organization.name} / {self.name}"
 
# 4. Document / Article
class Document(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
workspace = models.ForeignKey(Workspace, on_delete=models.CASCADE, related_name="documents")
author = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True, related_name="authored_documents")
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200)
content = models.TextField()
is_published = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
 
class Meta:
unique_together = ("workspace", "slug")
 
def __str__(self):
return self.title

3. Custom QuerySets for Multi-Tenant Isolation

Multi-tenant applications must never leak data between different organizations. Implement a custom manager that scopes all queries strictly to the active tenant:

Python
class TenantScopedQuerySet(models.QuerySet):
def for_organization(self, organization):
return self.filter(workspace__organization=organization)
 
class DocumentManager(models.Manager):
def get_queryset(self):
return TenantScopedQuerySet(self.model, using=self._db)
 
def for_organization(self, organization):
return self.get_queryset().for_organization(organization)

4. Key Architectural Takeaways

  1. 1
    UUID Primary Keys: Using UUIDs for Organization, Workspace, and Document prevents ID enumeration attacks (where competitors guess /workspace/1, /workspace/2).
  2. 2
    Compound Unique Constraints: unique_together = ("organization", "slug") allows two different companies to both have a workspace named "engineering", each with their own isolated URL!

Interactive Quiz

Practice Quiz

Question 1
What security hazard do UUID primary keys mitigate compared to auto-incrementing integers (1, 2, 3)?
Question 2
Why is unique_together = ("workspace", "slug") essential on the Document model?
Question 3
What is the primary operational rule of Multi-Tenant database design?
Question 4
Why is on_delete=models.SET_NULL used for Document author instead of models.CASCADE?
Question 5
How does USERNAME_FIELD = "email" alter authentication?
Next Lesson

CRUD Operations

Continue learning with hands-on practice, examples, and exercises in the upcoming topic.

Related Lessons

Practice Quiz

Test your understanding of this lesson with 5 questions. Each question has one correct answer.

PrevNext