Django introduction
last modified November 13, 2026 Python 3.15
In this article we show how to start with Django: install the framework, create a project and an application, write a view, map it to a URL, render a template, and run the development server. The examples use Django 6.1 and Python 3.15.
Django
Django is a high-level Python web framework that encourages rapid development and clean, pragmatic design. It is often described as batteries included: the framework ships with an ORM, a template engine, a form library, an authentication system, an automatic administration site, a caching framework, and a test runner.
Django follows the MTV (model, template, view) pattern. A model describes the data and maps it to a database table, a template describes how the data is presented, and a view is a Python function or class that receives an HTTP request and returns an HTTP response. A URL configuration connects URLs to views.
A Django project is a whole web application with its configuration. A project consists of one or more apps, which are Python packages that each do one thing, such as a blog or a polling feature. An app can be reused in several projects.
Django installation
We use uv, a fast Python package and
project manager, to create the virtual environment and install Django. uv is
installed separately; see its documentation for the installation options. The
package is published on PyPI as Django. Django 6.1 supports
Python 3.12 to 3.15; the examples were tested with Python 3.15.
We create a project directory and initialize it with uv init --bare,
which creates only a pyproject.toml file, without any sample code.
The uv add command creates the .venv virtual
environment, installs Django into it, records the dependency in
pyproject.toml, and writes the exact versions to the
uv.lock file.
$ mkdir djangointro $ cd djangointro $ uv init --bare $ uv add django
There is no need to activate the environment. The uv run command
runs a command inside the project's environment, creating or updating the
environment first if necessary. We use it for all commands in this article.
Verifying the installation
The django module can be run as a program; the
--version option prints the installed version.
$ uv run python -m django --version 6.1.3
Creating a project
The django-admin command-line tool is installed with Django into the project's environment. Its
startproject subcommand creates the skeleton of a new project.
We name the project config: Django uses the name for the
configuration package, and the same name in every project means that the settings
module is always config.settings. The dot at the end tells Django
to create the project in the current directory; without it, Django would
create an extra directory level.
$ uv run django-admin startproject config .
Project structure
The command created the manage.py script and the
config package. Next to them are the pyproject.toml
and uv.lock files created by uv; the hidden .venv
directory is not shown.
. ├── config │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── manage.py ├── pyproject.toml └── uv.lock
Project files
The files have the following roles.
manage.py
A command-line utility for the project. It is a thin wrapper around
django-admin that sets the DJANGO_SETTINGS_MODULE
environment variable, so the commands know which project they work with. We
use it to run the server, create apps, apply migrations, and run tests.
config/settings.py
The project settings: installed apps, middleware, the database, templates, time zone, and static files.
config/urls.py
The root URL configuration. It maps URL paths to views.
config/wsgi.py config/asgi.py
Entry points for WSGI-compatible and ASGI-compatible web servers. The
development server uses the WSGI one; production servers such as Gunicorn
(WSGI) or Uvicorn (ASGI) import application from these modules.
The development server
Django includes a lightweight web server for development. We start it with the
runserver command. The server reloads itself when the source code
changes.
In the output, the server listens on port 8000 of the loopback interface. The warning about
unapplied migrations refers to the database tables needed by the built-in apps
(authentication, sessions, and the admin site). We fix this in the next
section. When we open http://127.0.0.1:8000/
in a browser, Django shows its welcome page. The development server is not
meant for production use. The address and port can be changed, for example
with uv run python manage.py runserver 0.0.0.0:9000.
$ uv run python manage.py runserver 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 13, 2026 - 16:37:20 Django version 6.1.3, using settings 'config.settings' Starting WSGI 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/6.1/howto/deployment/
Applying migrations
A migration is a Python file that describes a change to the
database schema. The migrate command applies all migrations that
have not been applied yet. By default, Django uses an SQLite database stored
in the db.sqlite3 file, so no database server is needed.
$ uv run python manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying auth.0012_alter_user_first_name_max_length... OK Applying sessions.0001_initial... OK
Creating an app
The startapp subcommand creates a new app. We create an app
called hello.
$ uv run python manage.py startapp hello
App structure
Django creates the hello package. The models.py file
will hold the data models, views.py the views,
admin.py the admin site registrations, and tests.py
the tests. The apps.py file contains the app configuration class,
HelloConfig. The migrations directory stores the
migrations of the app's models.
hello ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── tests.py └── views.py
Registering the app
Django knows only the apps listed in the INSTALLED_APPS setting.
We add the new app to the list.
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'hello',
]
The entry 'hello' is the name of the app package. It is a
shortcut for 'hello.apps.HelloConfig'; Django finds the
configuration class on its own. Registering the app lets Django discover its
models, migrations, and templates.
The first view
A view takes an HttpRequest and returns an
HttpResponse. We write two views in hello/views.py.
from django.http import HttpResponse
from django.shortcuts import render
def index(request):
return HttpResponse('Hello from Django!')
def greet(request, name):
return render(request, 'hello/greet.html', {'name': name})
Each view is an ordinary Python function.
def index(request):
return HttpResponse('Hello from Django!')
The index() view returns a plain text response. The response
object has status code 200 and a text/html content type by
default. See Django HttpRequest and
Django HttpResponse for details on the
two objects.
def greet(request, name):
return render(request, 'hello/greet.html', {'name': name})
The greet() view receives the name argument captured
from the URL. The render() shortcut loads a template, renders it
with the given context dictionary, and returns the result as an
HttpResponse.
Templates
A template is a text file with placeholders for dynamic data. Django looks for
templates in the templates directory of each installed app,
because the default settings enable APP_DIRS. We put the template
into hello/templates/hello/. The extra hello
subdirectory is a namespace: template names are shared by all apps, so two apps
with a greet.html file would otherwise clash.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Greeting</title>
</head>
<body>
<h1>Hello, {{ name }}!</h1>
</body>
</html>
The {{ name }} expression prints the value of the
name variable from the context. Django escapes HTML special
characters in variables automatically.
URL configuration
Views are reachable only after they are mapped to URLs. By convention, each
app has its own urls.py file.
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
path('greet/<str:name>/', views.greet, name='greet'),
]
The urlpatterns list contains path() entries. Each
entry has a route, a view, and an optional name.
path('greet/<str:name>/', views.greet, name='greet'),
The <str:name> part is a path converter. It matches a
non-empty string without a slash and passes it to the view as the
name argument. The name parameter of
path() gives the route a name, so code and templates can build the
URL with reverse() or {% url %} instead of
hard-coding it.
Next, we include the app's URL configuration in the project's root configuration.
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('hello.urls')),
]
The include() function delegates all URLs with the given prefix,
here the empty one, to the app's URL configuration. The generated file also
contains a long docstring, which we omit. The admin/ route
serves the built-in administration site.
Running the application
We start the server in the background and request the home page with the
curl tool. The -i option includes the response
headers. The response is the plain text returned by the index()
view; the headers added by the security middleware
(X-Frame-Options, X-Content-Type-Options, and
others) are part of the default configuration.
$ uv run python manage.py runserver & $ curl -i http://127.0.0.1:8000/ HTTP/1.1 200 OK Date: Fri, 13 Nov 2026 16:39:35 GMT Server: WSGIServer CPython/3.15.2 Content-Type: text/html; charset=utf-8 X-Frame-Options: DENY Content-Length: 18 X-Content-Type-Options: nosniff Referrer-Policy: same-origin Cross-Origin-Opener-Policy: same-origin Hello from Django!
Rendering a template
The /greet/Peter/ URL matches the second route. The
name converter captures Peter, the view passes it to
the template, and the template engine inserts it into the HTML.
$ curl http://127.0.0.1:8000/greet/Peter/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Greeting</title>
</head>
<body>
<h1>Hello, Peter!</h1>
</body>
</html>
Automatic escaping
In this request the name is the string <b>, URL-encoded as
%3Cb%3E. The template engine escapes it, so the browser shows the
characters instead of interpreting them as a tag. This protects the page from
cross-site scripting (XSS) through values that come from the user.
$ curl http://127.0.0.1:8000/greet/%3Cb%3E/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Greeting</title>
</head>
<body>
<h1>Hello, <b>!</h1>
</body>
</html>
A URL that does not exist
A URL that matches no route gives status 404. The -s option hides
the progress meter, -o /dev/null discards the body, and
-w prints the status code. In debug mode the body is a detailed
error page listing the URL patterns that were tried.
$ curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8000/nothing/
404
The request log
The development server prints one line for every request: the time, the request line, the status code, and the size of the response body. Requests that end with a 404 status also log the missing path.
[13/Nov/2026 16:39:35] "GET / HTTP/1.1" 200 18 [13/Nov/2026 16:39:35] "GET /greet/Peter/ HTTP/1.1" 200 153 [13/Nov/2026 16:39:35] "GET /greet/%3Cb%3E/ HTTP/1.1" 200 157 Not Found: /nothing/ [13/Nov/2026 16:39:35] "GET /nothing/ HTTP/1.1" 404 2788
Testing
Every app has a tests.py file. Django tests are built on the
unittest module of the standard library; the
TestCase class adds a test client and a separate test database,
so tests never touch our data.
from django.test import TestCase
from django.urls import reverse
class HelloTests(TestCase):
def test_index(self):
response = self.client.get(reverse('index'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Hello from Django!')
def test_greet_escapes_name(self):
response = self.client.get(reverse('greet', args=['<b>']))
self.assertContains(response, 'Hello, <b>!')
The test client self.client issues requests directly to the
application without a running server. The reverse() function
builds the URL from the route name that we gave in urls.py; the
assertContains() method checks the status code and that the
response body contains the text. The test command discovers and
runs the tests.
$ uv run python manage.py test Found 2 test(s). Creating test database for alias 'default'... System check identified no issues (0 silenced). .. ---------------------------------------------------------------------- Ran 2 tests in 0.007s OK Destroying test database for alias 'default'...
Project settings
The config/settings.py module is a plain Python module whose
upper-case names are the settings. The most important ones are the following.
DEBUG- turns the debug mode on. In debug mode Django shows detailed error pages. It must beFalsein production.SECRET_KEY- a secret value used for cryptographic signing (sessions, tokens, password reset links). The generated key is markeddjango-insecure; use a different key from an environment variable in production.ALLOWED_HOSTS- the host names the site may serve. WithDEBUG = Trueand an empty list, onlylocalhostand loopback addresses are accepted; withDEBUG = Falseit must be set.INSTALLED_APPS- the apps enabled in the project.MIDDLEWARE- the list of request/response processing hooks, such as security headers, sessions, and CSRF protection.ROOT_URLCONF- the module with the root URL configuration,config.urls.TEMPLATES- the template engine configuration, including theDIRSlist andAPP_DIRSflag.DATABASES- the database connections; the default is SQLite indb.sqlite3.LANGUAGE_CODE,TIME_ZONE, andUSE_TZ- internationalization settings. Time zone support is on by default and the time zone isUTC.STATIC_URL- the URL prefix of static files.
The diffsettings command prints the settings that differ from the
Django defaults, which is handy for reviewing a project.
Common manage.py commands
Running uv run python manage.py help --commands lists the available
commands. We list the ones we use most often.
runserver- starts the development serverstartapp- creates a new appmakemigrations- creates migrations from model changesmigrate- applies migrations to the databasecreatesuperuser- creates an administrator account for the admin siteshell- opens an interactive Python shell with Django loadeddbshell- opens the database clientcheck- checks the project for common problemstest- runs the testscollectstatic- collects static files into one directory for deploymentdumpdataandloaddata- export and import database content
In this article we created a Django project and an app, wrote a function-based view and a template, mapped them to URLs, ran the development server, and wrote tests. The other Django tutorials on this site show how to work with requests, responses, files, JSON, and email.
Source
Django documentation: Writing your first Django app
In this article we have installed Django with uv, created a project and an app, written a view, mapped it to a URL, rendered a template, and tested the result. The request and response objects are described in Django HttpRequest and Django HttpResponse.
Author
List all Django tutorials.