diff --git a/.coveragerc b/.coveragerc index 86a32ae..e914860 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,4 +1,3 @@ [run] branch = True source = pythonie - diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c8a348d..cbb7ce5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..c8bdd67 --- /dev/null +++ b/.pre-commit-config.yaml @@ -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 diff --git a/CLAUDE.md b/CLAUDE.md index fc0b6b3..f6ee050 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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. @@ -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. \ No newline at end of file +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. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1bd5f33..a09587d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 @@ -57,6 +58,11 @@ website/ git clone 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 @@ -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 @@ -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" @@ -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 diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 73007fd..74c7b5f 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -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) @@ -109,6 +110,11 @@ pythonie/ git clone 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 @@ -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 @@ -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= git commit` or `git commit --no-verify` only +in an emergency: CI runs the same hooks. + ### 4. Create Atomic Migrations ```bash diff --git a/README.md b/README.md index 61d2681..cb6da3f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Website for Python Ireland (python.ie / pycon.ie) community, built with Django 6 ## Prerequisites -- Python 3.13 (see `mise.toml`) +- Python 3.13, [uv](https://docs.astral.sh/uv/) and [prek](https://github.com/j178/prek) (git hooks), all pinned in `mise.toml`: run `mise install`, or see [Git Hooks](#git-hooks-prek) for other ways to install prek - Docker & Docker Compose (for containerized development - recommended) - [Task](https://taskfile.dev/) (optional but recommended) - Redis (only for local non-Docker development) @@ -12,41 +12,47 @@ Website for Python Ireland (python.ie / pycon.ie) community, built with Django 6 ## Quick Start (Docker - Recommended) -1. Build the Docker image: +1. Install the git hooks (the ruff and Django hooks run on the host through `uv run`): + ```bash + uv sync --all-groups + prek install + ``` + +2. Build the Docker image: ```bash task docker:build # or: make docker-build ``` -2. Start supporting services: +3. Start supporting services: ```bash docker compose up -d postgres redis ``` -3. Run database migrations: +4. Run database migrations: ```bash task django:migrate ``` -4. Generate sample data (creates pages, navigation, meetups): +5. Generate sample data (creates pages, navigation, meetups): ```bash task django:generate-sample-data # or: docker compose run --rm web python pythonie/manage.py generate_sample_data --settings=pythonie.settings.dev ``` -5. Create a superuser: +6. Create a superuser: ```bash docker compose run --rm web python pythonie/manage.py createsuperuser --settings=pythonie.settings.dev ``` -6. Start the development server: +7. Start the development server: ```bash task run # or: docker compose run --rm --service-ports web python pythonie/manage.py runserver 0.0.0.0:8000 ``` -7. Visit http://127.0.0.1:8000/ to see the site with sample content -8. Access Wagtail admin at http://127.0.0.1:8000/admin/ +8. Visit http://127.0.0.1:8000/ to see the site with sample content +9. Access Wagtail admin at http://127.0.0.1:8000/admin/ ## Local Setup (Without Docker) @@ -56,14 +62,15 @@ If you prefer to develop without Docker: 2. Clone your fork: `git clone git@github.com:YourGitHubName/website.git` 3. Ensure you are running Python 3.13: `python -V` should output `Python 3.13.x` 4. Install dependencies: `uv sync --all-groups` (creates and populates a `.venv` automatically) -5. Set up the database: `uv run python pythonie/manage.py migrate --settings=pythonie.settings.dev` -6. Generate sample data: `task django:generate-sample-data` (or `uv run python pythonie/manage.py generate_sample_data --settings=pythonie.settings.dev`) -7. Create a superuser: `uv run python pythonie/manage.py createsuperuser --settings=pythonie.settings.dev` -8. Install and run Redis server locally: `redis-server` -9. Set Redis environment variable: `export REDISCLOUD_URL=127.0.0.1:6379` -10. Run the server: `uv run python pythonie/manage.py runserver --settings=pythonie.settings.dev` -11. Visit http://127.0.0.1:8000/ to see the site with sample content -12. Visit http://127.0.0.1:8000/admin/ to log in to Wagtail admin +5. Install the git hooks: `prek install` (see [Git Hooks](#git-hooks-prek)) +6. Set up the database: `uv run python pythonie/manage.py migrate --settings=pythonie.settings.dev` +7. Generate sample data: `task django:generate-sample-data` (or `uv run python pythonie/manage.py generate_sample_data --settings=pythonie.settings.dev`) +8. Create a superuser: `uv run python pythonie/manage.py createsuperuser --settings=pythonie.settings.dev` +9. Install and run Redis server locally: `redis-server` +10. Set Redis environment variable: `export REDISCLOUD_URL=127.0.0.1:6379` +11. Run the server: `uv run python pythonie/manage.py runserver --settings=pythonie.settings.dev` +12. Visit http://127.0.0.1:8000/ to see the site with sample content +13. Visit http://127.0.0.1:8000/admin/ to log in to Wagtail admin ## Project Structure @@ -185,6 +192,26 @@ 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, to run ruff, django-upgrade, the Django system checks, the missing migrations check and generic file checks on every commit. Like uv, prek is not a project dependency: install it on your machine, then enable the hooks in your clone. + +```bash +# 1. Install prek (once per machine), with one of: +mise install # installs the versions pinned in mise.toml +brew install prek +uv tool install prek + +# 2. Enable the git hooks (once per clone) +uv sync --all-groups # the ruff and Django hooks run through `uv run` +prek install + +# Run every hook on the whole repository (same as CI) +prek run --all-files +``` + +See [CONTRIBUTING.md](CONTRIBUTING.md#git-hooks-prek) for the list of hooks and how to skip one in an emergency. + ## Environment Variables For Docker development, create/edit `development.env`: diff --git a/development.env b/development.env index 94ad002..f0c13cb 100644 --- a/development.env +++ b/development.env @@ -4,4 +4,3 @@ PGUSER=postgres PGPASSWORD=pythonie PGHOST=postgres REDISCLOUD_URL=redis://redis:6379 - diff --git a/mise.toml b/mise.toml index d0daeac..389e5f8 100644 --- a/mise.toml +++ b/mise.toml @@ -1,4 +1,5 @@ [tools] +prek = "0.5.3" python = "3.13" task = "3.53.1" uv = "0.12.19" diff --git a/pythonie/core/templates/base.html b/pythonie/core/templates/base.html index dbc01c2..073a166 100644 --- a/pythonie/core/templates/base.html +++ b/pythonie/core/templates/base.html @@ -46,10 +46,10 @@ {% compress css %} - + - + diff --git a/pythonie/core/templates/core/meetup.html b/pythonie/core/templates/core/meetup.html index 7366501..e157bbf 100644 --- a/pythonie/core/templates/core/meetup.html +++ b/pythonie/core/templates/core/meetup.html @@ -17,7 +17,7 @@
Sponsors:
diff --git a/pythonie/core/templates/navbar_tree.html b/pythonie/core/templates/navbar_tree.html index 9b1f0fc..1a4cb29 100644 --- a/pythonie/core/templates/navbar_tree.html +++ b/pythonie/core/templates/navbar_tree.html @@ -10,9 +10,9 @@