Welcome to Aparsoft! This guide will take you from zero to contributing code on the AI Chatbot project. Take it step by step — don't rush, ask questions, and have fun!
- Prerequisites
- Project Overview
- Day 1: Setup
- Day 2: Understand the Architecture
- Day 3: Django Basics
- Day 4: Models & ORM
- Day 5: APIs, Serializers, ViewSets
- Day 6: LangGraph & LangChain
- Day 7: Management Commands
- Your First Task
- Troubleshooting
- Resources
Before you start, make sure you have these installed:
| Tool | Version | Why | Install |
|---|---|---|---|
| Docker Desktop | Latest | Runs PostgreSQL + Redis for you | docker.com |
| Git | Latest | Version control | git-scm.com — see Git & SSH Setup Guide |
| VS Code | Latest | Code editor (recommended) | code.visualstudio.com |
| Python | 3.12+ | Backend language | python.org |
| Node.js | 20+ | Frontend runtime | nodejs.org |
VS Code Extensions you should install:
- Python (Microsoft)
- Django (Baptiste Darthenay)
- Docker (Microsoft)
- GitLens (GitKraken)
- Prettier (Prettier)
- ESLint (Microsoft)
An AI Chatbot web application where users can:
- Chat with an AI assistant (like ChatGPT)
- Upload documents and ask questions about them (RAG — Retrieval Augmented Generation)
- Give feedback on AI responses (thumbs up/down)
- Customize their AI settings (model, temperature, etc.)
┌──────────────────────────────────────────────────────┐
│ Frontend │
│ Next.js 15 + React 19 │
│ (TypeScript, Tailwind CSS) │
└──────────────────────┬───────────────────────────────┘
│ REST API + WebSocket
┌──────────────────────▼───────────────────────────────┐
│ Backend │
│ Django 5.2 + DRF │
│ LangGraph + LangChain │
│ Celery (background tasks) │
└───┬──────────────┬──────────────┬────────────────────┘
│ │ │
▼ ▼ ▼
┌────────┐ ┌──────────┐ ┌──────────────┐
│Postgres│ │ Redis │ │ OpenAI API │
│pgvector│ │ (cache, │ │ (GPT models)│
│ │ │ broker) │ │ │
└────────┘ └──────────┘ └──────────────┘
django-nextjs-chatbot/
├── backend/ ← Django project (YOUR MAIN FOCUS)
│ ├── apps/
│ │ ├── accounts/ ← User authentication
│ │ ├── chatbot/ ← AI chatbot features ⭐
│ │ └── core/ ← Shared utilities
│ ├── config/ ← Django settings
│ └── manage.py ← Django command-line tool
├── frontend/ ← Next.js app
├── docker/ ← Docker init scripts
├── docs/ ← Documentation (this file!)
└── docker-compose.yml ← Docker services config
📖 New to Git or SSH? Follow the Git & SSH Setup Guide first — it covers installation, SSH key generation, and connecting to GitHub on both Windows and WSL/Linux.
git clone <repo-url>
cd django-nextjs-chatbot# Copy the template
cp .env.example .env
# Open it and fill in your OpenAI API key (ask your mentor for one)
# The rest works with defaults for local developmentThis starts PostgreSQL (with pgvector) and Redis. You only need these two — you'll run Django locally for hot-reload.
# Start only the databases
docker compose up db redis -d
# Wait ~10 seconds, then check they're healthy:
docker compose psYou should see:
NAME STATUS
chatbot-db Up (healthy)
chatbot-redis Up (healthy)
What's running:
| Service | Host Port | What it does |
|---|---|---|
| PostgreSQL + pgvector | localhost:5434 |
Three databases: chatbot_db, langchain_pgvector, langchain_history |
| Redis | localhost:6381 |
Cache, Celery broker, WebSocket channels |
💡 Why non-standard ports? So Docker doesn't clash with any Postgres/Redis you might already have on your machine.
cd backend
# Create venv
python3 -m venv venv
# Activate it
# On Linux/Mac:
source venv/bin/activate
# On Windows:
# venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt# Create database tables
python manage.py migrate
# Create your admin user
python manage.py createsuperuser
# Follow the prompts (email, password)
# Start the dev server
python manage.py runserverOpen these URLs:
- 🏠 Django Admin: http://localhost:8000/chatbot-admin/
- 📊 API Root: http://localhost:8000/api/v1/
- ✅ If you see the admin login page, you're golden!
This is THE most important concept. Memorize this flow:
User clicks "Send Message"
│
▼
┌──────────────────┐
│ urls.py │ ← URL routing: /api/v1/chat/ → ChatSessionViewSet
└────────┬─────────┘
│
▼
┌──────────────────┐
│ ViewSet │ ← Business logic orchestration
│ (thin!) │ Calls model methods, returns Response
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Serializer │ ← Data validation + JSON conversion
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Model │ ← Database layer (FATTY models!)
│ (fat!) │ Business logic lives here
└──────────────────┘
❌ BAD — Logic in the viewset:
def create(self, request):
prefs = UserPreference.objects.get(user=request.user)
session = ChatSession.objects.create(
user=request.user,
model_name=prefs.default_model,
temperature=prefs.default_temperature,
)
✅ GOOD — Logic in the model, viewset is thin:
def create(self, request):
session = ChatSession.create_for_user(request.user)
Rule of thumb: If it's business logic, put it in the model. The viewset should be ~5-10 lines max.
This is the second most important concept:
┌─────────────────────────────────┐ ┌──────────────────────────────┐
│ Django Models (ORM) │ │ LangGraph Checkpointer │
│ PostgreSQL: chatbot_db │ │ PostgreSQL: langchain_history│
├─────────────────────────────────┤ ├──────────────────────────────┤
│ ✓ Session titles & metadata │ │ ✓ Actual chat messages │
│ ✓ User preferences │ │ ✓ Conversation state │
│ ✓ Token usage & costs │ │ ✓ Checkpoints (snapshots) │
│ ✓ Message feedback │ │ ✓ Automatic summaries │
│ ✓ File upload metadata │ │ │
│ ✓ Tool configurations │ │ │
│ ✗ NOT messages! │ │ ✗ NOT user preferences! │
└─────────────────────────────────┘ └──────────────────────────────┘
▲ ▲
│ │
Django ORM queries LangGraph API calls
ChatSession.objects.filter() checkpointer.get_state()
Why? LangGraph's checkpointer is purpose-built for conversation state. We don't duplicate that in Django models — we just store metadata (titles, settings, analytics).
- MVT Pattern — Model, View, Template (we use ViewSets instead of Templates)
- Apps — Self-contained modules. Our main app is
chatbot/ - Migrations — How Django creates database tables from Python code
- ORM — Object-Relational Mapper. Write Python, get SQL.
# Database
python manage.py makemigrations # Generate migration files from model changes
python manage.py migrate # Apply migrations to database
python manage.py showmigrations # See which migrations have been applied
# Server
python manage.py runserver # Start dev server (localhost:8000)
python manage.py runserver 0.0.0.0:8000 # Start on all interfaces
# Shell — interactive Python with Django loaded
python manage.py shell
>>> from apps.chatbot.models import ChatSession
>>> ChatSession.objects.count()
>>> session = ChatSession.create_for_user(user)
>>> session.get_langgraph_config()
# Superuser
python manage.py createsuperuser # Create admin user
python manage.py changepassword <username> # Reset admin password
# Static files
python manage.py collectstatic # Gather static filesconfig/settings/
├── base.py ← Shared settings (all environments)
├── development.py ← Local dev (DEBUG=True, local DB)
└── production.py ← Production (DEBUG=False, managed DB)
⚠️ Never put secrets in settings files! Use environment variables viapython-decouple.
Read each model file in backend/apps/chatbot/models/:
| Model | File | Teaches You |
|---|---|---|
ChatSession |
chat_session.py |
UUID primary keys, FK, JSON fields |
UserPreference |
user_preference.py |
OneToOneField, defaults pattern |
TokenUsage |
user_preference.py |
DecimalField, aggregates, @classmethod |
MessageFeedback |
message_feedback.py |
unique_together, admin review pattern |
UserDocument |
user_document.py |
FileField, state machines, pgvector |
SystemPromptTemplate |
system_prompt.py |
SlugField, template rendering |
UserTool |
user_tool.py |
TOOL_REGISTRY pattern, config merging |
UserAPIKey |
user_api_key.py |
BinaryField, Fernet encryption |
Every model in our project follows these patterns:
class MyModel(TimestampedModel):
# ... fields ...
# 1. Properties — computed values (no DB query)
@property
def display_name(self):
return f"{self.first_name} {self.last_name}"
# 2. Instance methods — actions on a single row
def archive(self):
self.is_active = False
self.save(update_fields=["is_active"])
# 3. Class methods — queries / factories
@classmethod
def get_active_for_user(cls, user):
return cls.objects.filter(user=user, is_active=True)
@classmethod
def create_for_user(cls, user, **kwargs):
return cls.objects.create(user=user, **kwargs)
# 4. to_display_dict — for API responses
def to_display_dict(self):
return {"id": self.id, "name": self.display_name}python manage.py shell
# 1. Create a user preference
>>> from django.contrib.auth import get_user_model
>>> User = get_user_model()
>>> user = User.objects.first()
# 2. Create preferences
>>> from apps.chatbot.models import UserPreference
>>> prefs = UserPreference.get_or_create_for_user(user)
>>> prefs.default_model
'gpt-5-mini'
# 3. Create a session using preferences
>>> from apps.chatbot.models import ChatSession
>>> session = ChatSession.create_for_user(user, title="My First Chat")
>>> session.thread_id # This is the LangGraph thread ID!
'550e8400-e29b-41d4-a716-446655440000'
# 4. Get the LangGraph config
>>> session.get_langgraph_config()
{'configurable': {'thread_id': '550e8400-...'}}
# 5. Query active sessions
>>> ChatSession.get_active_for_user(user)
<QuerySet [<ChatSession: My First Chat (admin@aparsoft.com)>]>
# 6. Seed tools for user
>>> from apps.chatbot.models import UserTool
>>> UserTool.seed_all_tools(user)
>>> UserTool.get_enabled_for_user(user)
[]
# (All seeded as disabled — user opts in)
# 7. Enable a tool
>>> tool = UserTool.enable_tool(user, "web_search")
>>> tool.get_effective_config() # Merged config
{'max_results': 5}We use Django REST Framework (DRF) with ViewSets:
HTTP Request → URL Router → ViewSet → Serializer → Model → Response
# chatbot/api/views/chat_session_views.py
class ChatSessionViewSet(viewsets.ModelViewSet):
serializer_class = ChatSessionSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
return ChatSession.get_active_for_user(self.request.user)
def perform_create(self, serializer):
session = ChatSession.create_for_user(
self.request.user,
**serializer.validated_data
)
serializer.instance = sessionThat's it. The viewset doesn't know about preferences or defaults — the model handles all of that.
# chatbot/api/urls.py
from rest_framework.routers import DefaultRouter
from .views import ChatSessionViewSet
router = DefaultRouter()
router.register(r'sessions', ChatSessionViewSet, basename='chat-session')
urlpatterns = router.urlsThis auto-generates:
GET /api/v1/sessions/→ List sessionsPOST /api/v1/sessions/→ Create sessionGET /api/v1/sessions/{id}/→ Get sessionPATCH /api/v1/sessions/{id}/→ Update sessionDELETE /api/v1/sessions/{id}/→ Delete session
LangGraph is a framework for building stateful AI agents. It handles:
- Conversation history (checkpoints)
- Tool usage (web search, code execution)
- Summarization (when conversations get long)
- Error recovery (replay from checkpoints)
from apps.chatbot.models import ChatSession
# 1. Django creates the session (metadata)
session = ChatSession.create_for_user(user)
# 2. LangGraph uses the session ID as thread_id
config = session.get_langgraph_config()
# → {"configurable": {"thread_id": "uuid-here"}}
# 3. LangGraph stores messages in its own checkpointer
# (PostgreSQL: langchain_history database)
response = agent.invoke({"messages": [HumanMessage(content="Hello")]}, config)
# 4. Django updates analytics
session.update_analytics(message_count=2, tokens_used=150)
# 5. TokenUsage tracks the cost
TokenUsage.create_from_response(user, session, response)pgvector is a PostgreSQL extension that stores vector embeddings — mathematical representations of text. When a user uploads a document:
- Document text is split into chunks
- Each chunk is converted to a vector (array of numbers) by OpenAI
- Vectors are stored in PostgreSQL with pgvector
- When the user asks a question, their question is also vectorized
- pgvector finds the most similar chunks (semantic search)
- Those chunks are sent to the AI as context
User uploads "Python Guide.pdf"
│
▼
Text Splitter → 50 chunks
│
▼
OpenAI Embeddings → 50 vectors (each is 1536 numbers)
│
▼
pgvector stores them in: langchain_pgvector database
│
▼
User asks "How do I create a class?"
│
▼
pgvector finds the 5 most similar chunks
│
▼
AI gets those chunks + the question → great answer!
Management commands are CLI scripts that run Django operations. They live in:
apps/chatbot/management/commands/
├── seed_demo.py # Create demo data
├── seed_tools.py # Seed TOOL_REGISTRY for all users
└── cleanup_sessions.py # Archive old sessions
# management/commands/seed_tools.py
from django.core.management.base import BaseCommand
from apps.chatbot.models import UserTool
from django.contrib.auth import get_user_model
User = get_user_model()
class Command(BaseCommand):
help = 'Seed tools from TOOL_REGISTRY for all users'
def add_arguments(self, parser):
parser.add_argument(
'--user-id',
type=int,
help='Seed for a specific user only',
)
def handle(self, *args, **options):
if options['user_id']:
users = User.objects.filter(id=options['user_id'])
else:
users = User.objects.all()
for user in users:
results = UserTool.seed_all_tools(user)
created = sum(1 for _, c in results if c)
self.stdout.write(
self.style.SUCCESS(f"Seeded {created} tools for {user.email}")
)
self.stdout.write(self.style.SUCCESS("Done!"))python manage.py seed_tools # All users
python manage.py seed_tools --user-id 1 # Specific user
python manage.py seed_demo # Create demo dataNow you're ready to contribute! Here are good first issues:
- Create a management command
seed_demothat creates:- 2 users with preferences
- 5 chat sessions per user
- Some sample feedback
- Add a
durationproperty toChatSessionthat returns time since creation - Write unit tests for
ChatSession.create_for_user()
- Create a serializer + ViewSet for
ChatSessionusing the thin viewset pattern - Create a serializer + ViewSet for
MessageFeedbackwith thecreate_feedbackclass method - Add a management command
cleanup_sessionsthat archives sessions older than 90 days
- Create the chat endpoint that connects Django → LangGraph → streaming response
- Implement document upload API that triggers Celery task for pgvector processing
- Add WebSocket support for real-time chat streaming
# Make sure Docker containers are running
docker compose up db redis -d
docker compose ps # Should show "healthy"
# Make sure you have .env
cp .env.example .env# You forgot to activate the virtual environment!
cd backend
source venv/bin/activate# PostgreSQL isn't running or isn't healthy yet
docker compose logs db
docker compose restart db
# Wait 10 seconds after restart# Find what's using it
lsof -i :8000
# Kill it
kill -9 <PID>
# Or use a different port
python manage.py runserver 8001Method 1: Using the changepassword management command (recommended)
If you know the admin username, this is the cleanest approach:
python manage.py changepassword <username>You'll be prompted to input and confirm the new password:
Changing password for user '<username>'
Password:
Password (again):
Password changed successfully for user '<username>'
Method 2: Using the Django interactive shell
If you forgot the exact admin username, use the shell to find it and reset the password:
python manage.py shellfrom django.contrib.auth import get_user_model
User = get_user_model()
# List all superuser usernames if you forgot yours
print(User.objects.filter(is_superuser=True).values_list('username', flat=True))
# Fetch the user and reset password
user = User.objects.get(username='your_admin_username')
user.set_password('your_new_secure_password')
user.save()Method 3: Emergency fallback — create a new superuser
If the existing account is locked out or corrupted, create a parallel admin account:
python manage.py createsuperuserFollow the prompts to set a new username, email, and password. Once logged into Django Admin, you can inspect, edit, or delete the old admin profile from there.
# Nuclear option — start fresh (DELETES ALL DATA)
docker compose down -v
docker compose up db redis -d
# Wait for healthy, then:
python manage.py migrate
python manage.py createsuperuser- Django Lessons — 10 detailed lessons
- Model Architecture
- Contributing Guide
- Tutorial series: @aparsoft-ai
💬 Stuck? Ask in the team Slack channel. No question is too basic — we all started somewhere!