Skip to main content
Django Basics Tutorial
CHAPTER 18 Beginner

Building a Complete Django Project

Updated: May 14, 2026
50 min read

# CHAPTER 18

Building a Complete Django Project

1. Introduction

You have studied Models, Views, Templates, Forms, Security, and REST APIs. Theoretical knowledge is essential, but hiring managers want to see practical execution. In this chapter, we will synthesize all your knowledge to architect a real-world, multi-app portfolio project: a Developer Job Board platform. This project mimics the exact technical requirements you will encounter in a professional backend engineering role.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Architect a multi-app Django project structure.
  • Define complex relational database schemas.
  • Implement full CRUD operations securely using ModelForms.
  • Protect data mutation routes using the @loginrequired decorator.

3. Project Overview: The Job Board

Requirements:
  • The project will consist of two modular apps: users (Authentication) and jobs (Core Logic).
  • Employers must be able to register, log in, and upload a company logo (Image Uploads).
  • Authenticated Employers can Create, Update, and Delete job postings.
  • Unauthenticated Job Seekers can Read all postings on the public homepage.
  • Crucial Security Rule: Employer A cannot edit or delete Employer B's job posts.

4. Step 1: The Architecture

Create a new project job
board and two apps:
bash
123
django-admin startproject core .
python manage.py startapp users
python manage.py startapp jobs

*Don't forget to add both apps to INSTALLED_APPS in settings.py!*

5. Step 2: The Database Schemas

Open jobs/models.py. Notice how we link the Job to a specific User using a Foreign Key relationship.
python
1234567891011121314
from django.db import models
from django.contrib.auth.models import User # Built-in Auth model

class Job(models.Model):
    # Establish the relationship: This job was posted by a specific User (Employer)
    employer = models.ForeignKey(User, on_delete=models.CASCADE)
    title = models.CharField(max_length=200)
    description = models.TextField()
    salary = models.IntegerField()
    is_active = models.BooleanField(default=True)
    date_posted = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title

*(Run makemigrations and migrate to build the table).*

6. Step 3: The Job Form

Open jobs/forms.py. We use a ModelForm so Django handles the heavy lifting.
python
12345678
from django import forms
from .models import Job

class JobForm(forms.ModelForm):
    class Meta:
        model = Job
        # Notice we DO NOT include 'employer'. We will handle that securely in the View!
        fields = ['title', 'description', 'salary']

7. Step 4: The Controller Logic (Views)

Open jobs/views.py. Here we enforce our strict security rules.
python
12345678910111213141516171819202122232425262728293031323334353637383940414243
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from .models import Job
from .forms import JobForm

# 1. READ ALL (Public)
def home(request):
    jobs = Job.objects.filter(is_active=True).order_by('-date_posted')
    return render(request, 'jobs/home.html', {'jobs': jobs})

# 2. CREATE (Secure)
@login_required
def create_job(request):
    if request.method == 'POST':
        form = JobForm(request.POST)
        if form.is_valid():
            # Stop! Don't save it to the DB just yet!
            job = form.save(commit=False) 
            
            # Securely attach the logged-in user as the employer
            job.employer = request.user 
            
            # Now save it permanently
            job.save()
            return redirect('jobs-home')
    else:
        form = JobForm()
    return render(request, 'jobs/job_form.html', {'form': form})

# 3. DELETE (Highly Secure)
@login_required
def delete_job(request, pk):
    job = get_object_or_404(Job, pk=pk)
    
    # SECURITY CHECK: Does the logged-in user own this specific job post?
    if job.employer != request.user:
        return render(request, '403.html') # Forbidden!
        
    if request.method == 'POST':
        job.delete()
        return redirect('jobs-home')
        
    return render(request, 'jobs/job_confirm_delete.html', {'job': job})

8. Step 5: Routing the Traffic

Connect the URLs in jobs/urls.py:
python
12345678
from django.urls import path
from . import views

urlpatterns = [
    path('', views.home, name='jobs-home'),
    path('job/new/', views.create_job, name='job-create'),
    path(&#039;job/<int:pk>/delete/', views.delete_job, name='job-delete'),
]

9. Reviewing the Architecture

Look closely at the create_job View function. We deliberately excluded the employer field from the HTML form. If we included it as a dropdown menu, a malicious user could change the HTML and post a job under a different company's name! Instead, we used form.save(commit=False). This creates the Python object in temporary memory. We then securely bind job.employer = request.user (which is guaranteed to be accurate via Django's secure session cookie), and then finalize the .save(). This is the hallmark of professional backend security.

10. Summary

You just built a multi-user SaaS backend!
  1. 1. Models established the relational constraints between Users and Jobs.
  1. 2. Forms automated the input generation and sanitized the data.
  1. 3. Auth Decorators protected the routes and identified the user.
  1. 4. Views handled the database queries and enforced strict ownership authorization.
You now have a portfolio-ready project demonstrating your mastery of the Django MVT architecture.

11. Next Chapter Recommendation

Your application is brilliant, but it only exists on your laptop. It's time to show it to the world. Proceed to Chapter 19: Deploying Django Applications.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·