diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d946af7b..c1d14d66 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,8 +33,8 @@ jobs: - name: Upload packages to Jazzband if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@master + uses: pypa/gh-action-pypi-publish@release/v1 with: user: jazzband password: ${{ secrets.JAZZBAND_RELEASE_KEY }} - repository_url: https://jazzband.co/projects/django-simple-history/upload + repository-url: https://jazzband.co/projects/django-simple-history/upload diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f3d23b49..5b95a9e6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: fail-fast: false matrix: python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13-dev'] - django-version: ['3.2', '4.2', '5.0', 'main'] + django-version: ['4.2', '5.0', 'main'] exclude: # Exclude py3.8 and py3.9 for Django main and 5.0 @@ -25,14 +25,6 @@ jobs: - python-version: '3.9' django-version: 'main' - # Exclude py3.11, py3.12 and py3.13 for Django 3.2 - - python-version: '3.11' - django-version: '3.2' - - python-version: '3.12' - django-version: '3.2' - - python-version: '3.13-dev' - django-version: '3.2' - services: postgres: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d93e4d7a..2e4e384a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,9 +35,19 @@ repos: - id: check-docstring-first - id: check-executables-have-shebangs - id: check-merge-conflict + - id: check-toml - id: debug-statements - id: detect-private-key + - repo: https://github.com/tox-dev/pyproject-fmt + rev: 2.1.3 + hooks: + - id: pyproject-fmt + - repo: https://github.com/abravalheri/validate-pyproject + rev: v0.18 + hooks: + - id: validate-pyproject + - repo: https://github.com/adrienverge/yamllint rev: v1.35.1 hooks: diff --git a/CHANGES.rst b/CHANGES.rst index 63c8db01..9d638095 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,8 +4,14 @@ Changes Unreleased ---------- -- Update package metadata to use modern standards - +- Dropped support for Django 3.2, which reached end-of-life on 2024-04-01 (gh-1344) +- Removed the temporary requirement on ``asgiref>=3.6`` added in 3.5.0, + now that the minimum required Django version is 4.2 (gh-1344) +- Migrated package building from using the deprecated ``setup.py`` to using + ``pyproject.toml`` (with Hatchling as build backend); + ``setup.py`` has consequently been removed (gh-1348) + +.. Start of PyPI readme 3.6.0 (2024-05-26) ------------------ diff --git a/README.rst b/README.rst index c8442f68..f054be6e 100644 --- a/README.rst +++ b/README.rst @@ -1,47 +1,50 @@ -django-simple-history -===================== +django-simple-history |pypi-version| +==================================== -.. image:: https://github.com/jazzband/django-simple-history/actions/workflows/test.yml/badge.svg +.. Start of PyPI readme + +|jazzband| |build-status| |docs| |coverage| |maintainability| |code-style| |downloads| + +.. |pypi-version| image:: https://img.shields.io/pypi/v/django-simple-history.svg + :target: https://pypi.org/project/django-simple-history/ + :alt: PyPI Version + +.. |jazzband| image:: https://jazzband.co/static/img/badge.svg + :target: https://jazzband.co/ + :alt: Jazzband + +.. |build-status| image:: https://github.com/jazzband/django-simple-history/actions/workflows/test.yml/badge.svg :target: https://github.com/jazzband/django-simple-history/actions/workflows/test.yml :alt: Build Status -.. image:: https://readthedocs.org/projects/django-simple-history/badge/?version=latest +.. |docs| image:: https://readthedocs.org/projects/django-simple-history/badge/?version=latest :target: https://django-simple-history.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status -.. image:: https://img.shields.io/codecov/c/github/jazzband/django-simple-history/master.svg +.. |coverage| image:: https://img.shields.io/codecov/c/github/jazzband/django-simple-history/master.svg :target: https://app.codecov.io/github/jazzband/django-simple-history?branch=master :alt: Test Coverage -.. image:: https://img.shields.io/pypi/v/django-simple-history.svg - :target: https://pypi.org/project/django-simple-history/ - :alt: PyPI Version - -.. image:: https://api.codeclimate.com/v1/badges/66cfd94e2db991f2d28a/maintainability +.. |maintainability| image:: https://api.codeclimate.com/v1/badges/66cfd94e2db991f2d28a/maintainability :target: https://codeclimate.com/github/jazzband/django-simple-history/maintainability :alt: Maintainability -.. image:: https://static.pepy.tech/badge/django-simple-history - :target: https://pepy.tech/project/django-simple-history - :alt: Downloads - -.. image:: https://img.shields.io/badge/code%20style-black-000000.svg +.. |code-style| image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/psf/black :alt: Code Style -.. image:: https://jazzband.co/static/img/badge.svg - :target: https://jazzband.co/ - :alt: Jazzband +.. |downloads| image:: https://static.pepy.tech/badge/django-simple-history + :target: https://pepy.tech/project/django-simple-history + :alt: Downloads -django-simple-history stores Django model state on every create/update/delete. +``django-simple-history`` stores Django model state on every create/update/delete. This app supports the following combinations of Django and Python: ========== ======================== Django Python ========== ======================== -3.2 3.8, 3.9, 3.10 4.2 3.8, 3.9, 3.10, 3.11, 3.12, 3.13-dev 5.0 3.10, 3.11, 3.12, 3.13-dev main 3.10, 3.11, 3.12, 3.13-dev @@ -50,9 +53,9 @@ main 3.10, 3.11, 3.12, 3.13-dev Getting Help ------------ -Documentation is available at https://django-simple-history.readthedocs.io/ +Documentation is available at https://django-simple-history.readthedocs.io/en/stable/ -Pull requests are welcome. Read the `CONTRIBUTING`_ file for tips on +Pull requests are welcome. Read the `CONTRIBUTING`_ file for tips on submitting a pull request. .. _CONTRIBUTING: https://github.com/jazzband/django-simple-history/blob/master/CONTRIBUTING.rst diff --git a/docs/index.rst b/docs/index.rst index e6eaaf74..2276ba9f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -41,7 +41,6 @@ This app supports the following combinations of Django and Python: ========== ======================= Django Python ========== ======================= -3.2 3.8, 3.9, 3.10 4.2 3.8, 3.9, 3.10, 3.11, 3.12, 3.13-dev 5.0 3.10, 3.11, 3.12, 3.13-dev main 3.10, 3.11, 3.12, 3.13-dev diff --git a/pyproject.toml b/pyproject.toml index 04c6db20..36d123e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,76 +1,83 @@ [build-system] -requires = ["hatchling", "hatch-vcs", "hatch-fancy-pypi-readme"] build-backend = "hatchling.build" +requires = [ + "hatch-fancy-pypi-readme", + "hatch-vcs", + "hatchling", +] [project] name = "django-simple-history" -dynamic = ["version", "readme"] description = "Store model history and view/revert changes from admin site." -license = "BSD-3-Clause" -requires-python = ">=3.8" -authors = [ - { name = "Corey Bertram", email = "corey@qr7.com" }, -] maintainers = [ { name = "Trey Hunner" }, ] +authors = [ + { name = "Corey Bertram", email = "corey@qr7.com" }, +] +requires-python = ">=3.8" classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Django", - "Framework :: Django", - "Framework :: Django :: 3.2", "Framework :: Django :: 4.2", "Framework :: Django :: 5.0", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Programming Language :: Python", - "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", "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", + # DEV: uncomment this when the `pyproject-fmt` pre-commit hook stops removing it + #"Programming Language :: Python :: 3.13", ] -# DEV: Remove `asgiref` when the minimum required Django version is 4.2 -dependencies = ["asgiref>=3.6"] - -[project.urls] -Changelog = "https://github.com/jazzband/django-simple-history/blob/master/CHANGES.rst" -Documentation = "https://django-simple-history.readthedocs.io/" -Source = "https://github.com/jazzband/django-simple-history" -Tracker = "https://github.com/jazzband/django-simple-history/issues" +dynamic = [ + "readme", + "version", +] +dependencies = [ +] +urls.Changelog = "https://github.com/jazzband/django-simple-history/blob/master/CHANGES.rst" +urls.Documentation = "https://django-simple-history.readthedocs.io/en/stable/" +urls.Homepage = "https://github.com/jazzband/django-simple-history" +urls.Source = "https://github.com/jazzband/django-simple-history" +urls.Tracker = "https://github.com/jazzband/django-simple-history/issues" [tool.hatch.version] source = "vcs" +fallback-version = "0.0.0" [tool.hatch.version.raw-options] -version_scheme = "post-release" +version_scheme = "no-guess-dev" local_scheme = "node-and-date" -fallback_version = "0.0.0" - -[tool.hatch.metadata.hooks.fancy-pypi-readme] -content-type = "text/x-rst" -fragments = [ - { path = "README.rst" }, - { path = "CHANGES.rst" }, -] [tool.hatch.build.targets.wheel] -packages = ["simple_history"] +# Jazzband's release process is limited to 2.2 metadata +core-metadata-version = "2.2" +packages = [ + "simple_history", +] [tool.hatch.build.targets.sdist] -include = [ - "/docs", - "/simple_history", - "*.rst", - "*.txt", +# Jazzband's release process is limited to 2.2 metadata +core-metadata-version = "2.2" + +[tool.hatch.metadata.hooks.fancy-pypi-readme] +content-type = "text/x-rst" +fragments = [ + { path = "README.rst", start-after = ".. Start of PyPI readme\n\n" }, + { text = "\n====\n\nChangelog\n=========\n\n" }, + { path = "CHANGES.rst", start-after = ".. Start of PyPI readme\n\n" }, ] [tool.black] line-length = 88 -target-version = ["py38"] +target-version = [ + "py38", +] [tool.isort] profile = "black" @@ -79,12 +86,19 @@ py_version = "38" [tool.coverage.run] parallel = true branch = true -source = ["simple_history"] +source = [ + "simple_history", +] [tool.coverage.paths] -source = ["simple_history", ".tox/*/site-packages"] +source = [ + "simple_history", + ".tox/*/site-packages", +] [tool.coverage.report] show_missing = true skip_covered = true -omit = ["requirements/*"] +omit = [ + "requirements/*", +] diff --git a/requirements/postgres.txt b/requirements/postgres.txt index aaa87fe1..d240df45 100644 --- a/requirements/postgres.txt +++ b/requirements/postgres.txt @@ -1,4 +1 @@ -# DEV: Replace this with `psycopg[binary]` when the minimum required Django version is -# 4.2 or higher, as this is likely to be deprecated in the future -# (see https://docs.djangoproject.com/en/4.2/releases/4.2/#psycopg-3-support) -psycopg2-binary==2.9.9 +psycopg[binary]==3.1.19 diff --git a/requirements/test.txt b/requirements/test.txt index 0b7414dc..ae6ed25c 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,3 +1 @@ -r ./coverage.txt -# DEV: Remove this requirement entirely when the minimum required Django version is 4.2 -asgiref>=3.6 diff --git a/runtests.py b/runtests.py index 62ebbef7..de2e9c19 100755 --- a/runtests.py +++ b/runtests.py @@ -135,6 +135,12 @@ def __getitem__(self, item): }, } ], + STORAGES={ + "default": { + # Speeds up tests and prevents locally storing files created through them + "BACKEND": "django.core.files.storage.InMemoryStorage", + }, + }, DEFAULT_AUTO_FIELD="django.db.models.AutoField", USE_TZ=False, ) @@ -146,16 +152,6 @@ def __getitem__(self, item): DEFAULT_SETTINGS["MIDDLEWARE"] = MIDDLEWARE -# DEV: Merge these settings into DEFAULT_SETTINGS when the minimum required -# Django version is 4.2 or higher -if django.VERSION >= (4, 2): - DEFAULT_SETTINGS["STORAGES"] = { - "default": { - # Speeds up tests and prevents locally storing files created through them - "BACKEND": "django.core.files.storage.InMemoryStorage", - }, - } - def get_default_settings(*, database_name=DEFAULT_DATABASE_NAME): return { diff --git a/simple_history/tests/tests/test_admin.py b/simple_history/tests/tests/test_admin.py index 02b5bdc5..d85e51a5 100644 --- a/simple_history/tests/tests/test_admin.py +++ b/simple_history/tests/tests/test_admin.py @@ -1,6 +1,7 @@ from datetime import datetime, timedelta from unittest.mock import ANY, patch +import django from django.contrib.admin import AdminSite from django.contrib.admin.utils import quote from django.contrib.auth import get_user_model @@ -621,6 +622,7 @@ def test_history_form_view_without_getting_history(self): context = { **admin_site.each_context(request), # Verify this is set for original object + "log_entries": ANY, "original": poll, "change_history": False, "title": "Revert %s" % force_str(poll), @@ -650,9 +652,9 @@ def test_history_form_view_without_getting_history(self): "save_on_top": admin.save_on_top, "root_path": getattr(admin_site, "root_path", None), } - # This key didn't exist prior to Django 4.2 - if "log_entries" in context: - context["log_entries"] = ANY + # DEV: Remove this when support for Django 4.2 has been dropped + if django.VERSION < (5, 0): + del context["log_entries"] mock_render.assert_called_once_with( request, admin.object_history_form_template, context @@ -680,6 +682,7 @@ def test_history_form_view_getting_history(self): context = { **admin_site.each_context(request), # Verify this is set for history object not poll object + "log_entries": ANY, "original": history.instance, "change_history": True, "title": "Revert %s" % force_str(history.instance), @@ -709,9 +712,9 @@ def test_history_form_view_getting_history(self): "save_on_top": admin.save_on_top, "root_path": getattr(admin_site, "root_path", None), } - # This key didn't exist prior to Django 4.2 - if "log_entries" in context: - context["log_entries"] = ANY + # DEV: Remove this when support for Django 4.2 has been dropped + if django.VERSION < (5, 0): + del context["log_entries"] mock_render.assert_called_once_with( request, admin.object_history_form_template, context @@ -739,6 +742,7 @@ def test_history_form_view_getting_history_with_setting_off(self): context = { **admin_site.each_context(request), # Verify this is set for history object not poll object + "log_entries": ANY, "original": poll, "change_history": False, "title": "Revert %s" % force_str(poll), @@ -768,9 +772,9 @@ def test_history_form_view_getting_history_with_setting_off(self): "save_on_top": admin.save_on_top, "root_path": getattr(admin_site, "root_path", None), } - # This key didn't exist prior to Django 4.2 - if "log_entries" in context: - context["log_entries"] = ANY + # DEV: Remove this when support for Django 4.2 has been dropped + if django.VERSION < (5, 0): + del context["log_entries"] mock_render.assert_called_once_with( request, admin.object_history_form_template, context @@ -798,6 +802,7 @@ def test_history_form_view_getting_history_abstract_external(self): context = { **admin_site.each_context(request), # Verify this is set for history object + "log_entries": ANY, "original": history.instance, "change_history": True, "title": "Revert %s" % force_str(history.instance), @@ -829,9 +834,9 @@ def test_history_form_view_getting_history_abstract_external(self): "save_on_top": admin.save_on_top, "root_path": getattr(admin_site, "root_path", None), } - # This key didn't exist prior to Django 4.2 - if "log_entries" in context: - context["log_entries"] = ANY + # DEV: Remove this when support for Django 4.2 has been dropped + if django.VERSION < (5, 0): + del context["log_entries"] mock_render.assert_called_once_with( request, admin.object_history_form_template, context @@ -862,6 +867,7 @@ def test_history_form_view_accepts_additional_context(self): context = { **admin_site.each_context(request), # Verify this is set for original object + "log_entries": ANY, "anything_else": "will be merged into context", "original": poll, "change_history": False, @@ -892,9 +898,9 @@ def test_history_form_view_accepts_additional_context(self): "save_on_top": admin.save_on_top, "root_path": getattr(admin_site, "root_path", None), } - # This key didn't exist prior to Django 4.2 - if "log_entries" in context: - context["log_entries"] = ANY + # DEV: Remove this when support for Django 4.2 has been dropped + if django.VERSION < (5, 0): + del context["log_entries"] mock_render.assert_called_once_with( request, admin.object_history_form_template, context diff --git a/simple_history/tests/tests/test_manager.py b/simple_history/tests/tests/test_manager.py index a10eda51..75bf94a7 100644 --- a/simple_history/tests/tests/test_manager.py +++ b/simple_history/tests/tests/test_manager.py @@ -1,7 +1,6 @@ from datetime import datetime, timedelta from operator import attrgetter -import django from django.contrib.auth import get_user_model from django.db import IntegrityError from django.test import TestCase, override_settings, skipUnlessDBFeature @@ -199,13 +198,6 @@ def setUp(self): Poll(id=4, question="Question 4", pub_date=datetime.now()), ] - # DEV: Remove this method when the minimum required Django version is 4.2 - def assertQuerySetEqual(self, *args, **kwargs): - if django.VERSION < (4, 2): - return self.assertQuerysetEqual(*args, **kwargs) - else: - return super().assertQuerySetEqual(*args, **kwargs) - def test_simple_bulk_history_create(self): created = Poll.history.bulk_history_create(self.data) self.assertEqual(len(created), 4) @@ -334,13 +326,6 @@ def setUp(self): Poll(id=4, question="Question 4", pub_date=datetime.now()), ] - # DEV: Remove this method when the minimum required Django version is 4.2 - def assertQuerySetEqual(self, *args, **kwargs): - if django.VERSION < (4, 2): - return self.assertQuerysetEqual(*args, **kwargs) - else: - return super().assertQuerySetEqual(*args, **kwargs) - def test_simple_bulk_history_create(self): created = Poll.history.bulk_history_create(self.data, update=True) self.assertEqual(len(created), 4) diff --git a/tox.ini b/tox.ini index d5f6c4ec..7f7804e7 100644 --- a/tox.ini +++ b/tox.ini @@ -1,12 +1,11 @@ [tox] isolated_build = true envlist = - py{38,39,310}-dj32-{sqlite3,postgres,mysql,mariadb}, py{38,39,310,311,312}-dj42-{sqlite3,postgres,mysql,mariadb}, py{310,311,312}-dj50-{sqlite3,postgres,mysql,mariadb}, py{310,311,312}-djmain-{sqlite3,postgres,mysql,mariadb}, # DEV: Add `313` to the Python versions above (so that postgres is tested with 3.13) - # when `psycopg2-binary` supports 3.13 + # when `psycopg` provides binaries for 3.13 py313-dj{42,50,main}-{sqlite3,mysql,mariadb}, docs, lint @@ -22,7 +21,6 @@ python = [gh-actions:env] DJANGO = - 3.2: dj32 4.2: dj42 5.0: dj50 main: djmain @@ -36,7 +34,6 @@ exclude = __init__.py,simple_history/registry_tests/migration_test_app/migration [testenv] deps = -rrequirements/test.txt - dj32: Django>=3.2,<3.3 dj42: Django>=4.2,<4.3 dj50: Django>=5.0,<5.1 djmain: https://github.com/django/django/tarball/main