From 5d1f3f7dab2be6107bb9e25efff4b02e7053570b Mon Sep 17 00:00:00 2001 From: Dan Moore Date: Mon, 2 Feb 2026 21:39:26 -0500 Subject: [PATCH 1/8] Add testing support for Django 6.0 and Python 3.14 --- .github/workflows/test.yml | 2 +- README.rst | 3 ++- pyproject.toml | 1 + tox.ini | 16 ++++++++++------ 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fa9f290..c15dc7e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v1 diff --git a/README.rst b/README.rst index 5a397d5..f81f4ca 100644 --- a/README.rst +++ b/README.rst @@ -90,7 +90,8 @@ The codebase is targeted and tested against: * Django 3.2.x against Python 3.8, 3.9, 3.10 * Django 4.2.x against Python 3.8, 3.9, 3.10, 3.11, 3.12 -* Django 5.2.x against Python 3.10, 3.11, 3.12, 3.13 +* Django 5.2.x against Python 3.10, 3.11, 3.12, 3.13, 3.14 +* Django 6.0.x against Python 3.12, 3.13, 3.14 To run the tests against all target environments, install `tox `_ and then execute the command:: diff --git a/pyproject.toml b/pyproject.toml index c22c933..cf6d86b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", ] dependencies = [ diff --git a/tox.ini b/tox.ini index b4f8c91..a01c24d 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,11 @@ [tox] envlist = flake8, - py{39,310}-django{32}, - py{39}-django{42}, + py{39,310}-django{32} + py{39}-django{42} py{310,311,312}-django{42} - py{310,311,312,313}-django{52} + py{310,311,312,313,314}-django{52} + py{312,313,314}-django{60} [gh-actions] python = @@ -13,6 +14,7 @@ python = 3.11: py311 3.12: py312 3.13: py313 + 3.14: py314 [build-system] build-backend = "hatchling.build" @@ -29,11 +31,13 @@ basepython = py311: python3.11 py312: python3.12 py313: python3.13 + py314: python3.14 deps = hatch>=1.7.0 - django32: Django>=3.2,<4 - django42: Django>=4.2,<5 - django52: Django>=5.2,<6 + django32: Django>=3.2.9,<4 # Django 3.2.9 is required for Python 3.10 support + django42: Django>=4.2.8,<5 # Django 4.2.8 is required for Python 3.12 support + django52: Django>=5.2.8,<6 # Django 5.2.8 is required for Python 3.14 support + django60: Django>=6.0,<6.1 extras = tests [testenv:flake8] From 7c3dd31bb5b04054176d51e93a809577cf92c2bc Mon Sep 17 00:00:00 2001 From: Dan Moore Date: Mon, 2 Feb 2026 21:49:20 -0500 Subject: [PATCH 2/8] Remove Python 3.9 which is EOL --- .github/workflows/test.yml | 2 +- README.rst | 4 ++-- pyproject.toml | 3 +-- setup.cfg | 5 +++-- tox.ini | 5 +---- 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c15dc7e..d085462 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v1 diff --git a/README.rst b/README.rst index f81f4ca..93877eb 100644 --- a/README.rst +++ b/README.rst @@ -88,8 +88,8 @@ Targets & testing The codebase is targeted and tested against: -* Django 3.2.x against Python 3.8, 3.9, 3.10 -* Django 4.2.x against Python 3.8, 3.9, 3.10, 3.11, 3.12 +* Django 3.2.x against Python 3.10 +* Django 4.2.x against Python 3.10, 3.11, 3.12 * Django 5.2.x against Python 3.10, 3.11, 3.12, 3.13, 3.14 * Django 6.0.x against Python 3.12, 3.13, 3.14 diff --git a/pyproject.toml b/pyproject.toml index cf6d86b..e39f3d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ authors = [ ] description = "Group accounts for Django" readme = "README.rst" -requires-python = ">=3.9" +requires-python = ">=3.10" license = {text = "BSD License"} classifiers = [ "Development Status :: 5 - Production/Stable", @@ -15,7 +15,6 @@ classifiers = [ "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", diff --git a/setup.cfg b/setup.cfg index 0a5719d..35db5d2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,10 +16,11 @@ classifiers = License :: OSI Approved :: BSD License Operating System :: OS Independent Programming Language :: Python - 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 Programming Language :: Python :: Implementation :: CPython Development Status :: 5 - Production/Stable diff --git a/tox.ini b/tox.ini index a01c24d..45300f8 100644 --- a/tox.ini +++ b/tox.ini @@ -1,15 +1,13 @@ [tox] envlist = flake8, - py{39,310}-django{32} - py{39}-django{42} + py{310}-django{32} py{310,311,312}-django{42} py{310,311,312,313,314}-django{52} py{312,313,314}-django{60} [gh-actions] python = - 3.9: py39 3.10: py310 3.11: py311 3.12: py312 @@ -26,7 +24,6 @@ setenv = PYTHONPATH = {toxinidir}:{toxinidir}/organizations commands = pytest {posargs} --cov=organizations basepython = - py39: python3.9 py310: python3.10 py311: python3.11 py312: python3.12 From 3f6ec7791a542f0bf76fa8b6695050ef1a106506 Mon Sep 17 00:00:00 2001 From: Dan Moore Date: Mon, 2 Feb 2026 21:51:50 -0500 Subject: [PATCH 3/8] Remove support for Django 3.2 which is EOL --- README.rst | 1 - pyproject.toml | 2 +- setup.cfg | 2 +- tox.ini | 1 - 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 93877eb..23b6ccc 100644 --- a/README.rst +++ b/README.rst @@ -88,7 +88,6 @@ Targets & testing The codebase is targeted and tested against: -* Django 3.2.x against Python 3.10 * Django 4.2.x against Python 3.10, 3.11, 3.12 * Django 5.2.x against Python 3.10, 3.11, 3.12, 3.13, 3.14 * Django 6.0.x against Python 3.12, 3.13, 3.14 diff --git a/pyproject.toml b/pyproject.toml index e39f3d6..c0ed58e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ classifiers = [ "Programming Language :: Python :: Implementation :: CPython", ] dependencies = [ - "Django>=3.2", + "Django>=4.2", "django-extensions>=2.0.8", ] dynamic = ["version"] diff --git a/setup.cfg b/setup.cfg index 35db5d2..f4ab3f1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,7 +31,7 @@ packages = find: package_dir= =src install_requires = - Django>=3.2.0 + Django>=4.2.0 django-extensions>=2.0.8 python_requires = >=3.8 diff --git a/tox.ini b/tox.ini index 45300f8..5b13173 100644 --- a/tox.ini +++ b/tox.ini @@ -31,7 +31,6 @@ basepython = py314: python3.14 deps = hatch>=1.7.0 - django32: Django>=3.2.9,<4 # Django 3.2.9 is required for Python 3.10 support django42: Django>=4.2.8,<5 # Django 4.2.8 is required for Python 3.12 support django52: Django>=5.2.8,<6 # Django 5.2.8 is required for Python 3.14 support django60: Django>=6.0,<6.1 From 68cbb97dc3726bbbdb830ada272ef4d61a2c8eba Mon Sep 17 00:00:00 2001 From: Ben Lopatin Date: Sun, 8 Mar 2026 12:14:03 -0400 Subject: [PATCH 4/8] Update migrations and docs --- README.rst | 17 ++++------ docs/getting_started.rst | 15 +++++++++ ...0005_alter_custominvitation_id_and_more.py | 33 +++++++++++++++++++ ..._id_alter_accountinvitation_id_and_more.py | 33 +++++++++++++++++++ ...r_id_alter_vendorinvitation_id_and_more.py | 33 +++++++++++++++++++ 5 files changed, 120 insertions(+), 11 deletions(-) create mode 100644 test_abstract/migrations/0005_alter_custominvitation_id_and_more.py create mode 100644 test_accounts/migrations/0005_alter_account_id_alter_accountinvitation_id_and_more.py create mode 100644 test_vendors/migrations/0005_alter_vendor_id_alter_vendorinvitation_id_and_more.py diff --git a/README.rst b/README.rst index 23b6ccc..a987665 100644 --- a/README.rst +++ b/README.rst @@ -146,6 +146,12 @@ By default you will need to install `django-extensions` or comparable libraries if you plan on adding Django Organizations as an installed app to your Django project. See below on configuring. +Upgrading +--------- + +Upgrading django-organizations is expected to be a low-impact operation. See the Upgrading +section in the Getting Started docs for additional considerations. + Configuring ----------- @@ -268,14 +274,3 @@ License Anyone is free to use or modify this software under the terms of the BSD license. - -Sponsors -======== - -`Muster `_ is building precision advocacy software to impact policy through grassroots action. - -.. image:: https://www.muster.com/hs-fs/hubfs/muster_logo-2.png?width=600&name=muster_logo-2.png - :target: https://www.muster.com/home?utm_source=github&campaign=opensource - :width: 400 - :alt: Alternative text - diff --git a/docs/getting_started.rst b/docs/getting_started.rst index b438037..2123475 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -97,6 +97,21 @@ consistent**, including keyword arguments. Otherwise you will end up generating extraneous and possibly conflicting migrations in your own app. The SlugField must accept the `populate_from` keyword argument. +Upgrading +========= + +If you are upgrading from version 2.5.0 *and* have custom models *and* +created them without a project-specified +`primary key field type `_ +*and* you created your models prior to the default use of `BigInt` as the +underlying autoincrementing ID field, *then* be informed that running `makemigrations` will +create new migrations updating these fields from `IntegerField` to `BigIntegerField` +(wrapped by `BigAutoField`). This applies to your project and your project migrations, however, so +you are free to treat this however you want, e.g. if you want to keep `IntegerFields` +as the field type, configure your project settings as much or separate the `database +and state operations `_ +in the migration (maybe not ideal but safe with regard to your database). + Users and multi-account membership ================================== diff --git a/test_abstract/migrations/0005_alter_custominvitation_id_and_more.py b/test_abstract/migrations/0005_alter_custominvitation_id_and_more.py new file mode 100644 index 0000000..49033c5 --- /dev/null +++ b/test_abstract/migrations/0005_alter_custominvitation_id_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 6.0.3 on 2026-03-08 15:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('test_abstract', '0004_alter_customorganization_slug'), + ] + + operations = [ + migrations.AlterField( + model_name='custominvitation', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='customorganization', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='customowner', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='customuser', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + ] diff --git a/test_accounts/migrations/0005_alter_account_id_alter_accountinvitation_id_and_more.py b/test_accounts/migrations/0005_alter_account_id_alter_accountinvitation_id_and_more.py new file mode 100644 index 0000000..045b8ac --- /dev/null +++ b/test_accounts/migrations/0005_alter_account_id_alter_accountinvitation_id_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 6.0.3 on 2026-03-08 15:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('test_accounts', '0004_alter_account_users_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='account', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='accountinvitation', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='accountowner', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='accountuser', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + ] diff --git a/test_vendors/migrations/0005_alter_vendor_id_alter_vendorinvitation_id_and_more.py b/test_vendors/migrations/0005_alter_vendor_id_alter_vendorinvitation_id_and_more.py new file mode 100644 index 0000000..027af64 --- /dev/null +++ b/test_vendors/migrations/0005_alter_vendor_id_alter_vendorinvitation_id_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 6.0.3 on 2026-03-08 15:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('test_vendors', '0004_alter_vendor_users_alter_vendorinvitation_invited_by_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='vendor', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='vendorinvitation', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='vendorowner', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='vendoruser', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + ] From b51b102a968b494bf119af057233333f2386edb1 Mon Sep 17 00:00:00 2001 From: Ben Lopatin Date: Sun, 8 Mar 2026 12:26:04 -0400 Subject: [PATCH 5/8] Set DEFAULT_AUTO_FIELD --- conftest.py | 1 + docs/getting_started.rst | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/conftest.py b/conftest.py index 21be2a6..e32eb16 100644 --- a/conftest.py +++ b/conftest.py @@ -16,6 +16,7 @@ def pytest_configure(): DATABASES={ "default": {"ENGINE": "django.db.backends.sqlite3", "NAME": "test.sqlite3"} }, + DEFAULT_AUTO_FIELD="django.db.models.AutoField", INSTALLED_APPS=[ "django.contrib.auth", "django.contrib.contenttypes", diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 2123475..f930d34 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -100,13 +100,13 @@ must accept the `populate_from` keyword argument. Upgrading ========= -If you are upgrading from version 2.5.0 *and* have custom models *and* -created them without a project-specified -`primary key field type `_ -*and* you created your models prior to the default use of `BigInt` as the -underlying autoincrementing ID field, *then* be informed that running `makemigrations` will -create new migrations updating these fields from `IntegerField` to `BigIntegerField` -(wrapped by `BigAutoField`). This applies to your project and your project migrations, however, so +Updating Django from a pre-6.0 version to 6.0 or above may result in creating +new migrations for custom organization models if you do not already have +`DEFAULT_AUTO_FIELD` set in your project. This is because in Django 6.0 the +default field changed from `AutoField` (with an `IntegerField` under the hood) +to a `BigAutoField` (with a `BigIntegerField` under the hood). + +This applies to your project and your project migrations, however, so you are free to treat this however you want, e.g. if you want to keep `IntegerFields` as the field type, configure your project settings as much or separate the `database and state operations `_ From 38560a4881de5f8ef0a5824ac1a118607d953184 Mon Sep 17 00:00:00 2001 From: Ben Lopatin Date: Sun, 8 Mar 2026 12:30:40 -0400 Subject: [PATCH 6/8] Remove migrations, rely on test settings --- ...0005_alter_custominvitation_id_and_more.py | 33 ------------------- ..._id_alter_accountinvitation_id_and_more.py | 33 ------------------- ...r_id_alter_vendorinvitation_id_and_more.py | 33 ------------------- 3 files changed, 99 deletions(-) delete mode 100644 test_abstract/migrations/0005_alter_custominvitation_id_and_more.py delete mode 100644 test_accounts/migrations/0005_alter_account_id_alter_accountinvitation_id_and_more.py delete mode 100644 test_vendors/migrations/0005_alter_vendor_id_alter_vendorinvitation_id_and_more.py diff --git a/test_abstract/migrations/0005_alter_custominvitation_id_and_more.py b/test_abstract/migrations/0005_alter_custominvitation_id_and_more.py deleted file mode 100644 index 49033c5..0000000 --- a/test_abstract/migrations/0005_alter_custominvitation_id_and_more.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 6.0.3 on 2026-03-08 15:59 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('test_abstract', '0004_alter_customorganization_slug'), - ] - - operations = [ - migrations.AlterField( - model_name='custominvitation', - name='id', - field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), - ), - migrations.AlterField( - model_name='customorganization', - name='id', - field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), - ), - migrations.AlterField( - model_name='customowner', - name='id', - field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), - ), - migrations.AlterField( - model_name='customuser', - name='id', - field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), - ), - ] diff --git a/test_accounts/migrations/0005_alter_account_id_alter_accountinvitation_id_and_more.py b/test_accounts/migrations/0005_alter_account_id_alter_accountinvitation_id_and_more.py deleted file mode 100644 index 045b8ac..0000000 --- a/test_accounts/migrations/0005_alter_account_id_alter_accountinvitation_id_and_more.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 6.0.3 on 2026-03-08 15:59 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('test_accounts', '0004_alter_account_users_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='account', - name='id', - field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), - ), - migrations.AlterField( - model_name='accountinvitation', - name='id', - field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), - ), - migrations.AlterField( - model_name='accountowner', - name='id', - field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), - ), - migrations.AlterField( - model_name='accountuser', - name='id', - field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), - ), - ] diff --git a/test_vendors/migrations/0005_alter_vendor_id_alter_vendorinvitation_id_and_more.py b/test_vendors/migrations/0005_alter_vendor_id_alter_vendorinvitation_id_and_more.py deleted file mode 100644 index 027af64..0000000 --- a/test_vendors/migrations/0005_alter_vendor_id_alter_vendorinvitation_id_and_more.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 6.0.3 on 2026-03-08 15:59 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('test_vendors', '0004_alter_vendor_users_alter_vendorinvitation_invited_by_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='vendor', - name='id', - field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), - ), - migrations.AlterField( - model_name='vendorinvitation', - name='id', - field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), - ), - migrations.AlterField( - model_name='vendorowner', - name='id', - field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), - ), - migrations.AlterField( - model_name='vendoruser', - name='id', - field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), - ), - ] From d906928421c216716f09d975dbc9402f073507cf Mon Sep 17 00:00:00 2001 From: Ben Lopatin Date: Sun, 8 Mar 2026 12:31:46 -0400 Subject: [PATCH 7/8] Update flake8 version --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 5b13173..9632302 100644 --- a/tox.ini +++ b/tox.ini @@ -39,7 +39,7 @@ extras = tests [testenv:flake8] basepython=python3 deps= - flake8==3.12.7 + flake8==7.3.0 commands= flake8 src/organizations tests From 1e0e0d1b6dd290fdb54b5ae5d84a19126a17b565 Mon Sep 17 00:00:00 2001 From: Ben Lopatin Date: Sun, 8 Mar 2026 12:36:45 -0400 Subject: [PATCH 8/8] Update version --- HISTORY.rst | 7 +++++++ src/organizations/__init__.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 0744ec6..4ff9aaf 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,13 @@ History ======= +2.6.0 +----- + +* Drop Python 3.9 support +* Add Python 3.14 support +* Add Django 6.0 support + 2.5.0 ----- diff --git a/src/organizations/__init__.py b/src/organizations/__init__.py index 24304d2..242d6df 100644 --- a/src/organizations/__init__.py +++ b/src/organizations/__init__.py @@ -2,4 +2,4 @@ __author__ = "Ben Lopatin" __email__ = "ben@benlopatin.com" -__version__ = "2.5.0" +__version__ = "2.6.0"