Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-django-reversion for
openSUSE:Factory checked in at 2026-03-31 16:26:36
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-django-reversion (Old)
and /work/SRC/openSUSE:Factory/.python-django-reversion.new.1999 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-django-reversion"
Tue Mar 31 16:26:36 2026 rev:18 rq:1343784 version:6.1.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-django-reversion/python-django-reversion.changes
2024-11-12 19:22:51.539079826 +0100
+++
/work/SRC/openSUSE:Factory/.python-django-reversion.new.1999/python-django-reversion.changes
2026-03-31 16:26:37.577740330 +0200
@@ -1,0 +2,14 @@
+Mon Mar 30 21:57:16 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 6.1.0:
+ * Added history_order_by_date and get_version_ordering to
+ VersionAdmin (@micmarc).
+ * Prevent Django signals firing when viewing historical
+ versions in admin. Django model signals (pre_save, post_save,
+ pre_delete, post_delete, m2m_changed) are now muted during
+ GET requests to revision views, preventing unintended side
+ effects from signal handlers when users view historical data.
+ (@romanek-adam-b2c2)
+- refresh only-sqlite-test-db.patch
+
+-------------------------------------------------------------------
Old:
----
django_reversion-5.1.0.tar.gz
New:
----
django_reversion-6.1.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-django-reversion.spec ++++++
--- /var/tmp/diff_new_pack.siYzei/_old 2026-03-31 16:26:38.581782427 +0200
+++ /var/tmp/diff_new_pack.siYzei/_new 2026-03-31 16:26:38.581782427 +0200
@@ -1,7 +1,7 @@
#
# spec file for package python-django-reversion
#
-# Copyright (c) 2024 SUSE LLC
+# Copyright (c) 2026 SUSE LLC and contributors
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -18,22 +18,22 @@
%{?sle15_python_module_pythons}
Name: python-django-reversion
-Version: 5.1.0
+Version: 6.1.0
Release: 0
Summary: A Django extension that provides version control for model
instances
License: BSD-3-Clause
URL: https://github.com/etianen/django-reversion
Source:
https://files.pythonhosted.org/packages/source/d/django_reversion/django_reversion-%{version}.tar.gz
-Patch0: only-sqlite-test-db.patch
-BuildRequires: %{python_module Django > 2.0}
-BuildRequires: %{python_module base > 3.7}
+Patch: only-sqlite-test-db.patch
+BuildRequires: %{python_module Django >= 4.2}
+BuildRequires: %{python_module base >= 3.9}
BuildRequires: %{python_module pip}
BuildRequires: %{python_module pytest-django}
BuildRequires: %{python_module setuptools}
BuildRequires: %{python_module wheel}
BuildRequires: fdupes
BuildRequires: python-rpm-macros
-Requires: python-Django > 2.0
+Requires: python-Django >= 4.2
Obsoletes: python-django-reversion-doc
Obsoletes: python-django-reversion-lang
BuildArch: noarch
++++++ django_reversion-5.1.0.tar.gz -> django_reversion-6.1.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_reversion-5.1.0/CHANGELOG.rst
new/django_reversion-6.1.0/CHANGELOG.rst
--- old/django_reversion-5.1.0/CHANGELOG.rst 2024-08-09 23:30:19.000000000
+0200
+++ new/django_reversion-6.1.0/CHANGELOG.rst 2025-12-12 21:23:38.000000000
+0100
@@ -3,11 +3,26 @@
django-reversion changelog
==========================
+6.1.0 - 2025-12-12
+------------------
+
+- Added ``history_order_by_date`` and ``get_version_ordering`` to
``VersionAdmin`` (@micmarc).
+
+
+6.0.0 - 2025-09-20
+------------------
+
+- Prevent Django signals firing when viewing historical versions in admin.
+ Django model signals (pre_save, post_save, pre_delete, post_delete,
m2m_changed)
+ are now muted during GET requests to revision views, preventing unintended
+ side effects from signal handlers when users view historical data.
(@romanek-adam-b2c2)
+
+
5.1.0 - 2024-08-09
------------------
- Django 5 support (@jeremy-engel).
-- Use bulk_create`` on supported databases (@stianjensen).
+- Use ``bulk_create`` on supported databases (@stianjensen).
5.0.12 - 2024-01-30
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_reversion-5.1.0/PKG-INFO
new/django_reversion-6.1.0/PKG-INFO
--- old/django_reversion-5.1.0/PKG-INFO 2024-08-09 23:30:27.057682300 +0200
+++ new/django_reversion-6.1.0/PKG-INFO 2025-12-12 21:23:49.294037300 +0100
@@ -1,6 +1,6 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
Name: django-reversion
-Version: 5.1.0
+Version: 6.1.0
Summary: An extension to the Django web framework that provides version
control for model instances.
Home-page: https://github.com/etianen/django-reversion
Author: Dave Hall
@@ -12,15 +12,25 @@
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
Classifier: Framework :: Django
-Requires-Python: >=3.8
+Requires-Python: >=3.9
License-File: LICENSE
Requires-Dist: django>=4.2
+Dynamic: author
+Dynamic: author-email
+Dynamic: classifier
+Dynamic: description
+Dynamic: home-page
+Dynamic: license
+Dynamic: license-file
+Dynamic: requires-dist
+Dynamic: requires-python
+Dynamic: summary
================
django-reversion
@@ -35,8 +45,8 @@
Requirements
============
-- Python 3.7 or later
-- Django 3.2 or later
+- Python 3.8 or later
+- Django 4.2 or later
Features
========
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_reversion-5.1.0/README.rst
new/django_reversion-6.1.0/README.rst
--- old/django_reversion-5.1.0/README.rst 2024-08-09 23:30:19.000000000
+0200
+++ new/django_reversion-6.1.0/README.rst 2025-12-12 21:23:38.000000000
+0100
@@ -11,8 +11,8 @@
Requirements
============
-- Python 3.7 or later
-- Django 3.2 or later
+- Python 3.8 or later
+- Django 4.2 or later
Features
========
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django_reversion-5.1.0/django_reversion.egg-info/PKG-INFO
new/django_reversion-6.1.0/django_reversion.egg-info/PKG-INFO
--- old/django_reversion-5.1.0/django_reversion.egg-info/PKG-INFO
2024-08-09 23:30:26.000000000 +0200
+++ new/django_reversion-6.1.0/django_reversion.egg-info/PKG-INFO
2025-12-12 21:23:49.000000000 +0100
@@ -1,6 +1,6 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
Name: django-reversion
-Version: 5.1.0
+Version: 6.1.0
Summary: An extension to the Django web framework that provides version
control for model instances.
Home-page: https://github.com/etianen/django-reversion
Author: Dave Hall
@@ -12,15 +12,25 @@
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
Classifier: Framework :: Django
-Requires-Python: >=3.8
+Requires-Python: >=3.9
License-File: LICENSE
Requires-Dist: django>=4.2
+Dynamic: author
+Dynamic: author-email
+Dynamic: classifier
+Dynamic: description
+Dynamic: home-page
+Dynamic: license
+Dynamic: license-file
+Dynamic: requires-dist
+Dynamic: requires-python
+Dynamic: summary
================
django-reversion
@@ -35,8 +45,8 @@
Requirements
============
-- Python 3.7 or later
-- Django 3.2 or later
+- Python 3.8 or later
+- Django 4.2 or later
Features
========
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django_reversion-5.1.0/django_reversion.egg-info/SOURCES.txt
new/django_reversion-6.1.0/django_reversion.egg-info/SOURCES.txt
--- old/django_reversion-5.1.0/django_reversion.egg-info/SOURCES.txt
2024-08-09 23:30:27.000000000 +0200
+++ new/django_reversion-6.1.0/django_reversion.egg-info/SOURCES.txt
2025-12-12 21:23:49.000000000 +0100
@@ -40,6 +40,7 @@
reversion/models.py
reversion/revisions.py
reversion/signals.py
+reversion/utils.py
reversion/views.py
reversion/locale/ar/LC_MESSAGES/django.mo
reversion/locale/ar/LC_MESSAGES/django.po
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_reversion-5.1.0/docs/admin.rst
new/django_reversion-6.1.0/docs/admin.rst
--- old/django_reversion-5.1.0/docs/admin.rst 2024-08-09 23:30:19.000000000
+0200
+++ new/django_reversion-6.1.0/docs/admin.rst 2025-12-12 21:23:38.000000000
+0100
@@ -5,16 +5,6 @@
django-reversion can be used to add rollback and recovery to your admin site.
-.. Important::
- Using the admin integration's preview feature will restore your model
inside a temporary transaction, then roll
- back the transaction once the preview is rendered.
-
- The ``Model.save()`` method, along with any ``pre_save`` and ``post_save``
signals, will be run as part of the preview transaction. Any non-transactional
side-effects of these functions (e.g. filesystem, cache) will **not be rolled
back** at the end of the preview.
-
- The ``raw=True`` flag will be set in ``pre_save`` and ``post_save``
signals, allowing you to distinguish preview transactions from regular database
transactions and avoid non-transactional side-effects.
-
- Alternatively, use `transaction.on_commit()
<https://docs.djangoproject.com/en/4.2/topics/db/transactions/#django.db.transaction.on_commit>`_
to register side-effects to be carried out only on committed transactions.
-
.. Warning::
The admin integration requires that your database engine supports
transactions. This is the case for PostgreSQL, SQLite and MySQL InnoDB. If you
are using MySQL MyISAM, upgrade your database tables to InnoDB!
@@ -100,6 +90,11 @@
If ``True``, revisions will be displayed with the most recent revision
first.
+``history_order_by_date = False``
+
+ If ``True``, revisions will be ordered by ``date_created`` instead of the
numeric version ID.
+
+
.. _VersionAdmin_register:
``reversion_register(model, **options)``
@@ -117,3 +112,21 @@
``options``
Registration options, see :ref:`reversion.register() <register>`.
+
+.. _VersionAdmin_get_version_ordering:
+
+``get_version_ordering(request)``
+
+ Method that returns a tuple specifying the field names (relative to the
``Version`` model) for ordering. Semantics are similar to the built-in
``get_ordering`` method in Django's ``ModelAdmin``.
+
+ Implementations may override this method to achieve custom or dynamic
ordering of the version queryset. The return value must be a list or tuple.
Calling ``super()`` returns the default ordering which takes
``history_latest_first`` and ``history_order_by_date`` into account; this call
may be omitted if the default ordering is not required.
+
+ .. code:: python
+
+ def get_version_ordering(self, request):
+ if request.user.is_superuser:
+ return ("-revision__date_created", "revision__comment")
+ return super().get_version_ordering(request)
+
+ ``request``
+ The current request.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_reversion-5.1.0/docs/conf.py
new/django_reversion-6.1.0/docs/conf.py
--- old/django_reversion-5.1.0/docs/conf.py 2024-08-09 23:30:19.000000000
+0200
+++ new/django_reversion-6.1.0/docs/conf.py 2025-12-12 21:23:38.000000000
+0100
@@ -21,6 +21,7 @@
# sys.path.insert(0, os.path.abspath('.'))
import os
+
from reversion import __version__
# -- General configuration ------------------------------------------------
@@ -45,35 +46,35 @@
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
-source_suffix = '.rst'
+source_suffix = ".rst"
# The encoding of source files.
#
# source_encoding = 'utf-8-sig'
# The master toctree document.
-master_doc = 'index'
+master_doc = "index"
# General information about the project.
-project = 'django-reversion'
-copyright = '2016, Dave Hall'
-author = 'Dave Hall'
+project = "django-reversion"
+copyright = "2016, Dave Hall"
+author = "Dave Hall"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
-version = '.'.join(str(x) for x in __version__[:2])
+version = ".".join(str(x) for x in __version__[:2])
# The full version, including alpha/beta/rc tags.
-release = '.'.join(str(x) for x in __version__)
+release = ".".join(str(x) for x in __version__)
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
-language = 'en'
+language = "en"
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
@@ -87,7 +88,7 @@
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
-exclude_patterns = ['_build', '_include', 'Thumbs.db', '.DS_Store']
+exclude_patterns = ["_build", "_include", "Thumbs.db", ".DS_Store"]
# The reST default role (used for this markup: `text`) to use for all
# documents.
@@ -109,7 +110,7 @@
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
@@ -126,10 +127,8 @@
# -- Options for HTML output ----------------------------------------------
# Use RTD theme locally.
-if not os.environ.get('READTHEDOCS', None) == 'True':
- import sphinx_rtd_theme
+if not os.environ.get("READTHEDOCS", None) == "True":
html_theme = "sphinx_rtd_theme"
- html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# The name for this set of Sphinx documents.
# "<project> v<release> documentation" by default.
@@ -234,34 +233,36 @@
# html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
-htmlhelp_basename = 'django-reversiondoc'
+htmlhelp_basename = "django-reversiondoc"
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
- # The paper size ('letterpaper' or 'a4paper').
- #
- # 'papersize': 'letterpaper',
-
- # The font size ('10pt', '11pt' or '12pt').
- #
- # 'pointsize': '10pt',
-
- # Additional stuff for the LaTeX preamble.
- #
- # 'preamble': '',
-
- # Latex figure (float) alignment
- #
- # 'figure_align': 'htbp',
+ # The paper size ('letterpaper' or 'a4paper').
+ #
+ # 'papersize': 'letterpaper',
+ # The font size ('10pt', '11pt' or '12pt').
+ #
+ # 'pointsize': '10pt',
+ # Additional stuff for the LaTeX preamble.
+ #
+ # 'preamble': '',
+ # Latex figure (float) alignment
+ #
+ # 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
- (master_doc, 'django-reversion.tex', 'django-reversion Documentation',
- 'Dave Hall', 'manual'),
+ (
+ master_doc,
+ "django-reversion.tex",
+ "django-reversion Documentation",
+ "Dave Hall",
+ "manual",
+ ),
]
# The name of an image file (relative to this directory) to place at the top of
@@ -296,8 +297,7 @@
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
- (master_doc, 'django-reversion', 'django-reversion Documentation',
- [author], 1)
+ (master_doc, "django-reversion", "django-reversion Documentation",
[author], 1)
]
# If true, show URL addresses after external links.
@@ -311,9 +311,15 @@
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
- (master_doc, 'django-reversion', 'django-reversion Documentation',
- author, 'django-reversion', 'One line description of project.',
- 'Miscellaneous'),
+ (
+ master_doc,
+ "django-reversion",
+ "django-reversion Documentation",
+ author,
+ "django-reversion",
+ "One line description of project.",
+ "Miscellaneous",
+ ),
]
# Documents to append as an appendix to all manuals.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_reversion-5.1.0/reversion/__init__.py
new/django_reversion-6.1.0/reversion/__init__.py
--- old/django_reversion-5.1.0/reversion/__init__.py 2024-08-09
23:30:19.000000000 +0200
+++ new/django_reversion-6.1.0/reversion/__init__.py 2025-12-12
21:23:38.000000000 +0100
@@ -36,4 +36,4 @@
get_registered_models,
)
-__version__ = VERSION = (5, 1, 0)
+__version__ = VERSION = (6, 1, 0)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_reversion-5.1.0/reversion/admin.py
new/django_reversion-6.1.0/reversion/admin.py
--- old/django_reversion-5.1.0/reversion/admin.py 2024-08-09
23:30:19.000000000 +0200
+++ new/django_reversion-6.1.0/reversion/admin.py 2025-12-12
21:23:38.000000000 +0100
@@ -1,21 +1,25 @@
-from contextlib import contextmanager
-from django.db import models, transaction, connections
+from contextlib import contextmanager, nullcontext
+
from django.contrib import admin, messages
from django.contrib.admin import options
from django.contrib.admin.utils import unquote, quote
from django.contrib.contenttypes.admin import GenericInlineModelAdmin
from django.contrib.contenttypes.fields import GenericRelation
from django.core.exceptions import PermissionDenied, ImproperlyConfigured
+from django.db import models, transaction, connections
+from django.db.models.signals import pre_save, post_save, pre_delete,
post_delete, m2m_changed
from django.shortcuts import get_object_or_404, render, redirect
from django.urls import reverse, re_path
+from django.utils.encoding import force_str
+from django.utils.formats import localize
from django.utils.text import capfirst
from django.utils.timezone import template_localtime
from django.utils.translation import gettext as _
-from django.utils.encoding import force_str
-from django.utils.formats import localize
+
from reversion.errors import RevertError
from reversion.models import Version
from reversion.revisions import is_active, register, is_registered,
set_comment, create_revision, set_user
+from reversion.utils import mute_signals
class _RollBackRevisionView(Exception):
@@ -38,10 +42,25 @@
history_latest_first = False
+ history_order_by_date = False
+
def reversion_register(self, model, **kwargs):
"""Registers the model with reversion."""
register(model, **kwargs)
+ def get_version_ordering(self, request):
+ """Hook for specifying custom field ordering for the version
queryset."""
+ # Default ordering logic uses version ID only
+ order_fields = ["pk"]
+ # Setting history_order_by_date causes revision date to be used as the
primary sort key
+ # Keep version ID as secondary in case of identical revision dates
+ if self.history_order_by_date:
+ order_fields.insert(0, "revision__date_created")
+ # Setting history_latest_first causes order to be reversed on all
fields
+ if self.history_latest_first:
+ order_fields = [f"-{field}" for field in order_fields]
+ return tuple(order_fields)
+
@contextmanager
def create_revision(self, request):
with create_revision():
@@ -58,11 +77,10 @@
"reversion/%s" % template_name,
)
- def _reversion_order_version_queryset(self, queryset):
+ def _reversion_order_version_queryset(self, request, queryset):
"""Applies the correct ordering to the given version queryset."""
- if not self.history_latest_first:
- queryset = queryset.order_by("pk")
- return queryset
+ ordering = self.get_version_ordering(request) or ()
+ return queryset.order_by(*ordering)
# Messages.
@@ -165,9 +183,19 @@
# Check that database transactions are supported.
if not connections[version.db].features.uses_savepoints:
raise ImproperlyConfigured("Cannot use VersionAdmin with a
database that does not support savepoints.")
+
+ # Determine whether to mute signals based on request method
+ if request.method == "GET":
+ # For GET requests (viewing revisions), mute all Django model
signals
+ # to prevent unintended side effects from signal handlers
+ signal_context = mute_signals(pre_save, post_save, pre_delete,
post_delete, m2m_changed)
+ else:
+ # For POST requests (actual reverts), allow signals to fire
normally
+ signal_context = nullcontext()
+
# Run the view.
try:
- with transaction.atomic(using=version.db):
+ with transaction.atomic(using=version.db), signal_context:
# Revert the revision.
version.revision.revert(delete=True)
# Run the normal changeform view.
@@ -246,6 +274,7 @@
model = self.model
opts = model._meta
deleted = self._reversion_order_version_queryset(
+ request,
Version.objects.get_deleted(self.model).select_related("revision")
)
# Set the app name.
@@ -286,10 +315,10 @@
),
}
for version
- in
self._reversion_order_version_queryset(Version.objects.get_for_object_reference(
+ in self._reversion_order_version_queryset(request,
Version.objects.get_for_object_reference(
self.model,
unquote(object_id), # Underscores in primary key get quoted
to "_5F"
- ).select_related("revision__user"))
+ ).select_related("revision", "revision__user"))
]
# Compile the context.
context = {"action_list": action_list}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_reversion-5.1.0/reversion/utils.py
new/django_reversion-6.1.0/reversion/utils.py
--- old/django_reversion-5.1.0/reversion/utils.py 1970-01-01
01:00:00.000000000 +0100
+++ new/django_reversion-6.1.0/reversion/utils.py 2025-12-12
21:23:38.000000000 +0100
@@ -0,0 +1,47 @@
+"""
+Utility functions for django-reversion.
+"""
+from contextlib import contextmanager
+
+
+@contextmanager
+def mute_signals(*signals):
+ """
+ Context manager that temporarily disables and then restores Django signals.
+
+ This is useful when performing operations that shouldn't trigger signal
handlers,
+ such as viewing historical revisions where the signals were already fired
when
+ the data was originally saved.
+
+ Args:
+ *signals (django.dispatch.dispatcher.Signal): any Django signals to
mute
+
+ Example:
+ from django.db.models.signals import pre_save, post_save
+
+ with mute_signals(pre_save, post_save):
+ # Any save operations here won't fire pre_save or post_save signals
+ obj.save()
+ """
+ paused = {}
+
+ # Store current receivers and mute signals (thread-safe)
+ for signal in signals:
+ with signal.lock:
+ # Store the current receivers for restoration later
+ paused[signal] = signal.receivers[:]
+ # Clear the receivers list to mute the signal
+ signal.receivers = []
+ # Clear cache since we're bypassing connect/disconnect
+ signal.sender_receivers_cache.clear()
+
+ try:
+ yield
+ finally:
+ # Restore the original receivers (thread-safe)
+ for signal, original_receivers in paused.items():
+ with signal.lock:
+ # Restore original receivers, preserving any new ones added
during muting
+ signal.receivers = original_receivers + signal.receivers
+ # Clear cache to ensure consistency
+ signal.sender_receivers_cache.clear()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_reversion-5.1.0/setup.py
new/django_reversion-6.1.0/setup.py
--- old/django_reversion-5.1.0/setup.py 2024-08-09 23:30:19.000000000 +0200
+++ new/django_reversion-6.1.0/setup.py 2025-12-12 21:23:38.000000000 +0100
@@ -39,7 +39,7 @@
install_requires=[
"django>=4.2",
],
- python_requires=">=3.8",
+ python_requires=">=3.9",
classifiers=[
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
@@ -47,11 +47,11 @@
"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",
"Framework :: Django",
],
)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django_reversion-5.1.0/tests/test_app/tests/test_admin.py
new/django_reversion-6.1.0/tests/test_app/tests/test_admin.py
--- old/django_reversion-5.1.0/tests/test_app/tests/test_admin.py
2024-08-09 23:30:19.000000000 +0200
+++ new/django_reversion-6.1.0/tests/test_app/tests/test_admin.py
2025-12-12 21:23:38.000000000 +0100
@@ -1,7 +1,11 @@
import re
+from datetime import datetime, timedelta
+
from django.contrib import admin
from django.contrib.contenttypes.admin import GenericTabularInline
+from django.db.models.signals import pre_save, post_save, pre_delete,
post_delete, m2m_changed
from django.shortcuts import resolve_url
+
import reversion
from reversion.admin import VersionAdmin
from reversion.models import Version
@@ -128,6 +132,69 @@
self.assertEqual(self.obj.parent_name, "parent v1")
+class AdminRevisionViewSignalsTest(LoginMixin, AdminMixin, TestBase):
+ """Test that Django model signals are muted during GET requests to
revision views."""
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.signal_fired = None
+
+ def signal_receiver(self, sender, **kwargs):
+ """Receiver that tracks which signal was fired."""
+ self.signal_fired = sender
+ raise RuntimeError(f"Django signal was fired for {sender}!")
+
+ def setUp(self):
+ super().setUp()
+ with reversion.create_revision():
+ self.obj = TestModelParent.objects.create()
+
+ # Connect all the model signals that should be muted
+ self.signals_to_test = [pre_save, post_save, pre_delete, post_delete,
m2m_changed]
+ for signal in self.signals_to_test:
+ signal.connect(receiver=self.signal_receiver,
sender=TestModelParent)
+
+ def tearDown(self):
+ # Disconnect all signals
+ for signal in self.signals_to_test:
+ signal.disconnect(receiver=self.signal_receiver,
sender=TestModelParent)
+ super().tearDown()
+
+ def testGetForRevisionViewDoesntFireDjangoSignals(self):
+ """Test that viewing a revision (GET request) doesn't fire Django
model signals."""
+ self.signal_fired = None
+
+ # This should NOT fire any signals since it's a GET request
+ response = self.client.get(resolve_url(
+ "admin:test_app_testmodelparent_revision",
+ self.obj.pk,
+ Version.objects.get_for_object(self.obj)[0].pk,
+ ))
+
+ # The request should succeed (no signal should have been fired)
+ self.assertEqual(response.status_code, 200)
+ self.assertIsNone(self.signal_fired, "No signals should fire during
GET request")
+
+ def testPostForRevisionViewFiresDjangoSignals(self):
+ """Test that reverting a revision (POST request) properly fires Django
model signals."""
+ self.signal_fired = None
+
+ # This SHOULD fire signals since it's a POST request (actual revert)
+ with self.assertRaises(RuntimeError) as cm:
+ self.client.post(resolve_url(
+ "admin:test_app_testmodelparent_revision",
+ self.obj.pk,
+ Version.objects.get_for_object(self.obj)[0].pk,
+ ), {
+ "name": "v1",
+ "parent_name": "parent v1",
+ })
+
+ # Verify that signals were indeed fired during POST
+ self.assertIn("Django signal was fired", str(cm.exception))
+ self.assertIsNotNone(self.signal_fired, "Signals should fire during
POST request")
+
+
class AdminRecoverViewTest(LoginMixin, AdminMixin, TestBase):
def setUp(self):
@@ -184,6 +251,255 @@
Version.objects.get_for_model(TestModelParent).get().pk,
))
+ def testHistorylistViewOrderDefault(self):
+ # Create an object and multiple revisions.
+ with reversion.create_revision():
+ obj = TestModelParent.objects.create(name="v1", parent_name="p1")
+ with reversion.create_revision():
+ obj.name = "v2"
+ obj.save()
+ with reversion.create_revision():
+ obj.name = "v3"
+ obj.save()
+
+ # Fetch history page.
+ response =
self.client.get(resolve_url("admin:test_app_testmodelparent_history", obj.pk))
+ content = response.content.decode()
+
+ # Compute expected order: default VersionAdmin orders by pk ascending
(oldest first).
+ version_ids =
list(Version.objects.get_for_object(obj).values_list("pk", flat=True))
+ expected_order = sorted(version_ids)
+
+ # Build the URLs as rendered in the history list and assert their
order.
+ urls_in_order = [
+ resolve_url(
+ "admin:test_app_testmodelparent_revision",
+ obj.pk,
+ vid,
+ )
+ for vid in expected_order
+ ]
+
+ # Ensure each subsequent URL appears later in the content than the
previous one.
+ last_index = -1
+ for url in urls_in_order:
+ index = content.find(url)
+ self.assertNotEqual(index, -1, f"Expected to find {url} in history
page")
+ self.assertGreater(index, last_index, "History list is not ordered
by ascending version pk (oldest first)")
+ last_index = index
+
+
+class AdminHistoryViewLatestFirstTest(LoginMixin, TestBase):
+
+ class TestModelParentAdminLatestFirst(VersionAdmin):
+ history_latest_first = True
+
+ def setUp(self):
+ super().setUp()
+ # Register a custom admin with history_latest_first enabled
+ admin.site.register(TestModelParent,
self.TestModelParentAdminLatestFirst)
+ self.reloadUrls()
+
+ def tearDown(self):
+ super().tearDown()
+ admin.site.unregister(TestModelParent)
+ self.reloadUrls()
+
+ def testHistorylistViewOrderLatestFirst(self):
+ # Create an object and multiple revisions.
+ with reversion.create_revision():
+ obj = TestModelParent.objects.create(name="v1", parent_name="p1")
+ with reversion.create_revision():
+ obj.name = "v2"
+ obj.save()
+ with reversion.create_revision():
+ obj.name = "v3"
+ obj.save()
+
+ # Fetch history page.
+ response =
self.client.get(resolve_url("admin:test_app_testmodelparent_history", obj.pk))
+ content = response.content.decode()
+
+ # Expected order: with history_latest_first=True, versions are ordered
by pk descending (newest first).
+ version_ids =
list(Version.objects.get_for_object(obj).values_list("pk", flat=True))
+ expected_order = sorted(version_ids, reverse=True)
+
+ urls_in_order = [
+ resolve_url("admin:test_app_testmodelparent_revision", obj.pk, vid)
+ for vid in expected_order
+ ]
+
+ last_index = -1
+ for url in urls_in_order:
+ index = content.find(url)
+ self.assertNotEqual(index, -1, f"Expected to find {url} in history
page")
+ self.assertGreater(index, last_index, "History list is not ordered
by descending version pk (newest first)")
+ last_index = index
+
+
+class AdminHistoryViewOrderByDateTest(LoginMixin, TestBase):
+
+ class TestModelParentAdminOrderByDate(VersionAdmin):
+ history_order_by_date = True
+
+ def setUp(self):
+ super().setUp()
+ # Register a custom admin with history_order_by_date enabled
+ admin.site.register(TestModelParent,
self.TestModelParentAdminOrderByDate)
+ self.reloadUrls()
+
+ def tearDown(self):
+ super().tearDown()
+ admin.site.unregister(TestModelParent)
+ self.reloadUrls()
+
+ def testHistorylistViewOrderByDate(self):
+ # Create an object and multiple revisions with increasing timestamps.
+ with reversion.create_revision():
+ obj = TestModelParent.objects.create(name="v1", parent_name="p1")
+ # Use an out-of-sequence date to verify correct ordering
+ reversion.set_date_created(datetime.now() + timedelta(days=1))
+ with reversion.create_revision():
+ obj.name = "v2"
+ obj.save()
+ with reversion.create_revision():
+ obj.name = "v3"
+ obj.save()
+
+ # Fetch history page.
+ response =
self.client.get(resolve_url("admin:test_app_testmodelparent_history", obj.pk))
+ content = response.content.decode()
+
+ # Expected order: ordered by revision creation date ascending (oldest
date first).
+ versions = (
+ Version.objects.get_for_object(obj)
+ .select_related("revision")
+ .order_by("revision__date_created", "pk")
+ )
+ expected_order = list(versions.values_list("pk", flat=True))
+
+ urls_in_order = [
+ resolve_url("admin:test_app_testmodelparent_revision", obj.pk, vid)
+ for vid in expected_order
+ ]
+
+ last_index = -1
+ for url in urls_in_order:
+ index = content.find(url)
+ self.assertNotEqual(index, -1, f"Expected to find {url} in history
page")
+ self.assertGreater(index, last_index, "History list is not ordered
by revision date (oldest first)")
+ last_index = index
+
+
+class AdminHistoryViewLatestFirstOrderByDateTest(LoginMixin, TestBase):
+
+ class TestModelParentAdminLatestFirstOrderByDate(VersionAdmin):
+ history_latest_first = True
+ history_order_by_date = True
+
+ def setUp(self):
+ super().setUp()
+ # Register a custom admin with both flags enabled
+ admin.site.register(TestModelParent,
self.TestModelParentAdminLatestFirstOrderByDate)
+ self.reloadUrls()
+
+ def tearDown(self):
+ super().tearDown()
+ admin.site.unregister(TestModelParent)
+ self.reloadUrls()
+
+ def testHistorylistViewOrderLatestFirstByDate(self):
+ # Create an object and multiple revisions with increasing timestamps.
+ with reversion.create_revision():
+ obj = TestModelParent.objects.create(name="v1", parent_name="p1")
+ # Use an out-of-sequence date to verify correct ordering
+ reversion.set_date_created(datetime.now() + timedelta(days=1))
+ with reversion.create_revision():
+ obj.name = "v2"
+ obj.save()
+ with reversion.create_revision():
+ obj.name = "v3"
+ obj.save()
+
+ # Fetch history page.
+ response =
self.client.get(resolve_url("admin:test_app_testmodelparent_history", obj.pk))
+ content = response.content.decode()
+
+ # Expected order: ordered by revision creation date descending (newest
date first).
+ versions = (
+ Version.objects.get_for_object(obj)
+ .select_related("revision")
+ .order_by("-revision__date_created", "-pk")
+ )
+ expected_order = list(versions.values_list("pk", flat=True))
+
+ urls_in_order = [
+ resolve_url("admin:test_app_testmodelparent_revision", obj.pk, vid)
+ for vid in expected_order
+ ]
+
+ last_index = -1
+ for url in urls_in_order:
+ index = content.find(url)
+ self.assertNotEqual(index, -1, f"Expected to find {url} in history
page")
+ self.assertGreater(index, last_index, "History list is not ordered
by revision date (newest first)")
+ last_index = index
+
+
+class AdminHistoryViewCustomOrderingTest(LoginMixin, TestBase):
+ class TestModelParentAdminCustomOrdering(VersionAdmin):
+ def get_version_ordering(self, request):
+ return ("revision__comment",)
+
+ def setUp(self):
+ super().setUp()
+ # Register a custom admin with history_order_by_date enabled
+ admin.site.register(TestModelParent,
self.TestModelParentAdminCustomOrdering)
+ self.reloadUrls()
+
+ def tearDown(self):
+ super().tearDown()
+ admin.site.unregister(TestModelParent)
+ self.reloadUrls()
+
+ def testHistorylistViewCustomOrdering(self):
+ # Create an object and multiple revisions with increasing timestamps.
+ with reversion.create_revision():
+ obj = TestModelParent.objects.create(name="v1", parent_name="p1")
+ reversion.set_comment("B")
+ with reversion.create_revision():
+ obj.name = "v2"
+ obj.save()
+ reversion.set_comment("A")
+ with reversion.create_revision():
+ obj.name = "v3"
+ obj.save()
+ reversion.set_comment("C")
+
+ # Fetch history page.
+ response =
self.client.get(resolve_url("admin:test_app_testmodelparent_history", obj.pk))
+ content = response.content.decode()
+
+ # Expected order: ordered by revision comment ascending.
+ versions = (
+ Version.objects.get_for_object(obj)
+ .select_related("revision")
+ .order_by("revision__comment")
+ )
+ expected_order = list(versions.values_list("pk", flat=True))
+
+ urls_in_order = [
+ resolve_url("admin:test_app_testmodelparent_revision", obj.pk, vid)
+ for vid in expected_order
+ ]
+
+ last_index = -1
+ for url in urls_in_order:
+ index = content.find(url)
+ self.assertNotEqual(index, -1, f"Expected to find {url} in history
page")
+ self.assertGreater(index, last_index, "History list is not ordered
by comment ascending")
+ last_index = index
+
class AdminQuotingTest(LoginMixin, AdminMixin, TestBase):
++++++ only-sqlite-test-db.patch ++++++
--- /var/tmp/diff_new_pack.siYzei/_old 2026-03-31 16:26:38.829792826 +0200
+++ /var/tmp/diff_new_pack.siYzei/_new 2026-03-31 16:26:38.829792826 +0200
@@ -1,14 +1,6 @@
-commit 89982858fd1d7b070acba079dac8d35872541d4e
-Author: John Vandenberg <[email protected]>
-Date: Tue Dec 28 09:52:48 2021 +0800
-
- Remove mysql and postgres databases
-
-Index: django-reversion-5.0.12/tests/test_project/settings.py
-===================================================================
---- django-reversion-5.0.12.orig/tests/test_project/settings.py
-+++ django-reversion-5.0.12/tests/test_project/settings.py
-@@ -81,20 +81,6 @@ DATABASES = {
+--- a/tests/test_project/settings.py
++++ b/tests/test_project/settings.py
+@@ -81,20 +81,6 @@
"ENGINE": "django.db.backends.sqlite3",
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
},