
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.
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
bashpoetry new blogsite --srccd blogsitepoetry 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:
bashdjango-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:
bashmkdir temp_projectpoetry run django-admin startproject blogsite temp_project
This creates the following directory structure in the project root, next to the src/ directory:
bashtemp_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:
bashrm -rf src/* # Make sure the target dir is emptymv temp_project/blogsite src
7.3.4 Modify manage.py
Finally, let's move the manage.py to project root:
bashmv 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.pyfrom pathlib import Path# Add this BEFORE calling execute_from_command_line()BASE_DIR = Path(__file__).resolve().parentsys.path.append(str(BASE_DIR / "src"))os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blogsite.settings')
7.3.5 Project Cleanup
Delete the temp folder:
bashrm -rf temp_project
This creates the final project structure:
bashblogsite/├── 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
bashpoetry run python manage.py runserver
You’ll see the following output on the command line:
bashWatching for file changes with StatReloaderPerforming 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:
bashpython 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:
bashmkdir -p src/appsmkdir -p src/apps/blogpoetry run python manage.py startapp blog src/apps/blog
New structure:
bashblogsite/├── src/| ├── blogsite/│ └── apps/| └── blog/│ ├── migrations/│ ├── admin.py│ ├── apps.py│ ├── models.py│ ├── tests.py│ └── views.py
Enable it in settings.py:
src/blogsite/settings.pyINSTALLED_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.py1from django.http import HttpResponse23def 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.py1from django.urls import path2from . import views34urlpatterns = [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.py1from django.apps import AppConfig23class 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.py1from django.contrib import admin2from django.urls import path, include34urlpatterns = [5 path("admin/", admin.site.urls),6 path("", include("blog.urls")), # <-- root goes to blog app7]
Run the following in the terminal:
bashpoetry 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
bashpoetry 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.py1import pytest2from django.test import Client34@pytest.mark.django_db5def test_home_view():6 client = Client()7 response = client.get("/")8 assert response.status_code == 2009 assert "Hello, Django Blog!" in response.content.decode()
Run tests:
bashpoetry install # Run once to apply the updates to pyproject.tomlpoetry 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.0django: version: 5.2.8, settings: blogsite.settings (from ini)rootdir: .../chapter_7/blogsiteconfigfile: pyproject.tomlplugins: django-4.11.1collected 1 itemtests/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