Introduction to Django

Chapter Outline

Chapter 7: Introduction to Django

In this chapter, you will learn:

  • What Django is and how it follows the MVT (Model-View-Template) pattern.
  • How to set up a Django project with Poetry.
  • The Django project/app structure.
  • How to create your first app and render a simple page.
  • How to run tests in Django with Pytest-Django.

7.1 What is Django

In the previous section, we delved into developing APIs using FastAPI. FastAPI is a modern, lightweight, high-performance framework focused on building web APIs. It emphasizes speed, developer productivity, and type-driven validation through Pydantic, and it adheres to an asynchronous-first design that excels in microservices, backend APIs, and systems that need to handle high concurrency.

Django is another Python framework designed to develop web applications. It however, is a batteries‑included web framework designed for building full‑stack applications quickly and is particularly well‑suited for server‑rendered HTML, authentication, admin dashboards and a structured ORM. It follows the “convention over configuration” approach and the Model-View-Template (MVT) architectural pattern, making it ideal for large, cohesive, server-rendered applications that benefit from strong structure and consistency.

While FastAPI gives you the flexibility to pick your own ORM, template engine, or authentication approach, Django provides a vertically integrated ecosystem that handles all of these pieces for you. In the end, Django is best suited for full-stack applications with server-rendered HTML, whereas FastAPI shines in modern API development and microservice architectures.

7.2 Understanding the MVT Pattern

Unlike FastAPI’s explicit routing + views, Django uses MVT:

  • Model → Defines the database schema.
  • View → Python function/class that handles requests.
  • Template → HTML that is rendered and returned.
flowchart LR U[User Request] --> V[View] V --> M[Model - Database] V --> T[Template] M --> V T --> V V --> U[Response - HTML/JSON]

This separation keeps your business logic in views and models, while allowing templates to focus on presentation. Understanding MVT is essential because it guides how Django applications are structured, promotes maintainable code, and helps developers reason clearly about where different pieces of logic belong in a Django project.

7.3 Django Project

A Django project is the overall web application: it defines global configuration, settings, URL routing, middleware, and deployment behavior. Every Django project contains a central configuration package—usually named after the project itself—that stores settings.py, urls.py, wsgi.py, and ASGI configuration. This “project layer” is responsible for orchestrating the entire application. It doesn’t contain business logic or domain-specific code; instead, it organizes the pieces that make up your site. The project acts as the container that orchestrates all the pieces.

7.3.1 Install Django With Poetry

bash
poetry new blogsite --src
cd blogsite
poetry add django

We're using Poetry to add a new project called blogsite. We also install the django, pytest and pytest-django modules as dependencies. Your are now ready to bootstrap your first Django project.

7.3.2 Start a Django Project With django-admin

We use the django-admin tool to start a project. Here is the command format:

bash
django-admin startproject <project-name> <target-dir>

This creates the following directory structure:

bash
<target>/
├── manage.py
└── <project_name>/
├── settings.py
├── urls.py
└── ...

7.3.3 Organize Source

We're going to use the src/ as the <target>. This is similar to the project structure of the FastAPI applications we'd worked on in the previous section. In order to do this, first we need to start the Django project within a temporary directory:

bash
mkdir temp_project
poetry run django-admin startproject blogsite temp_project

This creates the following directory structure in the project root, next to the src/ directory:

bash
temp_project/
├── manage.py
└── blogsite/
├── settings.py
├── urls.py
└── ...

Let's move the content of the temp_project directory to src/. So we run the following command:

bash
rm -rf src/* # Make sure the target dir is empty
mv temp_project/blogsite src

7.3.4 Modify manage.py

Finally, let's move the manage.py to project root:

bash
mv temp_project/manage.py manage.py

Now we must add src to the sys.path in manage.py. Unless we do this, Python cannot import anything from src/ where our blogsite project directory resides.

manage.py
from pathlib import Path
# Add this BEFORE calling execute_from_command_line()
BASE_DIR = Path(__file__).resolve().parent
sys.path.append(str(BASE_DIR / "src"))
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blogsite.settings')

7.3.5 Project Cleanup

Delete the temp folder:

bash
rm -rf temp_project

This creates the final project structure:

bash
blogsite/
├── src/
│ └── blogsite/
│ ├── __init__.py
│ ├── settings.py # configuration (DB, installed apps, middleware)
│ ├── urls.py # URL routing entrypoint
│ ├── wsgi.py # An entry-point for ASGI-compatible web servers
│ └── asgi.py # An entry-point for WSGI-compatible web servers
├── tests/
├── README.md
├── manage.py # CLI for Django commands
└── pyproject.toml

7.3.6 Run the Development Server

bash
poetry run python manage.py runserver

You’ll see the following output on the command line:

bash
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
November ***
Django version 5.2.8, using settings 'blogsite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
WARNING: This is a development server. Do not use it in a production setting. Use a production WSGI or ASGI server instead.
For more information on production servers see: https://docs.djangoproject.com/en/5.2/howto/deployment/

Visit http://127.0.0.1:8000/ → you’ll see Django’s default welcome page.

7.4 Django Apps

Once the project skeleton is built, the next step is to create feature‑specific apps (e.g., a blog or user profile module).

A Django app, is a self-contained module that implements a specific feature or domain of your site. Each app typically provides its own models, views, templates, admin configuration, and URLs. For example, a blog, a comments system, a payments module, or a user profile area would each be packaged as separate Django apps. A single Django project can include many apps, and each app can be reused across multiple projects.

Apps are registered using INSTALLED_APPS in settings.py, which tells Django to load their models, migrations, and configuration. In practice, Django apps help you keep your project modular and scalable: each app owns one piece of functionality, and the Django project coordinates them into a cohesive whole.

7.4.1 Creating a Blog App

In order to create an app within the Django project, we use the manage.py CLI utility. Here is the command format:

bash
python manage.py startapp <app-name> <target-dir>

Just like earlier, we'd like to place the app source inside the src/ directory. Let's create a blog app within the blogsite project. To create your app, make sure you’re in the same directory as manage.py and type this command:

bash
mkdir -p src/apps
mkdir -p src/apps/blog
poetry run python manage.py startapp blog src/apps/blog

New structure:

bash
blogsite/
├── src/
| ├── blogsite/
│ └── apps/
| └── blog/
│ ├── migrations/
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── tests.py
│ └── views.py

Enable it in settings.py:

src/blogsite/settings.py
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"apps.blog", # <-- added
]

7.4.2 Adding a View

A Django view is the function or class responsible for handling an incoming HTTP request and returning an HTTP response. It acts as the core of your application’s request–response cycle: the view receives data from the URL router, performs any necessary business logic (such as querying the database, validating input, or calling services), and then returns a response—often HTML, JSON, or a redirect. Views are intentionally decoupled from templates and models, which keeps the application modular: the view decides what data to send, templates decide how to present it, and models decide how data is stored.

Create a simple view:

src/apps/blog/views.py
1from django.http import HttpResponse
2
3def home(request):
4 """Basic home view for the blog app."""
5 return HttpResponse("Hello, Django Blog!")

7.4.3 Mapping View With URLs

A Django URL is a mapping between a specific URL pattern and the view that should handle requests to that pattern. Django’s URL routing system looks at the incoming request path, matches it against the patterns defined in your urls.py modules, and then dispatches the request to the corresponding view function or class. This separation of routing from business logic keeps the application organized: URLs define how users reach a resource, while views define what happens once they do.

Create app-level URLs:

src/apps/blog/urls.py
1from django.urls import path
2from . import views
3
4urlpatterns = [
5 path("", views.home, name="home"),
6]

7.4.4 Launch the App

We need to make some additional changes so that the app is properly registered with the Django project. Update the app name for blog to match the directory structure:

src/blogsite/urls.py
1from django.apps import AppConfig
2
3class BlogConfig(AppConfig):
4 default_auto_field = "django.db.models.BigAutoField"
5 name = "apps.blog"

Add an __init__.py file to apps directory. And finally wire it into the project URLs:

src/blogsite/urls.py
1from django.contrib import admin
2from django.urls import path, include
3
4urlpatterns = [
5 path("admin/", admin.site.urls),
6 path("", include("blog.urls")), # <-- root goes to blog app
7]

Run the following in the terminal:

bash
poetry run python manage.py runserver

Now visit http://127.0.0.1:8000/ → you’ll see "Hello, Django Blog!".

7.5 Testing With pytest-django

Just like the previous chapters, we're going to use pytest to test our application.

7.5.1 Install Dependencies

bash
poetry add --group dev pytest pytest-django

7.5.2 Config pyproject.toml

toml
[tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "blogsite.settings"
pythonpath = ["src"]

This makes pytest behave like manage.py by making src/ importable.

7.5.3 Simple Test

Write a simple test:

tests/apps/blog/test_views.py
1import pytest
2from django.test import Client
3
4@pytest.mark.django_db
5def test_home_view():
6 client = Client()
7 response = client.get("/")
8 assert response.status_code == 200
9 assert "Hello, Django Blog!" in response.content.decode()

Run tests:

bash
poetry install # Run once to apply the updates to pyproject.toml
poetry run pytest

You should see something similar to this on the console:

bash
================================= test session starts ==================================
platform darwin -- Python 3.10.17, pytest-9.0.1, pluggy-1.6.0
django: version: 5.2.8, settings: blogsite.settings (from ini)
rootdir: .../chapter_7/blogsite
configfile: pyproject.toml
plugins: django-4.11.1
collected 1 item
tests/apps/blog/test_views.py . [100%]
================================== 1 passed in 0.09s ===================================

7.6 Chapter Summary

In this chapter we showed you how to setup a Django project, and create an app within the project. We added a custom view, and tied it to an app URL. We also discussed the essence of the MVT pattern.

In the next chapter, we’ll add actual HTML templates and form handling to the Blog app.

7.7 Further Reading

Check your understanding

Test your knowledge of Introduction to Django

Feedback