From 83a24f0d1dea058bff647bd3b0c2d32ba91fe2f2 Mon Sep 17 00:00:00 2001 From: Giannis Terzopoulos Date: Fri, 1 May 2026 22:35:32 +0200 Subject: [PATCH 1/9] Migrate to `pyproject.toml`, add back coverage report --- .gitignore | 3 ++ MANIFEST.in | 3 +- Makefile | 15 +++++--- codecov.yml | 4 +++ pyproject.toml | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++ setup.cfg | 32 ----------------- setup.py | 57 ------------------------------ 7 files changed, 113 insertions(+), 96 deletions(-) create mode 100644 codecov.yml create mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.gitignore b/.gitignore index 518348d..89edb21 100644 --- a/.gitignore +++ b/.gitignore @@ -3,12 +3,15 @@ __pycache__/ .cache/ .tox/ .coverage +.coverage.* .DS_Store .python-version *.egg-info/ *.py[cod] coverage.xml +htmlcov/ build/ dist/ +venv/ src/wagtailbakery/build/ src/wagtailbakery/tests/public/ diff --git a/MANIFEST.in b/MANIFEST.in index e3f7812..8ba64a1 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,7 +5,6 @@ include CHANGELOG.md include LICENSE include MANIFEST.in include README.md -include setup.cfg -include setup.py +include pyproject.toml recursive-include src/wagtailbakery *.html *.py diff --git a/Makefile b/Makefile index 319c0fa..373c2cd 100644 --- a/Makefile +++ b/Makefile @@ -8,19 +8,24 @@ help: ## See what commands are available. all: install clean test lint ## Install, test and lint the project. clean: ## Remove Python file artifacts. - find . -type f -name "*.py[co]" -delete - find . -type d -name "__pycache__" -delete + find . -name '*.pyc' -exec rm -rf {} + + find . -name '__pycache__' -exec rm -rf {} + + find . -name '*.egg-info' -exec rm -rf {} + + rm -rf build dist install: ## Install dependencies. pip install -e .[test] flake8: ## Run flake8 on the project. - flake8 src/ + flake8 src/ tests/ isort: ## Run isort on the project. - isort --check-only --diff --recursive src/ + isort --check-only --diff src/ tests/ lint: flake8 isort ## Lint the project. test: ## Test the project. - py.test + pytest --cov + +cov: ## Generate coverage report (manually open htmlcov/index.html in browser) + pytest --cov --cov-report html diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..809bfc0 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,4 @@ +coverage: + precision: 1 + round: nearest + range: "70...100" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..bf0e410 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,95 @@ +[build-system] +requires = ["setuptools >= 61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "wagtail-bakery" +version = "0.9.0" +description = "A set of helpers for baking your Django Wagtail site out as flat files." +readme = "README.md" +authors = [ + { name = "Wagtail Nest team", email = "hello@wagtail.org" }, +] +requires-python = ">=3.8" +classifiers = [ + "Development Status :: 3 - Alpha", + "Environment :: Web Environment", + "Framework :: Django", + "Framework :: Wagtail", + "Framework :: Wagtail :: 5", + "Framework :: Wagtail :: 6", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Topic :: Software Development :: Build Tools", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +dependencies = [ + "django-bakery~=0.13.1", + "wagtail>=5.2", +] + +[project.optional-dependencies] +test = [ + "coverage[toml]>=7.13.5,<8.0", + "factory-boy", + "flake8", + "isort", + "pytest-cov", + "pytest-django", + "pytest", +] + +[project.urls] +Changelog = "https://github.com/wagtail-nest/wagtail-bakery/blob/main/CHANGELOG.md" +Homepage = "https://github.com/wagtail-nest/wagtail-bakery/" +"Issue tracker" = "https://github.com/wagtail-nest/wagtail-bakery/issues/" +Source = "https://github.com/wagtail-nest/wagtail-bakery/" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.pytest.ini_options] +testpaths = ["tests"] + +[tool.isort] +line_length = 100 +multi_line_output = 4 +known_third_party = ["bakery", "django", "factory", "pytest", "wagtail"] + +[tool.coverage.run] +branch = true +source_pkgs = ["wagtailbakery"] +omit = ["tests/*"] + +[tool.coverage.paths] +source = ["src/wagtailbakery"] + +[tool.coverage.report] +show_missing = true +exclude_lines = [ + # Have to re-enable the standard pragma + "pragma: no cover", + + # Don't complain about missing debug-only code: + "def __repr__", + "if self.debug", + + # Don't complain if tests don't hit defensive assertion code: + "raise AssertionError", + "raise NotImplementedError", + + # Don't complain if non-runnable code isn't run: + "if 0:", + "if __name__ == .__main__.:", + + # Don't complain about abstract methods, they aren't run: + "@(abc.)?abstractmethod", + + # Nor complain about type checking + "if TYPE_CHECKING:", +] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 253a633..0000000 --- a/setup.cfg +++ /dev/null @@ -1,32 +0,0 @@ -[coverage:run] -omit=src/wagtailbakery/tests/* - -[flake8] -# D100: Missing docstring in public module -# D101: Missing docstring in public class -# D102: Missing docstring in public method -# D103: Missing docstring in public function -# D105: Missing docstring in magic method -# D200: One-line docstring should fit on one line with quotes -# D202: No blank lines allowed after function docstring -# D204: 1 blank line required after docstring -# D205: 1 blank line required between summary line and description -# D209: Multi-line docstring closing quotes should be on a separate line -# D400: First line should end with a period -# D401: First line should be in imperative mood -# E303: Too many blank lines -# E501: Line too long -# W503: line break before binary operator (superseded by W504 line break after binary operator) -# N805: First argument of a method should be named 'self' -# N806: Variable in function should be lowercase -ignore = D100,D101,D102,D103,D105,D200,D202,D204,D205,D209,D400,D401,E303,E501,W503,N805,N806 -exclude=*/migrations/ -max-line-length = 120 - -[isort] -line_length=100 -multi_line_output=4 -known_third_party=bakery,django,factory,pytest,wagtail - -[tool:pytest] -testpaths=tests diff --git a/setup.py b/setup.py deleted file mode 100644 index 82d507c..0000000 --- a/setup.py +++ /dev/null @@ -1,57 +0,0 @@ -from setuptools import find_packages, setup - -with open('README.md', 'r') as fh: - long_description = fh.read() - -install_requires = [ - 'django-bakery~=0.13.1', - 'wagtail>=4.1', -] - -test_requires = [ - 'factory-boy', - 'flake8', - 'isort', - 'pytest', - 'pytest-cov', - 'pytest-django', -] - -setup( - name='wagtail-bakery', - version='0.8.0', - description='A set of helpers for baking your Django Wagtail site out as flat files.', - long_description=long_description, - long_description_content_type='text/markdown', - author='Rob Moorman and Wagtail Core Team', - author_email='hello@wagtail.org', - url="https://github.com/wagtail-nest/wagtail-bakery/", - project_urls={ - "Source": "https://github.com/wagtail-nest/wagtail-bakery/", - "Issue tracker": "https://github.com/wagtail-nest/wagtail-bakery/issues/", - "Changelog": "https://github.com/wagtail-nest/wagtail-bakery/blob/main/CHANGELOG.md", - }, - install_requires=install_requires, - tests_require=test_requires, - extras_require={'test': test_requires}, - package_dir={'': 'src'}, - packages=find_packages('src'), - include_package_data=True, - zip_safe=False, - license='MIT', - classifiers=[ - 'Environment :: Web Environment', - 'License :: OSI Approved :: MIT License', - 'Framework :: Django', - 'Framework :: Wagtail', - 'Framework :: Wagtail :: 5', - 'Framework :: Wagtail :: 6', - 'Operating System :: Unix', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - ], -) From 638ab1568cfb315011c2e6888bd14b60253eed1b Mon Sep 17 00:00:00 2001 From: Giannis Terzopoulos Date: Fri, 1 May 2026 22:36:22 +0200 Subject: [PATCH 2/9] Bump GitHub Actions versions --- .github/workflows/publish-test.yml | 14 +++++++------- .github/workflows/publish.yml | 14 +++++++------- .github/workflows/test.yml | 23 +++++++++++++++++++---- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/.github/workflows/publish-test.yml b/.github/workflows/publish-test.yml index 4e06981..f330a7c 100644 --- a/.github/workflows/publish-test.yml +++ b/.github/workflows/publish-test.yml @@ -13,17 +13,17 @@ jobs: needs: ['test'] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - - uses: actions/setup-python@v3 + - uses: actions/setup-python@v6 with: - python-version: 3.11 + python-version: 3.13 - run: | - pip install setuptools - python setup.py sdist + pip install build + python -m build . - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v5 with: path: ./dist @@ -38,7 +38,7 @@ jobs: # Mandatory for trusted publishing id-token: write steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v5 - name: Publish package distributions to TestPyPI uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e4f25fc..e52c0cc 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,17 +14,17 @@ jobs: needs: ['test'] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - - uses: actions/setup-python@v3 + - uses: actions/setup-python@v6 with: - python-version: 3.11 + python-version: 3.13 - run: | - pip install setuptools - python setup.py sdist + pip install build + python -m build . - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v5 with: path: ./dist @@ -39,7 +39,7 @@ jobs: # Mandatory for trusted publishing id-token: write steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v5 - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bade5f6..50015d1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,14 +30,17 @@ jobs: toxenv: wagtailmain experimental: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python }} + cache: 'pip' - name: Install dependencies run: | - python -m pip install setuptools tox tox-py + python -m pip install tox tox-py coverage # This step runs only for jobs NOT in the include matrix - name: Run tox targets for Python ${{ matrix.python }} @@ -52,4 +55,16 @@ jobs: tox -e ${{ matrix.toxenv }} env: TOXENV: ${{ matrix.toxenv }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate coverage report + run: | + coverage combine + coverage xml + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v6 + with: + token: ${{ secrets.CODECOV_TOKEN }} + file: coverage.xml + flags: unittests + fail_ci_if_error: true From 94b9f34905d5529bf35ea3febe1d2325cdc42be3 Mon Sep 17 00:00:00 2001 From: Giannis Terzopoulos Date: Fri, 1 May 2026 22:37:59 +0200 Subject: [PATCH 3/9] Bump supported Python, Django and Wagtail versions - Drop Python 3.8 and 3.9 (end of life) - Add Python 3.13 and 3.14 - Drop Django 4.2, 5.0 and 5.1 - Add Django 5.2 and 6.0 - Drop Wagtail 5.2, 6.0, 6.1, 6.2, 6.3 and 6.4 - Add Wagtail 7.0, 7.3 LTS and 7.4 --- .github/workflows/test.yml | 8 +++--- CHANGELOG.md | 12 ++++++--- README.md | 9 ++++--- pyproject.toml | 15 ++++++----- tox.ini | 52 +++++++++++++++++--------------------- 5 files changed, 48 insertions(+), 48 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 50015d1..5b4968a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,19 +14,19 @@ jobs: continue-on-error: ${{ matrix.experimental }} strategy: matrix: - python: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python: ["3.10", "3.11", "3.12", "3.13", "3.14"] experimental: [false] toxenv: ["py"] include: # Linting - - python: "3.8" + - python: "3.13" toxenv: flake8 experimental: false - - python: "3.8" + - python: "3.13" toxenv: isort experimental: false # Future Wagtail release from main branch (allowed to fail) - - python: "3.12" + - python: "3.13" toxenv: wagtailmain experimental: true steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index e9d8b95..92083cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added -- Compatibility with Wagtail 6.0 and 6.1. [#95](https://github.com/wagtail-nest/wagtail-bakery/pull/95) -- Compatibility with Django 5.0. [#95](https://github.com/wagtail-nest/wagtail-bakery/pull/95) +- Compatibility with Python 3.13 and 3.14. +- Compatibility with Django 5.2 and 6.0. +- Compatibility with Wagtail 7.0, 7.3 LTS, and 7.4. +- Switch `setup.py` to `pyproject.toml`. +- Added back coverage reporting. ### Fixed @@ -19,8 +22,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Removed -- Drop support for Wagtail before 5.2. [#95](https://github.com/wagtail-nest/wagtail-bakery/pull/95) -- Drop support for Django before 4.2. [#95](https://github.com/wagtail-nest/wagtail-bakery/pull/95) +- Drop support for Python before 3.10. +- Drop support for Django before 5.2. +- Drop support for Wagtail before 7.0. ## [0.8.0](https://github.com/wagtail-nest/wagtail-bakery/compare/0.7.0...0.8.0) diff --git a/README.md b/README.md index 0e4f0ff..d952952 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ A set of helpers for baking your Django Wagtail site out as flat files. [![License: MIT](https://img.shields.io/pypi/l/wagtail-bakery)](https://github.com/wagtail-nest/wagtail-bakery/blob/main/LICENSE) [![Build Status](https://github.com/wagtail-nest/wagtail-bakery/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/wagtail-nest/wagtail-bakery/actions/workflows/test.yml) +[![codecov](https://codecov.io/gh/wagtail-nest/wagtail-bakery/graph/badge.svg)](https://codecov.io/gh/wagtail-nest/wagtail-bakery) [![Version](https://img.shields.io/pypi/v/wagtail-bakery.svg)](https://pypi.python.org/pypi/wagtail-bakery/) [![Monthly downloads](https://img.shields.io/pypi/dm/wagtail-bakery.svg?logo=Downloads)](https://pypi.python.org/pypi/wagtail-bakery/) @@ -31,9 +32,9 @@ If you want to send an encrypted email (optional), the public key ID for securit ## Supported Versions -- Python 3.8 - 3.12 -- Django 4.2 - 5.0 -- Wagtail >= 5.2 +- Python 3.10 - 3.14 +- Django 5.2 - 6.0 +- Wagtail >= 7.0 We aim to support the Wagtail versions as [supported](http://docs.wagtail.org/en/latest/releases/upgrading.html) by Wagtail (current LTS, current stable). @@ -163,7 +164,7 @@ Test as follow: ### Releases 1. Create a new branch (e.g. `release/1.1.3`) for the release of the new version. -1. Update the version number in `setup.py` following [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +1. Update the version number in `pyproject.toml` following [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 1. Update `CHANGELOG.md`. 1. On GitHub, create a pull request and squash merge it. 1. On GitHub, if this is a minor release bump (for example `1.1.0` or `1.2.0` but not `1.1.1`, `1.2.3`), create a `stable/1.1.x` branch from `main`. diff --git a/pyproject.toml b/pyproject.toml index bf0e410..d7ed66b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,27 +10,28 @@ readme = "README.md" authors = [ { name = "Wagtail Nest team", email = "hello@wagtail.org" }, ] -requires-python = ">=3.8" +requires-python = ">=3.10" classifiers = [ "Development Status :: 3 - Alpha", "Environment :: Web Environment", "Framework :: Django", + "Framework :: Django :: 5.2", + "Framework :: Django :: 6.0", "Framework :: Wagtail", - "Framework :: Wagtail :: 5", - "Framework :: Wagtail :: 6", + "Framework :: Wagtail :: 7", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Topic :: Software Development :: Build Tools", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] dependencies = [ - "django-bakery~=0.13.1", - "wagtail>=5.2", + "django-bakery~=0.13.5", + "wagtail>=7.0", ] [project.optional-dependencies] diff --git a/tox.ini b/tox.ini index 15b1130..2735d50 100644 --- a/tox.ini +++ b/tox.ini @@ -1,39 +1,33 @@ -[variables] -linting_folders=src/wagtailbakery/ tests/ examples/ - [tox] +# Necessary because we are using pyproject.toml +isolated_build = true envlist= - py{38,39,310,311}-django42-wagtail52 # Wagtail 5.2 LTS - all supported Pythons - supported Django - py{310,311,312}-django50-wagtail52 # Wagtail 5.2 LTS - All supported Pythons for Django 5.0 - py{38,39,310,311,312}-django42-wagtail{60,61} # Wagtail 6.0 and 6.1 - all supported Python - supported Django - py{310,311,312}-django50-wagtail{60,61} # Wagtail 6.0 and 6.1 - all supported Python - supported Django - wagtailmain # Wagtail main latest compatible version + # Django 5.2, supported Wagtail versions (LTS + latest) + py{310,311,312,313}-django52-wagtail{70,73,74} + # Django 6.0, supported Wagtail versions (Django 6.0 support was added in Wagtail 7.3) + py{312,313,314}-django60-wagtail{73,74} + # Wagtail main latest compatible version + wagtailmain [testenv] -commands=py.test --cov=wagtailbakery --cov-report=xml {posargs} +commands=pytest --cov=wagtailbakery {posargs} +setenv= + COVERAGE_FILE=.coverage.{envname} + COVERAGE_CORE=sysmon deps= - django42: django>=4.2,<5 - django50: django>=5.0,<5.1 - wagtail52: wagtail>=5.2,<5.3 # Current LTS - wagtail60: wagtail>=6.0,<6.1 - wagtail61: wagtail>=6.1,<6.2 + django52: django>=5.2,<5.3 + django60: django>=6.0,<6.1 + wagtail70: wagtail>=7.0,<7.1 + wagtail73: wagtail>=7.3,<7.4 # Current LTS + wagtail74: wagtail>=7.4rc1,<7.5 extras=test [testenv:wagtailmain] -commands=py.test --cov=wagtailbakery --cov-report=xml {posargs} +commands=pytest --cov=wagtailbakery {posargs} +setenv= + COVERAGE_FILE=.coverage.{envname} + COVERAGE_CORE=sysmon deps= - django>=5.0,<5.1 - git+https://github.com/wagtail/wagtail.git@main#egg=Wagtail + django>=6.0,<6.1 + https://github.com/wagtail/wagtail/archive/refs/heads/main.zip extras=test - -[testenv:flake8] -commands=flake8 {[variables]linting_folders} -basepython=python3.8 -skip_install=true -deps=flake8 - -[testenv:isort] -commands=isort --check-only --diff {[variables]linting_folders} -basepython=python3.8 -skip_install=true -deps=isort From 9199b846bd01c908815b1be66b4d40d0720c58e7 Mon Sep 17 00:00:00 2001 From: Giannis Terzopoulos Date: Fri, 1 May 2026 22:38:17 +0200 Subject: [PATCH 4/9] Update CI comment --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5b4968a..9547264 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,7 +48,7 @@ jobs: run: | tox --py current - # This step runs only for jobs in the include matrix and covers linting + # This step runs only for jobs in the include matrix - name: Run tox targets for Python ${{ matrix.python }} (${{ matrix.toxenv }}) if: ${{ matrix.toxenv != 'py' }} run: | From 290041d00582b57f532fce2dfc741c651df8ce4e Mon Sep 17 00:00:00 2001 From: Giannis Terzopoulos Date: Fri, 1 May 2026 22:38:37 +0200 Subject: [PATCH 5/9] Format report build failure script --- .circleci/report_nightly_build_failure.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.circleci/report_nightly_build_failure.py b/.circleci/report_nightly_build_failure.py index ef54fe5..bfd1a5d 100644 --- a/.circleci/report_nightly_build_failure.py +++ b/.circleci/report_nightly_build_failure.py @@ -3,16 +3,23 @@ This reports an error to the #nightly-build-failures Slack channel. """ + import os + import requests -if 'SLACK_WEBHOOK_URL' in os.environ: +if "SLACK_WEBHOOK_URL" in os.environ: print("Reporting to #nightly-build-failures slack channel") - response = requests.post(os.environ['SLACK_WEBHOOK_URL'], json={ - "text": "A Nightly build failed. See " + os.environ['CIRCLE_BUILD_URL'], - }) + response = requests.post( + os.environ["SLACK_WEBHOOK_URL"], + json={ + "text": "A Nightly build failed. See " + os.environ["CIRCLE_BUILD_URL"], + }, + ) print("Slack responded with:", response) else: - print("Unable to report to #nightly-build-failures slack channel because SLACK_WEBHOOK_URL is not set") + print( + "Unable to report to #nightly-build-failures slack channel because SLACK_WEBHOOK_URL is not set" + ) From 22f37ba3efcc21a56fcea35ceaa89113e6a984ea Mon Sep 17 00:00:00 2001 From: Giannis Terzopoulos Date: Fri, 1 May 2026 23:19:11 +0200 Subject: [PATCH 6/9] Pin setuptools<81 in test extra PyFilesystem2 (fs), pulled in transitively via django-bakery, still calls `pkg_resources.declare_namespace(__name__)` at import time. setuptools 81 deprecated `pkg_resources` and 82 dropped it from the wheel, so `import fs` explodes with `ModuleNotFoundError: No module named 'pkg_resources'` on fresh Python 3.14 envs. Pin setuptools<81 until upstream `fs` ships a fix. --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index d7ed66b..2e4b5f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,9 @@ test = [ "pytest-cov", "pytest-django", "pytest", + # `fs` (transitive via django-bakery) still calls pkg_resources.declare_namespace, + # which setuptools 81+ removed. Pin until PyFilesystem2 ships a fix. + "setuptools<81", ] [project.urls] From 8c2fff90dd68190cc52563459fd95c59b26b919e Mon Sep 17 00:00:00 2001 From: Giannis Terzopoulos Date: Fri, 1 May 2026 23:24:40 +0200 Subject: [PATCH 7/9] Reformat with Black --- .../aws/example/migrations/0001_initial.py | 106 ++++++++++--- examples/aws/example/models.py | 9 +- examples/aws/example/settings.py | 140 ++++++++---------- examples/aws/example/urls.py | 14 +- .../example/migrations/0001_initial.py | 106 ++++++++++--- examples/multisite/example/models.py | 9 +- examples/multisite/example/settings.py | 134 ++++++++--------- examples/multisite/example/urls.py | 14 +- .../site/example/migrations/0001_initial.py | 106 ++++++++++--- examples/site/example/models.py | 9 +- examples/site/example/settings.py | 134 ++++++++--------- examples/site/example/urls.py | 14 +- src/wagtailbakery/__init__.py | 2 +- src/wagtailbakery/api_views.py | 52 +++++-- src/wagtailbakery/apps.py | 4 +- src/wagtailbakery/models.py | 8 +- src/wagtailbakery/signal_handlers.py | 6 +- src/wagtailbakery/views.py | 33 +++-- tests/conftest.py | 119 ++++++++------- tests/factories/page.py | 8 +- tests/factories/site.py | 4 +- tests/fixtures.py | 43 +++--- tests/integration/test_api_views.py | 105 +++++++------ tests/integration/test_models.py | 33 +++-- tests/integration/test_redirect.py | 17 ++- tests/integration/test_routablepagemixin.py | 12 +- tests/integration/test_views.py | 24 +-- tests/migrations/0001_initial.py | 20 ++- tests/migrations/0002_eventpage.py | 22 ++- tests/models.py | 32 ++-- tests/urls.py | 4 +- 31 files changed, 783 insertions(+), 560 deletions(-) diff --git a/examples/aws/example/migrations/0001_initial.py b/examples/aws/example/migrations/0001_initial.py index ad3e02a..33288e2 100644 --- a/examples/aws/example/migrations/0001_initial.py +++ b/examples/aws/example/migrations/0001_initial.py @@ -11,52 +11,116 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('wagtailcore', '0083_workflowcontenttype'), + ("wagtailcore", "0083_workflowcontenttype"), ] operations = [ migrations.CreateModel( - name='BlogListPage', + name="BlogListPage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')), - ('body', wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock())], use_json_field=True)), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.page", + ), + ), + ( + "body", + wagtail.fields.StreamField( + [("paragraph", wagtail.blocks.RichTextBlock())], + use_json_field=True, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('wagtailcore.page', models.Model), + bases=("wagtailcore.page", models.Model), ), migrations.CreateModel( - name='BlogPostPage', + name="BlogPostPage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')), - ('body', wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock())], use_json_field=True)), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.page", + ), + ), + ( + "body", + wagtail.fields.StreamField( + [("paragraph", wagtail.blocks.RichTextBlock())], + use_json_field=True, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('wagtailcore.page', models.Model), + bases=("wagtailcore.page", models.Model), ), migrations.CreateModel( - name='GenericPage', + name="GenericPage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')), - ('body', wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock())], use_json_field=True)), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.page", + ), + ), + ( + "body", + wagtail.fields.StreamField( + [("paragraph", wagtail.blocks.RichTextBlock())], + use_json_field=True, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('wagtailcore.page', models.Model), + bases=("wagtailcore.page", models.Model), ), migrations.CreateModel( - name='HomePage', + name="HomePage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')), - ('body', wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock())], use_json_field=True)), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.page", + ), + ), + ( + "body", + wagtail.fields.StreamField( + [("paragraph", wagtail.blocks.RichTextBlock())], + use_json_field=True, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('wagtailcore.page', models.Model), + bases=("wagtailcore.page", models.Model), ), ] diff --git a/examples/aws/example/models.py b/examples/aws/example/models.py index 65a562d..c8b06f8 100644 --- a/examples/aws/example/models.py +++ b/examples/aws/example/models.py @@ -8,16 +8,11 @@ class AbstractExamplePage(Page, AutoPublishingWagtailBakeryModel): body = StreamField( - [ - ('paragraph', blocks.RichTextBlock()) - ], + [("paragraph", blocks.RichTextBlock())], use_json_field=True, ) - content_panels = [ - FieldPanel('title'), - FieldPanel('body') - ] + content_panels = [FieldPanel("title"), FieldPanel("body")] class Meta: abstract = True diff --git a/examples/aws/example/settings.py b/examples/aws/example/settings.py index 9e1f606..e16cac1 100755 --- a/examples/aws/example/settings.py +++ b/examples/aws/example/settings.py @@ -24,92 +24,86 @@ # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '_vyer8o8)0jv9xbwmq8vf)9j(*#plkr+pryg*@0j9*tg5p(6ze' +SECRET_KEY = "_vyer8o8)0jv9xbwmq8vf)9j(*#plkr+pryg*@0j9*tg5p(6ze" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = ['*'] +ALLOWED_HOSTS = ["*"] # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - - 'wagtail.contrib.forms', - 'wagtail.contrib.redirects', - 'wagtail.embeds', - 'wagtail.sites', - 'wagtail.users', - 'wagtail.snippets', - 'wagtail.documents', - 'wagtail.images', - 'wagtail.search', - 'wagtail.admin', - 'wagtail', - - 'wagtail.contrib.settings', - - 'modelcluster', - 'taggit', - - 'bakery', - 'wagtailbakery', - - 'example', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "wagtail.contrib.forms", + "wagtail.contrib.redirects", + "wagtail.embeds", + "wagtail.sites", + "wagtail.users", + "wagtail.snippets", + "wagtail.documents", + "wagtail.images", + "wagtail.search", + "wagtail.admin", + "wagtail", + "wagtail.contrib.settings", + "modelcluster", + "taggit", + "bakery", + "wagtailbakery", + "example", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - - 'wagtail.contrib.redirects.middleware.RedirectMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "wagtail.contrib.redirects.middleware.RedirectMiddleware", ] -ROOT_URLCONF = 'example.urls' +ROOT_URLCONF = "example.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [ - os.path.join(BASE_DIR, 'templates'), + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [ + os.path.join(BASE_DIR, "templates"), ], - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + "loaders": [ + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", ], - 'loaders': [ - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', - ] }, }, ] -WSGI_APPLICATION = 'example.wsgi.application' +WSGI_APPLICATION = "example.wsgi.application" # Database # https://docs.djangoproject.com/en/1.10/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "db.sqlite3"), } } @@ -123,9 +117,9 @@ # Internationalization # https://docs.djangoproject.com/en/1.10/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -137,22 +131,20 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.10/howto/static-files/ -MEDIA_URL = '/media/' -MEDIA_ROOT = os.path.join(BASE_DIR, 'public/media') +MEDIA_URL = "/media/" +MEDIA_ROOT = os.path.join(BASE_DIR, "public/media") -STATIC_URL = '/static/' -STATIC_ROOT = os.path.join(BASE_DIR, 'public/static') +STATIC_URL = "/static/" +STATIC_ROOT = os.path.join(BASE_DIR, "public/static") -STATICFILES_DIRS = [ - os.path.join(BASE_DIR, 'static') -] +STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")] # Wagtail # https://docs.wagtail.org/en/stable/getting_started/integrating_into_django.html -WAGTAIL_SITE_NAME = 'Example' -WAGTAILADMIN_BASE_URL = 'http://127.0.0.1:8000' +WAGTAIL_SITE_NAME = "Example" +WAGTAILADMIN_BASE_URL = "http://127.0.0.1:8000" # Wagtail bakery @@ -160,13 +152,11 @@ ALLOW_BAKERY_AUTO_PUBLISHING = True -BUILD_DIR = os.path.join(BASE_DIR, '../build') +BUILD_DIR = os.path.join(BASE_DIR, "../build") -BAKERY_VIEWS = ( - 'wagtailbakery.views.AllPublishedPagesView', -) +BAKERY_VIEWS = ("wagtailbakery.views.AllPublishedPagesView",) -AWS_BUCKET_NAME = 'wagtail-bakery' +AWS_BUCKET_NAME = "wagtail-bakery" -AWS_ACCESS_KEY_ID = env.str('AWS_ACCESS_KEY_ID', '') -AWS_SECRET_ACCESS_KEY = env.str('AWS_SECRET_ACCESS_KEY', '') +AWS_ACCESS_KEY_ID = env.str("AWS_ACCESS_KEY_ID", "") +AWS_SECRET_ACCESS_KEY = env.str("AWS_SECRET_ACCESS_KEY", "") diff --git a/examples/aws/example/urls.py b/examples/aws/example/urls.py index 38b0be4..8f68426 100755 --- a/examples/aws/example/urls.py +++ b/examples/aws/example/urls.py @@ -13,6 +13,7 @@ 1. Import the include() function: from django.urls import path, include 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ + import os from django.conf import settings @@ -23,11 +24,11 @@ from wagtail.documents import urls as wagtaildocs_urls urlpatterns = [ - path('admin/', admin.site.urls), - path('cms/', include(wagtailadmin_urls)), - path('documents/', include(wagtaildocs_urls)), - path('pages/', include(wagtail_urls)), - path('', include(wagtail_urls)), + path("admin/", admin.site.urls), + path("cms/", include(wagtailadmin_urls)), + path("documents/", include(wagtaildocs_urls)), + path("pages/", include(wagtail_urls)), + path("", include(wagtail_urls)), ] if settings.DEBUG: @@ -36,4 +37,5 @@ urlpatterns += staticfiles_urlpatterns() urlpatterns += static( - settings.MEDIA_URL, document_root=os.path.join(settings.MEDIA_ROOT)) + settings.MEDIA_URL, document_root=os.path.join(settings.MEDIA_ROOT) + ) diff --git a/examples/multisite/example/migrations/0001_initial.py b/examples/multisite/example/migrations/0001_initial.py index 4695827..aada6f0 100644 --- a/examples/multisite/example/migrations/0001_initial.py +++ b/examples/multisite/example/migrations/0001_initial.py @@ -11,52 +11,116 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('wagtailcore', '0083_workflowcontenttype'), + ("wagtailcore", "0083_workflowcontenttype"), ] operations = [ migrations.CreateModel( - name='BlogListPage', + name="BlogListPage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')), - ('body', wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock())], use_json_field=True)), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.page", + ), + ), + ( + "body", + wagtail.fields.StreamField( + [("paragraph", wagtail.blocks.RichTextBlock())], + use_json_field=True, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('wagtailcore.page',), + bases=("wagtailcore.page",), ), migrations.CreateModel( - name='BlogPostPage', + name="BlogPostPage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')), - ('body', wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock())], use_json_field=True)), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.page", + ), + ), + ( + "body", + wagtail.fields.StreamField( + [("paragraph", wagtail.blocks.RichTextBlock())], + use_json_field=True, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('wagtailcore.page',), + bases=("wagtailcore.page",), ), migrations.CreateModel( - name='GenericPage', + name="GenericPage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')), - ('body', wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock())], use_json_field=True)), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.page", + ), + ), + ( + "body", + wagtail.fields.StreamField( + [("paragraph", wagtail.blocks.RichTextBlock())], + use_json_field=True, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('wagtailcore.page',), + bases=("wagtailcore.page",), ), migrations.CreateModel( - name='HomePage', + name="HomePage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')), - ('body', wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock())], use_json_field=True)), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.page", + ), + ), + ( + "body", + wagtail.fields.StreamField( + [("paragraph", wagtail.blocks.RichTextBlock())], + use_json_field=True, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('wagtailcore.page',), + bases=("wagtailcore.page",), ), ] diff --git a/examples/multisite/example/models.py b/examples/multisite/example/models.py index f78d099..17332dd 100644 --- a/examples/multisite/example/models.py +++ b/examples/multisite/example/models.py @@ -6,16 +6,11 @@ class AbstractExamplePage(Page): body = StreamField( - [ - ('paragraph', blocks.RichTextBlock()) - ], + [("paragraph", blocks.RichTextBlock())], use_json_field=True, ) - content_panels = [ - FieldPanel('title'), - FieldPanel('body') - ] + content_panels = [FieldPanel("title"), FieldPanel("body")] class Meta: abstract = True diff --git a/examples/multisite/example/settings.py b/examples/multisite/example/settings.py index 0894211..a94e312 100755 --- a/examples/multisite/example/settings.py +++ b/examples/multisite/example/settings.py @@ -20,92 +20,86 @@ # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '_vyer8o8)0jv9xbwmq8vf)9j(*#plkr+pryg*@0j9*tg5p(6ze' +SECRET_KEY = "_vyer8o8)0jv9xbwmq8vf)9j(*#plkr+pryg*@0j9*tg5p(6ze" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = ['*'] +ALLOWED_HOSTS = ["*"] # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - - 'wagtail.contrib.forms', - 'wagtail.contrib.redirects', - 'wagtail.embeds', - 'wagtail.sites', - 'wagtail.users', - 'wagtail.snippets', - 'wagtail.documents', - 'wagtail.images', - 'wagtail.search', - 'wagtail.admin', - 'wagtail', - - 'wagtail.contrib.settings', - - 'modelcluster', - 'taggit', - - 'bakery', - 'wagtailbakery', - - 'example', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "wagtail.contrib.forms", + "wagtail.contrib.redirects", + "wagtail.embeds", + "wagtail.sites", + "wagtail.users", + "wagtail.snippets", + "wagtail.documents", + "wagtail.images", + "wagtail.search", + "wagtail.admin", + "wagtail", + "wagtail.contrib.settings", + "modelcluster", + "taggit", + "bakery", + "wagtailbakery", + "example", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - - 'wagtail.contrib.redirects.middleware.RedirectMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "wagtail.contrib.redirects.middleware.RedirectMiddleware", ] -ROOT_URLCONF = 'example.urls' +ROOT_URLCONF = "example.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [ - os.path.join(BASE_DIR, 'templates'), + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [ + os.path.join(BASE_DIR, "templates"), ], - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + "loaders": [ + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", ], - 'loaders': [ - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', - ] }, }, ] -WSGI_APPLICATION = 'example.wsgi.application' +WSGI_APPLICATION = "example.wsgi.application" # Database # https://docs.djangoproject.com/en/1.10/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "db.sqlite3"), } } @@ -119,9 +113,9 @@ # Internationalization # https://docs.djangoproject.com/en/1.10/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -133,31 +127,27 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.10/howto/static-files/ -MEDIA_URL = '/media/' -MEDIA_ROOT = os.path.join(BASE_DIR, 'public/media') +MEDIA_URL = "/media/" +MEDIA_ROOT = os.path.join(BASE_DIR, "public/media") -STATIC_URL = '/static/' -STATIC_ROOT = os.path.join(BASE_DIR, 'public/static') +STATIC_URL = "/static/" +STATIC_ROOT = os.path.join(BASE_DIR, "public/static") -STATICFILES_DIRS = [ - os.path.join(BASE_DIR, 'static') -] +STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")] # Wagtail # https://docs.wagtail.org/en/stable/getting_started/integrating_into_django.html -WAGTAIL_SITE_NAME = 'Example' -WAGTAILADMIN_BASE_URL = 'http://127.0.0.1:8000' +WAGTAIL_SITE_NAME = "Example" +WAGTAILADMIN_BASE_URL = "http://127.0.0.1:8000" # Wagtail bakery # https://github.com/wagtail-nest/wagtail-bakery -BUILD_DIR = os.path.join(BASE_DIR, '../build') +BUILD_DIR = os.path.join(BASE_DIR, "../build") BAKERY_MULTISITE = True -BAKERY_VIEWS = ( - 'wagtailbakery.views.AllPublishedPagesView', -) +BAKERY_VIEWS = ("wagtailbakery.views.AllPublishedPagesView",) diff --git a/examples/multisite/example/urls.py b/examples/multisite/example/urls.py index 38b0be4..8f68426 100755 --- a/examples/multisite/example/urls.py +++ b/examples/multisite/example/urls.py @@ -13,6 +13,7 @@ 1. Import the include() function: from django.urls import path, include 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ + import os from django.conf import settings @@ -23,11 +24,11 @@ from wagtail.documents import urls as wagtaildocs_urls urlpatterns = [ - path('admin/', admin.site.urls), - path('cms/', include(wagtailadmin_urls)), - path('documents/', include(wagtaildocs_urls)), - path('pages/', include(wagtail_urls)), - path('', include(wagtail_urls)), + path("admin/", admin.site.urls), + path("cms/", include(wagtailadmin_urls)), + path("documents/", include(wagtaildocs_urls)), + path("pages/", include(wagtail_urls)), + path("", include(wagtail_urls)), ] if settings.DEBUG: @@ -36,4 +37,5 @@ urlpatterns += staticfiles_urlpatterns() urlpatterns += static( - settings.MEDIA_URL, document_root=os.path.join(settings.MEDIA_ROOT)) + settings.MEDIA_URL, document_root=os.path.join(settings.MEDIA_ROOT) + ) diff --git a/examples/site/example/migrations/0001_initial.py b/examples/site/example/migrations/0001_initial.py index 4695827..aada6f0 100644 --- a/examples/site/example/migrations/0001_initial.py +++ b/examples/site/example/migrations/0001_initial.py @@ -11,52 +11,116 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('wagtailcore', '0083_workflowcontenttype'), + ("wagtailcore", "0083_workflowcontenttype"), ] operations = [ migrations.CreateModel( - name='BlogListPage', + name="BlogListPage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')), - ('body', wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock())], use_json_field=True)), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.page", + ), + ), + ( + "body", + wagtail.fields.StreamField( + [("paragraph", wagtail.blocks.RichTextBlock())], + use_json_field=True, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('wagtailcore.page',), + bases=("wagtailcore.page",), ), migrations.CreateModel( - name='BlogPostPage', + name="BlogPostPage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')), - ('body', wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock())], use_json_field=True)), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.page", + ), + ), + ( + "body", + wagtail.fields.StreamField( + [("paragraph", wagtail.blocks.RichTextBlock())], + use_json_field=True, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('wagtailcore.page',), + bases=("wagtailcore.page",), ), migrations.CreateModel( - name='GenericPage', + name="GenericPage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')), - ('body', wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock())], use_json_field=True)), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.page", + ), + ), + ( + "body", + wagtail.fields.StreamField( + [("paragraph", wagtail.blocks.RichTextBlock())], + use_json_field=True, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('wagtailcore.page',), + bases=("wagtailcore.page",), ), migrations.CreateModel( - name='HomePage', + name="HomePage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')), - ('body', wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock())], use_json_field=True)), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.page", + ), + ), + ( + "body", + wagtail.fields.StreamField( + [("paragraph", wagtail.blocks.RichTextBlock())], + use_json_field=True, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('wagtailcore.page',), + bases=("wagtailcore.page",), ), ] diff --git a/examples/site/example/models.py b/examples/site/example/models.py index f78d099..17332dd 100644 --- a/examples/site/example/models.py +++ b/examples/site/example/models.py @@ -6,16 +6,11 @@ class AbstractExamplePage(Page): body = StreamField( - [ - ('paragraph', blocks.RichTextBlock()) - ], + [("paragraph", blocks.RichTextBlock())], use_json_field=True, ) - content_panels = [ - FieldPanel('title'), - FieldPanel('body') - ] + content_panels = [FieldPanel("title"), FieldPanel("body")] class Meta: abstract = True diff --git a/examples/site/example/settings.py b/examples/site/example/settings.py index f2d9824..ea2cb32 100755 --- a/examples/site/example/settings.py +++ b/examples/site/example/settings.py @@ -20,92 +20,86 @@ # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '_vyer8o8)0jv9xbwmq8vf)9j(*#plkr+pryg*@0j9*tg5p(6ze' +SECRET_KEY = "_vyer8o8)0jv9xbwmq8vf)9j(*#plkr+pryg*@0j9*tg5p(6ze" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = ['*'] +ALLOWED_HOSTS = ["*"] # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - - 'wagtail.contrib.forms', - 'wagtail.contrib.redirects', - 'wagtail.embeds', - 'wagtail.sites', - 'wagtail.users', - 'wagtail.snippets', - 'wagtail.documents', - 'wagtail.images', - 'wagtail.search', - 'wagtail.admin', - 'wagtail', - - 'wagtail.contrib.settings', - - 'modelcluster', - 'taggit', - - 'bakery', - 'wagtailbakery', - - 'example', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "wagtail.contrib.forms", + "wagtail.contrib.redirects", + "wagtail.embeds", + "wagtail.sites", + "wagtail.users", + "wagtail.snippets", + "wagtail.documents", + "wagtail.images", + "wagtail.search", + "wagtail.admin", + "wagtail", + "wagtail.contrib.settings", + "modelcluster", + "taggit", + "bakery", + "wagtailbakery", + "example", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - - 'wagtail.contrib.redirects.middleware.RedirectMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "wagtail.contrib.redirects.middleware.RedirectMiddleware", ] -ROOT_URLCONF = 'example.urls' +ROOT_URLCONF = "example.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [ - os.path.join(BASE_DIR, 'templates'), + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [ + os.path.join(BASE_DIR, "templates"), ], - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + "loaders": [ + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", ], - 'loaders': [ - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', - ] }, }, ] -WSGI_APPLICATION = 'example.wsgi.application' +WSGI_APPLICATION = "example.wsgi.application" # Database # https://docs.djangoproject.com/en/1.10/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "db.sqlite3"), } } @@ -119,9 +113,9 @@ # Internationalization # https://docs.djangoproject.com/en/1.10/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -133,29 +127,25 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.10/howto/static-files/ -MEDIA_URL = '/media/' -MEDIA_ROOT = os.path.join(BASE_DIR, 'public/media') +MEDIA_URL = "/media/" +MEDIA_ROOT = os.path.join(BASE_DIR, "public/media") -STATIC_URL = '/static/' -STATIC_ROOT = os.path.join(BASE_DIR, 'public/static') +STATIC_URL = "/static/" +STATIC_ROOT = os.path.join(BASE_DIR, "public/static") -STATICFILES_DIRS = [ - os.path.join(BASE_DIR, 'static') -] +STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")] # Wagtail # https://docs.wagtail.org/en/stable/getting_started/integrating_into_django.html -WAGTAIL_SITE_NAME = 'Example' -WAGTAILADMIN_BASE_URL = 'http://127.0.0.1:8000' +WAGTAIL_SITE_NAME = "Example" +WAGTAILADMIN_BASE_URL = "http://127.0.0.1:8000" # Wagtail bakery # https://github.com/wagtail/wagtail-bakery -BUILD_DIR = os.path.join(BASE_DIR, '../build') +BUILD_DIR = os.path.join(BASE_DIR, "../build") -BAKERY_VIEWS = ( - 'wagtailbakery.views.AllPublishedPagesView', -) +BAKERY_VIEWS = ("wagtailbakery.views.AllPublishedPagesView",) diff --git a/examples/site/example/urls.py b/examples/site/example/urls.py index 318ae21..cc0c9e6 100755 --- a/examples/site/example/urls.py +++ b/examples/site/example/urls.py @@ -13,6 +13,7 @@ 1. Import the include() function: from django.urls import path, include 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ + import os from django.conf import settings @@ -23,11 +24,11 @@ from wagtail.documents import urls as wagtaildocs_urls urlpatterns = [ - path('admin/', admin.site.urls), - path('cms/', include(wagtailadmin_urls)), - path('documents/', include(wagtaildocs_urls)), - path('pages/', include(wagtail_urls)), - path('', include(wagtail_urls)), + path("admin/", admin.site.urls), + path("cms/", include(wagtailadmin_urls)), + path("documents/", include(wagtaildocs_urls)), + path("pages/", include(wagtail_urls)), + path("", include(wagtail_urls)), ] if settings.DEBUG: @@ -36,4 +37,5 @@ urlpatterns += staticfiles_urlpatterns() urlpatterns += static( - settings.MEDIA_URL, document_root=os.path.join(settings.MEDIA_ROOT)) + settings.MEDIA_URL, document_root=os.path.join(settings.MEDIA_ROOT) + ) diff --git a/src/wagtailbakery/__init__.py b/src/wagtailbakery/__init__.py index f34d0b5..83ed1c1 100644 --- a/src/wagtailbakery/__init__.py +++ b/src/wagtailbakery/__init__.py @@ -1 +1 @@ -default_app_config = 'wagtailbakery.apps.WagtailBakeryAppConfig' +default_app_config = "wagtailbakery.apps.WagtailBakeryAppConfig" diff --git a/src/wagtailbakery/api_views.py b/src/wagtailbakery/api_views.py index 586dd18..eaf16d8 100644 --- a/src/wagtailbakery/api_views.py +++ b/src/wagtailbakery/api_views.py @@ -18,9 +18,14 @@ class APIResponseError(Exception): def handle_api_error(response): if response.status_code == 400: - raise APIResponseError("API error: " + json.loads(response.render().content.decode('UTF-8'))['message']) + raise APIResponseError( + "API error: " + + json.loads(response.render().content.decode("UTF-8"))["message"] + ) - raise APIResponseError("Unexpected status code returned from API: %d" % response.status_code) + raise APIResponseError( + "Unexpected status code returned from API: %d" % response.status_code + ) class APIListingView(BuildableMixin): @@ -65,6 +70,7 @@ class APIDetailView(BuildableMixin): get_queryset: A method that returns a queryset of objects to include """ + @property def build_method(self): return self.build_queryset @@ -99,10 +105,12 @@ def unbuild_object(self, obj): def get_content(self, obj): # Create a dummy request - request = self.create_request('/?format=json&fields=*') - request.wagtailapi_router = WagtailAPIRouter('') + request = self.create_request("/?format=json&fields=*") + request.wagtailapi_router = WagtailAPIRouter("") - response = self.endpoint_class.as_view({'get': 'detail_view'})(request, pk=obj.pk) + response = self.endpoint_class.as_view({"get": "detail_view"})( + request, pk=obj.pk + ) if response.status_code == 200: return response.render().content @@ -116,13 +124,14 @@ class PagesAPIDetailView(APIDetailView): URL example: /api/pages/detail/1.json """ + endpoint_class = PagesAPIViewSet def get_build_path(self, page): - return 'api/pages/detail/{pk}.json'.format(pk=page.pk) + return "api/pages/detail/{pk}.json".format(pk=page.pk) def get_queryset(self): - if getattr(settings, 'BAKERY_MULTISITE', False): + if getattr(settings, "BAKERY_MULTISITE", False): return Page.objects.all().public().live() else: site = Site.objects.get(is_default_site=True) @@ -135,22 +144,32 @@ class PagesAPIListingView(APIListingView): URL example: /api/pages/1.json """ + def get_build_path(self, page_num): - return 'api/pages/{page_num}.json'.format(page_num=page_num) + return "api/pages/{page_num}.json".format(page_num=page_num) def fetch_page_listing(self, page_num, model=None): if model: - url = '/?format=json&fields=*&limit={}&offset={}&type={}.{}'.format(self.results_per_page, self.results_per_page * page_num, model._meta.app_label, model.__name__) + url = "/?format=json&fields=*&limit={}&offset={}&type={}.{}".format( + self.results_per_page, + self.results_per_page * page_num, + model._meta.app_label, + model.__name__, + ) else: - url = '/?format=json&fields=*&limit={}&offset={}'.format(self.results_per_page, self.results_per_page * page_num) + url = "/?format=json&fields=*&limit={}&offset={}".format( + self.results_per_page, self.results_per_page * page_num + ) request = self.create_request(url) - request.wagtailapi_router = WagtailAPIRouter('') - response = PagesAPIViewSet.as_view({'get': 'listing_view'})(request) + request.wagtailapi_router = WagtailAPIRouter("") + response = PagesAPIViewSet.as_view({"get": "listing_view"})(request) if response.status_code == 200: content = response.render().content - has_next_page = json.loads(content.decode('UTF-8'))['meta']['total_count'] > self.results_per_page * (page_num + 1) + has_next_page = json.loads(content.decode("UTF-8"))["meta"][ + "total_count" + ] > self.results_per_page * (page_num + 1) return content, has_next_page handle_api_error(response) @@ -166,8 +185,9 @@ class TypedPagesAPIListingView(PagesAPIListingView): URL example: /api/pages/blog_BlogPage/1.json """ + def get_build_path(self, model, page_num): - return 'api/pages/{app_label}_{class_name}/{page_num}.json'.format( + return "api/pages/{app_label}_{class_name}/{page_num}.json".format( app_label=model._meta.app_label, class_name=model.__name__, page_num=page_num, @@ -179,7 +199,9 @@ def get_content(self, model, page_num): def get_page_models(self): return [ content_type.model_class() - for content_type in ContentType.objects.filter(id__in=Page.objects.values_list('content_type_id', flat=True)) + for content_type in ContentType.objects.filter( + id__in=Page.objects.values_list("content_type_id", flat=True) + ) ] def build(self): diff --git a/src/wagtailbakery/apps.py b/src/wagtailbakery/apps.py index eeff3f1..69f2e10 100644 --- a/src/wagtailbakery/apps.py +++ b/src/wagtailbakery/apps.py @@ -4,8 +4,8 @@ class WagtailBakeryAppConfig(AppConfig): - name = 'wagtailbakery' - label = 'wagtailbakery' + name = "wagtailbakery" + label = "wagtailbakery" verbose_name = "Wagtail bakery" def ready(self): diff --git a/src/wagtailbakery/models.py b/src/wagtailbakery/models.py index 6cd979f..7b52472 100644 --- a/src/wagtailbakery/models.py +++ b/src/wagtailbakery/models.py @@ -5,7 +5,8 @@ class BuildableWagtailBakeryModel(BuildableModel): """ Buildable Wagtail bakery page model mixin class. """ - detail_views = ['wagtailbakery.views.WagtailBakeryView'] + + detail_views = ["wagtailbakery.views.WagtailBakeryView"] def _build_related(self): # TODO: Build related pages with get_static_site_paths @@ -19,8 +20,9 @@ class AutoPublishingWagtailBakeryModel(AutoPublishingBuildableModel): """ Auto publishing Wagtail bakery page model mixin class. """ - detail_views = ['wagtailbakery.views.WagtailBakeryView'] - publication_status_field = 'live' + + detail_views = ["wagtailbakery.views.WagtailBakeryView"] + publication_status_field = "live" def _build_related(self): # TODO: Build related pages with get_static_site_paths diff --git a/src/wagtailbakery/signal_handlers.py b/src/wagtailbakery/signal_handlers.py index 9d3259d..5d31f8f 100644 --- a/src/wagtailbakery/signal_handlers.py +++ b/src/wagtailbakery/signal_handlers.py @@ -16,7 +16,7 @@ def handle_unpublish(sender, instance, **kwargs): def register_signal_handlers(): - page_published.connect( - handle_publish, dispatch_uid='wagtailbakery_page_published') + page_published.connect(handle_publish, dispatch_uid="wagtailbakery_page_published") page_unpublished.connect( - handle_unpublish, dispatch_uid='wagtailbakery_page_unpublished') + handle_unpublish, dispatch_uid="wagtailbakery_page_unpublished" + ) diff --git a/src/wagtailbakery/views.py b/src/wagtailbakery/views.py index 057c88d..c51c4a4 100644 --- a/src/wagtailbakery/views.py +++ b/src/wagtailbakery/views.py @@ -18,6 +18,7 @@ class WagtailBakeryView(BuildableDetailView): An abstract class that can be inherited to create a buildable view that can be added to BAKERY_VIEWS setting. """ + def __init__(self, *args, **kwargs): self.handler = BaseHandler() self.handler.load_middleware() @@ -32,35 +33,34 @@ def get_content(self, obj): response = self.get(self.request) if isinstance(response, HttpResponseRedirect): return self.get_redirect_content(response, obj) - if hasattr(response, 'render'): + if hasattr(response, "render"): return response.render().content - if hasattr(response, 'content'): + if hasattr(response, "content"): return response.content raise AttributeError( - "'%s' object has no attribute 'render' or 'content'" % response) + "'%s' object has no attribute 'render' or 'content'" % response + ) def get_redirect_content(self, response, obj): context = { - 'page': obj, - 'self': obj, - 'redirect_url': response.url, + "page": obj, + "self": obj, + "redirect_url": response.url, } - content = render( - self.request, 'wagtailbakery/redirect.html', context).content + content = render(self.request, "wagtailbakery/redirect.html", context).content return response.make_bytes(content) def get_build_path(self, obj): url = self.get_url(obj) - if url.startswith('http'): + if url.startswith("http"): # Multisite has absolute urls url_parsed = urlparse(url) path = unquote(url_parsed.path[1:]) hostname = url_parsed.hostname - if getattr(settings, 'BAKERY_MULTISITE', False): - build_path = os.path.join( - settings.BUILD_DIR, hostname, path) + if getattr(settings, "BAKERY_MULTISITE", False): + build_path = os.path.join(settings.BUILD_DIR, hostname, path) else: build_path = os.path.join(settings.BUILD_DIR, path) else: @@ -72,7 +72,7 @@ def get_build_path(self, obj): os.path.exists(build_path) or os.makedirs(build_path) # Always append index.html at the end of the path - return os.path.join(build_path, 'index.html') + return os.path.join(build_path, "index.html") def get_url(self, obj): """Return Wagtail page url instead of Django's get_absolute_url.""" @@ -89,8 +89,7 @@ def build_object(self, obj): """ site = obj.get_site() logger.debug("Building %s" % obj) - self.request = RequestFactory( - SERVER_NAME=site.hostname).get(self.get_url(obj)) + self.request = RequestFactory(SERVER_NAME=site.hostname).get(self.get_url(obj)) self.set_kwargs(obj) path = self.get_build_path(obj) self.build_file(path, self.get_content(obj)) @@ -118,8 +117,9 @@ class AllPagesView(WagtailBakeryView): 'wagtailbakery.views.AllPagesView', ) """ + def get_queryset(self): - if getattr(settings, 'BAKERY_MULTISITE', False): + if getattr(settings, "BAKERY_MULTISITE", False): return Page.objects.all().public() else: site = Site.objects.get(is_default_site=True) @@ -138,6 +138,7 @@ class AllPublishedPagesView(AllPagesView): 'wagtailbakery.views.AllPublishedPagesView', ) """ + def get_queryset(self): pages = super().get_queryset() return pages.live() diff --git a/tests/conftest.py b/tests/conftest.py index 8a5bd49..e9cadf5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,91 +4,88 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -pytest_plugins = 'tests.fixtures' +pytest_plugins = "tests.fixtures" def pytest_configure(): wagtail_apps = [ - 'wagtail.contrib.forms', - 'wagtail.contrib.redirects', - 'wagtail.embeds', - 'wagtail.sites', - 'wagtail.users', - 'wagtail.snippets', - 'wagtail.documents', - 'wagtail.images', - 'wagtail.search', - 'wagtail.admin', - 'wagtail', + "wagtail.contrib.forms", + "wagtail.contrib.redirects", + "wagtail.embeds", + "wagtail.sites", + "wagtail.users", + "wagtail.snippets", + "wagtail.documents", + "wagtail.images", + "wagtail.search", + "wagtail.admin", + "wagtail", ] wagtail_middleware = [ - 'wagtail.contrib.redirects.middleware.RedirectMiddleware', + "wagtail.contrib.redirects.middleware.RedirectMiddleware", ] settings.configure( SECRET_KEY="secret_for_testing_only", DATABASES={ - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "db.sqlite3"), } }, INSTALLED_APPS=[ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - - ] + wagtail_apps + [ - - 'modelcluster', - 'taggit', - - 'bakery', - 'wagtailbakery', - - 'tests', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + ] + + wagtail_apps + + [ + "modelcluster", + "taggit", + "bakery", + "wagtailbakery", + "tests", ], MIDDLEWARE=[ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.locale.LocaleMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - ] + wagtail_middleware, - ROOT_URLCONF='tests.urls', - ALLOWED_HOSTS='*', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.locale.LocaleMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + ] + + wagtail_middleware, + ROOT_URLCONF="tests.urls", + ALLOWED_HOSTS="*", TEMPLATES=[ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [ - os.path.join(BASE_DIR, 'templates'), + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [ + os.path.join(BASE_DIR, "templates"), ], - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + "loaders": [ + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", ], - 'loaders': [ - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', - ] }, }, ], - WAGTAIL_SITE_NAME='Wagtail Bakery', - BUILD_DIR=os.path.join(BASE_DIR, 'build'), - BAKERY_VIEWS=( - 'wagtailbakery.views.AllPagesView', - ), + WAGTAIL_SITE_NAME="Wagtail Bakery", + BUILD_DIR=os.path.join(BASE_DIR, "build"), + BAKERY_VIEWS=("wagtailbakery.views.AllPagesView",), CELERY_ALWAYS_EAGER=True, STATIC_URL="/static/", ) diff --git a/tests/factories/page.py b/tests/factories/page.py index 6a21408..5693d16 100644 --- a/tests/factories/page.py +++ b/tests/factories/page.py @@ -5,10 +5,10 @@ class PageFactory(factory.django.DjangoModelFactory): depth = 2 numchild = 0 - path = '00010002' - title = 'Page' - slug = 'page' - url_path = '/page/' + path = "00010002" + title = "Page" + slug = "page" + url_path = "/page/" class Meta: model = Page diff --git a/tests/factories/site.py b/tests/factories/site.py index 0910f0d..d61bf5e 100644 --- a/tests/factories/site.py +++ b/tests/factories/site.py @@ -3,10 +3,10 @@ class SiteFactory(factory.django.DjangoModelFactory): - hostname = 'localhost' + hostname = "localhost" port = 80 is_default_site = True class Meta: model = Site - django_get_or_create = ('hostname', 'port') + django_get_or_create = ("hostname", "port") diff --git a/tests/fixtures.py b/tests/fixtures.py index 8a7278e..586fc83 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -5,7 +5,7 @@ def page(): from wagtail.models import Page - return Page.objects.get(slug='home') + return Page.objects.get(slug="home") @pytest.fixture @@ -20,27 +20,35 @@ def page_tree(page): from tests.factories.page import PageFactory # /first - first_page = PageFactory(depth=3, path='000100010001', slug='first', numchild=2) + first_page = PageFactory(depth=3, path="000100010001", slug="first", numchild=2) # /first/first - PageFactory(depth=4, path=f'{first_page.path}0001', slug='first') + PageFactory(depth=4, path=f"{first_page.path}0001", slug="first") # /first/second - PageFactory(depth=4, path=f'{first_page.path}0002', slug='second') + PageFactory(depth=4, path=f"{first_page.path}0002", slug="second") # /second - PageFactory(depth=3, path='000100010002', slug='second') + PageFactory(depth=3, path="000100010002", slug="second") # /third - PageFactory(depth=3, path='000100010003', slug='third') + PageFactory(depth=3, path="000100010003", slug="third") # /unicode-children - unicode_page = PageFactory(depth=3, path='000100010004', slug='unicode-children', numchild=3) + unicode_page = PageFactory( + depth=3, path="000100010004", slug="unicode-children", numchild=3 + ) # /unicode-children/latin-capital-letter-i-with-diaeresis-Ï - PageFactory(depth=4, path=f'{unicode_page.path}0001', slug='latin-capital-letter-i-with-diaeresis-Ï') + PageFactory( + depth=4, + path=f"{unicode_page.path}0001", + slug="latin-capital-letter-i-with-diaeresis-Ï", + ) # /unicode-children/cyrillic-capital-letter-ya-Я - PageFactory(depth=4, path=f'{unicode_page.path}0002', slug='cyrillic-capital-letter-ya-Я') + PageFactory( + depth=4, path=f"{unicode_page.path}0002", slug="cyrillic-capital-letter-ya-Я" + ) # /unicode-children/cjk-fire-火 - PageFactory(depth=4, path=f'{unicode_page.path}0003', slug='cjk-fire-火') + PageFactory(depth=4, path=f"{unicode_page.path}0003", slug="cjk-fire-火") page.numchild = 4 page.save() @@ -53,16 +61,13 @@ def multisite(site): from tests.factories.page import PageFactory from tests.factories.site import SiteFactory - page_2 = PageFactory(path='00010003', slug='page-2') - site_2 = SiteFactory( - hostname='site_2', is_default_site=False, root_page=page_2) + page_2 = PageFactory(path="00010003", slug="page-2") + site_2 = SiteFactory(hostname="site_2", is_default_site=False, root_page=page_2) - page_3 = PageFactory(path='00010004', slug='page-3') - site_3 = SiteFactory( - hostname='site_3', is_default_site=False, root_page=page_3) + page_3 = PageFactory(path="00010004", slug="page-3") + site_3 = SiteFactory(hostname="site_3", is_default_site=False, root_page=page_3) - page_4 = PageFactory(path='00010005', slug='page-4') - site_4 = SiteFactory( - hostname='site_4', is_default_site=False, root_page=page_4) + page_4 = PageFactory(path="00010005", slug="page-4") + site_4 = SiteFactory(hostname="site_4", is_default_site=False, root_page=page_4) return [site, site_2, site_3, site_4] diff --git a/tests/integration/test_api_views.py b/tests/integration/test_api_views.py index b8ae40d..e51e28c 100644 --- a/tests/integration/test_api_views.py +++ b/tests/integration/test_api_views.py @@ -3,21 +3,24 @@ import pytest from wagtailbakery.api_views import ( - PagesAPIDetailView, PagesAPIListingView, TypedPagesAPIListingView) + PagesAPIDetailView, + PagesAPIListingView, + TypedPagesAPIListingView, +) from ..models import EventPage -DEFAULT_PAGE_FIELDS = {'id', 'meta', 'title'} +DEFAULT_PAGE_FIELDS = {"id", "meta", "title"} DEFAULT_PAGE_META_FIELDS = { - 'type', - 'show_in_menus', - 'search_description', - 'first_published_at', - 'slug', - 'html_url', - 'seo_title', - 'locale', - 'alias_of', + "type", + "show_in_menus", + "search_description", + "first_published_at", + "slug", + "html_url", + "seo_title", + "locale", + "alias_of", } @@ -27,17 +30,17 @@ def test_wagtail_bakery_pages_api_detail_view(page_tree): # Check build path build_path = view.get_build_path(page_tree) - assert build_path == 'api/pages/detail/2.json' + assert build_path == "api/pages/detail/2.json" # Check child build path for first child page child_page = page_tree.get_descendants().first() build_path = view.get_build_path(child_page) - assert build_path == 'api/pages/detail/3.json' + assert build_path == "api/pages/detail/3.json" # Check child build path of the first grandchild page child_page = child_page.get_descendants().first() build_path = view.get_build_path(child_page) - assert build_path == 'api/pages/detail/4.json' + assert build_path == "api/pages/detail/4.json" @pytest.mark.django_db @@ -48,7 +51,7 @@ def test_wagtail_bakery_pages_api_detail_view_build_path_for_multisite(multisite # Check build path for homepage build_path = view.get_build_path(page) - assert build_path == 'api/pages/detail/2.json' + assert build_path == "api/pages/detail/2.json" @pytest.mark.django_db @@ -56,27 +59,27 @@ def test_wagtail_bakery_pages_api_detail_view_content(page_tree): view = PagesAPIDetailView() # Check content for homepage - content = json.loads(view.get_content(page_tree).decode('UTF-8')) + content = json.loads(view.get_content(page_tree).decode("UTF-8")) assert set(content.keys()) == DEFAULT_PAGE_FIELDS - assert set(content['meta'].keys()) == DEFAULT_PAGE_META_FIELDS.union({'parent'}) - assert content['id'] == 2 - assert content['title'] == "Welcome to your new Wagtail site!" + assert set(content["meta"].keys()) == DEFAULT_PAGE_META_FIELDS.union({"parent"}) + assert content["id"] == 2 + assert content["title"] == "Welcome to your new Wagtail site!" # Check content for first child page child_page = page_tree.get_descendants().first() - content = json.loads(view.get_content(child_page).decode('UTF-8')) + content = json.loads(view.get_content(child_page).decode("UTF-8")) assert set(content.keys()) == DEFAULT_PAGE_FIELDS - assert set(content['meta'].keys()) == DEFAULT_PAGE_META_FIELDS.union({'parent'}) - assert content['id'] == 3 - assert content['title'] == "Page" + assert set(content["meta"].keys()) == DEFAULT_PAGE_META_FIELDS.union({"parent"}) + assert content["id"] == 3 + assert content["title"] == "Page" # Check content of the first grandchild page grandchild_page = child_page.get_descendants().first() - content = json.loads(view.get_content(grandchild_page).decode('UTF-8')) + content = json.loads(view.get_content(grandchild_page).decode("UTF-8")) assert set(content.keys()) == DEFAULT_PAGE_FIELDS - assert set(content['meta'].keys()) == DEFAULT_PAGE_META_FIELDS.union({'parent'}) - assert content['id'] == 4 - assert content['title'] == "Page" + assert set(content["meta"].keys()) == DEFAULT_PAGE_META_FIELDS.union({"parent"}) + assert content["id"] == 4 + assert content["title"] == "Page" @pytest.mark.django_db @@ -85,11 +88,11 @@ def test_wagtail_bakery_pages_api_listing_view(): # Check build path build_path = view.get_build_path(0) - assert build_path == 'api/pages/0.json' + assert build_path == "api/pages/0.json" # Check build path for second page build_path = view.get_build_path(1) - assert build_path == 'api/pages/1.json' + assert build_path == "api/pages/1.json" @pytest.mark.django_db @@ -98,7 +101,7 @@ def test_wagtail_bakery_pages_api_listing_view_build_path_for_multisite(multisit # Check build path for homepage build_path = view.get_build_path(0) - assert build_path == 'api/pages/0.json' + assert build_path == "api/pages/0.json" @pytest.mark.django_db @@ -106,13 +109,13 @@ def test_wagtail_bakery_pages_api_listing_view_content(page_tree): view = PagesAPIListingView() # Check content - content = json.loads(view.get_content(0)[0].decode('UTF-8')) - assert set(content.keys()) == {'meta', 'items'} - assert set(content['meta'].keys()) == {'total_count'} + content = json.loads(view.get_content(0)[0].decode("UTF-8")) + assert set(content.keys()) == {"meta", "items"} + assert set(content["meta"].keys()) == {"total_count"} - page = content['items'][0] + page = content["items"][0] assert set(page.keys()) == DEFAULT_PAGE_FIELDS - assert set(page['meta'].keys()) == DEFAULT_PAGE_META_FIELDS + assert set(page["meta"].keys()) == DEFAULT_PAGE_META_FIELDS @pytest.mark.django_db @@ -121,37 +124,41 @@ def test_wagtail_bakery_typed_pages_api_listing_view(): # Check build path build_path = view.get_build_path(EventPage, 0) - assert build_path == 'api/pages/tests_EventPage/0.json' + assert build_path == "api/pages/tests_EventPage/0.json" # Check build path for second page build_path = view.get_build_path(EventPage, 1) - assert build_path == 'api/pages/tests_EventPage/1.json' + assert build_path == "api/pages/tests_EventPage/1.json" @pytest.mark.django_db -def test_wagtail_bakery_typed_pages_api_listing_view_build_path_for_multisite(multisite): +def test_wagtail_bakery_typed_pages_api_listing_view_build_path_for_multisite( + multisite, +): view = TypedPagesAPIListingView() # Check build path for homepage build_path = view.get_build_path(EventPage, 0) - assert build_path == 'api/pages/tests_EventPage/0.json' + assert build_path == "api/pages/tests_EventPage/0.json" @pytest.mark.django_db def test_wagtail_bakery_typed_pages_api_listing_view_content(page_tree): view = TypedPagesAPIListingView() - page_tree.add_child(instance=EventPage( - title="Test event page", - slug="test-event-page", - )) + page_tree.add_child( + instance=EventPage( + title="Test event page", + slug="test-event-page", + ) + ) # Check content - content = json.loads(view.get_content(EventPage, 0)[0].decode('UTF-8')) - assert set(content.keys()) == {'meta', 'items'} - assert set(content['meta'].keys()) == {'total_count'} + content = json.loads(view.get_content(EventPage, 0)[0].decode("UTF-8")) + assert set(content.keys()) == {"meta", "items"} + assert set(content["meta"].keys()) == {"total_count"} - page = content['items'][0] + page = content["items"][0] assert set(page.keys()) == DEFAULT_PAGE_FIELDS - assert set(page['meta'].keys()) == DEFAULT_PAGE_META_FIELDS - assert page['title'] == "Test event page" + assert set(page["meta"].keys()) == DEFAULT_PAGE_META_FIELDS + assert page["title"] == "Test event page" diff --git a/tests/integration/test_models.py b/tests/integration/test_models.py index e0c47a3..e11eb74 100644 --- a/tests/integration/test_models.py +++ b/tests/integration/test_models.py @@ -4,13 +4,13 @@ @pytest.mark.django_db def test_page(page): # Check if local url is returned when a single site is used - assert page.url == '/' + assert page.url == "/" @pytest.mark.django_db def test_site(site): assert site.is_default_site - assert site.hostname == 'localhost' + assert site.hostname == "localhost" @pytest.mark.django_db @@ -19,16 +19,16 @@ def test_page_tree(page_tree): assert children.count() == 4 # Check if children of homepage return correct urls - assert children[0].url == '/first/' - assert children[1].url == '/second/' - assert children[2].url == '/third/' - assert children[3].url == '/unicode-children/' + assert children[0].url == "/first/" + assert children[1].url == "/second/" + assert children[2].url == "/third/" + assert children[3].url == "/unicode-children/" # Check if children of first child return correct urls grandchildren = children[0].get_children() assert grandchildren.count() == 2 - assert grandchildren[0].url == '/first/first/' - assert grandchildren[1].url == '/first/second/' + assert grandchildren[0].url == "/first/first/" + assert grandchildren[1].url == "/first/second/" # Check if children of unicode-children return correct urls grandchildren = children[3].get_children() @@ -37,10 +37,13 @@ def test_page_tree(page_tree): # Unicode characters are URL-encoded with `url` but not `url_path`. # However manual testing shows that the page is indeed accessible with the non-encoded string. assert grandchildren[0].url_path == ( - '/home/unicode-children/latin-capital-letter-i-with-diaeresis-Ï/' + "/home/unicode-children/latin-capital-letter-i-with-diaeresis-Ï/" ) - assert grandchildren[1].url_path == '/home/unicode-children/cyrillic-capital-letter-ya-Я/' - assert grandchildren[2].url_path == '/home/unicode-children/cjk-fire-火/' + assert ( + grandchildren[1].url_path + == "/home/unicode-children/cyrillic-capital-letter-ya-Я/" + ) + assert grandchildren[2].url_path == "/home/unicode-children/cjk-fire-火/" @pytest.mark.django_db @@ -48,7 +51,7 @@ def test_multisite(multisite): assert len(multisite) == 4 # Check if full urls are returned when multiple sites are used - assert multisite[0].root_page.url == 'http://localhost/' - assert multisite[1].root_page.url == 'http://site_2/' - assert multisite[2].root_page.url == 'http://site_3/' - assert multisite[3].root_page.url == 'http://site_4/' + assert multisite[0].root_page.url == "http://localhost/" + assert multisite[1].root_page.url == "http://site_2/" + assert multisite[2].root_page.url == "http://site_3/" + assert multisite[3].root_page.url == "http://site_4/" diff --git a/tests/integration/test_redirect.py b/tests/integration/test_redirect.py index 895a200..411b483 100644 --- a/tests/integration/test_redirect.py +++ b/tests/integration/test_redirect.py @@ -11,9 +11,9 @@ def test_page_has_redirect_response(site): redirect_page = RedirectPage.objects.create( depth=1, - path='0002', - title='Page', - slug='page', + path="0002", + title="Page", + slug="page", ) # Make redirect page the root page @@ -21,9 +21,12 @@ def test_page_has_redirect_response(site): site.save() # Build static files - management.call_command('build', '--skip-static', '--skip-media') - assert os.path.exists(os.path.join(settings.BUILD_DIR, 'index.html')) + management.call_command("build", "--skip-static", "--skip-media") + assert os.path.exists(os.path.join(settings.BUILD_DIR, "index.html")) # Check if meta tag is present - content = open(os.path.join(settings.BUILD_DIR, 'index.html')).read() - assert '' in content # noqa + content = open(os.path.join(settings.BUILD_DIR, "index.html")).read() + assert ( + '' + in content + ) # noqa diff --git a/tests/integration/test_routablepagemixin.py b/tests/integration/test_routablepagemixin.py index ce4c4a6..53a8eb4 100644 --- a/tests/integration/test_routablepagemixin.py +++ b/tests/integration/test_routablepagemixin.py @@ -11,9 +11,9 @@ def test_page_has_multiple_routes(site): redirect_page = EventPage.objects.create( depth=1, - path='0002', - title='Page', - slug='page', + path="0002", + title="Page", + slug="page", ) # Make event page the root page @@ -21,5 +21,7 @@ def test_page_has_multiple_routes(site): site.save() # Build static files - management.call_command('build', '--skip-static', '--skip-media') - assert os.path.exists(os.path.join(settings.BUILD_DIR, 'eventpage', 'page', 'index.html')) + management.call_command("build", "--skip-static", "--skip-media") + assert os.path.exists( + os.path.join(settings.BUILD_DIR, "eventpage", "page", "index.html") + ) diff --git a/tests/integration/test_views.py b/tests/integration/test_views.py index e6b341c..449d4df 100644 --- a/tests/integration/test_views.py +++ b/tests/integration/test_views.py @@ -10,17 +10,17 @@ def test_wagtail_bakery_view_get_url(page_tree): # Check url for homepage url = view.get_url(page_tree) - assert url == '/' + assert url == "/" # Check child url for first child page child_page = page_tree.get_descendants().first() url = view.get_url(child_page) - assert url == '/first/' + assert url == "/first/" # Check child url of the first grandchild page child_page = child_page.get_descendants().first() url = view.get_url(child_page) - assert url == '/first/first/' + assert url == "/first/first/" @pytest.mark.django_db @@ -29,38 +29,39 @@ def test_wagtail_bakery_view_build_path(page_tree): # Check build path for homepage build_path = view.get_build_path(page_tree) - assert build_path == settings.BUILD_DIR + '/index.html' + assert build_path == settings.BUILD_DIR + "/index.html" # Check build path for first child page child_page = page_tree.get_children().first() build_path = view.get_build_path(child_page) - assert build_path == settings.BUILD_DIR + '/first/index.html' + assert build_path == settings.BUILD_DIR + "/first/index.html" # Check build path of the first grandchild page grandchild_page = child_page.get_children().first() build_path = view.get_build_path(grandchild_page) - assert build_path == settings.BUILD_DIR + '/first/first/index.html' + assert build_path == settings.BUILD_DIR + "/first/first/index.html" # Check build path for unicode-children child page child_page = page_tree.get_children().last() build_path = view.get_build_path(child_page) - assert build_path == settings.BUILD_DIR + '/unicode-children/index.html' + assert build_path == settings.BUILD_DIR + "/unicode-children/index.html" # Check build path for unicode-children grandchild pages grandchild_pages = child_page.get_children().all() build_path = view.get_build_path(grandchild_pages[0]) assert build_path == ( - settings.BUILD_DIR + '/unicode-children/latin-capital-letter-i-with-diaeresis-Ï/index.html' + settings.BUILD_DIR + + "/unicode-children/latin-capital-letter-i-with-diaeresis-Ï/index.html" ) build_path = view.get_build_path(grandchild_pages[1]) assert build_path == ( - settings.BUILD_DIR + '/unicode-children/cyrillic-capital-letter-ya-Я/index.html' + settings.BUILD_DIR + "/unicode-children/cyrillic-capital-letter-ya-Я/index.html" ) build_path = view.get_build_path(grandchild_pages[2]) - assert build_path == settings.BUILD_DIR + '/unicode-children/cjk-fire-火/index.html' + assert build_path == settings.BUILD_DIR + "/unicode-children/cjk-fire-火/index.html" @pytest.mark.django_db @@ -71,8 +72,7 @@ def test_wagtail_bakery_view_build_path_for_multisite(multisite): # Check build path for homepage build_path = view.get_build_path(page) - assert build_path == '%s/index.html' % ( - settings.BUILD_DIR) + assert build_path == "%s/index.html" % (settings.BUILD_DIR) @pytest.mark.django_db diff --git a/tests/migrations/0001_initial.py b/tests/migrations/0001_initial.py index 371d6e0..53adab7 100644 --- a/tests/migrations/0001_initial.py +++ b/tests/migrations/0001_initial.py @@ -9,18 +9,28 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('wagtailcore', '0032_add_bulk_delete_page_permission'), + ("wagtailcore", "0032_add_bulk_delete_page_permission"), ] operations = [ migrations.CreateModel( - name='RedirectPage', + name="RedirectPage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.Page", + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('wagtailcore.page',), + bases=("wagtailcore.page",), ), ] diff --git a/tests/migrations/0002_eventpage.py b/tests/migrations/0002_eventpage.py index 8509c4e..449b389 100644 --- a/tests/migrations/0002_eventpage.py +++ b/tests/migrations/0002_eventpage.py @@ -8,19 +8,29 @@ class Migration(migrations.Migration): dependencies = [ - ('wagtailcore', '0032_add_bulk_delete_page_permission'), - ('tests', '0001_initial'), + ("wagtailcore", "0032_add_bulk_delete_page_permission"), + ("tests", "0001_initial"), ] operations = [ migrations.CreateModel( - name='EventPage', + name="EventPage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.Page", + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=(RoutablePageMixin, 'wagtailcore.page'), + bases=(RoutablePageMixin, "wagtailcore.page"), ), ] diff --git a/tests/models.py b/tests/models.py index 6e104b1..a28ef19 100644 --- a/tests/models.py +++ b/tests/models.py @@ -5,27 +5,35 @@ class RedirectPage(Page): def serve(self, request, *args, **kwargs): - return redirect('http://www.example.com/') + return redirect("http://www.example.com/") class EventPage(RoutablePageMixin, Page): - @route(r'^$') + @route(r"^$") def current_events(self, request): - return render(request, 'current_events.html', { - 'page': self, - }) + return render( + request, + "current_events.html", + { + "page": self, + }, + ) - @route(r'^year/(\d+)/$') - @route(r'^year/current/$') + @route(r"^year/(\d+)/$") + @route(r"^year/current/$") def events_for_year(self, request, year=None): - return render(request, 'events_for_year.html', { - 'page': self, - 'year': year, - }) + return render( + request, + "events_for_year.html", + { + "page": self, + "year": year, + }, + ) def get_static_site_paths(self): return super().get_static_site_paths() @property def url(self): - return '/eventpage/{}'.format(self.slug) + return "/eventpage/{}".format(self.slug) diff --git a/tests/urls.py b/tests/urls.py index 886b9ca..1b41f46 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -3,6 +3,6 @@ from wagtail.admin import urls as wagtailadmin_urls urlpatterns = [ - path('admin/', include(wagtailadmin_urls)), - path('', include(wagtail_urls)), + path("admin/", include(wagtailadmin_urls)), + path("", include(wagtail_urls)), ] From 28417943da74d93e52760f28761a5093469757bb Mon Sep 17 00:00:00 2001 From: Giannis Terzopoulos Date: Fri, 1 May 2026 23:27:04 +0200 Subject: [PATCH 8/9] Ignore black formatting commit in `git blame` --- .git-blame-ignore-revs | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..d14a130 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,8 @@ +# git-blame ignored revisions +# To configure, run +# git config blame.ignoreRevsFile .git-blame-ignore-revs +# Requires Git > 2.23 +# See https://git-scm.com/docs/git-blame#Documentation/git-blame.txt---ignore-revs-fileltfilegt + +# Reformat with Black +8c2fff90dd68190cc52563459fd95c59b26b919e From ed7f9123a0b496a0d1394299b21e29e297997998 Mon Sep 17 00:00:00 2001 From: Giannis Terzopoulos Date: Fri, 1 May 2026 23:41:27 +0200 Subject: [PATCH 9/9] Align linting config with Black --- .flake8 | 5 +++++ .github/workflows/test.yml | 24 +++++++++++++++++------- CHANGELOG.md | 1 + Makefile | 17 +++++++---------- pyproject.toml | 14 +++++++++++--- 5 files changed, 41 insertions(+), 20 deletions(-) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..068d2e8 --- /dev/null +++ b/.flake8 @@ -0,0 +1,5 @@ +[flake8] +max-line-length = 88 +exclude = .tox,.git,*/migrations/*,venv,dist,build,*.pyc,*.egg-info +ignore = E203,E231,E266,E501,W503,B950,F405 +max-complexity = 11 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9547264..d46206c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,6 +8,23 @@ on: workflow_call: # Allow for the publish workflows to run the tests by calling this workflow jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - uses: actions/setup-python@v6 + with: + python-version: 3.13 + cache: 'pip' + - name: Install dependencies + run: | + python -m pip install .[test] + - name: Run linting + run: | + make format test: name: Python ${{ matrix.python}} ${{ matrix.toxenv }} runs-on: ubuntu-latest @@ -18,13 +35,6 @@ jobs: experimental: [false] toxenv: ["py"] include: - # Linting - - python: "3.13" - toxenv: flake8 - experimental: false - - python: "3.13" - toxenv: isort - experimental: false # Future Wagtail release from main branch (allowed to fail) - python: "3.13" toxenv: wagtailmain diff --git a/CHANGELOG.md b/CHANGELOG.md index 92083cd..4dc10ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Compatibility with Django 5.2 and 6.0. - Compatibility with Wagtail 7.0, 7.3 LTS, and 7.4. - Switch `setup.py` to `pyproject.toml`. +- Linting with `black`, `isort` and `flake8`. - Added back coverage reporting. ### Fixed diff --git a/Makefile b/Makefile index 373c2cd..fb0ef6a 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,11 @@ -.PHONY: help all clean install flake8 isort lint test +.PHONY: help all clean install format test .DEFAULT_GOAL := help help: ## See what commands are available. @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36mmake %-15s\033[0m # %s\n", $$1, $$2}' -all: install clean test lint ## Install, test and lint the project. +all: clean install format test ## Install, format and test the project. clean: ## Remove Python file artifacts. find . -name '*.pyc' -exec rm -rf {} + @@ -16,16 +16,13 @@ clean: ## Remove Python file artifacts. install: ## Install dependencies. pip install -e .[test] -flake8: ## Run flake8 on the project. - flake8 src/ tests/ - -isort: ## Run isort on the project. - isort --check-only --diff src/ tests/ - -lint: flake8 isort ## Lint the project. +format: ## Format the code. + black . + isort . + flake8 . test: ## Test the project. pytest --cov -cov: ## Generate coverage report (manually open htmlcov/index.html in browser) +cov: ## Generate coverage report (manually open htmlcov/index.html in browser) pytest --cov --cov-report html diff --git a/pyproject.toml b/pyproject.toml index 2e4b5f2..a2683c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,9 +36,12 @@ dependencies = [ [project.optional-dependencies] test = [ + "black>=26,<27", "coverage[toml]>=7.13.5,<8.0", "factory-boy", "flake8", + "flake8-black", + "flake8-isort", "isort", "pytest-cov", "pytest-django", @@ -61,9 +64,14 @@ where = ["src"] testpaths = ["tests"] [tool.isort] -line_length = 100 -multi_line_output = 4 -known_third_party = ["bakery", "django", "factory", "pytest", "wagtail"] +force_grid_wrap = 0 +include_trailing_comma = true +line_length = 88 +multi_line_output = 3 +use_parentheses = true + +[tool.black] +line-length = 88 [tool.coverage.run] branch = true