Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
[run]
branch = True
source = pythonie

24 changes: 24 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,30 @@ name: Test packages
on: [push, pull_request, workflow_dispatch]

jobs:
prek:
name: Run prek hooks
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v7.0.1

- name: Install uv
uses: astral-sh/setup-uv@v10.2.0
with:
python-version: "3.13"
enable-cache: true
cache-dependency-glob: "uv.lock"

# The local hooks (ruff, Django checks) run through `uv run`, with the
# versions locked in uv.lock, exactly as on a developer machine.
- name: Install dependencies
run: uv sync --all-groups

# Installs prek, caches the hook environments and runs
# `prek run --show-diff-on-failure --all-files`.
- name: Run prek on all files
uses: j178/prek-action@v3.0.0

test:
name: Run code quality checks and tests
runs-on: ubuntu-latest
Expand Down
83 changes: 83 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Git hooks, run with prek (https://github.com/j178/prek), a drop-in
# replacement for pre-commit that reads this same file.
#
# Like uv, prek is a machine tool, not a project dependency: it is pinned in
# mise.toml (`mise install`), or install it with `brew install prek` or
# `uv tool install prek`. The local hooks also need the project environment
# (`uv sync --all-groups`).
#
# prek install # install the git hook for this clone
# prek run --all-files # run every hook on the whole repository
#
# Revisions are pinned to tags and kept up to date by Renovate.

# Vendored third-party assets (Bootstrap, Font Awesome, SmartMenus) are kept
# byte-for-byte identical to upstream.
exclude: |
(?x)^(
pythonie/core/static/(css|fonts|js)/.*
)$

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
- id: end-of-file-fixer
- id: mixed-line-ending
args: [--fix=lf]
- id: check-yaml
- id: check-toml
- id: check-json
- id: check-merge-conflict
- id: check-case-conflict
- id: check-added-large-files
args: [--maxkb=500]
- id: debug-statements
- id: detect-private-key

# Keeps uv.lock in sync with pyproject.toml. The rev tracks the uv version
# pinned in mise.toml and the dev dependency group (Renovate "uv" group).
- repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.12.19
hooks:
- id: uv-lock

- repo: https://github.com/adamchainz/django-upgrade
rev: 1.32.0
hooks:
- id: django-upgrade
args: [--target-version, "6.0"]

# Ruff and Django checks run through `uv run` so they use the exact versions
# locked in uv.lock (the same ones CI uses): there is no second ruff version
# to keep in sync with pyproject.toml.
# The Django hooks unset DATABASE_URL so they always run against the local
# SQLite database, never against a remote one exported in the shell.
- repo: local
hooks:
- id: ruff-check
name: ruff check
entry: uv run --frozen ruff check --fix --force-exclude
language: system
types_or: [python, pyi]
require_serial: true
- id: ruff-format
name: ruff format
entry: uv run --frozen ruff format --force-exclude
language: system
types_or: [python, pyi]
require_serial: true
- id: django-check
name: django system checks
entry: env -u DATABASE_URL uv run --frozen python pythonie/manage.py check --settings=pythonie.settings.tests
language: system
files: ^pythonie/.*\.py$
pass_filenames: false
- id: django-missing-migrations
name: django missing migrations
entry: env -u DATABASE_URL uv run --frozen python pythonie/manage.py makemigrations --check --dry-run --settings=pythonie.settings.tests
language: system
files: ^pythonie/.*\.py$
pass_filenames: false
20 changes: 19 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,24 @@ task code:check
# or: toast code:check
```

### Git Hooks (prek)

[prek](https://github.com/j178/prek) (a Rust drop-in replacement for pre-commit) runs the hooks defined in `.pre-commit-config.yaml`. It is **not** a project dependency: like uv, it must already be installed on the machine. It is pinned in `mise.toml` (`mise install`); without mise, use `brew install prek` or `uv tool install prek`. CI installs it with `j178/prek-action`.

```bash
# Install the git hook (once per clone)
prek install

# Run every hook on the whole repository (also run by the CI prek job)
prek run --all-files

# Emergency bypass: skip one hook, or all of them
SKIP=django-missing-migrations git commit -m "..."
git commit --no-verify -m "..."
```

Hooks: pre-commit-hooks (whitespace, EOF, YAML/TOML/JSON, merge conflicts, large files, debug statements, private keys), uv-lock, django-upgrade (`--target-version 6.0`), and local hooks for ruff check/format, `manage.py check` and missing migrations. The ruff and Django hooks are `local` hooks running `uv run --frozen`, so ruff is pinned in one place only (`pyproject.toml`); there is no `ruff-pre-commit` rev to keep aligned. The Django hooks unset `DATABASE_URL` so they never reach a remote database. Vendored assets under `pythonie/core/static/{css,fonts,js}` are excluded. Renovate updates the hook revisions (`pre-commit` manager, enabled in `renovate.json`).

### Dependency Management

Uses `uv` for fast Python package management. Dependencies are declared in `pyproject.toml` (`[project.dependencies]` + `[dependency-groups]`) and locked in `uv.lock` (committed). Heroku's Python buildpack detects `uv.lock` and installs natively via `uv sync` against the slug's system Python — **never add a `requirements.txt` at the repo root**, the buildpack aborts the build if it finds more than one package-manager file (`requirements.txt`, `poetry.lock`, `uv.lock`) at once. `.python-version` (not `runtime.txt`, which is deprecated) pins the Python version for the buildpack.
Expand Down Expand Up @@ -270,4 +288,4 @@ All documentation and code comments must be written in English to ensure all con

### Git Commits

When creating git commits, do not include any mention of Claude, Claude Code, or AI assistance in commit messages. Commit messages should focus solely on describing the changes made, without attribution to the tool used to make them.
When creating git commits, do not include any mention of Claude, Claude Code, or AI assistance in commit messages. Commit messages should focus solely on describing the changes made, without attribution to the tool used to make them.
69 changes: 68 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ This project follows the [Python Community Code of Conduct](https://www.python.o
- **Docker** and **docker-compose** (recommended)
- **Git**
- **Task** (optional, for running predefined commands)
- **uv** and **prek** (git hooks), pinned in `mise.toml` (`mise install`), see [Git Hooks (prek)](#git-hooks-prek)

### Repository Structure

Expand Down Expand Up @@ -57,6 +58,11 @@ website/
git clone <repository-url>
cd website

# Install the git hooks (requires uv and prek, see "Git Hooks (prek)" below;
# the ruff and Django hooks run on the host through `uv run`)
uv sync --all-groups
prek install

# Build Docker image
task docker:build
# or: make docker-build
Expand All @@ -83,6 +89,9 @@ python3 --version # Should show 3.13.x
# Install dependencies (creates and populates a .venv automatically)
uv sync --all-groups

# Install the git hooks (requires prek, see "Git Hooks (prek)" below)
prek install

# Run migrations
uv run python pythonie/manage.py migrate --settings=pythonie.settings.dev

Expand Down Expand Up @@ -137,7 +146,8 @@ refactor/simplify-sponsor-model
# or: python -m ruff format pythonie
```

5. **Commit your changes** with clear messages:
5. **Commit your changes** with clear messages (the prek git hooks run
automatically on the staged files, see [Git Hooks (prek)](#git-hooks-prek)):
```bash
git add .
git commit -m "Add dark mode toggle feature"
Expand Down Expand Up @@ -174,6 +184,63 @@ task code:lint
task code:check
```

### Git Hooks (prek)

The repository uses [prek](https://github.com/j178/prek), a fast drop-in
replacement for [pre-commit](https://pre-commit.com/) that reads the same
`.pre-commit-config.yaml`. Like uv, prek is not a project dependency: it must
be installed on your machine. It is pinned in `mise.toml`, or install it
another way:

```bash
mise install # mise: installs the versions pinned in mise.toml
brew install prek # Homebrew
uv tool install prek # uv
```

The ruff and Django hooks run through `uv run`, so they also need the project
environment (`uv sync --all-groups`).

Then enable the hooks, once per clone (whether you develop with Docker or not):

```bash
# Install the git hook once per clone
prek install

# Run every hook on the whole repository
prek run --all-files

# Run a single hook
prek run ruff-check --all-files
```

The hooks run on every `git commit`, on the staged files only:

- **Generic checks** (pre-commit-hooks): trailing whitespace, end of file
newline, line endings, YAML/TOML/JSON syntax, merge conflict markers, large
files, leftover debugger imports, private keys
- **uv-lock**: keeps `uv.lock` in sync with `pyproject.toml`
- **django-upgrade**: rewrites deprecated Django idioms (target: Django 6.0)
- **ruff check / ruff format**: run through `uv run`, with the same ruff version
as CI
- **Django system checks and missing migrations**: `manage.py check` and
`manage.py makemigrations --check --dry-run`, always against the local SQLite
database (`DATABASE_URL` is ignored)

When a hook fixes files, the commit is aborted: review the changes, `git add`
them and commit again. CI runs `prek run --all-files` on every push and pull
request.

To bypass the hooks in an emergency (CI will still run them):

```bash
# Skip one or more hooks by id
SKIP=django-missing-migrations git commit -m "..."

# Skip all hooks
git commit --no-verify -m "..."
```

### Django/Wagtail Conventions

1. **Models**: Place in `models.py`, use explicit `Meta` classes
Expand Down
16 changes: 16 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ pythonie/
- Docker + docker-compose
- Task (or Make)
- Git
- uv and prek (git hooks), pinned in `mise.toml` (`mise install`); see CONTRIBUTING.md for other ways to install prek

### Initial Setup (Docker - Recommended)

Expand All @@ -109,6 +110,11 @@ pythonie/
git clone <repo-url>
cd website

# Install the prek git hooks (they run on the host: the ruff and Django hooks
# use `uv run`, so the host environment is needed too)
uv sync --all-groups
prek install

# 2. Build Docker image
task docker:build

Expand Down Expand Up @@ -137,6 +143,10 @@ task run
# 1. Install dependencies (ensure Python 3.13; creates and populates a .venv automatically)
uv sync --all-groups

# Install the prek git hooks (ruff, django-upgrade, missing migrations, ...)
# prek must be installed on the machine, see CONTRIBUTING.md
prek install

# 2. Migrate database
uv run python pythonie/manage.py migrate --settings=pythonie.settings.dev

Expand Down Expand Up @@ -1004,8 +1014,14 @@ task code:format # Format code with ruff
task code:lint # Lint code and fix issues
task code:check # Check without changes
task dependencies:security # Check for security vulnerabilities
prek run --all-files # Run every git hook (same as the CI prek job)
```

The prek git hooks (`prek install`, see CONTRIBUTING.md) run ruff,
django-upgrade, the Django system checks and the missing migrations check on
every commit. Use `SKIP=<hook-id> git commit` or `git commit --no-verify` only
in an emergency: CI runs the same hooks.

### 4. Create Atomic Migrations

```bash
Expand Down
Loading
Loading