# Session 7: WebApp - Backend (Django Ninja) ## Overview Welcome to Session 7! In this session, we'll focus on building the backend of our web application using Django and Django Ninja. Django is a high-level Python web framework that encourages rapid development and clean, pragmatic design. Django Ninja is a modern, fast, and async-ready API framework for Django that makes it easy to build REST APIs. ## Learning Objectives - Understand the basics of Django and its architecture - Learn how to set up a Django project with Django Ninja - Create API endpoints for our Todo application - Implement data models and database interactions - Handle authentication and permissions - Test API endpoints ## Topics Covered ### 1. Introduction to Django Django is a powerful web framework that follows the "batteries-included" philosophy, providing many built-in features for common web development tasks. #### Key Features: - **ORM (Object-Relational Mapper)**: Interact with your database using Python instead of SQL - **Admin Interface**: Auto-generated admin panel for managing your data - **Authentication System**: Built-in user authentication and authorization - **URL Routing**: Map URLs to views elegantly - **Template Engine**: Create dynamic HTML - **Form Handling**: Process and validate form data - **Security Features**: Protection against common web attacks ### 2. Setting Up a Django Project ```bash # Create a virtual environment python -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate # Install Django and Django Ninja pip install django django-ninja # Start a new Django project django-admin startproject todo_backend cd todo_backend # Create a new Django app python manage.py startapp tasks # Run migrations python manage.py migrate # Create a superuser for the admin panel python manage.py createsuperuser # Run the development server python manage.py runserver ``` #### Project Structure: ``` todo_backend/ ├── todo_backend/ # Project folder │ ├── __init__.py │ ├── asgi.py │ ├── settings.py # Project settings │ ├── urls.py # URL configuration │ └── wsgi.py ├── tasks/ # App folder │ ├── __init__.py │ ├── admin.py # Admin configuration │ ├── api.py # API endpoints (Django Ninja) │ ├── apps.py │ ├── models.py # Data models │ ├── tests.py # Unit tests │ └── views.py # View functions ├── manage.py # Django command-line utility └── requirements.txt # Project dependencies ``` ### 3. Django Models Models in Django define the structure of your database tables and allow you to interact with your data using Python code. ```python # tasks/models.py from django.db import models from django.contrib.auth.models import User class Task(models.Model): title = models.CharField(max_length=200) description = models.TextField(blank=True) completed = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='tasks') def __str__(self): return self.title class Meta: ordering = ['created_at'] ``` After defining your models, create and apply migrations: ```bash python manage.py makemigrations python manage.py migrate ``` Register your models with the admin interface: ```python # tasks/admin.py from django.contrib import admin from .models import Task @admin.register(Task) class TaskAdmin(admin.ModelAdmin): list_display = ('title', 'user', 'completed', 'created_at') list_filter = ('completed', 'created_at') search_fields = ('title', 'description') ``` ### 4. Introduction to Django Ninja Django Ninja is a web framework for building APIs with Django and Python 3.6+ type hints. #### Setting up Django Ninja: ```python # todo_backend/settings.py INSTALLED_APPS = [ # ...other apps 'tasks', 'ninja', ] ``` Create an API file: ```python # tasks/api.py from ninja import NinjaAPI, Schema from django.contrib.auth.models import User from typing import List, Optional from .models import Task api = NinjaAPI() # Schemas for request/response class TaskSchema(Schema): id: int title: str description: Optional[str] = None completed: bool created_at: str class TaskCreateSchema(Schema): title: str description: Optional[str] = None # API endpoints @api.get("/tasks", response=List[TaskSchema]) def list_tasks(request): tasks = Task.objects.filter(user=request.user) return tasks @api.post("/tasks", response=TaskSchema) def create_task(request, payload: TaskCreateSchema): task = Task.objects.create( title=payload.title, description=payload.description, user=request.user ) return task @api.get("/tasks/{task_id}", response=TaskSchema) def get_task(request, task_id: int): task = Task.objects.get(id=task_id, user=request.user) return task @api.put("/tasks/{task_id}", response=TaskSchema) def update_task(request, task_id: int, payload: TaskCreateSchema): task = Task.objects.get(id=task_id, user=request.user) task.title = payload.title task.description = payload.description task.save() return task @api.patch("/tasks/{task_id}/complete", response=TaskSchema) def complete_task(request, task_id: int): task = Task.objects.get(id=task_id, user=request.user) task.completed = not task.completed task.save() return task @api.delete("/tasks/{task_id}") def delete_task(request, task_id: int): task = Task.objects.get(id=task_id, user=request.user) task.delete() return {"success": True} ``` Include the API in your project's URL configuration: ```python # todo_backend/urls.py from django.contrib import admin from django.urls import path from tasks.api import api urlpatterns = [ path('admin/', admin.site.urls), path('api/', api.urls), ] ``` ### 5. Authentication with Django Ninja Django Ninja can work with Django's built-in authentication system: ```python # tasks/api.py from ninja import NinjaAPI, Schema from ninja.security import HttpBearer from django.contrib.auth.models import User from django.contrib.auth import authenticate from django.conf import settings import jwt from datetime import datetime, timedelta class TokenSchema(Schema): access_token: str token_type: str = "bearer" class LoginSchema(Schema): username: str password: str class AuthBearer(HttpBearer): def authenticate(self, request, token): try: payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"]) user_id = payload.get("user_id") user = User.objects.get(id=user_id) request.user = user return user except: return None api = NinjaAPI(auth=AuthBearer()) @api.post("/token", auth=None, response=TokenSchema) def login(request, credentials: LoginSchema): user = authenticate(username=credentials.username, password=credentials.password) if user is None: return api.create_response(request, {"detail": "Invalid credentials"}, status=401) token_expiry = datetime.utcnow() + timedelta(days=1) access_token = jwt.encode( {"user_id": user.id, "exp": token_expiry}, settings.SECRET_KEY, algorithm="HS256" ) return {"access_token": access_token} ``` ### 6. Adding Cross-Origin Resource Sharing (CORS) To allow our frontend to communicate with the backend, we need to configure CORS: ```bash pip install django-cors-headers ``` ```python # todo_backend/settings.py INSTALLED_APPS = [ # ...other apps 'corsheaders', ] MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', # Add this at the top # ...other middleware ] # Allow all origins in development (customize for production) CORS_ALLOW_ALL_ORIGINS = True ``` ### 7. Testing Django Ninja APIs Django provides a built-in testing framework that you can use to test your API endpoints: ```python # tasks/tests.py from django.test import TestCase from django.contrib.auth.models import User from .models import Task from django.urls import reverse import json import jwt from django.conf import settings from datetime import datetime, timedelta class TaskApiTests(TestCase): def setUp(self): # Create a test user self.user = User.objects.create_user(username='testuser', password='testpassword') # Create some test tasks Task.objects.create(title='Test Task 1', user=self.user) Task.objects.create(title='Test Task 2', user=self.user, completed=True) # Generate token token_expiry = datetime.utcnow() + timedelta(days=1) self.token = jwt.encode( {"user_id": self.user.id, "exp": token_expiry}, settings.SECRET_KEY, algorithm="HS256" ) def test_list_tasks(self): response = self.client.get( '/api/tasks', HTTP_AUTHORIZATION=f'Bearer {self.token}' ) self.assertEqual(response.status_code, 200) data = json.loads(response.content) self.assertEqual(len(data), 2) def test_create_task(self): payload = { 'title': 'New Task', 'description': 'Task description' } response = self.client.post( '/api/tasks', data=json.dumps(payload), content_type='application/json', HTTP_AUTHORIZATION=f'Bearer {self.token}' ) self.assertEqual(response.status_code, 200) data = json.loads(response.content) self.assertEqual(data['title'], 'New Task') # Verify task was created in database task_exists = Task.objects.filter(title='New Task').exists() self.assertTrue(task_exists) ``` Run the tests with: ```bash python manage.py test ``` ## Practice Exercises 1. Set up a Django project with Django Ninja 2. Create models for a Todo application 3. Implement API endpoints for CRUD operations on tasks 4. Add authentication to your API 5. Write tests for your API endpoints 6. Create a user registration endpoint ## Additional Resources - [Django Documentation](https://docs.djangoproject.com/) - [Django Ninja Documentation](https://django-ninja.rest-framework.com/) - [Django ORM Cookbook](https://books.agiliq.com/projects/django-orm-cookbook/en/latest/) - [JWT Authentication](https://jwt.io/) - [Django Testing Documentation](https://docs.djangoproject.com/en/stable/topics/testing/) ## Next Steps Now that you've learned how to build the backend of a web application with Django Ninja, you're ready to move on to [Session 8: WebApp - Database](Session-8-WebApp-Database.md) where you'll learn how to work with PostgreSQL and implement more advanced database features.