Merge branch 'master' into fix-prefetch-related

This commit is contained in:
Joseph Riddle 2023-11-27 12:55:53 -08:00 committed by GitHub
commit 55cd816939
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 142 additions and 156 deletions

View file

@ -1,4 +1,4 @@
# http://editorconfig.org
# https://editorconfig.org
root = true

View file

@ -11,14 +11,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: 3.8
python-version: 3.x
- name: Install dependencies
run: |

View file

@ -2,6 +2,9 @@ name: Test
on: [push, pull_request]
env:
FORCE_COLOR: 1
jobs:
build:
runs-on: ubuntu-latest
@ -9,7 +12,7 @@ jobs:
fail-fast: false
max-parallel: 5
matrix:
python-version: ['3.7', '3.8', '3.9', '3.10']
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12-dev']
services:
postgres:
@ -27,10 +30,10 @@ jobs:
--health-retries 5
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
@ -40,7 +43,7 @@ jobs:
echo "::set-output name=dir::$(pip cache dir)"
- name: Cache
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ${{ steps.pip-cache.outputs.dir }}
key:
@ -64,6 +67,6 @@ jobs:
DB_PORT: 5432
- name: Upload coverage
uses: codecov/codecov-action@v1
uses: codecov/codecov-action@v3
with:
name: Python ${{ matrix.python-version }}

View file

@ -1,6 +1,6 @@
repos:
- repo: https://github.com/PyCQA/isort
rev: 5.11.1
rev: 5.12.0
hooks:
- id: isort
args: ['--profile', 'black', '--check-only', '--diff']
@ -14,7 +14,7 @@ repos:
files: ^(model_utils|tests)/
- repo: https://github.com/asottile/pyupgrade
rev: v3.3.1
rev: v3.6.0
hooks:
- id: pyupgrade
args: [--py37-plus]
args: [--py38-plus]

18
.readthedocs.yml Normal file
View file

@ -0,0 +1,18 @@
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Project page: https://readthedocs.org/projects/django-model-utils/
version: 2
build:
os: ubuntu-22.04
tools:
python: "3"
python:
install:
- method: pip
path: .
sphinx:
configuration: docs/conf.py

View file

@ -1,8 +1,17 @@
Changelog
=========
4.3.2
-----
To be released
--------
- Remove ``SaveSignalHandlingModel``. This model used a modified copy of the internal Django method `Model.save_base()`
and had not been updated for upstream bug fixes changes since its addition. (GH-#582)
- Confirm support for `Django 4.2`
- Add support for `Python 3.11` (GH-#545)
- Add support for `Python 3.12` (GH-#545)
- Drop support for `Python 3.7` (GH-#545)
- Swedish translation (GH-#561)
- Use proper column name instead of attname (GH-#573)
- Fix `ValueError` when calling `prefetch_related` for tracked `ForeignKey` fields (Fixes GH-433)
4.3.1 (2022-11-15)
@ -253,7 +262,7 @@ Changelog
for the report. Thanks Matthew Schinckel for the fix. Merge of GH-130,
fixes GH-83.
.. _IPython: http://ipython.org/
.. _IPython: https://ipython.org/
2.0.3 (2014.03.19)

View file

@ -9,7 +9,7 @@ BUILDDIR = _build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from https://sphinx-doc.org/)
endif
# Internal variables.

View file

@ -11,7 +11,7 @@
# serve to show the default.
import os
from pkg_resources import get_distribution
import importlib.metadata
# 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
@ -49,7 +49,8 @@ parent_dir = os.path.dirname(os.path.dirname(__file__))
# |version| and |release|, also used in various other places throughout the
# built documents.
#
release = get_distribution('django-model-utils').version
release = importlib.metadata.version('django-model-utils')
# for example take major/minor
version = '.'.join(release.split('.')[:2])

View file

@ -56,7 +56,7 @@ if errorlevel 9009 (
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
echo.https://sphinx-doc.org/
exit /b 1
)

View file

@ -84,7 +84,7 @@ If you don't explicitly call ``select_subclasses()`` or ``get_subclass()``,
an ``InheritanceManager`` behaves identically to a normal ``Manager``; so
it's safe to use as your default manager for the model.
.. _contributed by Jeff Elmore: http://jeffelmore.org/2010/11/11/automatic-downcasting-of-inherited-models-in-django/
.. _contributed by Jeff Elmore: https://jeffelmore.org/2010/11/11/automatic-downcasting-of-inherited-models-in-django/
JoinManager
-----------

View file

@ -112,21 +112,3 @@ Also you can override the default uuid version. Versions 1,3,4 and 5 are now sup
.. _`UUIDField`: https://github.com/jazzband/django-model-utils/blob/master/docs/fields.rst#uuidfield
SaveSignalHandlingModel
-----------------------
An abstract base class model to pass a parameter ``signals_to_disable``
to ``save`` method in order to disable signals
.. code-block:: python
from model_utils.models import SaveSignalHandlingModel
class SaveSignalTestModel(SaveSignalHandlingModel):
name = models.CharField(max_length=20)
obj = SaveSignalTestModel(name='Test')
# Note: If you use `Model.objects.create`, the signals can't be disabled
obj.save(signals_to_disable=['pre_save'] # disable `pre_save` signal

View file

@ -20,4 +20,4 @@ Dependencies
``django-model-utils`` supports `Django`_ 3.2+ (latest bugfix
release in each series only) on Python 3.7+.
.. _Django: http://www.djangoproject.com/
.. _Django: https://www.djangoproject.com/

View file

@ -1,10 +1,10 @@
from pkg_resources import DistributionNotFound, get_distribution
import importlib.metadata
from .choices import Choices # noqa:F401
from .tracker import FieldTracker, ModelTracker # noqa:F401
try:
__version__ = get_distribution("django-model-utils").version
except DistributionNotFound: # pragma: no cover
__version__ = importlib.metadata.version('django-model-utils')
except importlib.metadata.PackageNotFoundError: # pragma: no cover
# package is not installed
__version__ = None

Binary file not shown.

View file

@ -0,0 +1,53 @@
# This file is distributed under the same license as the django-model-utils package.
#
# Translators:
# Tomas Walch <tomas@alternaliv.se>, 2022.
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-11-23 14:46+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: model_utils/models.py:25
msgid "created"
msgstr "skapad"
#: model_utils/models.py:26
msgid "modified"
msgstr "ändrad"
#: model_utils/models.py:50
msgid "start"
msgstr "start"
#: model_utils/models.py:51
msgid "end"
msgstr "slut"
#: model_utils/models.py:66
msgid "status"
msgstr "status"
#: model_utils/models.py:67
msgid "status changed"
msgstr "status ändrad"
#: tests/models.py:106 tests/models.py:115 tests/models.py:124
msgid "active"
msgstr "aktiv"
#: tests/models.py:107 tests/models.py:116 tests/models.py:125
msgid "deleted"
msgstr "borttagen"
#: tests/models.py:108 tests/models.py:117 tests/models.py:126
msgid "on hold"
msgstr "väntande"

View file

@ -194,7 +194,7 @@ class InheritanceQuerySet(InheritanceQuerySetMixin, QuerySet):
where_queries.append('(' + ' AND '.join([
'"{}"."{}" IS NOT NULL'.format(
model._meta.db_table,
field.attname, # Should this be something else?
field.column,
) for field in model._meta.parents.values()
]) + ')')

View file

@ -1,7 +1,6 @@
from django.core.exceptions import ImproperlyConfigured
from django.db import models, router, transaction
from django.db import models
from django.db.models.functions import Now
from django.db.models.signals import post_save, pre_save
from django.utils.translation import gettext_lazy as _
from model_utils.fields import (
@ -172,60 +171,3 @@ class UUIDModel(models.Model):
class Meta:
abstract = True
class SaveSignalHandlingModel(models.Model):
"""
An abstract base class model to pass a parameter ``signals_to_disable``
to ``save`` method in order to disable signals
"""
class Meta:
abstract = True
def save(self, signals_to_disable=None, *args, **kwargs):
"""
Add an extra parameters to hold which signals to disable
If empty, nothing will change
"""
self.signals_to_disable = signals_to_disable or []
super().save(*args, **kwargs)
def save_base(self, raw=False, force_insert=False,
force_update=False, using=None, update_fields=None):
"""
Copied from base class for a minor change.
This is an ugly overwriting but since Django's ``save_base`` method
does not differ between versions 1.8 and 1.10,
that way of implementing wouldn't harm the flow
"""
using = using or router.db_for_write(self.__class__, instance=self)
assert not (force_insert and (force_update or update_fields))
assert update_fields is None or len(update_fields) > 0
cls = origin = self.__class__
if cls._meta.proxy:
cls = cls._meta.concrete_model
meta = cls._meta
if not meta.auto_created and 'pre_save' not in self.signals_to_disable:
pre_save.send(
sender=origin, instance=self, raw=raw, using=using,
update_fields=update_fields,
)
with transaction.atomic(using=using, savepoint=False):
if not raw:
self._save_parents(cls, using, update_fields)
updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
self._state.db = using
self._state.adding = False
if not meta.auto_created and 'post_save' not in self.signals_to_disable:
post_save.send(
sender=origin, instance=self, created=(not updated),
update_fields=update_fields, raw=raw, using=using,
)
# Empty the signals in case it might be used somewhere else in future
self.signals_to_disable = []

0
model_utils/py.typed Normal file
View file

View file

@ -1,4 +1,4 @@
pytest==6.2.5
pytest-django==3.10.0
psycopg2-binary==2.8.6
psycopg2-binary==2.9.5
pytest-cov==2.10.1

View file

@ -29,7 +29,7 @@ setup(
maintainer='JazzBand',
url='https://github.com/jazzband/django-model-utils',
packages=find_packages(exclude=['tests*']),
python_requires=">=3.7",
python_requires=">=3.8",
install_requires=['Django>=3.2'],
classifiers=[
'Development Status :: 5 - Production/Stable',
@ -39,19 +39,23 @@ setup(
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.7',
'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',
'Framework :: Django',
'Framework :: Django :: 3.2',
'Framework :: Django :: 4.0',
'Framework :: Django :: 4.1',
'Framework :: Django :: 4.2',
],
zip_safe=False,
package_data={
'model_utils': [
'locale/*/LC_MESSAGES/django.po', 'locale/*/LC_MESSAGES/django.mo'
'locale/*/LC_MESSAGES/django.po',
'locale/*/LC_MESSAGES/django.mo',
'py.typed',
],
},
)

View file

@ -7,7 +7,6 @@ from model_utils import Choices
from model_utils.fields import MonitorField, SplitField, StatusField, UUIDField
from model_utils.managers import InheritanceManager, JoinManagerMixin, QueryManager
from model_utils.models import (
SaveSignalHandlingModel,
SoftDeletableModel,
StatusModel,
TimeFramedModel,
@ -67,6 +66,12 @@ class InheritanceManagerTestChild3(InheritanceManagerTestParent):
parent_link=True, on_delete=models.CASCADE)
class InheritanceManagerTestChild3_1(InheritanceManagerTestParent):
parent_ptr = models.OneToOneField(
InheritanceManagerTestParent, db_column="custom_parent_ptr",
parent_link=True, on_delete=models.CASCADE)
class InheritanceManagerTestChild4(InheritanceManagerTestParent):
other_onetoone = models.OneToOneField(
InheritanceManagerTestParent, related_name='non_inheritance_relation',
@ -440,10 +445,6 @@ class CustomNotPrimaryUUIDModel(models.Model):
uuid = UUIDField(primary_key=False)
class SaveSignalHandlingTestModel(SaveSignalHandlingModel):
name = models.CharField(max_length=20)
class TimeStampWithStatusModel(TimeStampedModel, StatusModel):
STATUS = Choices(
("active", _("active")),

View file

@ -5,6 +5,7 @@ from tests.models import (
InheritanceManagerTestChild1,
InheritanceManagerTestChild2,
InheritanceManagerTestChild3,
InheritanceManagerTestChild3_1,
InheritanceManagerTestChild4,
InheritanceManagerTestGrandChild1,
InheritanceManagerTestGrandChild1_2,
@ -141,6 +142,7 @@ class InheritanceManagerTests(TestCase):
'inheritancemanagertestchild1',
'inheritancemanagertestchild2',
'manual_onetoone', # this was set via parent_link & related_name
'inheritancemanagertestchild3_1',
'child4_onetoone',
]
self.assertEqual(set(results.subclasses),
@ -256,7 +258,9 @@ class InheritanceManagerUsingModelsTests(TestCase):
objs = InheritanceManagerTestParent.objects.select_subclasses().order_by('pk')
objsmodels = InheritanceManagerTestParent.objects.select_subclasses(
InheritanceManagerTestChild1, InheritanceManagerTestChild2,
InheritanceManagerTestChild3, InheritanceManagerTestChild4,
InheritanceManagerTestChild3,
InheritanceManagerTestChild3_1,
InheritanceManagerTestChild4,
InheritanceManagerTestGrandChild1,
InheritanceManagerTestGrandChild1_2).order_by('pk')
self.assertEqual(set(objs.subclasses), set(objsmodels.subclasses))
@ -278,6 +282,7 @@ class InheritanceManagerUsingModelsTests(TestCase):
models = (InheritanceManagerTestChild1,
InheritanceManagerTestChild2,
InheritanceManagerTestChild3,
InheritanceManagerTestChild3_1,
InheritanceManagerTestChild4,
InheritanceManagerTestGrandChild1,
InheritanceManagerTestGrandChild1_2)
@ -426,6 +431,12 @@ class InheritanceManagerUsingModelsTests(TestCase):
self.assertEqual([child3], list(results))
def test_limit_to_specific_subclass_with_custom_db_column(self):
item = InheritanceManagerTestChild3_1.objects.create()
results = InheritanceManagerTestParent.objects.instance_of(InheritanceManagerTestChild3_1)
self.assertEqual([item], list(results))
def test_limit_to_specific_grandchild_class(self):
grandchild1 = InheritanceManagerTestGrandChild1.objects.get()
results = InheritanceManagerTestParent.objects.instance_of(InheritanceManagerTestGrandChild1)

View file

@ -1,42 +0,0 @@
from django.db.models.signals import post_save, pre_save
from django.test import TestCase
from tests.models import SaveSignalHandlingTestModel
from tests.signals import post_save_test, pre_save_test
class SaveSignalHandlingModelTests(TestCase):
def test_pre_save(self):
pre_save.connect(pre_save_test, sender=SaveSignalHandlingTestModel)
obj = SaveSignalHandlingTestModel.objects.create(name='Test')
delattr(obj, 'pre_save_runned')
obj.name = 'Test A'
obj.save()
self.assertEqual(obj.name, 'Test A')
self.assertTrue(hasattr(obj, 'pre_save_runned'))
obj = SaveSignalHandlingTestModel.objects.create(name='Test')
delattr(obj, 'pre_save_runned')
obj.name = 'Test B'
obj.save(signals_to_disable=['pre_save'])
self.assertEqual(obj.name, 'Test B')
self.assertFalse(hasattr(obj, 'pre_save_runned'))
def test_post_save(self):
post_save.connect(post_save_test, sender=SaveSignalHandlingTestModel)
obj = SaveSignalHandlingTestModel.objects.create(name='Test')
delattr(obj, 'post_save_runned')
obj.name = 'Test A'
obj.save()
self.assertEqual(obj.name, 'Test A')
self.assertTrue(hasattr(obj, 'post_save_runned'))
obj = SaveSignalHandlingTestModel.objects.create(name='Test')
delattr(obj, 'post_save_runned')
obj.name = 'Test B'
obj.save(signals_to_disable=['post_save'])
self.assertEqual(obj.name, 'Test B')
self.assertFalse(hasattr(obj, 'post_save_runned'))

View file

@ -1,7 +1,7 @@
[tox]
envlist =
py{37,38,39,310}-dj32
py{38,39,310}-dj{40,41,main}
py{38,39,310,311,312}-dj{40,41,42,main}
flake8
isort
@ -11,6 +11,8 @@ python =
3.8: py38, flake8, isort
3.9: py39
3.10: py310
3.11: py311
3.12: py312
[testenv]
deps =
@ -19,6 +21,7 @@ deps =
dj32: Django==3.2.*
dj40: Django==4.0.*
dj41: Django==4.1.*
dj42: Django==4.2.*
djmain: https://github.com/django/django/archive/main.tar.gz
ignore_outcome =
djmain: True
@ -26,11 +29,12 @@ ignore_errors =
djmain: True
passenv =
CI
FORCE_COLOR
GITHUB_*
DB_*
usedevelop = True
commands =
pytest {posargs}
python -m pytest {posargs}
[testenv:flake8]
basepython =