# Set up a Django project with uv

Most [Django](https://www.djangoproject.com/) setup guides still tell you to install Python and a virtual environment by hand before you reach `runserver`. This tutorial uses [uv](https://pydevtools.com/handbook/reference/uv.md) for the Python install and the [virtual environment](https://pydevtools.com/handbook/explanation/what-is-a-virtual-environment.md). Every `manage.py` command runs against a pinned interpreter.

## Confirm uv is installed

If you do not already have uv, follow the [installation guide](https://pydevtools.com/handbook/how-to/how-to-install-uv.md). No separate Python install is required; uv will download an interpreter on first use.

Verify the install:

```console
$ uv --version
uv 0.11.7
```

The exact version will vary. This tutorial was verified with `uv 0.11.7` and `uv 0.11.8`.

## Initialize the uv project

Create the project shell with `uv init`. Pin to Python 3.13 explicitly so the rest of the tutorial is reproducible regardless of what your system has installed.

```console
$ uv init --python 3.13 mysite
Initialized project `mysite` at `/path/to/mysite`
$ cd mysite
```

`uv init --python 3.13` writes the starter files for the project:

{{< /filetree/folder >}}
{{< /filetree/container >}}

Notice the `.python-version` file. uv reads it on every `uv sync` and `uv run` so the project stays on Python 3.13, even on machines where the system Python is something else.

Open [pyproject.toml](https://pydevtools.com/handbook/reference/pyproject.toml.md):

```toml
[project]
name = "mysite"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = []
```

> [!NOTE]
> Without `--python 3.13`, uv writes whichever Python it finds newest on your machine into `requires-python`. On a fresh Mac with Python 3.14 installed, that produces `requires-python = ">=3.14"`, which can later block `uv python pin 3.13`. Pinning at init time avoids that conflict.

Django will replace `main.py` with its own entry point shortly. Delete the placeholder so it does not collide:

```bash
rm main.py
```
```powershell
del main.py
```
## Add Django as a dependency

```bash
uv add django
```

uv resolves a compatible Django version and creates `.venv/` for the project. It then downloads Django with its two runtime dependencies and writes a `uv.lock` that pins every transitive package:

```console
$ uv add django
Using CPython 3.13.13
Creating virtual environment at: .venv
Resolved 5 packages in 231ms
Downloading django (8.0MiB)
 Downloaded django
Prepared 3 packages in 621ms
Installed 3 packages in 88ms
 + asgiref==3.11.1
 + django==6.0.4
 + sqlparse==0.5.5
```

Notice the new `.venv/` directory. That is where Django and its dependencies live. You never `source .venv/bin/activate`; every Django command in this tutorial runs through `uv run`, which uses that venv automatically. The exact Django version on your machine will be whatever is current on PyPI when you run the command.

`pyproject.toml` now records the dependency:

```toml
dependencies = [
    "django>=6.0.4",
]
```

> [!TIP]
> Commit `pyproject.toml` and `uv.lock` to version control, and add `.venv/` and `db.sqlite3` to `.gitignore`. The lockfile guarantees teammates recreate the same environment with `uv sync`; the venv and the development database should not be shared.

## Generate the Django project files

Django's `startproject` command scaffolds the project layout. Run it through `uv run` so it executes against the project's venv:

```bash
uv run django-admin startproject mysite_config .
```

The trailing `.` tells Django to put `manage.py` in the current directory instead of creating yet another nested folder. After running, the directory looks like this:

{{< /filetree/folder >}}
    {{< /filetree/folder >}}
{{< /filetree/container >}}

Open `manage.py`. The first lines look like this:

```python {filename="manage.py"}
import os
import sys


def main():
    """Run administrative tasks."""
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite_config.settings')
```

`manage.py` is the project's command-line entry point. It points Django at `mysite_config.settings`. You will run it as `uv run python manage.py <command>` from now on.

## Apply the initial migrations

Django's built-in apps use database tables. Run the initial migrations now so the default admin and authentication features are ready when you start the server.

```bash
uv run python manage.py migrate
```

Django prints an `OK` line for each migration as it runs:

```console
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  ...
  Applying sessions.0001_initial... OK
```

Notice the new `db.sqlite3` file in the project root. Django's default `DATABASES` setting in `mysite_config/settings.py` points at `BASE_DIR / 'db.sqlite3'`, which keeps the SQLite file next to `manage.py`. It does not live inside `.venv/`, so a `uv sync --reinstall` (or even `rm -rf .venv`) leaves your data alone.

Switching to Postgres or MySQL later is a `settings.py` change plus `uv add psycopg[binary]` or `uv add mysqlclient`.

## Run the development server

```bash
uv run python manage.py runserver
```

The server starts on port 8000:

```console
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
Django version 6.0.4, using settings 'mysite_config.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
```

Visit <http://127.0.0.1:8000/> in a browser. Django's "The install worked successfully! Congratulations!" rocket page confirms that uv and Django are wired up correctly. Press `CONTROL-C` to stop the server.

> [!NOTE]
> If you see `error: Failed to spawn: python` instead, you almost certainly ran the command outside the project directory. `uv run` looks for `pyproject.toml` and `uv.lock` in the current directory tree; from anywhere else it has no project to use. `cd` back into `mysite/` and try again.

## Add your first app

A Django project is a collection of *apps*. Create one:

```bash
uv run python manage.py startapp polls
```

Django generates a `polls/` directory with the app skeleton:

{{< /filetree/folder >}}
    {{< /filetree/folder >}}
{{< /filetree/container >}}

Django does not auto-discover apps. Register `polls` in `mysite_config/settings.py` by adding it to `INSTALLED_APPS`:

```python {filename="mysite_config/settings.py"}
INSTALLED_APPS = [
    # ...existing built-in apps...
    'polls',
]
```

The string `'polls'` matches the directory name and the `name` attribute in `polls/apps.py`. Once registered, Django loads the app and includes its migrations. Models appear in the admin site only after you register them in `polls/admin.py`.

Confirm the project still passes Django's system check after the edit:

```console
$ uv run python manage.py check
System check identified no issues (0 silenced).
```

Notice that the check now counts `polls` as a registered app without errors. If the output shows `ERRORS:` instead, the most common cause is a typo in `INSTALLED_APPS`. Confirm `'polls'` matches the directory name exactly.

## Reproduce the environment

A teammate cloning the repo runs one command:

```console
$ uv sync
Using CPython 3.13.13
Creating virtual environment at: .venv
Resolved 5 packages in 2ms
Installed 3 packages in 66ms
 + asgiref==3.11.1
 + django==6.0.6
 + sqlparse==0.5.5
```

Notice that the installed versions match what you added earlier. uv reads `.python-version` and `uv.lock`, downloads Python 3.13 if needed, creates `.venv/`, and installs exactly the same package versions you developed against. The exact timings will differ. From there they have the same `uv run python manage.py runserver` you do. To switch Python versions later, run `uv python pin 3.14` (or whichever version you want); the new value must satisfy the `requires-python` bound in `pyproject.toml`. See [How to change the Python version of a uv project](https://pydevtools.com/handbook/how-to/how-to-change-the-python-version-of-a-uv-project.md) for the full workflow.

## Review the project structure

{{< /filetree/folder >}}
    {{< /filetree/folder >}}
      {{< /filetree/folder >}}
    {{< /filetree/folder >}}
{{< /filetree/container >}}

## Next steps

- The [official Django tutorial](https://docs.djangoproject.com/en/stable/intro/tutorial01/) builds the polls app out into a working web application. Run every command in it with `uv run python manage.py …` instead of plain `python manage.py …`.
- [Setting up testing with pytest and uv](https://pydevtools.com/handbook/tutorial/setting-up-testing-with-pytest-and-uv.md) covers adding [pytest](https://pydevtools.com/handbook/reference/pytest.md) as a dev dependency. Layer `pytest-django` on top with `uv add --dev pytest-django` to test Django views and models.
- [How to configure Ruff for Django](https://pydevtools.com/handbook/how-to/how-to-configure-ruff-for-django.md) enables the `DJ` rule set and tames lint noise from auto-generated migrations.
- [How to configure mypy and django-stubs in a uv project](https://pydevtools.com/handbook/how-to/how-to-configure-mypy-and-django-stubs-in-a-uv-project.md) adds Django-aware static type checking with the django-stubs plugin.
- [Setting up GitHub Actions with uv](https://pydevtools.com/handbook/tutorial/setting-up-github-actions-with-uv.md) shows how to run those tests in CI.
- [How to use uv in a Dockerfile](https://pydevtools.com/handbook/how-to/how-to-use-uv-in-a-dockerfile.md) walks through containerizing a uv-based project for deployment.
