My previous post
about packaging was several years ago.
With the raising popularity of uv
and just
, I have updated my basic packaging steps.
Start with uv
https://codeberg.org/kfdm/example-django/commit/98b50ab401af3752d897d439b6b02d6a556f6757
uv is quickly becoming a popular tool in the Python ecosystem. Using it will greatly simplify many steps.
Running uv init --package
will create our basic package layout and fill in some good defaults.
Install Django
https://codeberg.org/kfdm/example-django/commit/ea546e715c060256a13386fa38446ec96f268517
To prepare, we first remove the default app installed by uv
.
We will then use uv
to add django, and create a new start project.
uv add django
rm -rf src/example_django
uv run django-admin startproject example_django
Rearrange Files
https://codeberg.org/kfdm/example-django/commit/e0136d58feb5a0a35febc612eaab7194d2b5d755
django-admin
defaults to our current directory, but we want to move things around a bit.
# Move most of the files into our source directory
mv example_django/example_django src/
# Move our manage.py
mv example_django/manage.py src/example_django
I do this so that I can edit our pyproject.toml
with our new source and have our own django-admin wrapper.
[project.scripts]
example-django = "example_django.manage:main"
With these changes, we can now run uv run example-django
and we will be calling into our project more easily, and with an easier name, than trying to run something like uv run path/to/manage.py
.
DJANGO_SETTINGS_MODULE cleanup
https://codeberg.org/kfdm/example-django/commit/1bd0357ea38e2c0c00573dde800b9b4d7d082f19
To avoid duplicates, I like to move my DJANGO_SETTINGS_MODULE
environment to a single place.
I remove the instances from asgi.py
, wsgi.py
, and manage.py
and add this to __init__.py
# src/example_djagno/__init__.py
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example_django.settings")
Environment var cleanup
https://codeberg.org/kfdm/example-django/commit/bb5e33b743a383eb6286b3aa07c6a5e11908d108
I also tend to use django-environ
to configure settings for both development and deployment.
The settings one will want to configure will vary based on the actual project.
Basic configuration is fairly easy.
from pathlib import Path
from environ import Env
# Make sure that we resolve the root of our project, not the src direcotry
BASE_DIR = Path(__file__).resolve().parent.parent.parent
# This allows us to create a default ENV_FILE in our checkout directory
ENV_FILE = BASE_DIR / ".env"
# Sometimes I'll do this based on an XDG style path instead
# ENV_FILE = Path.home() / '.config' / django-example.env
# Then we populate our env object and optionally read the file if it exists
# I like to be explicit about the location
env = Env()
if ENV_FILE.exists():
env.read_env(ENV_FILE)
Using Just as a launcher
https://codeberg.org/kfdm/example-django/commit/f9b7d27162ad0aaeceb3b615ec432c19546e933d
Now that just
has default packages for more systems, I have been using it over make
in recent projects.
I start with a basic template
# Show list of tasks
_help:
@just --list
# Run django management command
[group('django')]
django *extra:
uv run example-django {{ extra }}
From this, we can build on extra tasks that we want to run. These are a few generic django rules I find useful.
[group('django')]
run *extra:
@just django runserver {{ extra }}
[group('django')]
migrate *extra:
@just django migrate {{ extra }}
[group('django')]
makemigrations *extra:
@just django makemigrations {{ extra }}
I will also add a rule for formatting.
[group('dev')]
format:
# Typically I use ruff for all my python/django checks
uvx ruff check --fix src
uvx ruff format src
# I have been testing djade for template checks
find src -name '*.html' -exec uvx djade "{}" \;
# and I like to make sure my just file is also tidy
just --fmt --unstable
for building with docker, I’ll often add a task there
DOCKER_TAG := "myorg/mypackage"
[group('docker')]
build:
uv lock --no-sources
docker build --tag ${DOCKER_TAG}:local .