From 5cee0b6486a41b4543cabad25d77e235d07dc560 Mon Sep 17 00:00:00 2001 From: frost Date: Wed, 28 Aug 2024 11:15:43 +0200 Subject: [PATCH] Updated to match the newest version of source repository --- .github/workflows/python-package.yml | 76 +++++++++ .github/workflows/python-publish.yml | 26 ++++ .gitignore | 2 + .readthedocs.yaml | 11 ++ .travis.yml | 53 ------- CHANGELOG.rst | 147 ++++++++++++++++-- README.rst | 18 +++ docs/admin.rst | 10 ++ docs/common-problems.rst | 9 ++ docs/conf.py | 30 ++-- reversion/__init__.py | 2 +- reversion/admin.py | 33 ++-- reversion/apps.py | 8 + reversion/locale/ar/LC_MESSAGES/django.mo | Bin 2637 -> 2596 bytes reversion/locale/cs/LC_MESSAGES/django.mo | Bin 2474 -> 2492 bytes reversion/locale/cs/LC_MESSAGES/django.po | 3 +- reversion/locale/da/LC_MESSAGES/django.mo | Bin 2384 -> 2343 bytes reversion/locale/de/LC_MESSAGES/django.mo | Bin 2599 -> 2601 bytes reversion/locale/de/LC_MESSAGES/django.po | 1 + reversion/locale/es/LC_MESSAGES/django.mo | Bin 2506 -> 2465 bytes reversion/locale/es_AR/LC_MESSAGES/django.mo | Bin 2507 -> 2466 bytes reversion/locale/fr/LC_MESSAGES/django.mo | Bin 2582 -> 2545 bytes reversion/locale/fr/LC_MESSAGES/django.po | 2 +- reversion/locale/he/LC_MESSAGES/django.mo | Bin 2530 -> 2626 bytes reversion/locale/he/LC_MESSAGES/django.po | 2 + reversion/locale/it/LC_MESSAGES/django.mo | Bin 2395 -> 2397 bytes reversion/locale/it/LC_MESSAGES/django.po | 1 + reversion/locale/nb/LC_MESSAGES/django.mo | Bin 2506 -> 2465 bytes reversion/locale/nl/LC_MESSAGES/django.mo | Bin 2450 -> 2409 bytes reversion/locale/pl/LC_MESSAGES/django.mo | Bin 2526 -> 2674 bytes reversion/locale/pl/LC_MESSAGES/django.po | 3 + reversion/locale/pt_BR/LC_MESSAGES/django.mo | Bin 2377 -> 2378 bytes reversion/locale/pt_BR/LC_MESSAGES/django.po | 1 + reversion/locale/ru/LC_MESSAGES/django.mo | Bin 2989 -> 3129 bytes reversion/locale/ru/LC_MESSAGES/django.po | 3 + reversion/locale/sk/LC_MESSAGES/django.mo | Bin 2489 -> 2496 bytes reversion/locale/sk/LC_MESSAGES/django.po | 3 +- reversion/locale/sl_SI/LC_MESSAGES/django.mo | Bin 2621 -> 2716 bytes reversion/locale/sl_SI/LC_MESSAGES/django.po | 2 + reversion/locale/sv/LC_MESSAGES/django.mo | Bin 2535 -> 2494 bytes reversion/locale/uk/LC_MESSAGES/django.mo | Bin 3451 -> 3558 bytes reversion/locale/uk/LC_MESSAGES/django.po | 6 +- reversion/locale/zh_CN/LC_MESSAGES/django.mo | Bin 2183 -> 0 bytes reversion/locale/zh_CN/LC_MESSAGES/django.po | 121 -------------- .../locale/zh_Hans/LC_MESSAGES/django.mo | Bin 2183 -> 2719 bytes .../locale/zh_Hans/LC_MESSAGES/django.po | 110 ++++++++----- reversion/management/commands/__init__.py | 4 +- .../commands/createinitialrevisions.py | 67 +++++--- .../0001_squashed_0004_auto_20160611_1202.py | 10 +- ...ndex_on_version_for_content_type_and_db.py | 17 ++ reversion/models.py | 93 ++++++++--- reversion/revisions.py | 48 +++--- reversion/views.py | 23 +-- setup.py | 26 ++-- tests/test_app/migrations/0001_initial.py | 7 + .../0002_alter_testmodel_related_and_more.py | 23 +++ tests/test_app/models.py | 8 + tests/test_app/tests/base.py | 6 +- tests/test_app/tests/test_admin.py | 2 +- tests/test_app/tests/test_api.py | 2 +- tests/test_app/tests/test_models.py | 17 +- tests/test_app/views.py | 8 +- tests/test_project/settings.py | 16 +- tests/test_project/urls.py | 2 +- 64 files changed, 690 insertions(+), 372 deletions(-) create mode 100644 .github/workflows/python-package.yml create mode 100644 .github/workflows/python-publish.yml create mode 100644 .readthedocs.yaml delete mode 100644 .travis.yml create mode 100644 reversion/apps.py delete mode 100644 reversion/locale/zh_CN/LC_MESSAGES/django.mo delete mode 100644 reversion/locale/zh_CN/LC_MESSAGES/django.po create mode 100644 reversion/migrations/0002_add_index_on_version_for_content_type_and_db.py create mode 100644 tests/test_app/migrations/0002_alter_testmodel_related_and_more.py diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 00000000..6d36bd68 --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,76 @@ +name: Python package + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + services: + postgres: + image: postgres + env: + POSTGRES_PASSWORD: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + mysql: + image: mysql + env: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: root + options: >- + --health-cmd "mysqladmin -uroot -proot ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 3306:3306 + env: + PYTHONDEVMODE: 1 + DJANGO_DATABASE_HOST_POSTGRES: localhost + DJANGO_DATABASE_USER_POSTGRES: postgres + DJANGO_DATABASE_NAME_POSTGRES: postgres + DJANGO_DATABASE_PASSWORD_POSTGRES: postgres + DJANGO_DATABASE_HOST_MYSQL: 127.0.0.1 + DJANGO_DATABASE_USER_MYSQL: root + DJANGO_DATABASE_NAME_MYSQL: root + DJANGO_DATABASE_PASSWORD_MYSQL: root + strategy: + matrix: + python-version: [3.8, 3.9, '3.10', '3.11', '3.12'] + django-version: + - '>=5.0,<6.0' + - '>=4.2,<5.0' + exclude: + - python-version: 3.9 + django-version: '>=5.0,<6.0' + - python-version: 3.8 + django-version: '>=5.0,<6.0' + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies (Django ${{ matrix.django-version }}) + run: | + python -m pip install --upgrade pip + python -m pip install --pre django'${{ matrix.django-version }}' + python -m pip install flake8 coverage sphinx sphinx_rtd_theme psycopg2 mysqlclient -e . + - name: Lint with flake8 + run: | + flake8 + - name: Check no missing migrations + run: | + tests/manage.py makemigrations --check + - name: Test with unittest + run: | + coverage run tests/manage.py test tests + coverage report + - name: Build docs + run: | + (cd docs && sphinx-build -n -W . _build) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 00000000..87b85830 --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,26 @@ +name: Upload Python Package + +on: + release: + types: [created] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* diff --git a/.gitignore b/.gitignore index 2cb006ac..1937b9e0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .project .pydevproject .settings +.venv *.pyc *.pyo dist @@ -13,3 +14,4 @@ docs/_build .coverage *.sqlite3 .idea +.idea/ diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..c8dc7d98 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,11 @@ +version: 2 +build: + os: ubuntu-20.04 + tools: + python: "3.9" +sphinx: + configuration: docs/conf.py +python: + install: + - method: pip + path: . diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 64b44432..00000000 --- a/.travis.yml +++ /dev/null @@ -1,53 +0,0 @@ -sudo: false -language: python -dist: xenial -python: - - 3.8 - - 3.7 - - 3.6 -cache: pip -env: - global: - - PYTHONWARNINGS=default,ignore::PendingDeprecationWarning,ignore::ResourceWarning - - DJANGO_DATABASE_USER_POSTGRES=postgres - - DJANGO_DATABASE_USER_MYSQL=travis - matrix: - - DJANGO='>=3.1,<3.2' - - DJANGO='>=3.0,<3.1' - - DJANGO='>=2.2,<3.0' - - DJANGO='>=2.1,<2.2' - - DJANGO='>=2.0,<2.1' -matrix: - fast_finish: true -addons: - apt: - packages: - - libmysqlclient-dev -services: - - postgresql - - mysql -install: - - pip install --pre django$DJANGO - - pip install flake8 coverage sphinx psycopg2 mysqlclient -e . -before_script: - - mysql -e 'create database test_project' - - psql -c 'create database test_project;' -U postgres; -script: - - flake8 - - coverage run tests/manage.py test tests - - (cd docs && sphinx-build -n -W . _build) -after_success: - - coverage report -deploy: - provider: pypi - user: etianen - password: - secure: XW4/9HiChbPJSJe4d/MRcO+ViPGhW1iQ8kVi814KJh7mCxOAKijpW5hfdc9oSKB6d8iYB3OzZ7naIUU9GMce40bpeTgPDLVBLCSYKRNLuVoJdh+Q6ItGUiFf8kAJz5jgopG80QnCpLA9JvYxKVJ4amfYWWm204eQmIEnRRAd+Jk= - on: - tags: true - condition: $DJANGO = '>=3.1,<3.2' - python: 3.6 - repo: etianen/django-reversion - distributions: sdist bdist_wheel -notifications: - email: false diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 536818e0..5336ec80 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,132 @@ django-reversion changelog ========================== +5.1.0 - 2024-08-09 +------------------ + +- Django 5 support (@jeremy-engel). +- Use bulk_create`` on supported databases (@stianjensen). + + +5.0.12 - 2024-01-30 +------------------- + +- Fix missing migration introduced in v5.0.11. + + +5.0.11 - 2024-01-29 +------------------- + +- Improved the Chinese translation (@zengqiu). + + +5.0.10 - 2023-12-30 +------------------- + +- Fix N+1 queries while rendering the ``recover_list.html`` template (@armonge). + + +5.0.9 - 2023-12-20 +------------------ + +- Broken release. + + +5.0.8 - 2023-11-08 +------------------ + +- Fix ``get_deleted`` (@siddarta-weis, @etianen). + + +5.0.7 - 2023-11-07 +------------------ + +- Speed up ``get_deleted`` (@caullla). + + +5.0.6 - 2023-09-29 +------------------ + +- Fix handling case of missing object in admin revert (@julianklotz) + + +5.0.5 - 2023-09-19 +------------------ + +- Handling case of missing object in admin revert (@etianen, @PavelPancocha) +- CI improvements (@etianen, @browniebroke) + + +5.0.4 - 2022-11-12 +------------------ + +- Fix warning log formatting for failed reverts (@tony). + + +5.0.3 - 2022-10-02 +------------------ + +- A revision will no longer be created if a transaction is marked as rollback, as this would otherwise cause an + additional database error (@proofit404). +- A warning log is now emitted if a revert fails due to database integrity errors, making debugging the final + ``RevertError`` easier. + + +5.0.2 - 2022-08-06 +------------------ + +- Fixed doc builds on readthedocs (@etianen). + + +5.0.1 - 2022-06-18 +------------------ + +- Fix admin detail view in multi-database configuration (@atten). + + +5.0.0 - 2022-02-20 +------------------ + +- Added support for using django-reversion contexts in ``asyncio`` tasks (@bellini666). +- **Breaking:** Dropped support for Python 3.6. + + +4.0.2 - 2022-01-30 +------------------ + +- Improved performance of `createinitialrevisions` management command (@philipstarkey). + + +4.0.1 - 2021-11-04 +------------------ + +- Django 4.0b support (@smithdc1, @kevinmarsh). +- Optimized ``VersionQuerySet.get_deleted``. + + +4.0.0 - 2021-07-09 +------------------ + +- **Breaking:** The ``create_revision`` view decorator and ``RevisionMiddleware`` no longer roll back the revision and + database transaction on response status code >= 400. It's the responsibility of the view to use `transaction.atomic()` + to roll back any invalid data. This can be enabled globally by setting ``ATOMIC_REQUESTS=True``. (@etianen) + + https://docs.djangoproject.com/en/3.1/ref/settings/#std:setting-DATABASE-ATOMIC_REQUESTS + +- Fixing gettext plural forms with Django (@martinsvoboda). +- Deprecation removals (@lociii, @Peter-van-Tol). +- CI testing improvements (@etianen, @michael-k). +- Documentation fixes (@erikrw, @jedie, @michael-k). + + +3.0.9 - 2021-01-22 +------------------ + +- Significant speedup to ``Version.objects.get_deleted(...)`` database query for PostgreSQL (@GeyseR). +- Testing against Django 3.1 (@michael-k). +- Django 4.0 compatibility improvements (@GitRon). + + 3.0.8 - 2020-08-31 ------------------ @@ -26,13 +152,14 @@ django-reversion changelog - Documentation fixes (@chicheng). -3.0.5 - 2019-02-12 +3.0.5 - 2019-12-02 ------------------ - Improved performance of `get_deleted` for large datasets (@jeremy-engel). - Django 3.0 compatibility (@claudep). - Drops Django < 1.11 compatibility (@claudep). -- Fixed errors in manageement commands when `django.contrib.admin` is not in `INSTALLED_APPS` (@irtimir). +- Drops Python 2.7 compatibility (@claudep). +- Fixed errors in management commands when `django.contrib.admin` is not in `INSTALLED_APPS` (@irtimir). 3.0.4 - 2019-05-22 @@ -77,7 +204,7 @@ django-reversion changelog ------------------ - **Breaking:** ``Revision.comment`` now contains the raw JSON change message generated by django admin, rather than - a string. Accesing ``Revision.comment`` directly is no longer recommended. Instead, use ``Revision.get_comment()``. + a string. Accessing ``Revision.comment`` directly is no longer recommended. Instead, use ``Revision.get_comment()``. (@RamezIssac). - **BREAKING:** django-reversion now uses ``_base_manager`` to calculate deleted models, not ``_default_manager``. This change will only affect models that perform default filtering in their ``_default_manager`` (@ivissani). @@ -434,7 +561,7 @@ Models .. code:: python - # New-style import for accesssing admin class. + # New-style import for accessing admin class. from reversion.admin import VersionAdmin # Use the admin class directly. @@ -461,7 +588,7 @@ Models .. code:: python - # New-style import for accesssing the low-level API. + # New-style import for accessing the low-level API. from reversion import revisions as reversion # Use low-level API methods from the revisions namespace. @@ -486,7 +613,7 @@ Models .. code:: python - # New-style import for accesssing the reversion signals. + # New-style import for accessing the reversion signals. from reversion.signals import pre_revision_commit, post_revision_commit # Use reversion signals directly. @@ -615,7 +742,7 @@ Models ---------------- * Django 1.5 compatibility. -* Experimantal Python 3.3 compatibility! +* Experimental Python 3.3 compatibility! 1.6.6 - 12/02/2013 @@ -655,7 +782,7 @@ Models ------------------ * Swedish translation. -* Fixing formating for PyPi readme and license. +* Fixing formatting for PyPi readme and license. * Minor features and bugfixes. @@ -719,8 +846,8 @@ Models * Added Polish translation. * Added French translation. * Improved resilience of unit tests. -* Improved scaleability of Version.object.get_deleted() method. -* Improved scaleability of createinitialrevisions command. +* Improved scalability of Version.object.get_deleted() method. +* Improved scalability of createinitialrevisions command. * Removed post_syncdb hook. * Added new createinitialrevisions management command. * Fixed DoesNotExistError with OneToOneFields and follow. diff --git a/README.rst b/README.rst index 7d37cc1f..ac9b6d42 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,8 @@ ========================== encrypted-django-reversion ========================== +|PyPI latest| |PyPI Version| |PyPI License| |TravisCI| |Docs| + This is a forked version of https://github.com/etianen/django-reversion @@ -17,8 +19,24 @@ from a TextField into a EncryptedTextField (from django-searchable-encrypted-fie Requirements ============ + +- Python 3.8 or later +- Django 4.2 or later - django-searchable-encrypted-fields>=0.1 +Features +======== + +- Roll back to any point in a model instance's history. +- Recover deleted model instances. +- Simple admin integration. + +Documentation +============= + +Check out the latest ``django-reversion`` documentation at `Getting Started `_ + + Installation ============ add this line to your requirement.txt diff --git a/docs/admin.rst b/docs/admin.rst index e0f72f1e..e9c1df15 100644 --- a/docs/admin.rst +++ b/docs/admin.rst @@ -5,6 +5,16 @@ Admin integration 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() `_ 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! diff --git a/docs/common-problems.rst b/docs/common-problems.rst index 0d3148ea..56c5a162 100644 --- a/docs/common-problems.rst +++ b/docs/common-problems.rst @@ -3,6 +3,15 @@ Common problems =============== +Incompatible version data +------------------------- + +Django-reversion stores the versions of a model as JSON. If a model changes, the migrations are not applied to the stored JSON data. Therefore it can happen that an old version can no longer be restored. In this case the following error occurs: + +.. code:: python + + reversion.errors.RevertError: Could not load - incompatible version data. + RegistrationError: class 'myapp.MyModel' has already been registered with Reversion ----------------------------------------------------------------------------------- diff --git a/docs/conf.py b/docs/conf.py index e3fcb427..dd2adcd5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- # # django-reversion documentation build configuration file, created by # sphinx-quickstart on Thu Jun 2 08:41:36 2016. @@ -21,6 +20,7 @@ # import sys # sys.path.insert(0, os.path.abspath('.')) +import os from reversion import __version__ # -- General configuration ------------------------------------------------ @@ -32,7 +32,11 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [] +extensions = ["sphinx.ext.autodoc", "sphinx.ext.intersphinx"] + +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), +} # Add any paths that contain templates here, relative to this directory. templates_path = [] @@ -69,7 +73,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: @@ -113,25 +117,19 @@ # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False +suppress_warnings = ["image.nonlocal_uri"] + # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -# html_theme = 'alabaster' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] +# Use RTD theme locally. +if not os.environ.get('READTHEDOCS', None) == 'True': + import sphinx_rtd_theme + html_theme = "sphinx_rtd_theme" + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # The name for this set of Sphinx documents. # " v documentation" by default. diff --git a/reversion/__init__.py b/reversion/__init__.py index f6bc91b7..98983a6e 100644 --- a/reversion/__init__.py +++ b/reversion/__init__.py @@ -36,4 +36,4 @@ get_registered_models, ) -__version__ = VERSION = (3, 0, 8) +__version__ = VERSION = (5, 1, 0) diff --git a/reversion/admin.py b/reversion/admin.py index 2bf7d553..5ab748a1 100644 --- a/reversion/admin.py +++ b/reversion/admin.py @@ -1,5 +1,5 @@ from contextlib import contextmanager -from django.db import models, transaction, connection +from django.db import models, transaction, connections from django.contrib import admin, messages from django.contrib.admin import options from django.contrib.admin.utils import unquote, quote @@ -10,13 +10,18 @@ from django.urls import reverse, re_path from django.utils.text import capfirst from django.utils.timezone import template_localtime -from django.utils.translation import ugettext as _ +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.views import _RollBackRevisionView + + +class _RollBackRevisionView(Exception): + + def __init__(self, response): + self.response = response class VersionAdmin(admin.ModelAdmin): @@ -48,8 +53,8 @@ def create_revision(self, request): def _reversion_get_template_list(self, template_name): opts = self.model._meta return ( - "reversion/%s/%s/%s" % (opts.app_label, opts.object_name.lower(), template_name), - "reversion/%s/%s" % (opts.app_label, template_name), + f"reversion/{opts.app_label}/{opts.object_name.lower()}/{template_name}", + f"reversion/{opts.app_label}/{template_name}", "reversion/%s" % template_name, ) @@ -111,7 +116,7 @@ def _reversion_introspect_inline_admin(self, inline): ): fk_name = field.name break - if fk_name and not inline_model._meta.get_field(fk_name).remote_field.is_hidden(): + if fk_name and not inline_model._meta.get_field(fk_name).remote_field.hidden: field = inline_model._meta.get_field(fk_name) accessor = field.remote_field.get_accessor_name() follow_field = accessor @@ -158,7 +163,7 @@ def change_view(self, request, object_id, form_url='', extra_context=None): def _reversion_revisionform_view(self, request, version, template_name, extra_context=None): # Check that database transactions are supported. - if not connection.features.uses_savepoints: + if not connections[version.db].features.uses_savepoints: raise ImproperlyConfigured("Cannot use VersionAdmin with a database that does not support savepoints.") # Run the view. try: @@ -173,14 +178,18 @@ def _reversion_revisionform_view(self, request, version, template_name, extra_co set_comment(_("Reverted to previous version, saved on %(datetime)s") % { "datetime": localize(template_localtime(version.revision.date_created)), }) - else: + elif response.status_code == 200: response.template_name = template_name # Set the template name to the correct template. response.render() # Eagerly render the response, so it's using the latest version. raise _RollBackRevisionView(response) # Raise exception to undo the transaction and revision. + else: + raise RevertError(_("Could not load %(object_repr)s version - not found") % { + "object_repr": version.object_repr, + }) except (RevertError, models.ProtectedError) as ex: opts = self.model._meta messages.error(request, force_str(ex)) - return redirect("{}:{}_{}_changelist".format(self.admin_site.name, opts.app_label, opts.model_name)) + return redirect(f"{self.admin_site.name}:{opts.app_label}_{opts.model_name}_changelist") except _RollBackRevisionView as ex: return ex.response return response @@ -236,7 +245,9 @@ def recoverlist_view(self, request, extra_context=None): raise PermissionDenied model = self.model opts = model._meta - deleted = self._reversion_order_version_queryset(Version.objects.get_deleted(self.model)) + deleted = self._reversion_order_version_queryset( + Version.objects.get_deleted(self.model).select_related("revision") + ) # Set the app name. request.current_app = self.admin_site.name # Get the rest of the context. @@ -270,7 +281,7 @@ def history_view(self, request, object_id, extra_context=None): { "revision": version.revision, "url": reverse( - "%s:%s_%s_revision" % (self.admin_site.name, opts.app_label, opts.model_name), + f"{self.admin_site.name}:{opts.app_label}_{opts.model_name}_revision", args=(quote(version.object_id), version.id) ), } diff --git a/reversion/apps.py b/reversion/apps.py new file mode 100644 index 00000000..04c88128 --- /dev/null +++ b/reversion/apps.py @@ -0,0 +1,8 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class ReversionConfig(AppConfig): + name = 'reversion' + verbose_name = _('Reversion') + default_auto_field = 'django.db.models.AutoField' diff --git a/reversion/locale/ar/LC_MESSAGES/django.mo b/reversion/locale/ar/LC_MESSAGES/django.mo index 1547399bbe817c0751b348a32d7333bb8be99f9d..1cdd8ca9c81cf9ddc88cf1b5ec22cb6f8fd5a4ac 100644 GIT binary patch delta 191 zcmX>rvP5LUnfj@W3=9WY7#Io|7#PCX7#MPZ^l>1~2c%8d85pF1v@?(f@#BEB0FcfF z($YY>4oJHJ=~+P92}nN#(xO0Gg@b`X6G&SDX?`G`0;FYubP15Q1=5p%bQF-jz`>x$ pU;|`mb3z=D0i;0|+yv4f3ts_gNg(|bDlWXalCgz(vl^>9D*(TM7GMAX delta 230 zcmZ1?a#m!*nfiT<3=9WY7#Io|7#Ql=7#MPZ^ami#2c%=!85pF1bPkXP@!Ns40Fa&r zq@{uMav<#nqz?mWCm_wm!N4F2qWW7f8zj>A67K7D(>~(osPACkKNb zgAI@o!U=J}L?8{afQbuYp)in^1oD-DG)UZLb0uR7vs#F*b5UwyNoIbYu1jJ`s+EF~ cfuX6cftjwMse*x_m5HUc0T68d#iGRu0GA&iq5uE@ diff --git a/reversion/locale/cs/LC_MESSAGES/django.mo b/reversion/locale/cs/LC_MESSAGES/django.mo index f060570da88f7b1ce431b2fb8f5cff3fa7d0668a..3c3988c7f12293be54c366e468a07ea03a0274f1 100644 GIT binary patch delta 294 zcmZY1KTCp96vy$O_&MLk|fO{zJjMDiID25jeDF^Mgdv&=8KUg5Yb2Mun!P zX5T=&H25m~KI$g-!sncO&hLJuf6~*{coLG<5h*3fk4bm;g~Sy?{D G`r$2|cqzXC delta 274 zcmXZUF-`(;5XIpkxCn$q6Ku3FL*W{uOa8N{#mZt(Y8$Pr+=7CLt3fENXgC0g7oe~( zRwlIeHXMKh(AaVT{Taew>da6z%O&g}Z QuW9{=!YIONCwH0s0YytGjsO4v diff --git a/reversion/locale/cs/LC_MESSAGES/django.po b/reversion/locale/cs/LC_MESSAGES/django.po index 94bc3547..f7ffa009 100644 --- a/reversion/locale/cs/LC_MESSAGES/django.po +++ b/reversion/locale/cs/LC_MESSAGES/django.po @@ -14,7 +14,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n>1 && n<5 ? 1 : 2;\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" #: admin.py:112 templates/reversion/change_list.html:8 #: templates/reversion/recover_list.html:10 diff --git a/reversion/locale/da/LC_MESSAGES/django.mo b/reversion/locale/da/LC_MESSAGES/django.mo index bc359e0edbf79774386c711e31505ca6c82e0fc1..7ab61bb585924a772a5dbe51eaaf032a1bfbcd06 100644 GIT binary patch delta 191 zcmca0v|MPynR+Ki28IJH3=9zr3=GU{3=Hu=ItEB{0_ieHcgs?O@OV+75DnK9JxC(w0CPB<{JnlCgzZEkxJ3C^fMpGe1w)C9x#cO2Np$ c&_vh3T-Vq@!O+mk&|KTV$iQIpFBV-E0K7CGfB*mh diff --git a/reversion/locale/de/LC_MESSAGES/django.mo b/reversion/locale/de/LC_MESSAGES/django.mo index 807f2b26c2c5f0ff4da2840cf7edc85a703e1575..cfb9aec455f245620c3630292d84853685037aae 100644 GIT binary patch delta 246 zcmZ23vQlKim3k{i1_pLk1_luZ1_mcK28I$K9S)>xf%F+5%?YHv*clkOfpiFv<^s|Q zP&yk(1GO@g0cmj{T?3@;fbS^+twMTt4O lZuv#I#a0S=1z>Kmt&z0?glVghr=V!7V5n)m*^cccGXRGTB`yE} delta 242 zcmZ1}vRq`sm3j+C1_pLk1_luZ1_lQ<28I$K9SWptf%GXL%?YGE*clkOfpid%<^s}j zP&yMx1GO@g0BLa`T?M4=fb=9FZ40C?0%=Jg&CkKWAPl56fwT&cwgu8UKst$o!Hhu+ z$mj6o=s-6ZMlMk{l47k!CPTGT3ywIU|LL(O_|IHmlL%?97`Wle`9#H=yq~ zP0u-J=U)|EgLA#l9qH{#OG%rSw8jT|=(m4H7?KaMgDKLY8Fq1w6U;EhJNEE}F@Dg; r*hqa$aEw!KsD%(Du39*+kwBk&cM0Tp~Sup$|>>&pJlgo9M+6aw2o|;{q$V!74s5 zh+&UN3{x1u21c-pCR!dTN}J(KkU%@_7N(I7+2i+-BYq1n|NM4c+fMdmOI58we>$9u eRjofAnDMMOa!Qv}&WIZYGhbG^p!GH5oH;%^wIBNc diff --git a/reversion/locale/es_AR/LC_MESSAGES/django.mo b/reversion/locale/es_AR/LC_MESSAGES/django.mo index fd04fc8ff593c60affdc05059167d55643804a2e..f1ad1a558ee39f001ce1bcda8d9423ed58c4019d 100644 GIT binary patch delta 199 zcmX>tyhwP$m3ntZ28K^83=FXh3=CFm3=9cCdIpf@0@6RBGz&WegEWxO52QJPv^J0i zN->xMX+a=u1EigSv>%Xm0n(Fzv=ETK4y0v)^eZ3@Qpdr;z#s{vrGT_GkT&69Fauj$ v02I&!3Y-Jd)j;|OkOt~uC;{3Aa!3u31{v56q(S1dHg_^kXWs0|+Qb3?jW-qV delta 238 zcmXZWKMO%&6vy!+*WZmhNeY89JoP+8T;uf;tmLLUSdf&#U^3WjGFX&d-O(hY)hn@B zeJ`fZIloTloRj^qchB`KBJ#3C`XVxliwyCGX)GsxD$LWjFoiv2L9#?y)F adJEqv*0gCTqm?m5({?CyUF)C_+gKlu2_IAd diff --git a/reversion/locale/fr/LC_MESSAGES/django.mo b/reversion/locale/fr/LC_MESSAGES/django.mo index f25ee02144cc69ef272a1efd906ff99f6c7e895c..15aa07756ee875548c273b135f208774148fa5ec 100644 GIT binary patch delta 197 zcmbOx@=whOab&&Afz{%p^4BP>R{-7!T=i0G{pC3)GiqLLcRAJd#4^8f$< diff --git a/reversion/locale/fr/LC_MESSAGES/django.po b/reversion/locale/fr/LC_MESSAGES/django.po index d51daa15..c05927e6 100644 --- a/reversion/locale/fr/LC_MESSAGES/django.po +++ b/reversion/locale/fr/LC_MESSAGES/django.po @@ -16,7 +16,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=n>1;\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" #: admin.py:143 templates/reversion/change_list.html:7 #: templates/reversion/recover_form.html:10 diff --git a/reversion/locale/he/LC_MESSAGES/django.mo b/reversion/locale/he/LC_MESSAGES/django.mo index 09271c373004746bc9a6da346905de7854d6057a..2257367a5d5a63966b52a3e1a42e4aac8dbd523c 100644 GIT binary patch delta 326 zcmZY1p-%z<6vy$$00n~6L8H9F!3DPi+}MBvlADbPn+A#j;bedo0)} z{2xqi78=#>-BFn7^Ly|2_P*n9iP^_^84!67iNr;uc_#9JT^!;LBUn2ZxxhQT!UkSq z8{K|{PdGWcNB7@7rD6{aoTIznU=lwgl9n>zhs7hNq9S>m;wx@3hK1O=9aseyX83Dd z@4WO|o$K4@es@q*r{|3W`=+FRv@?#{wr15Z)X@}y$uhO7mKN17bN|b%*Kuxn_ooN` SRG~DZJj(GXUs@l+zrjD-f;kre delta 227 zcmX>k@<@2Xk$O``28P2d3=F~y3=BV685m-LG%p(iLkW=12GSfr`T>yU1JW;nv>=fF z0i*?hG#5KWUK&Ve1NkaYdIFFJnfC`sD*$N$pdo5N+7L*C#3O*TG?32WU{GhU0Wvy( z0%<_{D3CS=(mI?B3}rw%0Z4NJ>Fb=Evlyo_tA*$~7o{eaWaj7Tx+IpQS}7PA7+C5W c8tEDuC>R=88JTMv07WMMWx2FDg6$PE02~z}+W-In diff --git a/reversion/locale/he/LC_MESSAGES/django.po b/reversion/locale/he/LC_MESSAGES/django.po index 3624f026..1a9b7097 100644 --- a/reversion/locale/he/LC_MESSAGES/django.po +++ b/reversion/locale/he/LC_MESSAGES/django.po @@ -14,6 +14,8 @@ msgstr "" "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 % " +"1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;\n" #: admin.py:112 templates/reversion/change_list.html:7 #: templates/reversion/recover_list.html:10 diff --git a/reversion/locale/it/LC_MESSAGES/django.mo b/reversion/locale/it/LC_MESSAGES/django.mo index cb1b6a3eb633f1db754e0603e1a10c03eb326e69..4467379b7d258cdc8b85999fa6f8a14b840ef50b 100644 GIT binary patch delta 230 zcmcaDbXRD?k$O8u28P2d3=Dz{3=B+c3=Hu=S_DWZ0O=AS%>ksZ0%Qkd^_8uL063K>8$*768)kfV2dV{?E>!#$X9#NC0g~ z1kyo3S{F!f0n)`l`Vo)@SroQ8lW{upWKGsKmt&z0? VglVghr=V!7V5n)m`5$X6GXMi;D6Ieh delta 226 zcmcaBbX#b`k$M|O28P2d3=Dz{K)}Yp5D%n;fOG9L}<^s}pp#0}R8i*J^ z012R0hVMYy7fAns(q8Nk^JV~P8KC$oAguzVj{|7|ApHhNO91J=>Ka(+8d)kB W8e18dXd3`UCjVu*xtWnIh8Y0%TqH{X diff --git a/reversion/locale/it/LC_MESSAGES/django.po b/reversion/locale/it/LC_MESSAGES/django.po index 13a1526f..af86e1c3 100644 --- a/reversion/locale/it/LC_MESSAGES/django.po +++ b/reversion/locale/it/LC_MESSAGES/django.po @@ -14,6 +14,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" #: .\admin.py:128 #: .\templates\reversion\change_list.html.py:7 diff --git a/reversion/locale/nb/LC_MESSAGES/django.mo b/reversion/locale/nb/LC_MESSAGES/django.mo index af21cdd44f868d1399f5f91c2de532d7a21fe98c..4dbe5ca84be8076785942e7b5e73fad83ef70fc0 100644 GIT binary patch delta 183 zcmX>lyij<;k@{(j3=9WY7#Kns7#PCY7#QM#^jaXz38Z=185p>Lv^bFF0Mbf8S`tWW zL;0>i8mN`Q4@mO^>0lro2&9vt^j07(0_3v;HOm5N86YhRq^+R*01gH<1}mUI9#Fs= iNUsFa`at?CkS+nzsz6r&-NGLv;&al0Mgz-S`tWy zK>2w<8mN__97yv6=^7v%2&8+U^gSRg0^}P2HOm5NS0F74q?4iiN)84!1}mVzbfADY zkiG<@^?|e;(Do7_?FXcRZecjQIg@cJvs#F*b5UwyNoIbYu1jJ`s+EF~fuW(Up@FWU Vxq^YEm9dGofsuj1=HD#KSODrDAK?H1 diff --git a/reversion/locale/nl/LC_MESSAGES/django.mo b/reversion/locale/nl/LC_MESSAGES/django.mo index 9a0bc83b74ae1380ad2d9e5ea0b0807dc2303984..6b195b4ab26e2407d6679e5ace6807103d88fb85 100644 GIT binary patch delta 191 zcmbOv{8DJbnfd@m28OFF3=9zr3=C>)3=AGXx*kY#1L+e$nh!`{0n+k7`Z18^0n*Iu z5ODz@?FQt_18FZHod%=@fb>QnEd!(v0ckNHeIH260qJi*+5kw4axgFi0ckf520aE_ qAY&#_AOT2U2hyBC+5$*`9N+|`K?eE(X^?pQ=1Rty%$u!Q!&v~J;}xI) delta 230 zcmaDUG)Z{Enfgjb28OFF3=9zr3=IBk3=AGXdIgZ?2GSpaG#`-u3#8?NG!Huv18E%~ z4HCBm(r!T714w%T>3$$B0HkjLX&E5>8c2%)X$}qs200)t52Ou%v>lKR0@C>$40;T< zK*k}UKmw3v-~=OvL?8`vKsJyD8CVV#@7P?)IFnf|MAx|}HL)Z!KTp>su_V<>!N|bS YNEeJ03=OPI47Cjm4GcE(vqrH10JCr&(*OVf diff --git a/reversion/locale/pl/LC_MESSAGES/django.mo b/reversion/locale/pl/LC_MESSAGES/django.mo index 3fe12d28fea2e167969ec4198a5af9d91e9e7904..1ee671a6835a53740b3dad7a7a55c52261f7662b 100644 GIT binary patch delta 394 zcmYMuD@+4H5XSLoO3U#Gkdz?6ED3FTf@Jq7DYV_IBPc@BARB@~m4jRZs>yj22_B1v zN`gY*Q0NLII35ba{~o03H?!Z(%#BK)i&wAFC?~R15SbH^pQ6YfPE3k)u!B>0i!*qa z^^dqr|BMd4;XHoe0{-GI&JxBRU-lT~K#z#=ce z_hkA!zkW|W&pZ9pkB@yXAd(4*Bt*msi}Z1g13Y6C^@vCu^XTU)X0eVM_A!YgOyL~6 zxcz%Uog8C1hXpL5tx5A&O#g+8Es7(K@rF&TaCL(d^dGrLy+hy{EDw$LX6Cqy)zauV d?#wP)$}|mCH!PFUR*jl6l{)o8FYgoH1-~G5Af5mK diff --git a/reversion/locale/pl/LC_MESSAGES/django.po b/reversion/locale/pl/LC_MESSAGES/django.po index 2b2b2b38..5ff24a14 100644 --- a/reversion/locale/pl/LC_MESSAGES/django.po +++ b/reversion/locale/pl/LC_MESSAGES/django.po @@ -15,6 +15,9 @@ msgstr "" "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 ? 0 : (n%10>=2 && n%10<=4) && (n" +"%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n" +"%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" #: admin.py:100 msgid "Initial version." diff --git a/reversion/locale/pt_BR/LC_MESSAGES/django.mo b/reversion/locale/pt_BR/LC_MESSAGES/django.mo index a8754138f78e44026867066b7de5d96b2c936896..61c4708fe5502730cec7f37aaff75b360592c9e1 100644 GIT binary patch delta 229 zcmX>pbV_K#k$N9S28P2d3=BdH3=9Hn3=F|QS{X=(0qJre%?+f_0ckEEeG5o)0_mqv z{(B$|)XMM+NP7b5|3KOoNPDtF%$p9RWq|xuKw1Sz9|zKcK>7`kmITuO*csFq%zz9D z4v5A6Kw2BfUks!Rf%JJG4MYr%n==`wGf&oJeX1FdQ(BamqwAJmlv`}2kXHca7TX$G VD?pgG8hHwK3Wl22n|ax6m;tP*Cm8?$ delta 226 zcmX>lbW&)-k$P`N28P2d3=BdH3=I5i3=F|QS_w#p0qHUz%?+f_0%9V1mITs&*%{Or%zzAW z4v58mKw2BfUj(EJf%G{b4MYqMn==`wGpmK@Iv1rTmSpDV>AEDAq*^H$85mgV8d&HW ZSt=MBTN#*W8vsQn|7CfxnV-#?835F$BkKSF diff --git a/reversion/locale/pt_BR/LC_MESSAGES/django.po b/reversion/locale/pt_BR/LC_MESSAGES/django.po index 76c4679c..b9af526d 100644 --- a/reversion/locale/pt_BR/LC_MESSAGES/django.po +++ b/reversion/locale/pt_BR/LC_MESSAGES/django.po @@ -14,6 +14,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" #: .\admin.py:128 #: .\templates\reversion\change_list.html.py:7 diff --git a/reversion/locale/ru/LC_MESSAGES/django.mo b/reversion/locale/ru/LC_MESSAGES/django.mo index c78390c64a158025fe0a39f842832d1ab024e877..741a7b4ac2b0b8f1eecac4d3a6b863264807569e 100644 GIT binary patch delta 379 zcmYMtKTZNs5XbRB1Ysq{#6&PA^0tt;h4H<0DU^LnY^*Gdoza-s^T$AJkOUJsfSrYn z7qD?F@4y-8y@lTgZmd2tnfc9pR=;bnvx&DNGG7(h5s{yw$TQ~GL|$-;+gK`zl(CLm z7~lq4tl$Ms@ft@sN8j~?cJT=#e8U)LSjDm@M^dNoJ&`*+!#;lCEpD!hd|?-Bs4D3w z_nuFa!d`B2*?An?cMi|{gYNKD^&S@O&_*q_EbT#W-)L)%YBZE9wQr44ZKW0K^ob3# zQEVeuE)qI6p&E~uk;Fs+5%U+ZR=bhgYm#sN=TCxcpc(Kddm39#v{lH3S{O*r z1=4&#dLxkL0n!J6bPJF^3#6-nbPxvvgCd9zq@{uMdmwEBq`85H*Z^q=2 && n" +"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n" +"%100>=11 && n%100<=14)? 2 : 3);\n" #: admin.py:122 #: templates/reversion/change_list.html:8 diff --git a/reversion/locale/sk/LC_MESSAGES/django.mo b/reversion/locale/sk/LC_MESSAGES/django.mo index cb6a257a4dbed813e9730b7a86e63fbfbfad1ac3..250bf5fb65533e84082a206389d2a372f749317b 100644 GIT binary patch delta 289 zcmdlfd_Z`@nfhsr3=9WY7#Knr7#PCY7#N~~^co<|38XpL85p>Mv=ER6@#TOtE0ERz z(xO0G4@i3hX%`^v1EePaX+a=;7f4G3>Gwcd14wgoFfbSZX%irA45U+mbR>|T$-$t< zUOnwx#u Ga+m=_cPYvM delta 280 zcmX>gyi<6>nfiW428IJH3=AO*3=9Em3=GjgdMS|R1k#M`3=G^rnj1)i_~JmC6-X-r zX;C1p4y3(-v@MYK0n(j7S`bKI1Jcq!`UQ~I0MaZR3=9T9S{Fzg1L-&*9SNi-aWLpH zSOOVufC5oKS`BCmFOY5o(jW`_fdt6FnNac7n=2VxnAJjbor_WvOEUBGbX^ilQmqt> z3=B@qvl$c|yk!NdbsHtGDV4z^7ppj>1 ZYows2rjTc2YXTB91PU5$4q?k-1^^HND|i3^ diff --git a/reversion/locale/sk/LC_MESSAGES/django.po b/reversion/locale/sk/LC_MESSAGES/django.po index f6750fb6..106d782d 100644 --- a/reversion/locale/sk/LC_MESSAGES/django.po +++ b/reversion/locale/sk/LC_MESSAGES/django.po @@ -16,7 +16,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" +"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n " +">= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);\n" #: admin.py:153 msgid "Initial version." diff --git a/reversion/locale/sl_SI/LC_MESSAGES/django.mo b/reversion/locale/sl_SI/LC_MESSAGES/django.mo index 0653addb1b7d703debee645f0a306acc25d98d87..6ba9b3e3dfc2ae314feedf2abfe4ec7e070af795 100644 GIT binary patch delta 302 zcmdlhGDmd6o%#+&28ME01_pKpAOI51K#BuM`vYkoARP>(=K$##ApHSI2LWkc4h9A; zAiV%ca{}qrKpJQS!&V?I0;G2XX?q}j0ZI!174ZZ40YF+3NGAYk6(C*3$zaDI4rI&+ z3RnW^?NEu2Ksp-87X#V?WHJ;1X*M8T1El$YbTd?Z(&m|r?5uGCIi*F3Il6B7MY+XR z3V8)!Zn3S2wE~1`tC6Q_XkcJ#Yp7taV4wh00^u3~xrPX?u|iD^Ov(f(WdxKm*0kO{ IpKTs90Gi$~u>b%7 delta 207 zcmXZWzY2m-6vy%3CDd%FAtOTCT$-C2%A_?25+u-gsx7FYu`wK)e3M=w!6&e9 zAYAy|a}MXvZBTfp&v9a;gC#kV9=5c?CvNbIG0rJwc*ZT3*ukhRxwu9~C_xW13~}H1 w!X9yjBQ!Jx=(}2I$Y)7nhP$S4!Ib!oJoVYeEgEAN1N`Tw`PDd9UfcK954lDb1ONa4 diff --git a/reversion/locale/sl_SI/LC_MESSAGES/django.po b/reversion/locale/sl_SI/LC_MESSAGES/django.po index 33353fbb..366cbb82 100644 --- a/reversion/locale/sl_SI/LC_MESSAGES/django.po +++ b/reversion/locale/sl_SI/LC_MESSAGES/django.po @@ -16,6 +16,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n" +"%100==4 ? 2 : 3);\n" #: admin.py:66 msgid "Initial version." diff --git a/reversion/locale/sv/LC_MESSAGES/django.mo b/reversion/locale/sv/LC_MESSAGES/django.mo index 5aefd0334c1047d91cc6b05682f12cb95192c99f..e3f29e235424de7e2906591f72ba8635050e574f 100644 GIT binary patch delta 207 zcmXZWF$%&^5QO0gF(!&to{gA3poQ24gn*)D>XcT3gxn!lNJ_y@@B**1uMf52ui z_$PejmiN>1?WyOzXm4%vTunnF<*3L&MD{U}8NM)y!}wn-(7_zt!vwA|j9bj$9(#C1 z6F;b-VmXf{X3-AjWkR~rBJ2obykQlKTz7GVDLmuvLsy&NtPZti>DoA;VY-=ErNVqf4`@J->39an(JMMbZ#gn;Ww*RnSudOE9hnj>#)y)9UOtnGqwYo}6UD;&un5WWP=j;QaW? kKEnro{{R;n?THg_a0@c$6m5iO(RtKF*a~mjl~mgL09$5M(f|Me delta 342 zcmXxbze)o^7zFSMn8Xp!s?JHVmKw7h!7HpkXo?O z%EmXiN3iz^EX6_)d#`aeYW17>cIUmE&fdQEzB0g77RUiO$pc*)(R=zyV>%cG`ZT3y zv|9kC=m(vo-+vay0L5OS*R)0lG@)f06oECmPhGz$qJepaM;?yoGd-Y7T`(HXih z0Zh{xb;BL%?#IbPW_QHf^S9c~Fg`tx{OvGq2Fi$D_qFz=qO=Lbtcnr2d&{PWl?`># zx@?CnyBsZQu~sY3Q!dJ!)mm*TQNge`toeVc+p3Ghh19jEPUm;RKeED&=Efv1`BLr& DUS>NA diff --git a/reversion/locale/uk/LC_MESSAGES/django.po b/reversion/locale/uk/LC_MESSAGES/django.po index 93a1db22..e7bdf6c4 100644 --- a/reversion/locale/uk/LC_MESSAGES/django.po +++ b/reversion/locale/uk/LC_MESSAGES/django.po @@ -13,8 +13,10 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" -"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != " +"11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % " +"100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || " +"(n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\n" #: reversion/admin.py:83 msgid "Initial version." msgstr "Початкова версія." diff --git a/reversion/locale/zh_CN/LC_MESSAGES/django.mo b/reversion/locale/zh_CN/LC_MESSAGES/django.mo deleted file mode 100644 index 6b0fa29e1c9f710f0b308a53f41ba40fcd7b06ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2183 zcma)*+fy4=9LHB%Z}C>^bw(Y}bcTUiH%+jkHI&*E(voQc5kg0N>1?uxu+CtLW;e-`I{pKGPj-_a?O4y`x1XG|zw^8A zZ@YJna#$}xUW5Dsc^&fELwI2Q35LMGzz@K~4|CjO;0X8xIOgnU!JXLO0C#~);8E}{ z*a+^}VV^gFuVCK+J_~B#%V0nFHh2|`fWLw@;2SXdA=m^m`!jej|MTEe;G%P$0Y$`k z7km%rRgZGq``{@MKW@t5yu$^DD>a`_E>?TCo`j(E+#?X?x4asT2!V66$y5_q$))1Bm$nXk7?X+bN|yAE*s3OK)N;BBdIBUt39Jj(km9#WRfZd z*NEu$8&Z;TO^8g@dbuVQo1h{Yk|39^ySa!)b-kE@F7!|mOBn{DyeE;Ok1aW{luJoG z&_pTx|1#3*RII8-5t4MT&Xo>I1>EwYXz$)Vj$>%esj>m4hti{P+spPAVcCiVsaDmn zMzTu5X%uNy)g)Co-KB_DgpX+cL*9fNu*9!J$WQ?lIaCLL;uB!yl=Madxe2?;@R zD(GegH4FK(H<5@c)sA`sgM>1No>iAtv{- zxR|COifqOt0iA>$K};e9T{5VfYuBm9#hs}r&TL$x$XMi3>`g>d*(mZskw0mVsE}sXms%G$EJt2ww(Nsd`qpFX%A}vwAf!V@b^K9OHq}Jm-zC-p)ck6bc=YCAX?2 zDmn-?91I64`FY(QSA(h;_)hp}Z#UvL=qJYhZW)cMW98O#YSgz!kMRd9dMvC?s__9O zu8NY9@R5Tt$#6yFlqSghF;z?IKB9CNw|c#|j@W;t7omt z*Q|-DZ04Hv`K&oNlwIq~uH7(Km(BEa;mRBo%%%1G4>P~_^*`XIx%ss@IAm*^>6_X0 z3)td*ZZK_4e9!LZm(J!FH#qC^Pld}XocYCoIlRmj<}Vdye&o#br1|XyTesxs&ZN08 zW?dXCe6_eGz71<(u42>4W{awMa{wB49Cl8K=3jU0Q+!*_$LVNo$$9&``|P)U;AKxx zt$f3zoIiKpFBx^V(j(^iDf{{#w=QNkr_JFdq-#!GM~@1Vv-rcJzjY~p`%8223@fCy zJY!7^GQHd5_D$BOQv8*MmFtjQ8$v0W$xJ4@Icg4%S>r2q1K<-@VJm-T6KdAjQlal^ u{?57F;!JL2AotysT>rYYnlaB$!v}Mo`{ufpUM)4#OplwH4SR@6ul@nBrxDoz diff --git a/reversion/locale/zh_CN/LC_MESSAGES/django.po b/reversion/locale/zh_CN/LC_MESSAGES/django.po deleted file mode 100644 index 75e72312..00000000 --- a/reversion/locale/zh_CN/LC_MESSAGES/django.po +++ /dev/null @@ -1,121 +0,0 @@ -# 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: 2014-06-12 14:21+0800\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=1; plural=0;\n" - -#: admin.py:160 -msgid "Initial version." -msgstr "初始版本" - -#: admin.py:194 templates/reversion/change_list.html:7 -#: templates/reversion/recover_form.html:11 -#: templates/reversion/recover_list.html:11 -#, python-format -msgid "Recover deleted %(name)s" -msgstr "恢复已删除的 %(name)s" - -#: admin.py:311 -#, python-format -msgid "Reverted to previous version, saved on %(datetime)s" -msgstr "恢复到 %(datetime)s 的版本" - -#: admin.py:313 -#, python-format -msgid "" -"The %(model)s \"%(name)s\" was reverted successfully. You may edit it again " -"below." -msgstr "%(model)s \"%(name)s\" 已成功恢复,你可以在下面在此编辑它。" - -#: admin.py:398 -#, python-format -msgid "Recover %(name)s" -msgstr "恢复 %(name)s" - -#: admin.py:412 -#, python-format -msgid "Revert %(name)s" -msgstr "恢复 %(name)s" - -#: models.py:55 -msgid "date created" -msgstr "创建日期" - -#: models.py:62 -msgid "user" -msgstr "用户" - -#: models.py:66 -msgid "comment" -msgstr "评论" - -#: templates/reversion/object_history.html:8 -msgid "" -"Choose a date from the list below to revert to a previous version of this " -"object." -msgstr "单击下方的日期以恢复当前对象到之前的版本。" - -#: templates/reversion/object_history.html:15 -#: templates/reversion/recover_list.html:24 -msgid "Date/time" -msgstr "时间" - -#: templates/reversion/object_history.html:16 -msgid "User" -msgstr "用户" - -#: templates/reversion/object_history.html:17 -msgid "Comment" -msgstr "评论" - -#: templates/reversion/object_history.html:38 -msgid "" -"This object doesn't have a change history. It probably wasn't added via this " -"admin site." -msgstr "此对象不存在任何变更历史,它可能不是通过管理站点添加的。" - -#: templates/reversion/recover_form.html:8 -#: templates/reversion/recover_list.html:8 -#: templates/reversion/revision_form.html:8 -msgid "Home" -msgstr "首页" - -#: templates/reversion/recover_form.html:18 -msgid "Press the save button below to recover this version of the object." -msgstr "单击保存按钮以恢复为此版本。" - -#: templates/reversion/recover_list.html:18 -msgid "" -"Choose a date from the list below to recover a deleted version of an object." -msgstr "单击下方的日期以恢复一个已删除的对象。" - -#: templates/reversion/recover_list.html:38 -msgid "There are no deleted objects to recover." -msgstr "没有可供恢复的已删除对象。" - -#: templates/reversion/revision_form.html:12 -msgid "History" -msgstr "历史" - -#: templates/reversion/revision_form.html:13 -#, python-format -msgid "Revert %(verbose_name)s" -msgstr "恢复 %(verbose_name)s" - -#: templates/reversion/revision_form.html:26 -msgid "Press the save button below to revert to this version of the object." -msgstr "单击保存按钮将此对象恢复到此版本。" diff --git a/reversion/locale/zh_Hans/LC_MESSAGES/django.mo b/reversion/locale/zh_Hans/LC_MESSAGES/django.mo index 6b0fa29e1c9f710f0b308a53f41ba40fcd7b06ee..490a373cb87ce0b9378b1a10f13446d4ce490c11 100644 GIT binary patch delta 1329 zcma*l|4S2L90%}^IyWmVwJg)3Lol6?f&LKDSJMilUy6PRvf|v%INR>V-A$=}ScKMu z%;ehCni+voQDgMg#OWVW)DP}%Gy1_G{3WQ~&vvX;R6g9Z*R$vDxzFeGJa@fpq9X9V z+!i9Vt>|0OZFxja_zALhE1$>?hv6|e3Xekrw!&QnL{0EAJPM<5FI% zbQnLSupJ&E3eaf`+Awj?y0HK&@&5~44ND4%*1<}+3D(1Aco5b@4W5GI5KCwovVdP< zF)Tp#ERj;k-d6?LbG5Jt@u>lWJ*c)B;vyNVcf%fd0LEYoT!K4cQxVY-*a=zXk04w3 z&T_%>BV-ADh7Ir=WQjYG4J+sjtVVqDV!$38ux^AQt8@x-a1PeN5AY;(pbQqU!_o!W z`fkf!%RcM=u;nOZr@9e+4LWN9wy+{S%xv3cj5nYoR=TmRVhzVl>(e8mGRV%5eKRZ( z*09^zwiY8+VTm=vj>+St7^)QRPrM}S@WtsZ`-eLFZ^yd^}$3uqMx;I(_8Hw z0dwTO`8<@0$Bq8UKT=5!1kK13>||nk(CD8vqHmIS`ZAMdC}s{u7p`2TmA%1?JT?dB zj9~27=L5f=UN$5aBqt^j&zKo9f+NO_aLy+FjGLGrH=d6EJ@e%3j1e17%#EjBh7tPJ z;>Zg!ZeBBnp3&k|cu}9W-!0C>OhscxJfxp+wCgFySY5jG%pB!DK*`$=&ERWmj(z|$ CrC);p delta 779 zcmZwEO=}ZD7zgl4nrs^1`l3}qt3x3+RZ=!<@ui0fwued;6nn5mkTpAMU^fdpiKQr{ zR>9&6R<;HyEzwv@5t@h!6;q{058@XPieNUzlZs!!|7;ddKJ0&gGjo`EW;fqqQ`~_@=C3qaJ!2#INP+uQ} zJ24-DO@w5Lg~1LiOu~b37M_NmVHEDguIJz&6bC%TOEjW~2>iK}5CIac{d7WAV8+Ah@7gqM2<7_Hln7`gKcu zeo-3{e0>r`#KHfn|B7jHMpde*_A`GjXc#P&ww1tFyFEC1ibkS_sWH8a(~d|qr5db@ zchE7FQ;S_?R+?#)XA%j<`K64mk9X4%GeZq^oH8w$rg+rLYBEJHGTj{Ows~+qkm1a- z6{&CEaID{AYC37AVgqWL^-@KS_r&BwvA9CxJ-tf&fP6%jUmKF&XTJ>Xv1dZ(rCRA$ zt@zIV(pa=};S)puZO&PFRh^o4?&PaC3eLj)pR4yO@1HmiN|o|+XKtzT_U@1Q0tVIP bh3~7m+J}cu@#eRQNqap!E6prbr&h>ce)E?g diff --git a/reversion/locale/zh_Hans/LC_MESSAGES/django.po b/reversion/locale/zh_Hans/LC_MESSAGES/django.po index 75e72312..b70ee59b 100644 --- a/reversion/locale/zh_Hans/LC_MESSAGES/django.po +++ b/reversion/locale/zh_Hans/LC_MESSAGES/django.po @@ -2,13 +2,13 @@ # 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: 2014-06-12 14:21+0800\n" +"POT-Creation-Date: 2024-01-25 16:26+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,104 +18,138 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: admin.py:160 +#: .\admin.py:70 msgid "Initial version." msgstr "初始版本" -#: admin.py:194 templates/reversion/change_list.html:7 -#: templates/reversion/recover_form.html:11 -#: templates/reversion/recover_list.html:11 -#, python-format -msgid "Recover deleted %(name)s" -msgstr "恢复已删除的 %(name)s" - -#: admin.py:311 +#: .\admin.py:178 #, python-format msgid "Reverted to previous version, saved on %(datetime)s" msgstr "恢复到 %(datetime)s 的版本" -#: admin.py:313 +#: .\admin.py:186 #, python-format -msgid "" -"The %(model)s \"%(name)s\" was reverted successfully. You may edit it again " -"below." -msgstr "%(model)s \"%(name)s\" 已成功恢复,你可以在下面在此编辑它。" +msgid "Could not load %(object_repr)s version - not found" +msgstr "无法载入 %(object_repr)s 版本 - 未找到" -#: admin.py:398 +#: .\admin.py:206 #, python-format msgid "Recover %(name)s" msgstr "恢复 %(name)s" -#: admin.py:412 +#: .\admin.py:222 #, python-format msgid "Revert %(name)s" msgstr "恢复 %(name)s" -#: models.py:55 +#: .\admin.py:259 .\templates\reversion\change_list.html:7 +#: .\templates\reversion\recover_form.html:10 +#: .\templates\reversion\recover_list.html:10 +#, python-format +msgid "Recover deleted %(name)s" +msgstr "恢复已删除的 %(name)s" + +#: .\apps.py:7 +msgid "Reversion" +msgstr "版本记录" + +#: .\models.py:39 +#, python-format +msgid "Could not save %(object_repr)s version - missing dependency." +msgstr "无法保存 %(object_repr)s 版本 - 缺少依赖" + +#: .\models.py:52 msgid "date created" msgstr "创建日期" -#: models.py:62 +#: .\models.py:61 msgid "user" msgstr "用户" -#: models.py:66 +#: .\models.py:67 msgid "comment" msgstr "评论" -#: templates/reversion/object_history.html:8 +#: .\models.py:116 +msgid "revision" +msgstr "修改" + +#: .\models.py:117 +msgid "revisions" +msgstr "修改" + +#: .\models.py:275 +#, python-format +msgid "Could not load %(object_repr)s version - incompatible version data." +msgstr "无法载入 %(object_repr)s 版本 - 不兼容的版本数据。" + +#: .\models.py:279 +#, python-format +msgid "Could not load %(object_repr)s version - unknown serializer %(format)s." +msgstr "无法载入 %(object_repr)s 版本 - 未知的序列化 %(format)s。" + +#: .\models.py:335 +#, fuzzy +msgid "version" +msgstr "版本" + +#: .\models.py:336 +msgid "versions" +msgstr "版本" + +#: .\templates\reversion\object_history.html:8 msgid "" "Choose a date from the list below to revert to a previous version of this " "object." msgstr "单击下方的日期以恢复当前对象到之前的版本。" -#: templates/reversion/object_history.html:15 -#: templates/reversion/recover_list.html:24 +#: .\templates\reversion\object_history.html:15 +#: .\templates\reversion\recover_list.html:23 msgid "Date/time" msgstr "时间" -#: templates/reversion/object_history.html:16 +#: .\templates\reversion\object_history.html:16 msgid "User" msgstr "用户" -#: templates/reversion/object_history.html:17 -msgid "Comment" -msgstr "评论" +#: .\templates\reversion\object_history.html:17 +msgid "Action" +msgstr "操作" -#: templates/reversion/object_history.html:38 +#: .\templates\reversion\object_history.html:38 msgid "" "This object doesn't have a change history. It probably wasn't added via this " "admin site." msgstr "此对象不存在任何变更历史,它可能不是通过管理站点添加的。" -#: templates/reversion/recover_form.html:8 -#: templates/reversion/recover_list.html:8 -#: templates/reversion/revision_form.html:8 +#: .\templates\reversion\recover_form.html:7 +#: .\templates\reversion\recover_list.html:7 +#: .\templates\reversion\revision_form.html:7 msgid "Home" msgstr "首页" -#: templates/reversion/recover_form.html:18 +#: .\templates\reversion\recover_form.html:20 msgid "Press the save button below to recover this version of the object." msgstr "单击保存按钮以恢复为此版本。" -#: templates/reversion/recover_list.html:18 +#: .\templates\reversion\recover_list.html:17 msgid "" "Choose a date from the list below to recover a deleted version of an object." msgstr "单击下方的日期以恢复一个已删除的对象。" -#: templates/reversion/recover_list.html:38 +#: .\templates\reversion\recover_list.html:37 msgid "There are no deleted objects to recover." msgstr "没有可供恢复的已删除对象。" -#: templates/reversion/revision_form.html:12 +#: .\templates\reversion\revision_form.html:11 msgid "History" msgstr "历史" -#: templates/reversion/revision_form.html:13 +#: .\templates\reversion\revision_form.html:12 #, python-format msgid "Revert %(verbose_name)s" msgstr "恢复 %(verbose_name)s" -#: templates/reversion/revision_form.html:26 +#: .\templates\reversion\revision_form.html:21 msgid "Press the save button below to revert to this version of the object." msgstr "单击保存按钮将此对象恢复到此版本。" diff --git a/reversion/management/commands/__init__.py b/reversion/management/commands/__init__.py index 87253a41..cb5a14ea 100644 --- a/reversion/management/commands/__init__.py +++ b/reversion/management/commands/__init__.py @@ -43,7 +43,7 @@ def get_models(self, options): try: model = apps.get_model(label) except LookupError: - raise CommandError("Unknown model: {}".format(label)) + raise CommandError(f"Unknown model: {label}") selected_models.add(model) else: # This is just an app - no model qualifier. @@ -51,7 +51,7 @@ def get_models(self, options): try: app = apps.get_app_config(app_label) except LookupError: - raise CommandError("Unknown app: {}".format(app_label)) + raise CommandError(f"Unknown app: {app_label}") selected_models.update(app.get_models()) for model in selected_models: if is_registered(model): diff --git a/reversion/management/commands/createinitialrevisions.py b/reversion/management/commands/createinitialrevisions.py index 628273a8..9d648fa1 100644 --- a/reversion/management/commands/createinitialrevisions.py +++ b/reversion/management/commands/createinitialrevisions.py @@ -2,7 +2,7 @@ from django.apps import apps from django.core.management import CommandError -from django.db import reset_queries, transaction, router +from django.db import connections, reset_queries, transaction, router from reversion.models import Revision, Version, _safe_subquery from reversion.management.commands import BaseRevisionCommand from reversion.revisions import create_revision, set_comment, add_to_revision, add_meta @@ -48,10 +48,13 @@ def handle(self, *app_labels, **options): model = apps.get_model(label) meta_models.append(model) except LookupError: - raise CommandError("Unknown model: {}".format(label)) + raise CommandError(f"Unknown model: {label}") meta_values = meta.values() - # Create revisions. + # Determine if we should use queryset.iterator() using = using or router.db_for_write(Revision) + server_side_cursors = not connections[using].settings_dict.get('DISABLE_SERVER_SIDE_CURSORS') + use_iterator = connections[using].vendor in ("postgresql",) and server_side_cursors + # Create revisions. with transaction.atomic(using=using): for model in self.get_models(options): # Check all models for empty revisions. @@ -70,28 +73,48 @@ def handle(self, *app_labels, **options): ), "object_id", ) + live_objs = live_objs.order_by() # Save all the versions. - ids = list(live_objs.values_list("pk", flat=True).order_by()) - total = len(ids) - for i in range(0, total, batch_size): - chunked_ids = ids[i:i+batch_size] - objects = live_objs.in_bulk(chunked_ids) - for obj in objects.values(): - with create_revision(using=using): - if meta: - for model, values in zip(meta_models, meta_values): - add_meta(model, **values) - set_comment(comment) - add_to_revision(obj, model_db=model_db) - created_count += 1 - reset_queries() - if verbosity >= 2: - self.stdout.write("- Created {created_count} / {total}".format( - created_count=created_count, - total=total, - )) + if use_iterator: + total = live_objs.count() + if total: + for obj in live_objs.iterator(batch_size): + self.create_revision(obj, using, meta, meta_models, meta_values, comment, model_db) + created_count += 1 + # Print out a message every batch_size if feeling extra verbose + if not created_count % batch_size: + self.batch_complete(verbosity, created_count, total) + else: + # Save all the versions. + ids = list(live_objs.values_list("pk", flat=True)) + total = len(ids) + for i in range(0, total, batch_size): + chunked_ids = ids[i:i+batch_size] + objects = live_objs.in_bulk(chunked_ids) + for obj in objects.values(): + self.create_revision(obj, using, meta, meta_models, meta_values, comment, model_db) + created_count += 1 + # Print out a message every batch_size if feeling extra verbose + self.batch_complete(verbosity, created_count, total) + # Print out a message, if feeling verbose. if verbosity >= 1: self.stdout.write("- Created {total} / {total}".format( total=total, )) + + def create_revision(self, obj, using, meta, meta_models, meta_values, comment, model_db): + with create_revision(using=using): + if meta: + for model, values in zip(meta_models, meta_values): + add_meta(model, **values) + set_comment(comment) + add_to_revision(obj, model_db=model_db) + + def batch_complete(self, verbosity, created_count, total): + reset_queries() + if verbosity >= 2: + self.stdout.write("- Created {created_count} / {total}".format( + created_count=created_count, + total=total, + )) diff --git a/reversion/migrations/0001_squashed_0004_auto_20160611_1202.py b/reversion/migrations/0001_squashed_0004_auto_20160611_1202.py index f35f5a33..afb1618c 100644 --- a/reversion/migrations/0001_squashed_0004_auto_20160611_1202.py +++ b/reversion/migrations/0001_squashed_0004_auto_20160611_1202.py @@ -23,7 +23,9 @@ class Migration(migrations.Migration): ('user', models.ForeignKey(blank=True, help_text='The user who created this revision.', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='user')), ], options={ - "ordering": ("-pk",) + "ordering": ("-pk",), + 'verbose_name': 'revision', + 'verbose_name_plural': 'revisions', }, ), migrations.CreateModel( @@ -39,11 +41,13 @@ class Migration(migrations.Migration): ('db', models.CharField(help_text='The database the model under version control is stored in.', max_length=191)), ], options={ - "ordering": ("-pk",) + "ordering": ("-pk",), + 'verbose_name': 'version', + 'verbose_name_plural': 'versions', }, ), migrations.AlterUniqueTogether( name='version', - unique_together=set([('db', 'content_type', 'object_id', 'revision')]), + unique_together={('db', 'content_type', 'object_id', 'revision')}, ), ] diff --git a/reversion/migrations/0002_add_index_on_version_for_content_type_and_db.py b/reversion/migrations/0002_add_index_on_version_for_content_type_and_db.py new file mode 100644 index 00000000..75a5a691 --- /dev/null +++ b/reversion/migrations/0002_add_index_on_version_for_content_type_and_db.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.6 on 2021-08-16 18:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reversion', '0001_squashed_0004_auto_20160611_1202'), + ] + + operations = [ + migrations.AddIndex( + model_name='version', + index=models.Index(fields=['content_type', 'db'], name='reversion_v_content_f95daf_idx'), + ), + ] diff --git a/reversion/models.py b/reversion/models.py index f9e61a56..7f64bb48 100644 --- a/reversion/models.py +++ b/reversion/models.py @@ -1,6 +1,8 @@ from collections import defaultdict from itertools import chain, groupby +import logging +import django from django.apps import apps from django.conf import settings from django.contrib.contenttypes.fields import GenericForeignKey @@ -13,7 +15,7 @@ from django.db.models.functions import Cast from django.utils.encoding import force_str from django.utils.functional import cached_property -from django.utils.translation import ugettext +from django.utils.translation import gettext from django.utils.translation import gettext_lazy as _ from encrypted_fields.fields import EncryptedTextField @@ -22,6 +24,9 @@ _get_content_type, _get_options) +logger = logging.getLogger(__name__) + + def _safe_revert(versions): unreverted_versions = [] for version in versions: @@ -29,9 +34,10 @@ def _safe_revert(versions): with transaction.atomic(using=version.db): version.revert() except (IntegrityError, ObjectDoesNotExist): + logger.warning(f'Could not revert to {version}', exc_info=True) unreverted_versions.append(version) if len(unreverted_versions) == len(versions): - raise RevertError(ugettext("Could not save %(object_repr)s version - missing dependency.") % { + raise RevertError(gettext("Could not save %(object_repr)s version - missing dependency.") % { "object_repr": unreverted_versions[0], }) if unreverted_versions: @@ -108,6 +114,8 @@ def __str__(self): return ", ".join(force_str(version) for version in self.version_set.all()) class Meta: + verbose_name = _('revision') + verbose_name_plural = _('revisions') app_label = "reversion" ordering = ("-pk",) @@ -134,20 +142,51 @@ def get_deleted(self, model, model_db=None): model_db = model_db or router.db_for_write(model) connection = connections[self.db] if self.db == model_db and connection.vendor in ("sqlite", "postgresql", "oracle"): - model_qs = ( - model._default_manager - .using(model_db) - .annotate(_pk_to_object_id=Cast("pk", Version._meta.get_field("object_id"))) - .filter(_pk_to_object_id=models.OuterRef("object_id")) - ) - subquery = ( - self.get_for_model(model, model_db=model_db) - .annotate(pk_not_exists=~models.Exists(model_qs)) - .filter(pk_not_exists=True) - .values("object_id") - .annotate(latest_pk=models.Max("pk")) - .values("latest_pk") - ) + pk_field_name = model._meta.pk.name + object_id_cast_target = model._meta.get_field(pk_field_name) + if django.VERSION >= (2, 1): + # django 2.0 contains a critical bug that doesn't allow the code below to work, + # fallback to casting primary keys then + # see https://code.djangoproject.com/ticket/29142 + if django.VERSION < (2, 2): + # properly cast autofields for django before 2.2 as it was fixed in django itself later + # see https://github.com/django/django/commit/ac25dd1f8d48accc765c05aebb47c427e51f3255 + object_id_cast_target = { + "AutoField": models.IntegerField(), + "BigAutoField": models.BigIntegerField(), + }.get(object_id_cast_target.__class__.__name__, object_id_cast_target) + casted_object_id = Cast(models.OuterRef("object_id"), object_id_cast_target) + model_qs = ( + model._default_manager + .using(model_db) + .filter(**{pk_field_name: casted_object_id}) + ) + else: + model_qs = ( + model._default_manager + .using(model_db) + .annotate(_pk_to_object_id=Cast("pk", Version._meta.get_field("object_id"))) + .filter(_pk_to_object_id=models.OuterRef("object_id")) + ) + # conditional expressions are being supported since django 3.0 + # DISTINCT ON works only for Postgres DB + if connection.vendor == "postgresql" and django.VERSION >= (3, 0): + subquery = ( + self.get_for_model(model, model_db=model_db) + .filter(~models.Exists(model_qs)) + .order_by("object_id", "-pk") + .distinct("object_id") + .values("pk") + ) + else: + subquery = ( + self.get_for_model(model, model_db=model_db) + .annotate(pk_not_exists=~models.Exists(model_qs)) + .filter(pk_not_exists=True) + .values("object_id") + .annotate(latest_pk=models.Max("pk")) + .values("latest_pk") + ) else: # We have to use a slow subquery. subquery = self.get_for_model(model, model_db=model_db).exclude( @@ -158,7 +197,8 @@ def get_deleted(self, model, model_db=None): latest_pk=models.Max("pk") ).order_by().values_list("latest_pk", flat=True) # Perform the subquery. - return self.filter(pk__in=subquery) + # Filter by model to reduce query execution time. + return self.get_for_model(model, model_db=model_db).filter(pk__in=subquery) def get_unique(self): last_key = None @@ -233,11 +273,11 @@ def _object_version(self): return list(serializers.deserialize(self.format, data, ignorenonexistent=True, use_natural_foreign_keys=version_options.use_natural_foreign_keys))[0] except DeserializationError: - raise RevertError(ugettext("Could not load %(object_repr)s version - incompatible version data.") % { + raise RevertError(gettext("Could not load %(object_repr)s version - incompatible version data.") % { "object_repr": self.object_repr, }) except serializers.SerializerDoesNotExist: - raise RevertError(ugettext("Could not load %(object_repr)s version - unknown serializer %(format)s.") % { + raise RevertError(gettext("Could not load %(object_repr)s version - unknown serializer %(format)s.") % { "object_repr": self.object_repr, "format": self.format, }) @@ -293,10 +333,17 @@ def __str__(self): return self.object_repr class Meta: + verbose_name = _('version') + verbose_name_plural = _('versions') app_label = 'reversion' unique_together = ( ("db", "content_type", "object_id", "revision"), ) + indexes = ( + models.Index( + fields=["content_type", "db"] + ), + ) ordering = ("-pk",) @@ -329,25 +376,25 @@ def _safe_subquery(method, left_query, left_field_name, right_subquery, right_fi ) ): return getattr(left_query, method)(**{ - "{}__in".format(left_field_name): list(right_subquery.iterator()), + f"{left_field_name}__in": list(right_subquery.iterator()), }) else: # If the left hand side is not a text field, we need to cast it. if not isinstance(left_field, (models.CharField, models.TextField)): - left_field_name_str = "{}_str".format(left_field_name) + left_field_name_str = f"{left_field_name}_str" left_query = left_query.annotate(**{ left_field_name_str: _Str(left_field_name), }) left_field_name = left_field_name_str # If the right hand side is not a text field, we need to cast it. if not isinstance(right_field, (models.CharField, models.TextField)): - right_field_name_str = "{}_str".format(right_field_name) + right_field_name_str = f"{right_field_name}_str" right_subquery = right_subquery.annotate(**{ right_field_name_str: _Str(right_field_name), }).values_list(right_field_name_str, flat=True) right_field_name = right_field_name_str # Use Exists if running on the same DB, it is much much faster - exist_annotation_name = "{}_annotation_str".format(right_subquery.model._meta.db_table) + exist_annotation_name = f"{right_subquery.model._meta.db_table}_annotation_str" right_subquery = right_subquery.filter(**{right_field_name: models.OuterRef(left_field_name)}) left_query = left_query.annotate(**{exist_annotation_name: models.Exists(right_subquery)}) return getattr(left_query, method)(**{exist_annotation_name: True}) diff --git a/reversion/revisions.py b/reversion/revisions.py index 6a8dafd2..5e2ed4f6 100644 --- a/reversion/revisions.py +++ b/reversion/revisions.py @@ -1,11 +1,11 @@ +from contextvars import ContextVar from collections import namedtuple, defaultdict from contextlib import contextmanager from functools import wraps -from threading import local from django.apps import apps from django.core import serializers from django.core.exceptions import ObjectDoesNotExist -from django.db import models, transaction, router +from django.db import models, transaction, router, connections from django.db.models.query import QuerySet from django.db.models.signals import post_save, m2m_changed from django.utils.encoding import force_str @@ -34,23 +34,17 @@ )) -class _Local(local): - - def __init__(self): - self.stack = () - - -_local = _Local() +_stack = ContextVar("reversion-stack", default=[]) def is_active(): - return bool(_local.stack) + return bool(_stack.get()) def _current_frame(): if not is_active(): raise RevisionManagementError("There is no active revision for this thread") - return _local.stack[-1] + return _stack.get()[-1] def _copy_db_versions(db_versions): @@ -79,16 +73,17 @@ def _push_frame(manage_manually, using): db_versions={using: {}}, meta=(), ) - _local.stack += (stack_frame,) + _stack.set(_stack.get() + [stack_frame]) def _update_frame(**kwargs): - _local.stack = _local.stack[:-1] + (_current_frame()._replace(**kwargs),) + _stack.get()[-1] = _current_frame()._replace(**kwargs) def _pop_frame(): prev_frame = _current_frame() - _local.stack = _local.stack[:-1] + stack = _stack.get() + del stack[-1] if is_active(): current_frame = _current_frame() db_versions = { @@ -147,8 +142,7 @@ def _follow_relations(obj): if isinstance(follow_obj, models.Model): yield follow_obj elif isinstance(follow_obj, (models.Manager, QuerySet)): - for follow_obj_instance in follow_obj.all(): - yield follow_obj_instance + yield from follow_obj.all() elif follow_obj is not None: raise RegistrationError("{name}.{follow_name} should be a Model or QuerySet".format( name=obj.__class__.__name__, @@ -217,6 +211,7 @@ def add_to_revision(obj, model_db=None): def _save_revision(versions, user=None, comment="", meta=(), date_created=None, using=None): from reversion.models import Revision + from reversion.models import Version # Only save versions that exist in the database. # Use _base_manager so we don't have problems when _default_manager is overriden model_db_pks = defaultdict(lambda: defaultdict(set)) @@ -254,9 +249,17 @@ def _save_revision(versions, user=None, comment="", meta=(), date_created=None, # Save the revision. revision.save(using=using) # Save version models. + + can_use_bulk_create = connections[using].features.can_return_rows_from_bulk_insert + for version in versions: version.revision = revision - version.save(using=using) + if not can_use_bulk_create: + version.save(using=using) + + if can_use_bulk_create: + Version.objects.using(using).bulk_create(versions) + # Save the meta information. for meta_model, meta_fields in meta: meta_model._base_manager.db_manager(using=using).create( @@ -283,8 +286,15 @@ def _create_revision_context(manage_manually, using, atomic): _push_frame(manage_manually, using) try: yield + if transaction.get_connection(using).in_atomic_block and transaction.get_rollback(using): + # Transaction is in invalid state due to catched exception within yield statement. + # Do not try to create Revision, otherwise it would lead to the transaction management error. + # + # Atomic block could be called manually around `create_revision` context manager. + # That's why we have to check connection flag instead of `atomic` variable value. + return # Only save for a db if that's the last stack frame for that db. - if not any(using in frame.db_versions for frame in _local.stack[:-1]): + if not any(using in frame.db_versions for frame in _stack.get()[:-1]): current_frame = _current_frame() _save_revision( versions=current_frame.db_versions[using].values(), @@ -304,7 +314,7 @@ def create_revision(manage_manually=False, using=None, atomic=True): return _ContextWrapper(_create_revision_context, (manage_manually, using, atomic)) -class _ContextWrapper(object): +class _ContextWrapper: def __init__(self, func, args): self._func = func diff --git a/reversion/views.py b/reversion/views.py index 7b7e8334..f5105517 100644 --- a/reversion/views.py +++ b/reversion/views.py @@ -3,12 +3,6 @@ from reversion.revisions import create_revision as create_revision_base, set_user, get_user -class _RollBackRevisionView(Exception): - - def __init__(self, response): - self.response = response - - def _request_creates_revision(request): return request.method not in ("OPTIONS", "GET", "HEAD") @@ -30,23 +24,16 @@ def decorator(func): @wraps(func) def do_revision_view(request, *args, **kwargs): if request_creates_revision(request): - try: - with create_revision_base(manage_manually=manage_manually, using=using, atomic=atomic): - response = func(request, *args, **kwargs) - # Check for an error response. - if response.status_code >= 400: - raise _RollBackRevisionView(response) - # Otherwise, we're good. - _set_user_from_request(request) - return response - except _RollBackRevisionView as ex: - return ex.response + with create_revision_base(manage_manually=manage_manually, using=using, atomic=atomic): + response = func(request, *args, **kwargs) + _set_user_from_request(request) + return response return func(request, *args, **kwargs) return do_revision_view return decorator -class RevisionMixin(object): +class RevisionMixin: """ A class-based view mixin that wraps the request in a revision. diff --git a/setup.py b/setup.py index 4ccb7184..1a72d8bf 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,11 @@ -from setuptools import setup, find_packages -from reversion import __version__ +from setuptools import find_packages, setup +from reversion import __version__ # Load in babel support, if available. try: from babel.messages import frontend as babel + cmdclass = { "compile_catalog": babel.compile_catalog, "extract_messages": babel.extract_messages, @@ -16,13 +17,13 @@ def read(filepath): - with open(filepath, "r", encoding="utf-8") as f: + with open(filepath, encoding="utf-8") as f: return f.read() setup( name="encrypted-django-reversion", - version='.'.join(str(x) for x in __version__), + version=".".join(str(x) for x in __version__), license="BSD", description="An extension to the Django web framework that provides version control for model instances.", long_description=read('README.rst'), @@ -32,13 +33,14 @@ def read(filepath): zip_safe=False, packages=find_packages(), package_data={ - "reversion": ["locale/*/LC_MESSAGES/django.*", "templates/reversion/*.html"]}, + "reversion": ["locale/*/LC_MESSAGES/django.*", "templates/reversion/*.html"] + }, cmdclass=cmdclass, install_requires=[ - "django>=1.11", + "django>=4.2", "django-searchable-encrypted-fields>=0.1", ], - python_requires='>=3.6', + python_requires=">=3.8", classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", @@ -46,9 +48,11 @@ def read(filepath): "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Framework :: Django", - ] + ], ) diff --git a/tests/test_app/migrations/0001_initial.py b/tests/test_app/migrations/0001_initial.py index c2020c9b..0b0a0cbd 100644 --- a/tests/test_app/migrations/0001_initial.py +++ b/tests/test_app/migrations/0001_initial.py @@ -108,4 +108,11 @@ class Migration(migrations.Migration): ('revision', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='reversion.revision')), ], ), + migrations.CreateModel( + name='TestModelWithUniqueConstraint', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=191, unique=True)), + ], + ), ] diff --git a/tests/test_app/migrations/0002_alter_testmodel_related_and_more.py b/tests/test_app/migrations/0002_alter_testmodel_related_and_more.py new file mode 100644 index 00000000..d02186f9 --- /dev/null +++ b/tests/test_app/migrations/0002_alter_testmodel_related_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.1 on 2024-01-30 19:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('test_app', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='testmodel', + name='related', + field=models.ManyToManyField(blank=True, related_name='+', to='test_app.testmodelrelated'), + ), + migrations.AlterField( + model_name='testmodel', + name='related_through', + field=models.ManyToManyField(blank=True, related_name='+', through='test_app.TestModelThrough', to='test_app.testmodelrelated'), + ), + ] diff --git a/tests/test_app/models.py b/tests/test_app/models.py index a21e94f8..bfb3a16c 100644 --- a/tests/test_app/models.py +++ b/tests/test_app/models.py @@ -142,3 +142,11 @@ class TestModelInlineByNaturalKey(models.Model): TestModelWithNaturalKey, on_delete=models.CASCADE, ) + + +class TestModelWithUniqueConstraint(models.Model): + + name = models.CharField( + max_length=191, + unique=True, + ) diff --git a/tests/test_app/tests/base.py b/tests/test_app/tests/base.py index a477fbad..75a8bd40 100644 --- a/tests/test_app/tests/base.py +++ b/tests/test_app/tests/base.py @@ -17,9 +17,9 @@ # Test helpers. -class TestBaseMixin(object): +class TestBaseMixin: - multi_db = True + databases = "__all__" def reloadUrls(self): reload(import_module(settings.ROOT_URLCONF)) @@ -76,7 +76,7 @@ class TestBaseTransaction(TestBaseMixin, TransactionTestCase): pass -class TestModelMixin(object): +class TestModelMixin: def setUp(self): super().setUp() diff --git a/tests/test_app/tests/test_admin.py b/tests/test_app/tests/test_admin.py index bad4c421..71e4f08f 100644 --- a/tests/test_app/tests/test_admin.py +++ b/tests/test_app/tests/test_admin.py @@ -218,7 +218,7 @@ def testHistoryWithQuotedPrimaryKey(self): response = self.client.get(history_url) self.assertContains(response, revision_url) response = self.client.get(revision_url) - self.assertContains(response, 'value="{}"'.format(pk)) + self.assertContains(response, f'value="{pk}"') class TestModelInlineAdmin(admin.TabularInline): diff --git a/tests/test_app/tests/test_api.py b/tests/test_app/tests/test_api.py index 500d1def..16ef56d7 100644 --- a/tests/test_app/tests/test_api.py +++ b/tests/test_app/tests/test_api.py @@ -32,7 +32,7 @@ def testIsRegisteredFalse(self): class GetRegisteredModelsTest(TestModelMixin, TestBase): def testGetRegisteredModels(self): - self.assertEqual(set(reversion.get_registered_models()), set((TestModel,))) + self.assertEqual(set(reversion.get_registered_models()), {TestModel}) class RegisterTest(TestBase): diff --git a/tests/test_app/tests/test_models.py b/tests/test_app/tests/test_models.py index 601f2e5f..2d2ca0a9 100644 --- a/tests/test_app/tests/test_models.py +++ b/tests/test_app/tests/test_models.py @@ -4,6 +4,7 @@ TestModel, TestModelRelated, TestModelParent, TestModelInline, TestModelNestedInline, TestModelInlineByNaturalKey, TestModelWithNaturalKey, + TestModelWithUniqueConstraint, ) from test_app.tests.base import TestBase, TestModelMixin, TestModelParentMixin import json @@ -317,7 +318,7 @@ def testM2MSave(self): obj.related.add(v1) obj.related.add(v2) version = Version.objects.get_for_object(obj).first() - self.assertEqual(set(version.field_dict["related"]), set((v1.pk, v2.pk,))) + self.assertEqual(set(version.field_dict["related"]), {v1.pk, v2.pk}) class RevertTest(TestModelMixin, TestBase): @@ -443,3 +444,17 @@ def testNaturalKeyInline(self): 'test_model_id': 1, 'id': 1, }) + + +class TransactionRollbackTest(TestBase): + + def setUp(self): + reversion.register(TestModelWithUniqueConstraint) + + def testTransactionInRollbackState(self): + with reversion.create_revision(): + try: + TestModelWithUniqueConstraint.objects.create(name='A') + TestModelWithUniqueConstraint.objects.create(name='A') + except Exception: + pass diff --git a/tests/test_app/views.py b/tests/test_app/views.py index d645ad06..90fbca2b 100644 --- a/tests/test_app/views.py +++ b/tests/test_app/views.py @@ -1,3 +1,4 @@ +from django.db import transaction from django.http import HttpResponse from django.views.generic.base import View from reversion.views import create_revision, RevisionMixin @@ -9,8 +10,9 @@ def save_obj_view(request): def save_obj_error_view(request): - TestModel.objects.create() - raise Exception("Boom!") + with transaction.atomic(): + TestModel.objects.create() + raise Exception("Boom!") @create_revision() @@ -21,7 +23,7 @@ def create_revision_view(request): class RevisionMixinView(RevisionMixin, View): def revision_request_creates_revision(self, request): - silent = request.META.get("HTTP_X_NOREVISION", "false") == "true" + silent = request.headers.get('X-Norevision', "false") == "true" return super().revision_request_creates_revision(request) and not silent def dispatch(self, request): diff --git a/tests/test_project/settings.py b/tests/test_project/settings.py index d9e7e46e..b5c6f78f 100644 --- a/tests/test_project/settings.py +++ b/tests/test_project/settings.py @@ -81,25 +81,23 @@ "ENGINE": "django.db.backends.sqlite3", "NAME": os.path.join(BASE_DIR, "db.sqlite3"), }, - "postgres": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": os.path.join(BASE_DIR, "test_project_postgres.sqlite3"), - # "ENGINE": "django.db.backends.postgresql_psycopg2", - # "NAME": os.environ.get("DJANGO_DATABASE_NAME_POSTGRES", "test_project"), + "ENGINE": "django.db.backends.postgresql_psycopg2", + "HOST": os.environ.get("DJANGO_DATABASE_HOST_POSTGRES", ""), + "NAME": os.environ.get("DJANGO_DATABASE_NAME_POSTGRES", "test_project"), "USER": os.environ.get("DJANGO_DATABASE_USER_POSTGRES", getpass.getuser()), "PASSWORD": os.environ.get("DJANGO_DATABASE_PASSWORD_POSTGRES", ""), }, "mysql": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": os.path.join(BASE_DIR, "test_project_mysql.sqlite3"), - # "ENGINE": "django.db.backends.mysql", - # "NAME": os.environ.get("DJANGO_DATABASE_NAME_MYSQL", "test_project"), + "ENGINE": "django.db.backends.mysql", + "HOST": os.environ.get("DJANGO_DATABASE_HOST_MYSQL", ""), + "NAME": os.environ.get("DJANGO_DATABASE_NAME_MYSQL", "test_project"), "USER": os.environ.get("DJANGO_DATABASE_USER_MYSQL", getpass.getuser()), "PASSWORD": os.environ.get("DJANGO_DATABASE_PASSWORD_MYSQL", ""), }, } +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" # Password validation # https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators diff --git a/tests/test_project/urls.py b/tests/test_project/urls.py index ed210aa9..23f0d3c0 100644 --- a/tests/test_project/urls.py +++ b/tests/test_project/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls import include +from django.urls import include from django.urls import path from django.contrib import admin