From 4e093fd03461768c1e591895b42f884e90b8039b Mon Sep 17 00:00:00 2001 From: Tim Heap Date: Wed, 29 Apr 2026 15:49:06 +1000 Subject: [PATCH 1/6] Remove all `from __future__` imports --- enumchoicefield/__init__.py | 2 -- enumchoicefield/enum.py | 2 -- enumchoicefield/fields.py | 2 -- enumchoicefield/utils.py | 2 -- 4 files changed, 8 deletions(-) diff --git a/enumchoicefield/__init__.py b/enumchoicefield/__init__.py index 400964a..1d12c18 100644 --- a/enumchoicefield/__init__.py +++ b/enumchoicefield/__init__.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - from .enum import ChoiceEnum from .fields import EnumChoiceField from .version import version diff --git a/enumchoicefield/enum.py b/enumchoicefield/enum.py index 1554a85..95b02bc 100644 --- a/enumchoicefield/enum.py +++ b/enumchoicefield/enum.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import enum diff --git a/enumchoicefield/fields.py b/enumchoicefield/fields.py index f09e888..4040554 100644 --- a/enumchoicefield/fields.py +++ b/enumchoicefield/fields.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - from django.db.models.fields import Field from .forms import EnumField diff --git a/enumchoicefield/utils.py b/enumchoicefield/utils.py index 6ea1396..03c4b74 100644 --- a/enumchoicefield/utils.py +++ b/enumchoicefield/utils.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - from django.db.models import Case, IntegerField, When From e81dfd14a29562adf05b5e24dbaecc4f9b7d51d3 Mon Sep 17 00:00:00 2001 From: Tim Heap Date: Wed, 29 Apr 2026 15:58:35 +1000 Subject: [PATCH 2/6] Use pyproject.toml for project metadata --- enumchoicefield/__init__.py | 5 +++- enumchoicefield/version.py | 1 - pyproject.toml | 43 ++++++++++++++++++++++++++++++++++ setup.cfg | 13 ----------- setup.py | 46 ------------------------------------- 5 files changed, 47 insertions(+), 61 deletions(-) delete mode 100644 enumchoicefield/version.py create mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100755 setup.py diff --git a/enumchoicefield/__init__.py b/enumchoicefield/__init__.py index 1d12c18..ab1194d 100644 --- a/enumchoicefield/__init__.py +++ b/enumchoicefield/__init__.py @@ -1,5 +1,8 @@ +import importlib.metadata + from .enum import ChoiceEnum from .fields import EnumChoiceField -from .version import version __all__ = ['ChoiceEnum', 'EnumChoiceField', 'version'] + +version = importlib.metadata.version('django-enumchoicefield') diff --git a/enumchoicefield/version.py b/enumchoicefield/version.py deleted file mode 100644 index 5918abd..0000000 --- a/enumchoicefield/version.py +++ /dev/null @@ -1 +0,0 @@ -version = '3.0.0' diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c701797 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,43 @@ +[build-system] +requires = [ + "setuptools >= 35.0.2", +] +build-backend = "setuptools.build_meta" + +[project] +name = "django-enumchoicefield" +version = "3.0.1" +description = "A choice field for Django using native Python Enums" +authors = [ + {name = "Tim Heap", email = "tim@timheap.me"}, +] +license = "BSD-2-Clause" +license-files = ["LICENSE"] +requires-python = ">=3.10" +dependencies = [ + "Django>=5.2" +] + +dynamic = ["readme"] + +classifiers = [ + "Environment :: Web Environment", + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Framework :: Django", +] + +[project.urls] +Documentation = "https://django-enumchoicefield.readthedocs.io/" +Source = "https://github.com/mx-moth/django-enumchoicefield" + +[tool.setuptools.dynamic] +readme = {file = ["README.rst"], content-type = "text/x-rst"} + +[tool.isort] +line_length = 79 +multi_line_output = 4 +known_first_party = "enumchoicefield,tests" +skip = "migrations" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index db24bc0..0000000 --- a/setup.cfg +++ /dev/null @@ -1,13 +0,0 @@ -[metadata] -description_file = README.rst - -[bdist_wheel] -universal = 1 - -[flake8] -exclude = migrations - -[isort] -line_length = 79 -multi_line_output = 4 -skip = migrations diff --git a/setup.py b/setup.py deleted file mode 100755 index a4e5854..0000000 --- a/setup.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python -""" -Install django-enumchoicefield using setuptools -""" -from setuptools import find_packages, setup - -with open('README.rst', 'r') as f: - readme = f.read() - -with open('enumchoicefield/version.py') as v: - version = None - exec(v.read()) # Get version - - -setup( - name='django-enumchoicefield', - version=version, - description='A choice field for Django using native Python Enums', - long_description=readme, - author='Tim Heap', - author_email='tim@timheap.me', - url='https://github.com/timheap/django-enumchoicefield', - - install_requires=['Django>=2.0'], - zip_safe=False, - license='BSD License', - - packages=find_packages(), - - include_package_data=True, - package_data={}, - - classifiers=[ - 'Environment :: Web Environment', - 'Intended Audience :: Developers', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Framework :: Django', - 'License :: OSI Approved :: BSD License', - ], -) From 0fa7f5f86a73abbc0605345ee6034fc5b416c849 Mon Sep 17 00:00:00 2001 From: Tim Heap Date: Wed, 29 Apr 2026 16:45:22 +1000 Subject: [PATCH 3/6] Fix docs build --- docs/conf.py | 23 +++++++++++++---------- docs/requirements.txt | 2 -- pyproject.toml | 6 ++++++ 3 files changed, 19 insertions(+), 12 deletions(-) delete mode 100644 docs/requirements.txt diff --git a/docs/conf.py b/docs/conf.py index 938c034..07cc01b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,11 +12,12 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import os -import sys +import importlib.metadata +import pathlib +import tomllib +from packaging import version as _version import sphinx_rtd_theme -from enumchoicefield.version import version as module_version # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -52,19 +53,24 @@ # General information about the project. project = 'Django EnumChoiceField' -copyright = '2016, Tim Heap' +copyright = '2026, Tim Heap' author = 'Tim Heap' -version = '.'.join(module_version.split('.')[:2]) +with open(pathlib.Path(__file__).parent.parent / 'pyproject.toml', 'rb') as f: + _pyproject = tomllib.load(f) + # The full version, including alpha/beta/rc tags. -release = module_version +release = _pyproject['project']['version'] +# The major.minor version +_v = _version.parse(release) +version = f'{_v.major}.{_v.minor}' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: @@ -118,9 +124,6 @@ # a list of builtin themes. html_theme = "sphinx_rtd_theme" -# Add any paths that contain custom themes here, relative to this directory. -html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] - # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 579bb10..0000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -Sphinx==2.3.1 -sphinx_rtd_theme diff --git a/pyproject.toml b/pyproject.toml index c701797..7b13292 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,12 @@ classifiers = [ "Framework :: Django", ] +[project.optional-dependencies] +docs = [ + "Sphinx~=9.1.0", + "sphinx_rtd_theme", +] + [project.urls] Documentation = "https://django-enumchoicefield.readthedocs.io/" Source = "https://github.com/mx-moth/django-enumchoicefield" From 0df05740d8e65485edddda52dee2077dda138ed9 Mon Sep 17 00:00:00 2001 From: Tim Heap Date: Wed, 29 Apr 2026 16:45:43 +1000 Subject: [PATCH 4/6] Update tests for modern Django --- enumchoicefield/tests/test_field.py | 2 +- enumchoicefield/tests/test_forms.py | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/enumchoicefield/tests/test_field.py b/enumchoicefield/tests/test_field.py index d9b724f..d9ddf1f 100644 --- a/enumchoicefield/tests/test_field.py +++ b/enumchoicefield/tests/test_field.py @@ -132,6 +132,6 @@ def test_exact(self): ChoiceModel.objects.get(choice=MyEnum.foo)) def test_in(self): - self.assertQuerysetEqual( + self.assertQuerySetEqual( ChoiceModel.objects.filter(choice__in=[MyEnum.bar, MyEnum.baz]), [self.bar, self.baz], transform=lambda x: x) diff --git a/enumchoicefield/tests/test_forms.py b/enumchoicefield/tests/test_forms.py index c12d4af..ae8252d 100644 --- a/enumchoicefield/tests/test_forms.py +++ b/enumchoicefield/tests/test_forms.py @@ -1,4 +1,3 @@ -import django from django import forms from django.core.exceptions import ValidationError from django.forms.utils import flatatt @@ -23,10 +22,18 @@ class TranslatedEnum(ChoiceEnum): class SelectTestCase(SimpleTestCase): - def assertSelectOptions(self, html, options, required=True, name='choice'): - attrs = {'id': 'id_' + name, 'name': name} - if django.VERSION >= (1, 10) and required: - attrs['required'] = True + def assertSelectOptions(self, html, options, required=True, name='choice', error=False): + attrs = { + 'id': 'id_' + name, + 'name': name, + 'required': required, + } + if error: + attrs.update({ + 'aria-describedby': 'id_' + name + '_error', + 'aria-invalid': 'true', + }) + select = '{options}'.format( attrs=flatatt(attrs), options=''.join(options)) self.assertHTMLEqual(select, html) @@ -143,7 +150,7 @@ def test_invalid_choice(self): '', '', '', - ]) + ], error=True) class TestLimitedMembers(SelectTestCase): From 5fecd0af3e6b5ee27022cd7470665c600bb48aba Mon Sep 17 00:00:00 2001 From: Tim Heap Date: Wed, 29 Apr 2026 16:46:25 +1000 Subject: [PATCH 5/6] Update tox.ini to modern practices --- pyproject.toml | 5 +++++ requirements-dev.txt | 2 -- requirements-test.txt | 1 - tox.ini | 36 +++++++++++++++--------------------- 4 files changed, 20 insertions(+), 24 deletions(-) delete mode 100644 requirements-dev.txt delete mode 100644 requirements-test.txt diff --git a/pyproject.toml b/pyproject.toml index 7b13292..f44c352 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,11 @@ classifiers = [ ] [project.optional-dependencies] +testing = [ + "dj-database-url~=3.1.2", + "ruff", +] + docs = [ "Sphinx~=9.1.0", "sphinx_rtd_theme", diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index a7e32c0..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,2 +0,0 @@ --r requirements-test.txt -tox==2.0.1 diff --git a/requirements-test.txt b/requirements-test.txt deleted file mode 100644 index 7b13ade..0000000 --- a/requirements-test.txt +++ /dev/null @@ -1 +0,0 @@ -dj-database-url~=0.4.1 diff --git a/tox.ini b/tox.ini index fe454da..40576e8 100644 --- a/tox.ini +++ b/tox.ini @@ -2,40 +2,34 @@ skip_missing_interpreters = True envlist = - py37-dj{2x,3x} - py38-dj{2x,3x,4x} - py39-dj{2x,3x,4x} - py310-dj{3x,4x} - flake8,isort,docs + py310-dj{52} + py311-dj{52} + py312-dj{52,60} + py313-dj{52,60} + py314-dj{52,60} + ruff,docs [testenv] commands = python runtests.py {posargs} -usedevelop = True +package = editable +extras = testing pip_pre = True setenv = PYTHONDONTWRITEBYTECODE=1 -passenv = DJANGO_SETTINGS_MODULE DATABASE_URL +passenv = "DJANGO_SETTINGS_MODULE,DATABASE_URL" deps = - -rrequirements-test.txt - dj2x: Django~=2.2.17 - dj3x: Django~=3.2.9 - dj4x: Django~=4.0.0 + dj52: Django~=5.2.0 + dj60: Django~=6.0.0 postgres: psycopg2 mysql: mysqlclient -[testenv:flake8] +[testenv:ruff] basepython = python3 -deps = flake8 -commands = flake8 enumchoicefield/ - -[testenv:isort] -basepython = python3 -deps = isort -commands = isort --check-only --diff enumchoicefield/ +commands = ruff check enumchoicefield/ [testenv:docs] basepython = python3 -deps = -r{toxinidir}/docs/requirements.txt +extras = testing,docs changedir = docs -whitelist_externals = make +allowlist_externals = make commands = make SPHINXOPTS=-nW clean html From 4f1c0a85b740d73d1f10eec8d6043143483dc014 Mon Sep 17 00:00:00 2001 From: Tim Heap Date: Wed, 29 Apr 2026 17:03:01 +1000 Subject: [PATCH 6/6] Add Github Actions support --- .github/workflows/ci.yaml | 90 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 .github/workflows/ci.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..6d75b0c --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,90 @@ +name: Test +on: + push: + branches: + - main + tags: + - "v[0-9]+.*" + pull_request: + branches: + - "*" + + workflow_dispatch: + +env: + python-version: "3.14" + +jobs: + test-matrix: + name: Python ${{ matrix.python-version }}, Django ${{ matrix.django-version }} + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + include: + - python-version: "3.10" + django-version: "5.2" + - python-version: "3.11" + django-version: "5.2" + - python-version: "3.12" + django-version: "5.2" + - python-version: "3.13" + django-version: "5.2" + - python-version: "3.14" + django-version: "5.2" + + - python-version: "3.13" + django-version: "6.0" + - python-version: "3.14" + django-version: "6.0" + + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + + - name: Install + shell: bash -l {0} + run: | + pip install -e.[testing] "Django~=${DJANGO_VERSION}" + env: + DJANGO_VERSION: ${{ matrix.django-version }} + + - name: Test + shell: bash -l {0} + run: | + ./runtests.py + + lint: + name: Lint and docs + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-python@v6 + with: + python-version: ${{ env.python-version }} + cache: 'pip' + + - name: Install + shell: bash -l {0} + run: | + pip install -e.[testing,docs] + env: + DJANGO_VERSION: ${{ matrix.django-version }} + + - name: Lint + shell: bash -l {0} + run: | + ruff check enumchoicefield/ + + - name: Docs + shell: bash -l {0} + run: | + cd ./docs + make SPHINXOPTS=-nW clean html