From d1e585172470d623613d98361cb9e0d6d0367863 Mon Sep 17 00:00:00 2001 From: Jim King Date: Wed, 11 Aug 2021 10:19:41 -0400 Subject: [PATCH 01/29] Change `make docs` and `make format` to use tox so they succeed. --- AUTHORS.rst | 1 + CHANGES.rst | 3 ++- CONTRIBUTING.rst | 4 +--- Makefile | 7 +++---- simple_history/registry_tests/tests.py | 4 ++-- simple_history/tests/tests/test_models.py | 2 +- tox.ini | 7 +++++++ 7 files changed, 17 insertions(+), 11 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index cdbdb09fb..75e1de8a0 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -56,6 +56,7 @@ Authors - Jesse Shapiro - Jihoon Baek (`jihoon796 `_) - Jim Gomez +- Jim King (`jeking3 `_) - Joao Junior (`joaojunior `_) - Joao Pedro Francese - `jofusa `_ diff --git a/CHANGES.rst b/CHANGES.rst index d321270f8..c552896b1 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,8 @@ Changes Unreleased ---------- - - Fixed bug where serializer of djangorestframework crashed if used with ``OrderingFilter`` (gh-821) +- Fixed bug where serializer of djangorestframework crashed if used with ``OrderingFilter`` (gh-821) +- Fixed `make format` so it works by using tox (gh-859) 3.0.0 (2021-04-16) ------------------ diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index ecf976c88..460bfa4cb 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -38,15 +38,13 @@ running tests:: This will install: - `tox`_: used for running the tests against all supported versions of Django - and Python + and Python as well as running tasks like lint, format, docs - `coverage`_: used for analyzing test coverage for tests -- `Sphinx`_: used for generating documentation If not using a virtualenv, the command should be prepended with ``sudo``. .. _tox: http://testrun.org/tox/latest// .. _coverage: http://nedbatchelder.com/code/coverage/ -.. _sphinx: http://sphinx-doc.org/ Documentation ------------- diff --git a/Makefile b/Makefile index dcd7c9025..04fc170e7 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ clean-pyc: find . -name '*~' -exec rm -f {} + init: - pip install "tox>=1.8" coverage Sphinx + pip install "tox>=1.8" coverage test: coverage erase @@ -24,7 +24,7 @@ test: docs: documentation documentation: - sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html + tox -e docs dist: clean pip install -U wheel @@ -44,5 +44,4 @@ release: dist twine upload dist/* format: - isort docs simple_history runtests.py setup.py - black docs simple_history runtests.py setup.py + tox -e format diff --git a/simple_history/registry_tests/tests.py b/simple_history/registry_tests/tests.py index cfa9d2769..727aa625f 100644 --- a/simple_history/registry_tests/tests.py +++ b/simple_history/registry_tests/tests.py @@ -198,7 +198,7 @@ def test_registering_with_tracked_abstract_base(self): class TestCustomAttrForeignKey(TestCase): - """ https://github.com/jazzband/django-simple-history/issues/431 """ + """https://github.com/jazzband/django-simple-history/issues/431""" def test_custom_attr(self): field = ModelWithCustomAttrForeignKey.history.model._meta.get_field("poll") @@ -219,7 +219,7 @@ def test_migrate_command(self): class TestModelWithHistoryInDifferentApp(TestCase): - """ https://github.com/jazzband/django-simple-history/issues/485 """ + """https://github.com/jazzband/django-simple-history/issues/485""" def test__different_app(self): appLabel = ModelWithHistoryInDifferentApp.history.model._meta.app_label diff --git a/simple_history/tests/tests/test_models.py b/simple_history/tests/tests/test_models.py index c386f632e..897688915 100644 --- a/simple_history/tests/tests/test_models.py +++ b/simple_history/tests/tests/test_models.py @@ -1189,7 +1189,7 @@ def test_migrations_include_order(self): class TestLatest(TestCase): - """"Test behavior of `latest()` without any field parameters""" + """Test behavior of `latest()` without any field parameters""" def setUp(self): poll = Poll.objects.create(question="Does `latest()` work?", pub_date=yesterday) diff --git a/tox.ini b/tox.ini index 720842ec2..d65eded51 100644 --- a/tox.ini +++ b/tox.ini @@ -43,6 +43,13 @@ commands = mariadb: coverage run -a runtests.py --database=mariadb coverage report +[testenv:format] +deps = -rrequirements/lint.txt +commands = + isort docs simple_history runtests.py setup.py + black docs simple_history runtests.py setup.py + flake8 simple_history + [testenv:lint] deps = -rrequirements/lint.txt commands = From aebb6c8040862678607a4b20a7ba2abfe5418972 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Aug 2021 17:04:00 +0000 Subject: [PATCH 02/29] Bump tox from 3.24.1 to 3.24.3 in /requirements Bumps [tox](https://github.com/tox-dev/tox) from 3.24.1 to 3.24.3. - [Release notes](https://github.com/tox-dev/tox/releases) - [Changelog](https://github.com/tox-dev/tox/blob/master/docs/changelog.rst) - [Commits](https://github.com/tox-dev/tox/compare/3.24.1...3.24.3) --- updated-dependencies: - dependency-name: tox dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements/tox.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/tox.txt b/requirements/tox.txt index e5be67511..bba4a6c56 100644 --- a/requirements/tox.txt +++ b/requirements/tox.txt @@ -1,3 +1,3 @@ -r ./coverage.txt -tox==3.24.1 +tox==3.24.3 tox-gh-actions==2.6.0 From 15d793a8e1bdf003e103ca24beabbcc8e476859f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Jul 2021 17:03:36 +0000 Subject: [PATCH 03/29] Bump black from 21.6b0 to 21.7b0 in /requirements Bumps [black](https://github.com/psf/black) from 21.6b0 to 21.7b0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/commits) --- updated-dependencies: - dependency-name: black dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements/lint.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/lint.txt b/requirements/lint.txt index 97f15ba67..22572739c 100644 --- a/requirements/lint.txt +++ b/requirements/lint.txt @@ -1,3 +1,3 @@ -black==21.6b0 +black==21.7b0 flake8==3.9.2 isort==5.9.2 From 4640fb66ee235a3c25ce9b3e8d9f081c96fed041 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Jul 2021 17:04:17 +0000 Subject: [PATCH 04/29] Bump sphinx from 4.1.0 to 4.1.2 in /requirements Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.1.0 to 4.1.2. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.1.0...v4.1.2) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements/docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/docs.txt b/requirements/docs.txt index b0315fc3d..f2b1eaa9c 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -1 +1 @@ -Sphinx==4.1.0 +Sphinx==4.1.2 From 9dd86076c6a1c982c568448f638bafcf2fb58f07 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Jul 2021 17:03:50 +0000 Subject: [PATCH 05/29] Bump isort from 5.9.2 to 5.9.3 in /requirements Bumps [isort](https://github.com/pycqa/isort) from 5.9.2 to 5.9.3. - [Release notes](https://github.com/pycqa/isort/releases) - [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md) - [Commits](https://github.com/pycqa/isort/compare/5.9.2...5.9.3) --- updated-dependencies: - dependency-name: isort dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements/lint.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/lint.txt b/requirements/lint.txt index 22572739c..fb4025d5c 100644 --- a/requirements/lint.txt +++ b/requirements/lint.txt @@ -1,3 +1,3 @@ black==21.7b0 flake8==3.9.2 -isort==5.9.2 +isort==5.9.3 From bc9ee726dcf2b7368005da9d547d6d4324fbff36 Mon Sep 17 00:00:00 2001 From: Tim Schilling Date: Fri, 27 Aug 2021 20:04:06 -0500 Subject: [PATCH 06/29] Remove previous code of conduct. The jazzband code of conduct is specified in CONTRIBUTING.rst and is available at https://jazzband.co/about/conduct This should complete jazzband/help#192 --- CODE_OF_CONDUCT.md | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 6855cd913..000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,46 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at ross@cadre.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] - -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ From c74332d888608639c3981f265e1941cbeb1ea797 Mon Sep 17 00:00:00 2001 From: Jim King Date: Wed, 1 Sep 2021 07:42:50 -0400 Subject: [PATCH 07/29] Ensure that latest() is idempotent. (#862) --- CHANGES.rst | 1 + simple_history/models.py | 2 +- ...thcustomattrforeignkey_options_and_more.py | 32 +++++++++++++++++++ simple_history/tests/tests/test_models.py | 2 +- 4 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 simple_history/registry_tests/migration_test_app/migrations/0003_alter_historicalmodelwithcustomattrforeignkey_options_and_more.py diff --git a/CHANGES.rst b/CHANGES.rst index c552896b1..3335e2df6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,7 @@ Unreleased - Fixed bug where serializer of djangorestframework crashed if used with ``OrderingFilter`` (gh-821) - Fixed `make format` so it works by using tox (gh-859) +- Fixed bug where latest() is not idempotent for identical ``history_date`` records (gh-861) 3.0.0 (2021-04-16) ------------------ diff --git a/simple_history/models.py b/simple_history/models.py index ffdac118b..fe750e80a 100644 --- a/simple_history/models.py +++ b/simple_history/models.py @@ -458,7 +458,7 @@ def get_meta_options(self, model): """ meta_fields = { "ordering": ("-history_date", "-history_id"), - "get_latest_by": "history_date", + "get_latest_by": ("history_date", "history_id"), } if self.user_set_verbose_name: name = self.user_set_verbose_name diff --git a/simple_history/registry_tests/migration_test_app/migrations/0003_alter_historicalmodelwithcustomattrforeignkey_options_and_more.py b/simple_history/registry_tests/migration_test_app/migrations/0003_alter_historicalmodelwithcustomattrforeignkey_options_and_more.py new file mode 100644 index 000000000..0f40470bc --- /dev/null +++ b/simple_history/registry_tests/migration_test_app/migrations/0003_alter_historicalmodelwithcustomattrforeignkey_options_and_more.py @@ -0,0 +1,32 @@ +# Generated by Django 4.0.dev20210802171549 on 2021-08-11 11:05 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "migration_test_app", + "0002_historicalmodelwithcustomattrforeignkey_modelwithcustomattrforeignkey", + ), + ] + + operations = [ + migrations.AlterModelOptions( + name="historicalmodelwithcustomattrforeignkey", + options={ + "get_latest_by": ("history_date", "history_id"), + "ordering": ("-history_date", "-history_id"), + "verbose_name": "historical model with custom attr foreign key", + }, + ), + migrations.AlterModelOptions( + name="historicalyar", + options={ + "get_latest_by": ("history_date", "history_id"), + "ordering": ("-history_date", "-history_id"), + "verbose_name": "historical yar", + }, + ), + ] diff --git a/simple_history/tests/tests/test_models.py b/simple_history/tests/tests/test_models.py index 897688915..bef745819 100644 --- a/simple_history/tests/tests/test_models.py +++ b/simple_history/tests/tests/test_models.py @@ -1219,7 +1219,7 @@ def test_sameinstant(self): self.write_history( [{"pk": 1, "history_date": yesterday}, {"pk": 2, "history_date": yesterday}] ) - assert HistoricalPoll.objects.latest().pk == 1 + assert HistoricalPoll.objects.latest().pk == 2 class TestMissingOneToOne(TestCase): From f95225aed3ee8ec4ddc26c2e57c8ddf2462b0e90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Sep 2021 22:25:13 +0000 Subject: [PATCH 08/29] Bump black from 21.7b0 to 21.8b0 in /requirements Bumps [black](https://github.com/psf/black) from 21.7b0 to 21.8b0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/commits) --- updated-dependencies: - dependency-name: black dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements/lint.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/lint.txt b/requirements/lint.txt index fb4025d5c..b059a72c4 100644 --- a/requirements/lint.txt +++ b/requirements/lint.txt @@ -1,3 +1,3 @@ -black==21.7b0 +black==21.8b0 flake8==3.9.2 isort==5.9.3 From 9195e561dce63c534e674167b65f31a072a72801 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Sep 2021 17:06:00 +0000 Subject: [PATCH 09/29] Bump black from 21.8b0 to 21.9b0 in /requirements Bumps [black](https://github.com/psf/black) from 21.8b0 to 21.9b0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/commits) --- updated-dependencies: - dependency-name: black dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements/lint.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/lint.txt b/requirements/lint.txt index b059a72c4..c8fd24694 100644 --- a/requirements/lint.txt +++ b/requirements/lint.txt @@ -1,3 +1,3 @@ -black==21.8b0 +black==21.9b0 flake8==3.9.2 isort==5.9.3 From 3b97e7e858553752f503553cc273abab0688849e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Sep 2021 17:05:12 +0000 Subject: [PATCH 10/29] Bump tox-gh-actions from 2.6.0 to 2.7.0 in /requirements Bumps [tox-gh-actions](https://github.com/ymyzk/tox-gh-actions) from 2.6.0 to 2.7.0. - [Release notes](https://github.com/ymyzk/tox-gh-actions/releases) - [Commits](https://github.com/ymyzk/tox-gh-actions/compare/v2.6.0...v2.7.0) --- updated-dependencies: - dependency-name: tox-gh-actions dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/tox.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/tox.txt b/requirements/tox.txt index bba4a6c56..dd57512c1 100644 --- a/requirements/tox.txt +++ b/requirements/tox.txt @@ -1,3 +1,3 @@ -r ./coverage.txt tox==3.24.3 -tox-gh-actions==2.6.0 +tox-gh-actions==2.7.0 From 8993c8a3d40176bc988c62a049e2efd5df90b937 Mon Sep 17 00:00:00 2001 From: ddusi <58924238+ddusi@users.noreply.github.com> Date: Thu, 16 Sep 2021 11:45:43 +0900 Subject: [PATCH 11/29] Fix pk error (#806) * fix update_change_reason Func for PrimaryKey (cherry picked from commit 8b625046854df81e15d114156d8a440483561e74) * Add authors (cherry picked from commit c4bb9b9a1a81c322620054abe0bc3df2208229d3) * Add changes (cherry picked from commit e308eb69d9ba9f91d56f0da21ecd3c923c349154) Co-authored-by: Jim King --- AUTHORS.rst | 1 + CHANGES.rst | 1 + simple_history/utils.py | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 75e1de8a0..e27d7d7fe 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -115,6 +115,7 @@ Authors - `Alex Todorov `_ - David Smith (`smithdc1 `_) - Shi Han Ng (`shihanng `_) +- `ddusi `_ Background ========== diff --git a/CHANGES.rst b/CHANGES.rst index 3335e2df6..c23a15a81 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,7 @@ Changes Unreleased ---------- +- Fixed `update_change_reason` in pk (gh-806) - Fixed bug where serializer of djangorestframework crashed if used with ``OrderingFilter`` (gh-821) - Fixed `make format` so it works by using tox (gh-859) - Fixed bug where latest() is not idempotent for identical ``history_date`` records (gh-861) diff --git a/simple_history/utils.py b/simple_history/utils.py index d2106faa7..959189915 100644 --- a/simple_history/utils.py +++ b/simple_history/utils.py @@ -11,7 +11,7 @@ def update_change_reason(instance, reason): attrs = {} model = type(instance) - manager = instance if instance.id is not None else model + manager = instance if instance.pk is not None else model history = get_history_manager_for_model(manager) history_fields = [field.attname for field in history.model._meta.fields] for field in instance._meta.fields: From c5943f4b1031f5577551c27f2a70c06fb202a1de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Sep 2021 17:04:47 +0000 Subject: [PATCH 12/29] Bump tox from 3.24.3 to 3.24.4 in /requirements Bumps [tox](https://github.com/tox-dev/tox) from 3.24.3 to 3.24.4. - [Release notes](https://github.com/tox-dev/tox/releases) - [Changelog](https://github.com/tox-dev/tox/blob/master/docs/changelog.rst) - [Commits](https://github.com/tox-dev/tox/compare/3.24.3...3.24.4) --- updated-dependencies: - dependency-name: tox dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements/tox.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/tox.txt b/requirements/tox.txt index dd57512c1..6e9dee6ce 100644 --- a/requirements/tox.txt +++ b/requirements/tox.txt @@ -1,3 +1,3 @@ -r ./coverage.txt -tox==3.24.3 +tox==3.24.4 tox-gh-actions==2.7.0 From e3065b95639fb82252dc289fb55c6760677678b2 Mon Sep 17 00:00:00 2001 From: Jake Howard Date: Thu, 16 Sep 2021 19:48:01 +0100 Subject: [PATCH 13/29] Optimise next prev record (#791) * Optimise `next_record` and `prev_record` Previously they called `.instance`, which was unnecessary as only the `_meta` was needed. By using the class itself, the need for an extra query is removed in cases where fields are excluded. * Add query assertions to other cases to reduce query creep * Update test names so they run, and fix them Methods are only run as test cases if they start with `test_` * Add self to `AUTHORS.rst` * Remove unnecessary Q object * Add changelog entry for #791 * Ensure history is still filtered by the correct model This means history for another model doesn't appear incorrectly. Co-authored-by: Jim King --- AUTHORS.rst | 1 + CHANGES.rst | 1 + simple_history/manager.py | 10 ++-- simple_history/models.py | 10 ++-- simple_history/tests/tests/test_models.py | 62 +++++++++++++++++------ simple_history/utils.py | 19 ++++++- 6 files changed, 76 insertions(+), 27 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index e27d7d7fe..87a93b75c 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -51,6 +51,7 @@ Authors - Hernan Esteves (`sevetseh28 `_) - Hielke Walinga (`hwalinga `_) - Jack Cushman (`jcushman `_) +- Jake Howard (`RealOrangeOne `_) - James Muranga (`jamesmura `_) - James Pulec - Jesse Shapiro diff --git a/CHANGES.rst b/CHANGES.rst index c23a15a81..7e01339fd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,7 @@ Changes Unreleased ---------- +- Fixed ``prev_record`` and ``next_record`` performance when using ``excluded_fields`` (gh-791) - Fixed `update_change_reason` in pk (gh-806) - Fixed bug where serializer of djangorestframework crashed if used with ``OrderingFilter`` (gh-821) - Fixed `make format` so it works by using tox (gh-859) diff --git a/simple_history/manager.py b/simple_history/manager.py index f720a3c87..24d19365a 100755 --- a/simple_history/manager.py +++ b/simple_history/manager.py @@ -2,7 +2,10 @@ from django.db.models import OuterRef, Subquery from django.utils import timezone -from simple_history.utils import get_change_reason_from_object +from simple_history.utils import ( + get_app_model_primary_key_name, + get_change_reason_from_object, +) class HistoryDescriptor: @@ -29,10 +32,7 @@ def get_queryset(self): if self.instance is None: return qs - if isinstance(self.instance._meta.pk, models.ForeignKey): - key_name = self.instance._meta.pk.name + "_id" - else: - key_name = self.instance._meta.pk.name + key_name = get_app_model_primary_key_name(self.instance) return self.get_super_queryset().filter(**{key_name: self.instance.pk}) def most_recent(self): diff --git a/simple_history/models.py b/simple_history/models.py index fe750e80a..ee17c2d1c 100644 --- a/simple_history/models.py +++ b/simple_history/models.py @@ -9,7 +9,7 @@ from django.contrib.auth import get_user_model from django.core.exceptions import ObjectDoesNotExist from django.db import models -from django.db.models import ManyToManyField, Q +from django.db.models import ManyToManyField from django.db.models.fields.proxy import OrderWrt from django.forms.models import model_to_dict from django.urls import reverse @@ -399,9 +399,9 @@ def get_next_record(self): """ Get the next history record for the instance. `None` if last. """ - history = utils.get_history_manager_for_model(self.instance) + history = utils.get_history_manager_from_history(self) return ( - history.filter(Q(history_date__gt=self.history_date)) + history.filter(history_date__gt=self.history_date) .order_by("history_date") .first() ) @@ -410,9 +410,9 @@ def get_prev_record(self): """ Get the previous history record for the instance. `None` if first. """ - history = utils.get_history_manager_for_model(self.instance) + history = utils.get_history_manager_from_history(self) return ( - history.filter(Q(history_date__lt=self.history_date)) + history.filter(history_date__lt=self.history_date) .order_by("history_date") .last() ) diff --git a/simple_history/tests/tests/test_models.py b/simple_history/tests/tests/test_models.py index bef745819..794b5f4aa 100644 --- a/simple_history/tests/tests/test_models.py +++ b/simple_history/tests/tests/test_models.py @@ -690,9 +690,12 @@ def test_get_prev_record(self): third_record = self.poll.history.filter(question="eh?").get() fourth_record = self.poll.history.filter(question="one more?").get() - self.assertRecordsMatch(second_record.prev_record, first_record) - self.assertRecordsMatch(third_record.prev_record, second_record) - self.assertRecordsMatch(fourth_record.prev_record, third_record) + with self.assertNumQueries(1): + self.assertRecordsMatch(second_record.prev_record, first_record) + with self.assertNumQueries(1): + self.assertRecordsMatch(third_record.prev_record, second_record) + with self.assertNumQueries(1): + self.assertRecordsMatch(fourth_record.prev_record, third_record) def test_get_prev_record_none_if_only(self): self.assertEqual(self.poll.history.count(), 1) @@ -705,14 +708,26 @@ def test_get_prev_record_none_if_earliest(self): first_record = self.poll.history.filter(question="what's up?").get() self.assertIsNone(first_record.prev_record) - def get_prev_record_with_custom_manager_name(self): - instance = CustomManagerNameModel(name="Test name 1") - instance.save() + def test_get_prev_record_with_custom_manager_name(self): + instance = CustomManagerNameModel.objects.create(name="Test name 1") instance.name = "Test name 2" - first_record = instance.log.filter(name="Test name").get() + instance.save() + first_record = instance.log.filter(name="Test name 1").get() second_record = instance.log.filter(name="Test name 2").get() - self.assertRecordsMatch(second_record.prev_record, first_record) + self.assertEqual(second_record.prev_record, first_record) + + def test_get_prev_record_with_excluded_field(self): + instance = PollWithExcludeFields.objects.create( + question="what's up?", pub_date=today + ) + instance.question = "ask questions?" + instance.save() + first_record = instance.history.filter(question="what's up?").get() + second_record = instance.history.filter(question="ask questions?").get() + + with self.assertNumQueries(1): + self.assertRecordsMatch(second_record.prev_record, first_record) def test_get_next_record(self): self.poll.question = "ask questions?" @@ -727,9 +742,12 @@ def test_get_next_record(self): fourth_record = self.poll.history.filter(question="one more?").get() self.assertIsNone(fourth_record.next_record) - self.assertRecordsMatch(first_record.next_record, second_record) - self.assertRecordsMatch(second_record.next_record, third_record) - self.assertRecordsMatch(third_record.next_record, fourth_record) + with self.assertNumQueries(1): + self.assertRecordsMatch(first_record.next_record, second_record) + with self.assertNumQueries(1): + self.assertRecordsMatch(second_record.next_record, third_record) + with self.assertNumQueries(1): + self.assertRecordsMatch(third_record.next_record, fourth_record) def test_get_next_record_none_if_only(self): self.assertEqual(self.poll.history.count(), 1) @@ -742,14 +760,26 @@ def test_get_next_record_none_if_most_recent(self): recent_record = self.poll.history.filter(question="ask questions?").get() self.assertIsNone(recent_record.next_record) - def get_next_record_with_custom_manager_name(self): - instance = CustomManagerNameModel(name="Test name 1") - instance.save() + def test_get_next_record_with_custom_manager_name(self): + instance = CustomManagerNameModel.objects.create(name="Test name 1") instance.name = "Test name 2" - first_record = instance.log.filter(name="Test name").get() + instance.save() + first_record = instance.log.filter(name="Test name 1").get() second_record = instance.log.filter(name="Test name 2").get() - self.assertRecordsMatch(first_record.next_record, second_record) + self.assertEqual(first_record.next_record, second_record) + + def test_get_next_record_with_excluded_field(self): + instance = PollWithExcludeFields.objects.create( + question="what's up?", pub_date=today + ) + instance.question = "ask questions?" + instance.save() + first_record = instance.history.filter(question="what's up?").get() + second_record = instance.history.filter(question="ask questions?").get() + + with self.assertNumQueries(1): + self.assertRecordsMatch(first_record.next_record, second_record) class CreateHistoryModelTests(unittest.TestCase): diff --git a/simple_history/utils.py b/simple_history/utils.py index 959189915..d1748db3f 100644 --- a/simple_history/utils.py +++ b/simple_history/utils.py @@ -2,7 +2,7 @@ import django from django.db import transaction -from django.db.models import ManyToManyField +from django.db.models import ForeignKey, ManyToManyField from django.forms.models import model_to_dict from simple_history.exceptions import AlternativeManagerError, NotHistoricalModelError @@ -40,11 +40,28 @@ def get_history_manager_for_model(model): return getattr(model, manager_name) +def get_history_manager_from_history(history_instance): + """ + Return the history manager, based on an existing history instance. + """ + key_name = get_app_model_primary_key_name(history_instance.instance_type) + return get_history_manager_for_model(history_instance.instance_type).filter( + **{key_name: getattr(history_instance, key_name)} + ) + + def get_history_model_for_model(model): """Return the history model for a given app model.""" return get_history_manager_for_model(model).model +def get_app_model_primary_key_name(model): + """Return the primary key name for a given app model.""" + if isinstance(model._meta.pk, ForeignKey): + return model._meta.pk.name + "_id" + return model._meta.pk.name + + def bulk_create_with_history( objs, model, From 2ea9fc9df5370a3ca18106d64518bc1e58f75deb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Sep 2021 17:05:08 +0000 Subject: [PATCH 14/29] Bump sphinx from 4.1.2 to 4.2.0 in /requirements Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.1.2 to 4.2.0. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.1.2...v4.2.0) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/docs.txt b/requirements/docs.txt index f2b1eaa9c..94c0e1037 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -1 +1 @@ -Sphinx==4.1.2 +Sphinx==4.2.0 From a721cb82cddc225dd4dfdc21a314998c566179ee Mon Sep 17 00:00:00 2001 From: Robert Durica Date: Sat, 18 Sep 2021 22:58:29 +0200 Subject: [PATCH 15/29] Added: Czech translations --- .../locale/cs_CZ/LC_MESSAGES/django.mo | Bin 0 -> 2178 bytes .../locale/cs_CZ/LC_MESSAGES/django.po | 134 ++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 simple_history/locale/cs_CZ/LC_MESSAGES/django.mo create mode 100644 simple_history/locale/cs_CZ/LC_MESSAGES/django.po diff --git a/simple_history/locale/cs_CZ/LC_MESSAGES/django.mo b/simple_history/locale/cs_CZ/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..d783e6b12b2440ccd547c3d9fba7fb3557a1253d GIT binary patch literal 2178 zcmZvcO>7%Q6vw9!3YhX$pp*}xmx#m(Wu2r{qH^L;V>?ZVouv7a5)O21PwY*0cdVUR z2m8voNFX5;^%Mz^dWd@I3904aSmMA5&h*+V2hDqQI{IDE?Dso30X_oX121B;*T5WT z&e;TC0y$_j`ry~l_9bX^4dO7PYXUU7a-iAw259U*1((4J_zZXtG zk3bWLKjQvl5I^=eUU~2=HeUc2K;y@^;6?BU(D?Hcm;!$Vhrly9{0;D3@Hub>G&(;4 z2f>@5N!iP2X545!hh`qLpLvWwy~phJD%vaYz>AZ)F0AXC-1aU+d6BC~c zXuSt14s(K#h8j(Lj-Onedo@9Gw$jpXvAA20x@j6#y&+$4CB3*;?UBmHMoJ}!QuyclzrxLSB$CSamChf{_totOhdz?2*j8g zqCKvt5+E4t9 zxx7VTTo4*w<8~Y_L`IqfS@TtEBT8MGmb1Bq?DYa|6joN}mlhK%q9J{4l~m2HS|0~B zWv$6HC6<>gM8h`ewoD?^v{}fmSfwQ^pQX8_bZP$5*3x1jQRGTnYd-gs!*L_iG`n6b z(qgt$py@&>J72t(&F5DNtE)3u>eT{X6V{sGZkmeO#p~;ktqs8ZXyx~CYl!>9QgwCw5&01Ic>x^65!nX=uMOJOEmZqy^ zTPK#Cz~_!ND}7g`$!mmNmANuS(Pw7VqfCY-X=H>v8pa2D6JzudO;DOJIg_Ew(d2Z7 zuAE*r8Ll#hgD@mxc^Q@;OeNwHum!co6TAg^=SePC`>)92mqu>wWDOO5Wns8;H--8nS%+qO~Db&0w*#k;G6 z6x-s>?tbS`vjvo;r#lDTeH@~j5|%g@*$>?Dqg!0DRhPFr2N;_R|8HzmwsgC9S@(YD zpz3w*gyp=8e}H-g^=>(Ke5-Anf`-qVwtkaFCI-0(XG8Y{y9wvt9=(+puFqgQwCKwPBsSmH?=*6W_C@ZLnUcbJa< zDf4p6k<|H7HI8m~4sQ#*6L_%*C Wqa5#u9YWxdZfI_MvKjOO%>DuBTUP!6 literal 0 HcmV?d00001 diff --git a/simple_history/locale/cs_CZ/LC_MESSAGES/django.po b/simple_history/locale/cs_CZ/LC_MESSAGES/django.po new file mode 100644 index 000000000..ead49dfbe --- /dev/null +++ b/simple_history/locale/cs_CZ/LC_MESSAGES/django.po @@ -0,0 +1,134 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2021-09-18 21:54+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n " +"<= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" +#: simple_history/admin.py:102 +#, python-format +msgid "View history: %s" +msgstr "Zobrazit historii: %s" + +#: simple_history/admin.py:104 +#, python-format +msgid "Change history: %s" +msgstr "Historie změn: %s" + +#: simple_history/admin.py:110 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "%(name)s \"%(obj)s\" bylo úspěšně změněno." + +#: simple_history/admin.py:116 +msgid "You may edit it again below" +msgstr "Níže jej můžete znovu upravit" + +#: simple_history/admin.py:216 +#, python-format +msgid "View %s" +msgstr "Zobrazit %s" + +#: simple_history/admin.py:218 +#, python-format +msgid "Revert %s" +msgstr "Vrátit změny: %s" + +#: simple_history/models.py:433 +msgid "Created" +msgstr "Vytvořeno" + +#: simple_history/models.py:433 +msgid "Changed" +msgstr "Změněno" + +#: simple_history/models.py:433 +msgid "Deleted" +msgstr "Smazáno" + +#: simple_history/templates/simple_history/_object_history_list.html:9 +msgid "Object" +msgstr "Objekt" + +#: simple_history/templates/simple_history/_object_history_list.html:13 +msgid "Date/time" +msgstr "Datum/čas" + +#: simple_history/templates/simple_history/_object_history_list.html:14 +msgid "Comment" +msgstr "Komentář" + +#: simple_history/templates/simple_history/_object_history_list.html:15 +msgid "Changed by" +msgstr "Změnil" + +#: simple_history/templates/simple_history/_object_history_list.html:16 +msgid "Change reason" +msgstr "Důvod změny" + +#: simple_history/templates/simple_history/_object_history_list.html:37 +msgid "None" +msgstr "Žádné" + +#: simple_history/templates/simple_history/object_history.html:11 +msgid "" +"Choose a date from the list below to revert to a previous version of this " +"object." +msgstr "Vyberte datum ze seznamu níže a vraťte se k předchozí verzi tohoto " +"objektu." + +#: simple_history/templates/simple_history/object_history.html:16 +msgid "This object doesn't have a change history." +msgstr "Tento objekt nemá historii změn." + +#: simple_history/templates/simple_history/object_history_form.html:7 +msgid "Home" +msgstr "Domů" + +#: simple_history/templates/simple_history/object_history_form.html:11 +msgid "History" +msgstr "Historie" + +#: simple_history/templates/simple_history/object_history_form.html:12 +#, python-format +msgid "View %(verbose_name)s" +msgstr "Zobrazit %(verbose_name)s" + +#: simple_history/templates/simple_history/object_history_form.html:12 +#, python-format +msgid "Revert %(verbose_name)s" +msgstr "Vrátit %(verbose_name)s" + +#: simple_history/templates/simple_history/object_history_form.html:25 +msgid "" +"Press the 'Revert' button below to revert to this version of the object. " +msgstr "Stisknutím tlačítka 'Vrátit změny' se vrátíte k této verzi objektu." + +#: simple_history/templates/simple_history/object_history_form.html:25 +msgid "Press the 'Change History' button below to edit the history." +msgstr "Chcete-li historii upravit, stiskněte tlačítko 'Změnit historii'" + +#: simple_history/templates/simple_history/submit_line.html:4 +msgid "Revert" +msgstr "Vrátit změny" + +#: simple_history/templates/simple_history/submit_line.html:6 +msgid "Change History" +msgstr "Historie změn" + +#: simple_history/templates/simple_history/submit_line.html:7 +msgid "Close" +msgstr "Zavřít" From 650213f770013254a57f6a851f3fb3af7091813c Mon Sep 17 00:00:00 2001 From: Robert Durica Date: Mon, 20 Sep 2021 19:58:49 +0200 Subject: [PATCH 16/29] Changed: Translation header for cs --- .../locale/cs_CZ/LC_MESSAGES/django.mo | Bin 2178 -> 2120 bytes .../locale/cs_CZ/LC_MESSAGES/django.po | 12 ++++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/simple_history/locale/cs_CZ/LC_MESSAGES/django.mo b/simple_history/locale/cs_CZ/LC_MESSAGES/django.mo index d783e6b12b2440ccd547c3d9fba7fb3557a1253d..2f6a5fb2828badc1034cfdc6e792dcd6e3e931e7 100644 GIT binary patch delta 304 zcmZn?JRvaQOML_*14ADR0|Ore1H*Gx1_mJ@{S8R-0%=Y*AO_OnK$;y$D+1{NAgu|c zIe>H(kmdr?%}}}zO3#GKFM-l)fi%!ahHX&(9v}@w3`f}*!WdkDj59!PqB9KYbd2hbJmzkQj<2IE(QsqP!mY%T<{MN(ZSup zf1s}7T34N1TwNW+Rlk9NaPYau`|{o!FJ3NPe-CdYLas`L%o9Q?G9k-w2hPDJoPmdM z3ZB3=JcHBl5i0Pha9sFS^gjweVHvrvqW*z+(eaTn-GnC054nS@@B!urJ;N$| zh3inl_BFTxXJH+#zynx;r=u6?ePW@_nkH*;>T}mK9Q*dZv^6~9%vCK%)fwG)G|SjN zc5JSQARDNiG>Ef)Fi293?sbKrHnTXbaf=zE!F1i_o>xaSjxK^IR6AjCu2I44RySW# g6Qa@YRg*OO&uCP!42!FNm}b2s*68ktmNzH=0I^?15&!@I diff --git a/simple_history/locale/cs_CZ/LC_MESSAGES/django.po b/simple_history/locale/cs_CZ/LC_MESSAGES/django.po index ead49dfbe..c67a760e9 100644 --- a/simple_history/locale/cs_CZ/LC_MESSAGES/django.po +++ b/simple_history/locale/cs_CZ/LC_MESSAGES/django.po @@ -3,21 +3,21 @@ # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # -#, fuzzy msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" +"Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-09-18 21:54+0200\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" +"PO-Revision-Date: 2021-09-20 19:50+0200\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: cs\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n " "<= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" + #: simple_history/admin.py:102 #, python-format msgid "View history: %s" From 2ffc483c39f8594243b57855c280da10be7a4611 Mon Sep 17 00:00:00 2001 From: Jake Howard Date: Wed, 6 Jan 2021 16:25:13 +0000 Subject: [PATCH 17/29] Simply check the history attributes rather than serializing. This stops unnecesary queries from `model_to_dict` for M2M values --- simple_history/models.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/simple_history/models.py b/simple_history/models.py index ee17c2d1c..5d6cb62f4 100644 --- a/simple_history/models.py +++ b/simple_history/models.py @@ -11,7 +11,6 @@ from django.db import models from django.db.models import ManyToManyField from django.db.models.fields.proxy import OrderWrt -from django.forms.models import model_to_dict from django.urls import reverse from django.utils import timezone from django.utils.encoding import smart_str @@ -594,20 +593,26 @@ def diff_against(self, old_history, excluded_fields=None): ) ) if excluded_fields is None: - excluded_fields = [] + excluded_fields = set() + + excluded_fields = set(excluded_fields).union(self._history_excluded_fields) + + fields = { + f.name + for f in old_history.instance_type._meta.fields + if f.name not in excluded_fields + } + changes = [] changed_fields = [] - old_values = model_to_dict(old_history.instance) - current_values = model_to_dict(self.instance) - for field, new_value in current_values.items(): - if field in excluded_fields: - continue - if field in old_values: - old_value = old_values[field] - if old_value != new_value: - change = ModelChange(field, old_value, new_value) - changes.append(change) - changed_fields.append(field) + + for field in fields: + old_value = getattr(old_history, field) + current_value = getattr(self, field) + + if old_value != current_value: + changes.append(ModelChange(field, old_value, current_value)) + changed_fields.append(field) return ModelDelta(changes, changed_fields, old_history, self) From 68497f32eac209624389ef60c9fefd71d5d0f29f Mon Sep 17 00:00:00 2001 From: Jake Howard Date: Wed, 6 Jan 2021 16:33:40 +0000 Subject: [PATCH 18/29] Add support for `included_fields` when diffing --- simple_history/models.py | 11 ++++------- simple_history/tests/tests/test_models.py | 13 +++++++++++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/simple_history/models.py b/simple_history/models.py index 5d6cb62f4..f87af7849 100644 --- a/simple_history/models.py +++ b/simple_history/models.py @@ -585,7 +585,7 @@ def __get__(self, instance, owner): class HistoricalChanges: - def diff_against(self, old_history, excluded_fields=None): + def diff_against(self, old_history, excluded_fields=None, included_fields=None): if not isinstance(old_history, type(self)): raise TypeError( ("unsupported type(s) for diffing: " "'{}' and '{}'").format( @@ -595,13 +595,10 @@ def diff_against(self, old_history, excluded_fields=None): if excluded_fields is None: excluded_fields = set() - excluded_fields = set(excluded_fields).union(self._history_excluded_fields) + if included_fields is None: + included_fields = {f.name for f in old_history.instance_type._meta.fields} - fields = { - f.name - for f in old_history.instance_type._meta.fields - if f.name not in excluded_fields - } + fields = set(included_fields).difference(excluded_fields) changes = [] changed_fields = [] diff --git a/simple_history/tests/tests/test_models.py b/simple_history/tests/tests/test_models.py index 794b5f4aa..f088e852a 100644 --- a/simple_history/tests/tests/test_models.py +++ b/simple_history/tests/tests/test_models.py @@ -668,6 +668,19 @@ def test_history_diff_with_excluded_fields(self): self.assertEqual(delta.changed_fields, []) self.assertEqual(delta.changes, []) + def test_history_diff_with_included_fields(self): + p = Poll.objects.create(question="what's up?", pub_date=today) + p.question = "what's up, man?" + p.save() + new_record, old_record = p.history.all() + delta = new_record.diff_against(old_record, included_fields=[]) + self.assertEqual(delta.changed_fields, []) + self.assertEqual(delta.changes, []) + + delta = new_record.diff_against(old_record, included_fields=["question"]) + self.assertEqual(delta.changed_fields, ["question"]) + self.assertEqual(len(delta.changes), 1) + class GetPrevRecordAndNextRecordTestCase(TestCase): def assertRecordsMatch(self, record_a, record_b): From 677aa51cb1f763b246f76a0fe61ba4c1f410f756 Mon Sep 17 00:00:00 2001 From: Jake Howard Date: Wed, 6 Jan 2021 16:53:53 +0000 Subject: [PATCH 19/29] Add `assertNumQueries` assertions to ensure no queries are run Queries are always run when using a base model for some reason. --- simple_history/tests/tests/test_models.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/simple_history/tests/tests/test_models.py b/simple_history/tests/tests/test_models.py index f088e852a..41a47ca93 100644 --- a/simple_history/tests/tests/test_models.py +++ b/simple_history/tests/tests/test_models.py @@ -623,7 +623,8 @@ def test_history_diff_includes_changed_fields(self): p.question = "what's up, man?" p.save() new_record, old_record = p.history.all() - delta = new_record.diff_against(old_record) + with self.assertNumQueries(0): + delta = new_record.diff_against(old_record) expected_change = ModelChange("question", "what's up?", "what's up, man") self.assertEqual(delta.changed_fields, ["question"]) self.assertEqual(delta.old_record, old_record) @@ -635,7 +636,8 @@ def test_history_diff_does_not_include_unchanged_fields(self): p.question = "what's up, man?" p.save() new_record, old_record = p.history.all() - delta = new_record.diff_against(old_record) + with self.assertNumQueries(0): + delta = new_record.diff_against(old_record) self.assertNotIn("pub_date", delta.changed_fields) def test_history_diff_includes_changed_fields_of_base_model(self): @@ -644,7 +646,9 @@ def test_history_diff_includes_changed_fields_of_base_model(self): r.name = "DonnutsKing" r.save() new_record, old_record = r.history.all() - delta = new_record.diff_against(old_record) + # Two queries due to base lookup + with self.assertNumQueries(2): + delta = new_record.diff_against(old_record) expected_change = ModelChange("name", "McDonna", "DonnutsKing") self.assertEqual(delta.changed_fields, ["name"]) self.assertEqual(delta.old_record, old_record) @@ -664,7 +668,8 @@ def test_history_diff_with_excluded_fields(self): p.question = "what's up, man?" p.save() new_record, old_record = p.history.all() - delta = new_record.diff_against(old_record, excluded_fields=("question",)) + with self.assertNumQueries(0): + delta = new_record.diff_against(old_record, excluded_fields=("question",)) self.assertEqual(delta.changed_fields, []) self.assertEqual(delta.changes, []) @@ -673,11 +678,13 @@ def test_history_diff_with_included_fields(self): p.question = "what's up, man?" p.save() new_record, old_record = p.history.all() - delta = new_record.diff_against(old_record, included_fields=[]) + with self.assertNumQueries(0): + delta = new_record.diff_against(old_record, included_fields=[]) self.assertEqual(delta.changed_fields, []) self.assertEqual(delta.changes, []) - delta = new_record.diff_against(old_record, included_fields=["question"]) + with self.assertNumQueries(0): + delta = new_record.diff_against(old_record, included_fields=["question"]) self.assertEqual(delta.changed_fields, ["question"]) self.assertEqual(len(delta.changes), 1) From 09b5078f7f1230ad0215cc77c9f710afbda286a4 Mon Sep 17 00:00:00 2001 From: Jake Howard Date: Wed, 6 Jan 2021 17:12:00 +0000 Subject: [PATCH 20/29] Document new `included_fields` argument to `diff_against` --- docs/history_diffing.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/history_diffing.rst b/docs/history_diffing.rst index 2b1fc730e..3e409068c 100644 --- a/docs/history_diffing.rst +++ b/docs/history_diffing.rst @@ -20,3 +20,5 @@ This may be useful when you want to construct timelines and need to get only the delta = new_record.diff_against(old_record) for change in delta.changes: print("{} changed from {} to {}".format(change.field, change.old, change.new)) + +``diff_against`` also accepts 2 arguments ``excluded_fields`` and ``included_fields`` to either explicitly include or exclude fields from being diffed. From afd572b2b97458aa2b44ae48ef2101cb2eeae8cb Mon Sep 17 00:00:00 2001 From: Jake Howard Date: Wed, 6 Jan 2021 17:14:32 +0000 Subject: [PATCH 21/29] Update changelog with #776 changes --- CHANGES.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 7e01339fd..5e2f0311b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -9,6 +9,8 @@ Unreleased - Fixed bug where serializer of djangorestframework crashed if used with ``OrderingFilter`` (gh-821) - Fixed `make format` so it works by using tox (gh-859) - Fixed bug where latest() is not idempotent for identical ``history_date`` records (gh-861) +- Support ``included_fields`` for ``history.diff_against`` (gh-776) +- Improve performance of ``history.diff_against`` by reducing number of queries to 0 in most cases (gh-776) 3.0.0 (2021-04-16) ------------------ From bc210ac63cac44ad533b3d4eae785952d3d24074 Mon Sep 17 00:00:00 2001 From: Jake Howard Date: Tue, 12 Jan 2021 17:14:24 +0000 Subject: [PATCH 22/29] Reuse `model_to_dict` This not only means comparison is done on primitive values, but as a side effect removes the extra query for base models --- simple_history/models.py | 11 +++++++++-- simple_history/tests/tests/test_models.py | 3 +-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/simple_history/models.py b/simple_history/models.py index f87af7849..d9aea9d9d 100644 --- a/simple_history/models.py +++ b/simple_history/models.py @@ -11,6 +11,7 @@ from django.db import models from django.db.models import ManyToManyField from django.db.models.fields.proxy import OrderWrt +from django.forms.models import model_to_dict from django.urls import reverse from django.utils import timezone from django.utils.encoding import smart_str @@ -603,9 +604,15 @@ def diff_against(self, old_history, excluded_fields=None, included_fields=None): changes = [] changed_fields = [] + old_values = model_to_dict(old_history, fields=fields) + current_values = model_to_dict(self, fields=fields) + for field in fields: - old_value = getattr(old_history, field) - current_value = getattr(self, field) + try: + old_value = old_values[field] + current_value = current_values[field] + except KeyError: + continue if old_value != current_value: changes.append(ModelChange(field, old_value, current_value)) diff --git a/simple_history/tests/tests/test_models.py b/simple_history/tests/tests/test_models.py index 41a47ca93..0687f9413 100644 --- a/simple_history/tests/tests/test_models.py +++ b/simple_history/tests/tests/test_models.py @@ -646,8 +646,7 @@ def test_history_diff_includes_changed_fields_of_base_model(self): r.name = "DonnutsKing" r.save() new_record, old_record = r.history.all() - # Two queries due to base lookup - with self.assertNumQueries(2): + with self.assertNumQueries(0): delta = new_record.diff_against(old_record) expected_change = ModelChange("name", "McDonna", "DonnutsKing") self.assertEqual(delta.changed_fields, ["name"]) From 65c66b65c90ff3729dc6c96f3a61935f5a418817 Mon Sep 17 00:00:00 2001 From: Jake Howard Date: Wed, 22 Sep 2021 12:39:55 +0100 Subject: [PATCH 23/29] Fail hard if an unknown field is provided --- simple_history/models.py | 7 ++----- simple_history/tests/tests/test_models.py | 12 ++++++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/simple_history/models.py b/simple_history/models.py index d9aea9d9d..056aa6197 100644 --- a/simple_history/models.py +++ b/simple_history/models.py @@ -608,11 +608,8 @@ def diff_against(self, old_history, excluded_fields=None, included_fields=None): current_values = model_to_dict(self, fields=fields) for field in fields: - try: - old_value = old_values[field] - current_value = current_values[field] - except KeyError: - continue + old_value = old_values[field] + current_value = current_values[field] if old_value != current_value: changes.append(ModelChange(field, old_value, current_value)) diff --git a/simple_history/tests/tests/test_models.py b/simple_history/tests/tests/test_models.py index 0687f9413..d673f2833 100644 --- a/simple_history/tests/tests/test_models.py +++ b/simple_history/tests/tests/test_models.py @@ -687,6 +687,18 @@ def test_history_diff_with_included_fields(self): self.assertEqual(delta.changed_fields, ["question"]) self.assertEqual(len(delta.changes), 1) + def test_history_with_unknown_field(self): + p = Poll.objects.create(question="what's up?", pub_date=today) + p.question = "what's up, man?" + p.save() + new_record, old_record = p.history.all() + with self.assertRaises(KeyError): + with self.assertNumQueries(0): + new_record.diff_against(old_record, included_fields=["unknown_field"]) + + with self.assertNumQueries(0): + new_record.diff_against(old_record, excluded_fields=["unknown_field"]) + class GetPrevRecordAndNextRecordTestCase(TestCase): def assertRecordsMatch(self, record_a, record_b): From e7c4cf46bac0bffa456247d94590cf20a85eef16 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Sep 2021 17:04:01 +0000 Subject: [PATCH 24/29] Bump tox-gh-actions from 2.7.0 to 2.8.0 in /requirements Bumps [tox-gh-actions](https://github.com/ymyzk/tox-gh-actions) from 2.7.0 to 2.8.0. - [Release notes](https://github.com/ymyzk/tox-gh-actions/releases) - [Commits](https://github.com/ymyzk/tox-gh-actions/compare/v2.7.0...v2.8.0) --- updated-dependencies: - dependency-name: tox-gh-actions dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/tox.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/tox.txt b/requirements/tox.txt index 6e9dee6ce..fd5244b36 100644 --- a/requirements/tox.txt +++ b/requirements/tox.txt @@ -1,3 +1,3 @@ -r ./coverage.txt tox==3.24.4 -tox-gh-actions==2.7.0 +tox-gh-actions==2.8.0 From b0b998c6a9fe0b8671083f479c965a9e610a1250 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Sep 2021 17:05:08 +0000 Subject: [PATCH 25/29] Bump tox-gh-actions from 2.8.0 to 2.8.1 in /requirements Bumps [tox-gh-actions](https://github.com/ymyzk/tox-gh-actions) from 2.8.0 to 2.8.1. - [Release notes](https://github.com/ymyzk/tox-gh-actions/releases) - [Commits](https://github.com/ymyzk/tox-gh-actions/compare/v2.8.0...v2.8.1) --- updated-dependencies: - dependency-name: tox-gh-actions dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements/tox.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/tox.txt b/requirements/tox.txt index fd5244b36..01b6ba49b 100644 --- a/requirements/tox.txt +++ b/requirements/tox.txt @@ -1,3 +1,3 @@ -r ./coverage.txt tox==3.24.4 -tox-gh-actions==2.8.0 +tox-gh-actions==2.8.1 From f38a77bfc004f9752a32fa1105344c014aac7d7c Mon Sep 17 00:00:00 2001 From: nex2hex Date: Wed, 11 Aug 2021 09:22:29 -0400 Subject: [PATCH 26/29] Add index to `history_date` field to improve performance. Opt-out of the index with `SETTINGS_HISTORY_DATE_INDEX=False`. Allow history_date indexing to be disabled or composite with model pk Co-authored-by: jeking3 --- CHANGES.rst | 7 +++++ docs/historical_model.rst | 20 ++++++++++++++ simple_history/models.py | 23 ++++++++++++++-- .../migrations/0004_history_date_indexing.py | 26 +++++++++++++++++++ simple_history/tests/tests/test_index.py | 18 +++++++++++++ simple_history/tests/tests/test_models.py | 22 +++++++++++++++- 6 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 simple_history/registry_tests/migration_test_app/migrations/0004_history_date_indexing.py create mode 100644 simple_history/tests/tests/test_index.py diff --git a/CHANGES.rst b/CHANGES.rst index 5e2f0311b..a59ac9054 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,13 @@ Changes Unreleased ---------- +Upgrade Implications: + +- Run `makemigrations` after upgrading to realize the benefit of indexing changes. + +Full list of changes: + +- Added index on `history_date` column; opt-out with setting `SIMPLE_HISTORY_DATE_INDEX` (gh-565) - Fixed ``prev_record`` and ``next_record`` performance when using ``excluded_fields`` (gh-791) - Fixed `update_change_reason` in pk (gh-806) - Fixed bug where serializer of djangorestframework crashed if used with ``OrderingFilter`` (gh-821) diff --git a/docs/historical_model.rst b/docs/historical_model.rst index e89ecd740..f46ea1f32 100644 --- a/docs/historical_model.rst +++ b/docs/historical_model.rst @@ -86,6 +86,26 @@ model, will work too. my_poll.save() +Indexed ``history_date`` +------------------------ + +Many queries use ``history_date`` as a filter. The as_of queries combine this with the +original model's promary key to extract point-in-time snapshots of history. By default +the ``history_date`` field is indexed. You can control this behavior using settings.py. + +.. code-block:: python + + # disable indexing on history_date + SIMPLE_HISTORY_DATE_INDEX = False + + # enable indexing on history_date (default setting) + SIMPLE_HISTORY_DATE_INDEX = True + + # enable composite indexing on history_date and model pk (to improve as_of queries) + # the string is case-insensitive + SIMPLE_HISTORY_DATE_INDEX = "Composite" + + Custom history table name ------------------------- diff --git a/simple_history/models.py b/simple_history/models.py index 056aa6197..4856221ae 100644 --- a/simple_history/models.py +++ b/simple_history/models.py @@ -7,7 +7,7 @@ from django.conf import settings from django.contrib import admin from django.contrib.auth import get_user_model -from django.core.exceptions import ObjectDoesNotExist +from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist from django.db import models from django.db.models import ManyToManyField from django.db.models.fields.proxy import OrderWrt @@ -426,7 +426,7 @@ def get_default_history_user(instance): extra_fields = { "history_id": self._get_history_id_field(), - "history_date": models.DateTimeField(), + "history_date": models.DateTimeField(db_index=self._date_indexing is True), "history_change_reason": self._get_history_change_reason_field(), "history_type": models.CharField( max_length=1, @@ -451,6 +451,23 @@ def get_default_history_user(instance): return extra_fields + @property + def _date_indexing(self): + """False, True, or 'composite'; default is True""" + result = getattr(settings, "SIMPLE_HISTORY_DATE_INDEX", True) + valid = True + if isinstance(result, str): + result = result.lower() + if result not in ("composite",): + valid = False + elif not isinstance(result, bool): + valid = False + if not valid: + raise ImproperlyConfigured( + "SIMPLE_HISTORY_DATE_INDEX must be one of (False, True, 'Composite')" + ) + return result + def get_meta_options(self, model): """ Returns a dictionary of fields that will be added to @@ -467,6 +484,8 @@ def get_meta_options(self, model): meta_fields["verbose_name"] = name if self.app: meta_fields["app_label"] = self.app + if self._date_indexing == "composite": + meta_fields["index_together"] = (("history_date", model._meta.pk.attname),) return meta_fields def post_save(self, instance, created, using=None, **kwargs): diff --git a/simple_history/registry_tests/migration_test_app/migrations/0004_history_date_indexing.py b/simple_history/registry_tests/migration_test_app/migrations/0004_history_date_indexing.py new file mode 100644 index 000000000..fbe0f04d0 --- /dev/null +++ b/simple_history/registry_tests/migration_test_app/migrations/0004_history_date_indexing.py @@ -0,0 +1,26 @@ +# Generated by Django 4.0.dev20210811195242 on 2021-08-13 10:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "migration_test_app", + "0003_alter_historicalmodelwithcustomattrforeignkey_options_and_more", + ), + ] + + operations = [ + migrations.AlterField( + model_name="historicalmodelwithcustomattrforeignkey", + name="history_date", + field=models.DateTimeField(db_index=True), + ), + migrations.AlterField( + model_name="historicalyar", + name="history_date", + field=models.DateTimeField(db_index=True), + ), + ] diff --git a/simple_history/tests/tests/test_index.py b/simple_history/tests/tests/test_index.py new file mode 100644 index 000000000..093a3ffc5 --- /dev/null +++ b/simple_history/tests/tests/test_index.py @@ -0,0 +1,18 @@ +from django.conf import settings +from django.db import models +from django.test import TestCase, override_settings + +from simple_history.models import HistoricalRecords + + +@override_settings(SIMPLE_HISTORY_DATE_INDEX="Composite") +class HistoricalIndexTest(TestCase): + def test_has_composite_index(self): + self.assertEqual(settings.SIMPLE_HISTORY_DATE_INDEX, "Composite") + + class Foo(models.Model): + history = HistoricalRecords() + + self.assertEqual( + ("history_date", "id"), Foo.history.model._meta.index_together[0] + ) diff --git a/simple_history/tests/tests/test_models.py b/simple_history/tests/tests/test_models.py index d673f2833..b0aede5af 100644 --- a/simple_history/tests/tests/test_models.py +++ b/simple_history/tests/tests/test_models.py @@ -5,8 +5,9 @@ import django from django.apps import apps +from django.conf import settings from django.contrib.auth import get_user_model -from django.core.exceptions import ObjectDoesNotExist +from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist from django.core.files.base import ContentFile from django.db import IntegrityError, models from django.db.models.fields.proxy import OrderWrt @@ -1038,6 +1039,25 @@ def test_most_recent_nonexistant(self): poll.delete() self.assertRaises(Poll.DoesNotExist, poll.history.most_recent) + def test_date_indexing_options(self): + records = HistoricalRecords() + delattr(settings, "SIMPLE_HISTORY_DATE_INDEX") + self.assertTrue(records._date_indexing) + settings.SIMPLE_HISTORY_DATE_INDEX = False + self.assertFalse(records._date_indexing) + settings.SIMPLE_HISTORY_DATE_INDEX = "Composite" + self.assertEqual(records._date_indexing, "composite") + settings.SIMPLE_HISTORY_DATE_INDEX = "foo" + with self.assertRaises(ImproperlyConfigured): + records._date_indexing + settings.SIMPLE_HISTORY_DATE_INDEX = 42 + with self.assertRaises(ImproperlyConfigured): + records._date_indexing + settings.SIMPLE_HISTORY_DATE_INDEX = None + with self.assertRaises(ImproperlyConfigured): + records._date_indexing + delattr(settings, "SIMPLE_HISTORY_DATE_INDEX") + def test_as_of(self): poll = Poll.objects.create(question="what's up?", pub_date=today) poll.question = "how's it going?" From 2a22590614639babe678a2a0b1618cc3d73c753c Mon Sep 17 00:00:00 2001 From: Jim King Date: Wed, 29 Sep 2021 08:45:52 -0400 Subject: [PATCH 27/29] add missing changelog for gh-885 --- CHANGES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.rst b/CHANGES.rst index a59ac9054..b71a133a1 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -18,6 +18,7 @@ Full list of changes: - Fixed bug where latest() is not idempotent for identical ``history_date`` records (gh-861) - Support ``included_fields`` for ``history.diff_against`` (gh-776) - Improve performance of ``history.diff_against`` by reducing number of queries to 0 in most cases (gh-776) +- Added Czech translations (gh-885) 3.0.0 (2021-04-16) ------------------ From f5340e5412f46e2ffc84613e5024981f24f18443 Mon Sep 17 00:00:00 2001 From: Danial Erfanian Date: Mon, 4 Oct 2021 03:03:03 +0330 Subject: [PATCH 28/29] On off flag (#832) * Add SIMPLE_HISTORY_ENABLED flag * Update AUTHORS * Consider SIMPLE_HISTORY_ENABLED in bulk queries Co-authored-by: Danial Erfanian --- AUTHORS.rst | 1 + docs/querying_history.rst | 6 ++++++ simple_history/manager.py | 3 +++ simple_history/models.py | 4 ++++ simple_history/tests/tests/test_manager.py | 7 ++++++- simple_history/tests/tests/test_models.py | 10 +++++++++ simple_history/tests/tests/test_utils.py | 24 +++++++++++++++++++++- 7 files changed, 53 insertions(+), 2 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 87a93b75c..d61a0927c 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -117,6 +117,7 @@ Authors - David Smith (`smithdc1 `_) - Shi Han Ng (`shihanng `_) - `ddusi `_ +- `DanialErfanian `_ Background ========== diff --git a/docs/querying_history.rst b/docs/querying_history.rst index 011c380c0..aa1798eff 100644 --- a/docs/querying_history.rst +++ b/docs/querying_history.rst @@ -145,6 +145,12 @@ If you want to save a model without a historical record, you can use the followi poll = Poll(question='something') poll.save_without_historical_record() +Or disable history records for all models by putting following lines in your ``settings.py`` file: + +.. code-block:: python + + SIMPLE_HISTORY_ENABLED = False + Filtering data using a relationship to the model ------------------------------------------------ diff --git a/simple_history/manager.py b/simple_history/manager.py index 24d19365a..5e52414fc 100755 --- a/simple_history/manager.py +++ b/simple_history/manager.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.db import connection, models from django.db.models import OuterRef, Subquery from django.utils import timezone @@ -137,6 +138,8 @@ def bulk_history_create( If called by bulk_update_with_history, use the update boolean and save the history_type accordingly. """ + if not getattr(settings, "SIMPLE_HISTORY_ENABLED", True): + return history_type = "+" if update: diff --git a/simple_history/models.py b/simple_history/models.py index 4856221ae..0c2ac1d74 100644 --- a/simple_history/models.py +++ b/simple_history/models.py @@ -489,12 +489,16 @@ def get_meta_options(self, model): return meta_fields def post_save(self, instance, created, using=None, **kwargs): + if not getattr(settings, "SIMPLE_HISTORY_ENABLED", True): + return if not created and hasattr(instance, "skip_history_when_saving"): return if not kwargs.get("raw", False): self.create_historical_record(instance, created and "+" or "~", using=using) def post_delete(self, instance, using=None, **kwargs): + if not getattr(settings, "SIMPLE_HISTORY_ENABLED", True): + return if self.cascade_delete_history: manager = getattr(instance, self.manager_name) manager.using(using).all().delete() diff --git a/simple_history/tests/tests/test_manager.py b/simple_history/tests/tests/test_manager.py index d39ea9eef..df9ece1dd 100644 --- a/simple_history/tests/tests/test_manager.py +++ b/simple_history/tests/tests/test_manager.py @@ -3,7 +3,7 @@ from django.contrib.auth import get_user_model from django.db import IntegrityError -from django.test import TestCase, skipUnlessDBFeature +from django.test import TestCase, skipUnlessDBFeature, override_settings from ..models import Document, Poll @@ -110,6 +110,11 @@ def test_simple_bulk_history_create(self): self.assertEqual(created, []) self.assertEqual(Poll.history.count(), 4) + @override_settings(SIMPLE_HISTORY_ENABLED=False) + def test_simple_bulk_history_create(self): + Poll.history.bulk_history_create(self.data) + self.assertEqual(Poll.history.count(), 0) + def test_bulk_history_create_with_change_reason(self): for poll in self.data: poll._change_reason = "reason" diff --git a/simple_history/tests/tests/test_models.py b/simple_history/tests/tests/test_models.py index b0aede5af..579cad0f1 100644 --- a/simple_history/tests/tests/test_models.py +++ b/simple_history/tests/tests/test_models.py @@ -267,6 +267,16 @@ def test_save_without_historical_record(self): }, ) + @override_settings(SIMPLE_HISTORY_ENABLED=False) + def test_save_with_disabled_history(self): + anthony = Person.objects.create(name="Anthony Gillard") + anthony.name = "something else" + anthony.save() + self.assertEqual(Person.history.count(), 0) + anthony.delete() + self.assertEqual(Person.history.count(), 0) + + def test_save_without_historical_record_for_registered_model(self): model = ExternalModelSpecifiedWithAppParam.objects.create( name="registered model" diff --git a/simple_history/tests/tests/test_utils.py b/simple_history/tests/tests/test_utils.py index fd12f4f78..fc26af6fe 100644 --- a/simple_history/tests/tests/test_utils.py +++ b/simple_history/tests/tests/test_utils.py @@ -3,7 +3,7 @@ from django.contrib.auth import get_user_model from django.db import IntegrityError, transaction -from django.test import TestCase, TransactionTestCase +from django.test import TestCase, TransactionTestCase, override_settings from django.utils import timezone from simple_history.exceptions import AlternativeManagerError, NotHistoricalModelError @@ -77,6 +77,13 @@ def test_bulk_create_history(self): self.assertEqual(Poll.objects.count(), 5) self.assertEqual(Poll.history.count(), 5) + @override_settings(SIMPLE_HISTORY_ENABLED=False) + def test_bulk_create_history_with_disabled_setting(self): + bulk_create_with_history(self.data, Poll) + + self.assertEqual(Poll.objects.count(), 5) + self.assertEqual(Poll.history.count(), 0) + def test_bulk_create_history_alternative_manager(self): bulk_create_with_history( self.data, @@ -291,6 +298,21 @@ def test_bulk_update_history(self): self.assertEqual(Poll.history.count(), 10) self.assertEqual(Poll.history.filter(history_type="~").count(), 5) + @override_settings(SIMPLE_HISTORY_ENABLED=False) + def test_bulk_update_history(self): + self.assertEqual(Poll.history.count(), 5) + # because setup called with enabled settings + bulk_update_with_history( + self.data, + Poll, + fields=["question"], + ) + + self.assertEqual(Poll.objects.count(), 5) + self.assertEqual(Poll.objects.get(id=4).question, "Updated question") + self.assertEqual(Poll.history.count(), 5) + self.assertEqual(Poll.history.filter(history_type="~").count(), 0) + def test_bulk_update_history_with_default_user(self): user = User.objects.create_user("tester", "tester@example.com") From 353a4ab81792c3a3d963bdaeb4579e1169a86271 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Oct 2021 17:04:10 +0000 Subject: [PATCH 29/29] Bump coverage from 5.5 to 6.0 in /requirements Bumps [coverage](https://github.com/nedbat/coveragepy) from 5.5 to 6.0. - [Release notes](https://github.com/nedbat/coveragepy/releases) - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst) - [Commits](https://github.com/nedbat/coveragepy/compare/coverage-5.5...6.0) --- updated-dependencies: - dependency-name: coverage dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements/coverage.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/coverage.txt b/requirements/coverage.txt index 7e55f294c..453da8f70 100644 --- a/requirements/coverage.txt +++ b/requirements/coverage.txt @@ -1,2 +1,2 @@ -coverage==5.5 +coverage==6.0 toml==0.10.2