diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..14f01789
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,11 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/" # Location of package manifests
+ schedule:
+ interval: "weekly"
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index ceac2fc8..9f05d488 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -24,19 +24,19 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Initialize CodeQL
- uses: github/codeql-action/init@v2
+ uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
queries: +security-and-quality
- name: Autobuild
- uses: github/codeql-action/autobuild@v2
+ uses: github/codeql-action/autobuild@v3
if: ${{ matrix.language == 'javascript' || matrix.language == 'python' }}
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v2
+ uses: github/codeql-action/analyze@v3
with:
category: "/language:${{ matrix.language }}"
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 6412cc0f..f2dee668 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -14,12 +14,12 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- name: Cache dependencies
- uses: actions/cache@v3.3.1
+ uses: actions/cache@v4.1.2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('docs/requirements.txt') }}
@@ -39,12 +39,12 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- name: Cache dependencies
- uses: actions/cache@v3.3.1
+ uses: actions/cache@v4.1.2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('docs/requirements.txt') }}
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 8687ed46..8878fd89 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -1,26 +1,17 @@
-name: Lint
+name: Ruff
-on: [push, pull_request]
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
+on:
+ push:
+ pull_request:
jobs:
ruff:
- name: ruff
runs-on: ubuntu-latest
+
steps:
- - name: Checkout
- uses: actions/checkout@v3
- - name: Set up Python
- uses: actions/setup-python@v4
- with:
- python-version: "3.11"
- cache: 'pip'
- - run: |
- python -m pip install --upgrade pip
- pip install ruff
- - name: Run Ruff
- run: |
- ruff djangocms_versioning tests
+ - uses: actions/checkout@v4
+
+ - run: python -Im pip install --user ruff
+
+ - name: Run ruff
+ run: ruff check --output-format=github djangocms_versioning tests
diff --git a/.github/workflows/publish-to-live-pypi.yml b/.github/workflows/publish-to-live-pypi.yml
index 2d8272e2..3208e6bb 100644
--- a/.github/workflows/publish-to-live-pypi.yml
+++ b/.github/workflows/publish-to-live-pypi.yml
@@ -15,9 +15,9 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up Python 3.10
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: '3.10'
diff --git a/.github/workflows/publish-to-test-pypi.yml b/.github/workflows/publish-to-test-pypi.yml
index fd1cf6fb..32ddf41a 100644
--- a/.github/workflows/publish-to-test-pypi.yml
+++ b/.github/workflows/publish-to-test-pypi.yml
@@ -15,9 +15,9 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up Python 3.10
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: '3.10'
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index e5897902..c4315c7a 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -7,24 +7,29 @@ concurrency:
cancel-in-progress: true
jobs:
- sqlite-unit-tests:
+ sqlite:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
- python-version: [ 3.9, "3.10", "3.11" ] # latest release minus two
+ python-version: [ 3.9, "3.10", "3.11", "3.12" ] # latest release minus two
requirements-file: [
dj32_cms41.txt,
- dj40_cms41.txt,
- dj41_cms41.txt,
dj42_cms41.txt,
+ dj50_cms41.txt,
+ dj51_cms41.txt,
]
+ exclude:
+ - requirements-file: dj50_cms41.txt
+ python-version: 3.9
+ - requirements-file: dj51_cms41.txt
+ python-version: 3.9
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v3
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
@@ -34,27 +39,32 @@ jobs:
python setup.py install
- name: Run coverage
- run: coverage run setup.py test
+ run: coverage run ./test_settings.py
- name: Upload Coverage to Codecov
- uses: codecov/codecov-action@v2
+ uses: codecov/codecov-action@v4
- postgres-unit-tests:
+ postgres:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
- python-version: [ 3.9, "3.10", "3.11" ] # latest release minus two
+ python-version: [ 3.9, "3.10", "3.11", "3.12" ] # latest release minus two
requirements-file: [
dj32_cms41.txt,
- dj40_cms41.txt,
- dj41_cms41.txt,
dj42_cms41.txt,
+ dj50_cms41.txt,
+ dj51_cms41.txt,
]
+ exclude:
+ - requirements-file: dj50_cms41.txt
+ python-version: 3.9
+ - requirements-file: dj51_cms41.txt
+ python-version: 3.9
services:
postgres:
- image: postgres:12
+ image: postgres:13
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
@@ -65,10 +75,10 @@ jobs:
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v3
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
@@ -78,25 +88,30 @@ jobs:
python setup.py install
- name: Run coverage
- run: coverage run setup.py test
+ run: coverage run ./test_settings.py
env:
DATABASE_URL: postgres://postgres:postgres@127.0.0.1/postgres
- name: Upload Coverage to Codecov
- uses: codecov/codecov-action@v2
+ uses: codecov/codecov-action@v4
- mysql-unit-tests:
+ mysql:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
- python-version: [ 3.9, "3.10", "3.11" ] # latest release minus two
+ python-version: [ 3.9, "3.10", "3.11", "3.12" ] # latest release minus two
requirements-file: [
dj32_cms41.txt,
- dj40_cms41.txt,
- dj41_cms41.txt,
dj42_cms41.txt,
+ dj50_cms41.txt,
+ dj51_cms41.txt,
]
+ exclude:
+ - requirements-file: dj50_cms41.txt
+ python-version: 3.9
+ - requirements-file: dj51_cms41.txt
+ python-version: 3.9
services:
mysql:
@@ -109,10 +124,10 @@ jobs:
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v3
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
@@ -122,9 +137,79 @@ jobs:
python setup.py install
- name: Run coverage
- run: coverage run setup.py test
+ run: coverage run ./test_settings.py
env:
DATABASE_URL: mysql://root@127.0.0.1/djangocms_test
- name: Upload Coverage to Codecov
- uses: codecov/codecov-action@v2
+ uses: codecov/codecov-action@v4
+
+ cms-develop-sqlite:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ python-version: ['3.11']
+ requirements-file: ['dj51_cms41.txt']
+ cms-version: [
+ 'https://github.com/django-cms/django-cms/archive/develop-4.tar.gz'
+ ]
+ os: [
+ ubuntu-20.04,
+ ]
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up Python ${{ matrix.python-version }}
+
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ python -m pip install -r tests/requirements/${{ matrix.requirements-file }}
+ python -m pip install ${{ matrix.cms-version }}
+ python setup.py install
+
+ - name: Run coverage
+ run: coverage run ./test_settings.py
+
+ - name: Upload Coverage to Codecov
+ uses: codecov/codecov-action@v4
+
+ sqlite-django-main:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ python-version: [ "3.11" ]
+ cms-version: [
+ 'https://github.com/django-cms/django-cms/archive/develop-4.tar.gz'
+ ]
+ django-version: [
+ 'https://github.com/django/django/archive/main.tar.gz'
+ ]
+ requirements-file: [
+ requirements_base.txt,
+ ]
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up Python ${{ matrix.python-version }}
+
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ python -m pip install -r tests/requirements/${{ matrix.requirements-file }}
+ python -m pip install ${{ matrix.cms-version }} ${{ matrix.django-version }}
+ python setup.py install
+
+ - name: Run coverage
+ run: coverage run ./test_settings.py
+
+ - name: Upload Coverage to Codecov
+ uses: codecov/codecov-action@v4
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ed8a6403..6922db3d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -14,8 +14,8 @@ repos:
- id: check-merge-conflict
- id: mixed-line-ending
- - repo: https://github.com/charliermarsh/ruff-pre-commit
- rev: "v0.0.264"
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.2.1
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 00000000..e1bd672f
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,20 @@
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+version: 2
+
+build:
+ os: ubuntu-22.04
+ tools:
+ python: "3.11"
+
+sphinx:
+ configuration: docs/conf.py
+ fail_on_warning: false
+
+formats:
+ - epub
+ - pdf
+
+python:
+ install:
+ - requirements: docs/requirements.txt
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 994c46d0..92f7272c 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -2,36 +2,120 @@
Changelog
=========
+2.1.0 (2024-07-12)
+==================
+
+* feat: add support for Django 5.0 and 5.1 (#429) by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/429
+* feat: Add versioning actions to settings (admin change view) of versioned objects by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/408
+* fix: Remove workaround for page-specific rendering by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/411
+* fix: Compare versions' back button sometimes returns to invalid URL by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/413
+
+
+* feat: Add versioning actions to settings (admin change view) of versioned objects by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/408
+* feat: Optimize db evaluation by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/416
+* feat: Prefetch page content version objects for faster page tree by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/418
+* fix: Remove workaround for page-specific rendering by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/411
+* fix: Compare versions' back button sometimes returns to invalid URL by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/413
+* fix: Preparation for changes in django CMS 4.2 by @jrief in https://github.com/django-cms/djangocms-versioning/pull/419
+* fix: Unnecessary complexity in ``current_content`` query set by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/417
+* fix: get_page_content retrieved non page-content objects from the toolbar by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/423
+
+
+**Full Changelog**: https://github.com/django-cms/djangocms-versioning/compare/2.0.2...2.1.0
+
+2.0.2 (2024-05-03)
+==================
+
+* fix: Do not show edit action for version objects where editing is not possible by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/405
+* feat: Add Arabic locale
+
+2.0.1 (2024-03-29)
+==================
+
+* feat: Add content object level publish permissions by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/390
+* fix: Create missing __init__.py in management folder by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/366
+* fix #363: Better UX in versioning listview by @jrief in https://github.com/django-cms/djangocms-versioning/pull/364
+* fix: Several fixes for the versioning forms: #382, #383, #384 by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/386
+* fix: For Django CMS 4.1.1 and later do not automatically register versioned CMS Menu by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/388
+* fix: Post requests from the side frame were sent to wrong URL by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/396
+* fix: Consistent use of action buttons by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/392
+* fix: Avoid duplication of placeholder checks for locked versions by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/393
+* ci: Add testing against django main by @marksweb in https://github.com/django-cms/djangocms-versioning/pull/353
+* ci: Improve efficiency of ruff workflow by @marksweb in https://github.com/django-cms/djangocms-versioning/pull/378
+* Chore: update ruff and pre-commit hook by @raffaellasuardini in https://github.com/django-cms/djangocms-versioning/pull/381
+* build(deps): bump actions/cache from 4.0.1 to 4.0.2 by @dependabot in https://github.com/django-cms/djangocms-versioning/pull/397
+
+New Contributors
+
+* @raffaellasuardini made their first contribution in https://github.com/django-cms/djangocms-versioning/pull/381
+* @jrief made their first contribution in https://github.com/django-cms/djangocms-versioning/pull/364
+
+2.0.0 (2023-12-29)
+==================
-Unreleased
-==========
-* feat: Reversable generic foreign key lookup from version
-* fix: formatted files through ruff to fix tests
-* fix: Remove version check when evaluating CMS PageContent objects
-
-2.0.0rc1
-========
-* fix: Only try modifying page language menu if it is present
-* fix: Added ``related_name`` attribute to the ``content_type`` foreign key of the ``Version`` model.
-* fix: burger menu adjusts to the design of django cms core dropdown
-* fix: bug that showed an archived version as unpublished in some cases in the state indicator
-* add: Dutch and French translations thanks to Stefan van den Eertwegh and François Palmierso
-* add: transifex support, German translations
-* add: Revert button as replacement for dysfunctional Edit button for unpublished
- versions
-* add: status indicators and drop down menus for django cms page tree
-* fix: only offer languages for plugin copy with available content
-* feat: Add support for Django 4.0, 4.1 and Python 3.10 and 3.11
-* fix: migrations for MySql
-* ci: Updated isort params in lint workflow to meet current requirements.
-* ci: Update actions to v3 where possible, and coverage to v2 due to v1 sunset in Feb
-* ci: Remove ``os`` from test workflow matrix because it's unused
-* ci: Added concurrency option to cancel in progress runs when new changes occur
-* fix: Added setting to make the field to identify a user configurable in ``ExtendedVersionAdminMixin.get_queryset()`` to fix issue for custom user models with no ``username``
-* ci: Run tests on sqlite, mysql and postgres db
-
-* feat: Compatibility with page content extension changes to django-cms
-* ci: Added basic linting pre-commit hooks
+What's Changed
+--------------
+* ci: Added concurrency to workflows by @marksweb in https://github.com/django-cms/djangocms-versioning/pull/271
+* ci: Remove ``os`` from test workflow matrix by @marksweb in https://github.com/django-cms/djangocms-versioning/pull/270
+* ci: Update actions to latest versions by @marksweb in https://github.com/django-cms/djangocms-versioning/pull/269
+* ci: Update isort params for v5 by @marksweb in https://github.com/django-cms/djangocms-versioning/pull/268
+* Add CodeQL workflow for GitHub code scanning by @lgtm-com in https://github.com/django-cms/djangocms-versioning/pull/297
+* feat: Django 4.0, 4.1 / Python 3.10/3.11, mysql support, running tests on sqlite, postgres and mysql by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/287
+* feat: Compat with cms page content extension changes by @marksweb in https://github.com/django-cms/djangocms-versioning/pull/291
+* fix: Additional change missed in #291 by @marksweb in https://github.com/django-cms/djangocms-versioning/pull/301
+* Add: Allow simple version management commands from the page tree indicator drop down menus by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/295
+* fix: Adds compatibility for User models with no username field [#292] by @marksweb in https://github.com/django-cms/djangocms-versioning/pull/293
+* feat: Use same icons in page tree state indicators and Manage verisons by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/302
+* fix: Remove patching the django CMS core by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/300
+* fix: test requirements after removing the patching pattern by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/303
+* feat: add localization and transifex support by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/305
+* feat: Add management command to create version objects by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/304
+* feat: add Dutch translations, transifex integration file by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/306
+* feat: French localization by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/307
+* feat: Albanian localization, Transifex integration by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/308
+* Some fixed strings are now translatable by @svandeneertwegh in https://github.com/django-cms/djangocms-versioning/pull/310
+* Translate '/djangocms_versioning/locale/en/LC_MESSAGES/django.po' in 'de' by @transifex-integration in https://github.com/django-cms/djangocms-versioning/pull/311
+* Translate '/djangocms_versioning/locale/en/LC_MESSAGES/django.po' in 'nl' by @transifex-integration in https://github.com/django-cms/djangocms-versioning/pull/312
+* fix: translation inconsistencies by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/313
+* feat: Add preview button to view published mode by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/316
+* feat: Huge performance improvement for admin_manager by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/318
+* fix: Minor usability improvements by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/317
+* fix: update messages by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/321
+* Translate 'djangocms_versioning/locale/en/LC_MESSAGES/django.po' in 'de' by @transifex-integration in https://github.com/django-cms/djangocms-versioning/pull/322
+* fix: deletion of version objects blocked by source fields by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/320
+* feat: allow reuse of status indicators by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/319
+* fix: burger menu to also work with new core icons by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/323
+* Translate 'djangocms_versioning/locale/en/LC_MESSAGES/django.po' in 'nl' by @transifex-integration in https://github.com/django-cms/djangocms-versioning/pull/328
+* ci: Switch flake8 and isort for ruff by @marksweb in https://github.com/django-cms/djangocms-versioning/pull/329
+* fix: Added related_name to version content type field by @marksweb in https://github.com/django-cms/djangocms-versioning/pull/274
+* feat: Django 4.2, Django CMS 4.1.0rc2 compatibility, and version locking by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/326
+* Translations for djangocms_versioning/locale/en/LC_MESSAGES/django.po in de by @transifex-integration in https://github.com/django-cms/djangocms-versioning/pull/330
+* Translations for djangocms_versioning/locale/en/LC_MESSAGES/django.po in nl by @transifex-integration in https://github.com/django-cms/djangocms-versioning/pull/331
+* fix: Modify language menu for pages only if it is present by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/333
+* feat: Add pypi actions by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/335
+* feat: Reversable generic foreign key lookup from version by @Aiky30 in https://github.com/django-cms/djangocms-versioning/pull/241
+* Add caching to PageContent __bool__ by @stefanw in https://github.com/django-cms/djangocms-versioning/pull/346
+* Fix tests by @FinalAngel in https://github.com/django-cms/djangocms-versioning/pull/349
+* Updates for file djangocms_versioning/locale/en/LC_MESSAGES/django.po in fr on branch master by @transifex-integration in https://github.com/django-cms/djangocms-versioning/pull/347
+* docs: List `DJANGOCMS_VERSIONING_LOCK_VERSIONS` in settings by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/350
+* docs: Update documentation by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/351
+* fix: Update templates for better styling w/o djangocms-admin-style by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/352
+* fix: PageContent extension's `copy_relations` method not called by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/344
+* Bugfix/use keyword arguments in admin render change form method by @vipulnarang95 in https://github.com/django-cms/djangocms-versioning/pull/356
+* Provide additional information when sending publish/unpublish events by @GaretJax in https://github.com/django-cms/djangocms-versioning/pull/348
+* fix: Preview link language by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/357
+* docs: Document version states by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/362
+* feat: Add configuration to manage redirect on publish by @fsbraun in https://github.com/django-cms/djangocms-versioning/pull/358
+
+New Contributors
+----------------
+* @marksweb made their first contribution in https://github.com/django-cms/djangocms-versioning/pull/271
+* @fsbraun made their first contribution in https://github.com/django-cms/djangocms-versioning/pull/287
+* @svandeneertwegh made their first contribution in https://github.com/django-cms/djangocms-versioning/pull/310
+* @stefanw made their first contribution in https://github.com/django-cms/djangocms-versioning/pull/346
+* @FinalAngel made their first contribution in https://github.com/django-cms/djangocms-versioning/pull/349
+* @vipulnarang95 made their first contribution in https://github.com/django-cms/djangocms-versioning/pull/356
+* @GaretJax made their first contribution in https://github.com/django-cms/djangocms-versioning/pull/348
1.2.2 (2022-07-20)
==================
diff --git a/README.rst b/README.rst
index e4c7bb1e..1556a22e 100644
--- a/README.rst
+++ b/README.rst
@@ -32,9 +32,11 @@ Add ``djangocms_versioning`` to your project's ``INSTALLED_APPS``.
Run::
- python manage.py migrate djangocms_versioning
+ python -m manage migrate djangocms_versioning
+ python -m manage create_versions --user-id
-to perform the application's database migrations.
+to perform the application's database migrations and (only if you have an existing database) add version objects
+needed to mark existing versions as draft.
=====
diff --git a/djangocms_versioning/__init__.py b/djangocms_versioning/__init__.py
index 0cda2d10..9aa3f903 100644
--- a/djangocms_versioning/__init__.py
+++ b/djangocms_versioning/__init__.py
@@ -1 +1 @@
-__version__ = "2.0.0rc1"
+__version__ = "2.1.0"
diff --git a/djangocms_versioning/admin.py b/djangocms_versioning/admin.py
index f3526ce3..3998dd72 100644
--- a/djangocms_versioning/admin.py
+++ b/djangocms_versioning/admin.py
@@ -8,6 +8,7 @@
from cms.models import PageContent
from cms.utils import get_language_from_request
from cms.utils.conf import get_cms_setting
+from cms.utils.helpers import is_editable_model
from cms.utils.urlutils import add_url_parameters, static_with_version
from django.conf import settings
from django.contrib import admin, messages
@@ -61,9 +62,14 @@
class VersioningChangeListMixin:
"""Mixin used for ChangeList classes of content models."""
- def get_queryset(self, request):
+ def get_queryset(self, request, exclude_parameters=None):
"""Limit the content model queryset to the latest versions only."""
- queryset = super().get_queryset(request)
+ if exclude_parameters:
+ # Django 5.0+ (facet support)
+ queryset = super().get_queryset(request, exclude_parameters)
+ else:
+ # Django 4.2 compatible get_queryset
+ queryset = super().get_queryset(request)
versionable = versionables.for_content(queryset.model)
"""Check if there is a method "self.get__from_request" for each extra grouping field.
@@ -134,7 +140,7 @@ def render_change_form(
"versioning_fallback_change_form_template"
] = super().change_form_template
- return super().render_change_form(request, context, add, change, form_url, obj)
+ return super().render_change_form(request, context, add=add, change=change, form_url=form_url, obj=obj)
def has_change_permission(self, request, obj=None):
# Add additional version checks
@@ -465,10 +471,26 @@ def _get_edit_link(self, obj, request, disabled=False):
f"admin:{version._meta.app_label}_{version._meta.model_name}_edit_redirect",
args=(version.pk,),
)
+ # Only show if no draft exists
+ if version.state == PUBLISHED:
+ pks_for_grouper = version.versionable.for_content_grouping_values(
+ obj
+ ).values_list("pk", flat=True)
+ drafts = Version.objects.filter(
+ object_id__in=pks_for_grouper,
+ content_type=version.content_type,
+ state=DRAFT,
+ )
+ if drafts.exists():
+ return ""
+ icon = "edit-new"
+ else:
+ icon = "edit"
+
return self.admin_action_button(
url,
- icon="pencil",
- title=_("Edit"),
+ icon=icon,
+ title=_("Edit") if icon == "edit" else _("New Draft"),
name="edit",
disabled=disabled,
action="post",
@@ -492,7 +514,7 @@ def get_actions_list(self):
actions = [
self._get_preview_link,
self._get_edit_link,
- ]
+ ]
if "state_indicator" not in self.versioning_list_display:
# State indicator mixin loaded?
actions.append(self._get_manage_versions_link)
@@ -541,7 +563,7 @@ def get_grouping_field_filters(self, request):
if value is not None:
yield field, value
- def get_queryset(self, request):
+ def get_queryset(self, request, exclude_parameters=None):
"""Adds support for querying the version model by grouping fields.
Filters by the value of grouping fields (specified in VersionableItem
@@ -551,7 +573,12 @@ def get_queryset(self, request):
for specifying filters that work without being shown in the UI
along with filter choices.
"""
- queryset = super().get_queryset(request)
+ if exclude_parameters:
+ # Django 5.0+ (facet support)
+ queryset = super().get_queryset(request, exclude_parameters)
+ else:
+ # Django 4.2 compatible get_queryset
+ queryset = super().get_queryset(request)
content_model = self.model_admin.model._source_model
versionable = versionables.for_content(content_model)
filters = dict(self.get_grouping_field_filters(request))
@@ -609,6 +636,9 @@ class VersionAdmin(ChangeListActionsMixin, admin.ModelAdmin, metaclass=MediaDefi
# def get_queryset(self, request):
# return super().get_queryset(request).prefetch_related('content')
+ class Media:
+ js = ["djangocms_versioning/js/versioning.js"]
+
def get_changelist(self, request, **kwargs):
return VersionChangeList
@@ -683,13 +713,13 @@ def _get_archive_link(self, obj, request, disabled=False):
icon="archive",
title=_("Archive"),
name="archive",
- disabled=not obj.can_be_archived(),
+ disabled=not obj.check_archive.as_bool(request.user),
)
def _get_publish_link(self, obj, request):
"""Helper function to get the html link to the publish action
"""
- if not obj.check_publish.as_bool(request.user):
+ if not obj.can_be_published():
# Don't display the link if it can't be published
return ""
publish_url = reverse(
@@ -702,14 +732,14 @@ def _get_publish_link(self, obj, request):
title=_("Publish"),
name="publish",
action="post",
- disabled=not obj.can_be_published(),
+ disabled=not obj.check_publish.as_bool(request.user),
keepsideframe=False,
)
def _get_unpublish_link(self, obj, request, disabled=False):
"""Helper function to get the html link to the unpublish action
"""
- if not obj.check_unpublish.as_bool(request.user):
+ if not obj.can_be_unpublished():
# Don't display the link if it can't be unpublished
return ""
unpublish_url = reverse(
@@ -721,12 +751,13 @@ def _get_unpublish_link(self, obj, request, disabled=False):
icon="unpublish",
title=_("Unpublish"),
name="unpublish",
- disabled=not obj.can_be_unpublished(),
+ disabled=not obj.check_unpublish.as_bool(request.user),
)
def _get_edit_link(self, obj, request, disabled=False):
"""Helper function to get the html link to the edit action
"""
+
if not obj.check_edit_redirect.as_bool(request.user):
return ""
@@ -744,7 +775,7 @@ def _get_edit_link(self, obj, request, disabled=False):
return ""
icon = "edit-new"
else:
- icon = "pencil"
+ icon = "edit"
# Don't open in the sideframe if the item is not sideframe compatible
keepsideframe = obj.versionable.content_model_is_sideframe_editable
@@ -756,7 +787,7 @@ def _get_edit_link(self, obj, request, disabled=False):
return self.admin_action_button(
edit_url,
icon=icon,
- title=_("Edit") if icon == "pencil" else _("New Draft"),
+ title=_("Edit") if icon == "edit" else _("New Draft"),
name="edit",
action="post",
disabled=disabled,
@@ -766,7 +797,7 @@ def _get_edit_link(self, obj, request, disabled=False):
def _get_revert_link(self, obj, request, disabled=False):
"""Helper function to get the html link to the revert action
"""
- if not obj.check_revert.as_bool(request.user):
+ if obj.state in (PUBLISHED, DRAFT):
# Don't display the link if it's a draft or published
return ""
@@ -779,13 +810,13 @@ def _get_revert_link(self, obj, request, disabled=False):
icon="undo",
title=_("Revert"),
name="revert",
- disabled=disabled,
+ disabled=not obj.check_revert.as_bool(request.user) or disabled,
)
def _get_discard_link(self, obj, request, disabled=False):
"""Helper function to get the html link to the discard action
"""
- if not obj.check_discard.as_bool(request.user):
+ if obj.state != DRAFT:
# Don't display the link if it's not a draft
return ""
@@ -798,7 +829,7 @@ def _get_discard_link(self, obj, request, disabled=False):
icon="bin",
title=_("Discard"),
name="discard",
- disabled=disabled,
+ disabled=not obj.check_discard.as_bool(request.user) or disabled,
)
def _get_unlock_link(self, obj, request):
@@ -809,12 +840,6 @@ def _get_unlock_link(self, obj, request):
if not conf.LOCK_VERSIONS or obj.state != DRAFT or not version_is_locked(obj):
return ""
- disabled = True
- # Check whether the lock can be removed
- # Check that the user has unlock permission
- if request.user.has_perm("djangocms_versioning.delete_versionlock"):
- disabled = False
-
unlock_url = reverse(f"admin:{obj._meta.app_label}_{self.model._meta.model_name}_unlock", args=(obj.pk,))
return self.admin_action_button(
unlock_url,
@@ -822,7 +847,33 @@ def _get_unlock_link(self, obj, request):
title=_("Unlock"),
name="unlock",
action="post",
- disabled=disabled,
+ disabled=not obj.check_unlock.as_bool(request.user),
+ )
+
+ def _get_settings_link(self, obj, request):
+ """
+ Generate a settings button for the Versioning Admin
+ """
+
+ # If the content object is not registered for frontend editing no action should be present
+ # Also, the content object must be registered with the admin site
+ content_model = obj.versionable.content_model
+ if not is_editable_model(content_model):
+ return ""
+
+ try:
+ settings_url = reverse(
+ f"admin:{content_model._meta.app_label}_{content_model._meta.model_name}_change",
+ args=(obj.content.pk,)
+ )
+ except Resolver404:
+ return ""
+
+ return self.admin_action_button(
+ settings_url,
+ icon="settings",
+ title=_("Settings"),
+ name="settings",
)
def get_actions_list(self):
@@ -851,6 +902,7 @@ def get_state_actions(self):
self._get_revert_link,
self._get_discard_link,
self._get_unlock_link,
+ self._get_settings_link,
]
@admin.action(
@@ -962,21 +1014,33 @@ def publish_view(self, request, object_id):
request, self.model._meta, object_id
)
+ requested_redirect = request.GET.get("next", None)
+ if conf.ON_PUBLISH_REDIRECT in ("preview", "published"):
+ redirect_url=get_preview_url(version.content)
+ else:
+ redirect_url=version_list_url(version.content)
+
if not version.can_be_published():
self.message_user(request, _("Version cannot be published"), messages.ERROR)
- return redirect(version_list_url(version.content))
+ return redirect(requested_redirect or redirect_url)
try:
version.check_publish(request.user)
except ConditionFailed as e:
self.message_user(request, force_str(e), messages.ERROR)
- return redirect(version_list_url(version.content))
+ return redirect(requested_redirect or redirect_url)
# Publish the version
version.publish(request.user)
+
# Display message
self.message_user(request, _("Version published"))
- # Redirect
- return redirect(version_list_url(version.content))
+
+ # Redirect to published?
+ if conf.ON_PUBLISH_REDIRECT == "published":
+ if hasattr(version.content, "get_absolute_url"):
+ redirect_url = version.content.get_absolute_url() or redirect_url
+
+ return redirect(requested_redirect or redirect_url)
def unpublish_view(self, request, object_id):
"""Unpublishes the specified version and redirects back to the
@@ -989,16 +1053,21 @@ def unpublish_view(self, request, object_id):
request, self.model._meta, object_id
)
+ if conf.ON_PUBLISH_REDIRECT in ("preview", "published"):
+ redirect_url=get_preview_url(version.content)
+ else:
+ redirect_url=version_list_url(version.content)
+
if not version.can_be_unpublished():
self.message_user(
request, _("Version cannot be unpublished"), messages.ERROR
)
- return redirect(version_list_url(version.content))
+ return redirect(redirect_url)
try:
version.check_unpublish(request.user)
except ConditionFailed as e:
self.message_user(request, force_str(e), messages.ERROR)
- return redirect(version_list_url(version.content))
+ return redirect(redirect_url)
if request.method != "POST":
context = {
@@ -1031,7 +1100,7 @@ def unpublish_view(self, request, object_id):
# Display message
self.message_user(request, _("Version unpublished"))
# Redirect
- return redirect(version_list_url(version.content))
+ return redirect(redirect_url)
def _get_edit_redirect_version(self, request, version):
"""Helper method to get the latest draft or create one if one does not exist."""
@@ -1086,7 +1155,7 @@ def edit_redirect_view(self, request, object_id):
return redirect(version_list_url(version.content))
# Redirect
- return redirect(get_editable_url(target.content))
+ return redirect(get_editable_url(target.content, request.GET.get("force_admin")))
def revert_view(self, request, object_id):
"""Reverts to the specified version i.e. creates a draft from it."""
@@ -1192,13 +1261,8 @@ def compare_view(self, request, object_id):
get_cms_setting("CMS_TOOLBAR_URL__DISABLE"): 1,
get_cms_setting("CMS_TOOLBAR_URL__PERSIST"): 0,
}
- v1_preview_url = add_url_parameters(
- reverse(
- "admin:cms_placeholder_render_object_preview",
- args=(v1.content_type_id, v1.object_id),
- ),
- **persist_params
- )
+ v1_preview_url = get_preview_url(v1.content)
+ v1_preview_url = add_url_parameters(v1_preview_url, **persist_params)
# Get the list of versions for the grouper. This is for use
# in the dropdown to choose a version.
version_list = Version.objects.filter_by_content_grouping_values(
@@ -1221,16 +1285,11 @@ def compare_view(self, request, object_id):
request, self.model._meta, request.GET["compare_to"]
)
else:
+ v2_preview_url = get_preview_url(v2.content)
context.update(
{
"v2": v2,
- "v2_preview_url": add_url_parameters(
- reverse(
- "admin:cms_placeholder_render_object_preview",
- args=(v2.content_type_id, v2.object_id),
- ),
- **persist_params
- ),
+ "v2_preview_url": add_url_parameters(v2_preview_url, **persist_params),
}
)
return TemplateResponse(
@@ -1322,10 +1381,7 @@ def changelist_view(self, request, extra_context=None):
# Check if custom breadcrumb template defined, otherwise
# fallback on default
breadcrumb_templates = [
- "admin/djangocms_versioning/{app_label}/{model_name}/versioning_breadcrumbs.html".format(
- app_label=breadcrumb_opts.app_label,
- model_name=breadcrumb_opts.model_name,
- ),
+ f"admin/djangocms_versioning/{breadcrumb_opts.app_label}/{breadcrumb_opts.model_name}/versioning_breadcrumbs.html",
"admin/djangocms_versioning/versioning_breadcrumbs.html",
]
extra_context["breadcrumb_template"] = select_template(breadcrumb_templates)
diff --git a/djangocms_versioning/apps.py b/djangocms_versioning/apps.py
index c1ebdcfd..a5019d4c 100644
--- a/djangocms_versioning/apps.py
+++ b/djangocms_versioning/apps.py
@@ -12,15 +12,18 @@ def ready(self):
from cms.models import contentmodels, fields
from cms.signals import post_obj_operation, post_placeholder_operation
+ from .conf import LOCK_VERSIONS
from .handlers import (
update_modified_date,
update_modified_date_for_pagecontent,
update_modified_date_for_placeholder_source,
)
- from .helpers import is_content_editable
+ from .helpers import is_content_editable, placeholder_content_is_unlocked_for_user
# Add check to PlaceholderRelationField
fields.PlaceholderRelationField.default_checks += [is_content_editable]
+ if LOCK_VERSIONS:
+ fields.PlaceholderRelationField.default_checks += [placeholder_content_is_unlocked_for_user]
# Remove uniqueness constraint from PageContent model to allow for different versions
pagecontent_unique_together = tuple(
diff --git a/djangocms_versioning/cms_config.py b/djangocms_versioning/cms_config.py
index bd113ccd..ae864f2b 100644
--- a/djangocms_versioning/cms_config.py
+++ b/djangocms_versioning/cms_config.py
@@ -3,7 +3,6 @@
from cms.app_base import CMSAppConfig, CMSAppExtension
from cms.extensions.models import BaseExtension
from cms.models import PageContent, Placeholder
-from cms.utils import get_language_from_request
from cms.utils.i18n import get_language_list, get_language_tuple
from cms.utils.plugins import copy_plugins_to_placeholder
from cms.utils.urlutils import admin_reverse
@@ -14,6 +13,7 @@
ObjectDoesNotExist,
PermissionDenied,
)
+from django.db.models import Prefetch
from django.http import (
HttpResponse,
HttpResponseBadRequest,
@@ -23,16 +23,15 @@
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
-from . import indicators, versionables
+from . import indicators
from .admin import VersioningAdminMixin
-from .conf import LOCK_VERSIONS
from .constants import INDICATOR_DESCRIPTIONS
from .datastructures import BaseVersionableItem, VersionableItem
from .exceptions import ConditionFailed
from .helpers import (
get_latest_admin_viewable_content,
inject_generic_relation_to_version,
- placeholder_content_is_unlocked_for_user,
+ is_editable,
register_versionadmin_proxy,
replace_admin_for_models,
replace_manager,
@@ -159,12 +158,6 @@ def handle_admin_field_modifiers(self, cms_config):
for key in modifier.keys():
self.add_to_field_extension[key] = modifier[key]
- def handle_locking(self):
- if LOCK_VERSIONS:
- from cms.models import fields
-
- fields.PlaceholderRelationField.default_checks += [placeholder_content_is_unlocked_for_user]
-
def configure_app(self, cms_config):
if hasattr(cms_config, "extended_admin_field_modifiers"):
self.handle_admin_field_modifiers(cms_config)
@@ -187,7 +180,6 @@ def configure_app(self, cms_config):
self.handle_version_admin(cms_config)
self.handle_content_model_generic_relation(cms_config)
self.handle_content_model_manager(cms_config)
- self.handle_locking()
def copy_page_content(original_content):
@@ -276,6 +268,8 @@ def on_page_content_archive(version):
class VersioningCMSPageAdminMixin(VersioningAdminMixin):
+ change_form_template = "admin/djangocms_versioning/page/change_form.html"
+
def get_readonly_fields(self, request, obj=None):
fields = super().get_readonly_fields(request, obj)
if obj:
@@ -289,28 +283,9 @@ def get_readonly_fields(self, request, obj=None):
fields.remove(f_name)
return fields
- def get_form(self, request, obj=None, **kwargs):
- form = super().get_form(request, obj, **kwargs)
- if obj:
- version = Version.objects.get_for_content(obj)
- if not version.check_modify.as_bool(request.user):
- for f_name in ["slug", "overwrite_url"]:
- form.declared_fields[f_name].widget.attrs["readonly"] = True
- return form
-
def get_queryset(self, request):
- urls = ("cms_pagecontent_get_tree",)
- queryset = super().get_queryset(request)
- if request.resolver_match.url_name in urls:
- versionable = versionables.for_content(queryset.model)
-
- # TODO: Improve the grouping filters to use anything defined in the
- # apps versioning config extra_grouping_fields
- grouping_filters = {}
- if "language" in versionable.extra_grouping_fields:
- grouping_filters["language"] = get_language_from_request(request)
-
- return queryset.filter(pk__in=versionable.distinct_groupers(**grouping_filters))
+ queryset = super().get_queryset(request)\
+ .prefetch_related(Prefetch("versions", to_attr="prefetched_versions"))
return queryset
# CAVEAT:
@@ -376,7 +351,18 @@ def get_indicator_menu(cls, request, page_content):
"""Get the indicator menu for PageContent object taking into account the
currently available versions"""
menu_template = "admin/cms/page/tree/indicator_menu.html"
- status = page_content.content_indicator()
+ if hasattr(page_content.page, "filtered_translations") and hasattr(page_content, "prefetched_versions"):
+ # get_tree has prefetched versions
+ versions = sorted(
+ [content.prefetched_versions[0] for content in page_content.page.filtered_translations],
+ key=lambda version: -version.pk,
+ )
+ for content in page_content.page.filtered_translations:
+ content.__dict__["content"] = content
+ status = page_content.content_indicator(versions)
+ else:
+ # No prefetched versions available, get them ourselves
+ status = page_content.content_indicator()
if not status or status == "empty": # pragma: no cover
return super().get_indicator_menu(request, page_content)
versions = page_content._version # Cache from .content_indicator()
@@ -408,5 +394,5 @@ class VersioningCMSConfig(CMSAppConfig):
)
]
cms_toolbar_mixin = CMSToolbarVersioningMixin
- PageContent.add_to_class("is_editable", indicators.is_editable)
+ PageContent.add_to_class("is_editable", is_editable)
PageContent.add_to_class("content_indicator", indicators.content_indicator)
diff --git a/djangocms_versioning/cms_menus.py b/djangocms_versioning/cms_menus.py
index 7d955384..51fd54ec 100644
--- a/djangocms_versioning/cms_menus.py
+++ b/djangocms_versioning/cms_menus.py
@@ -2,6 +2,11 @@
from cms.apphook_pool import apphook_pool
from cms.cms_menus import CMSMenu as OriginalCMSMenu, get_visible_nodes
from cms.models import Page
+
+try:
+ from cms.models import TreeNode
+except ImportError:
+ TreeNode = None
from cms.toolbar.utils import get_object_preview_url, get_toolbar_from_request
from cms.utils.page import get_page_queryset
from django.apps import apps
@@ -76,6 +81,11 @@ def _get_attrs_for_node(renderer, page_content):
class CMSMenu(Menu):
+ """This is a legacy class used by django CMS 4.0 and django CMS 4.1.0 only. Its language
+ fallback mechanism does not comply with django CMS' core's. Also, it is by far slower
+ than django CMS core's. As of django CMS 4.1.1, this class is by default deactivated.
+
+ See https://discord.com/channels/800813886689247262/1204047551570120755 for more information."""
def get_nodes(self, request):
site = self.renderer.site
language = self.renderer.request_language
@@ -94,7 +104,7 @@ def get_nodes(self, request):
# Depending on the toolbar mode, we need to get the correct version.
# On edit or preview mode: return DRAFT,
- # if DRAFT does not exists then return PUBLISHED.
+ # if DRAFT does not exist then return PUBLISHED.
# On public mode: return PUBLISHED.
if edit_or_preview:
states = [constants.DRAFT, constants.PUBLISHED]
@@ -106,8 +116,8 @@ def get_nodes(self, request):
versionable_item.content_model._base_manager.filter(
language=language, page__in=pages_qs, versions__state__in=states
)
- .order_by("page__node__path", "versions__state")
- .select_related("page", "page__node")
+ .order_by("page__node__path" if TreeNode else "page__path", "versions__state")
+ .select_related("page", "page__node" if TreeNode else "page")
.prefetch_related("versions")
)
added_pages = []
@@ -117,7 +127,7 @@ def get_nodes(self, request):
if page not in visible_pages_for_user:
# The page is restricted for the user.
- # Therefore we avoid adding it to the menu.
+ # Therefore, we avoid adding it to the menu.
continue
version = page_content.versions.all()[0]
diff --git a/djangocms_versioning/cms_toolbars.py b/djangocms_versioning/cms_toolbars.py
index d4fed366..fc635d00 100644
--- a/djangocms_versioning/cms_toolbars.py
+++ b/djangocms_versioning/cms_toolbars.py
@@ -1,5 +1,6 @@
from collections import OrderedDict
from copy import copy
+from typing import Optional
from cms.cms_toolbars import (
ADD_PAGE_LANGUAGE_BREAK,
@@ -35,9 +36,6 @@
class VersioningToolbar(PlaceholderToolbar):
- class Media:
- js = ("cms/js/admin/actions.js",)
-
def _get_versionable(self):
"""Helper method to get the versionable for the content type
of the version
@@ -79,7 +77,7 @@ def _add_publish_button(self):
_("Publish"),
url=publish_url,
disabled=False,
- extra_classes=["cms-btn-action", "js-action", "cms-form-post-method", "cms-versioning-js-publish-btn"],
+ extra_classes=["cms-btn-action", "cms-form-post-method", "cms-versioning-js-publish-btn"],
)
self.toolbar.add_item(item)
@@ -115,7 +113,7 @@ def _add_edit_button(self, disabled=False):
_("Edit") if draft_exists else _("New Draft"),
url=edit_url,
disabled=disabled,
- extra_classes=["cms-btn-action", "js-action", "cms-form-post-method", "cms-versioning-js-edit-btn"],
+ extra_classes=["cms-btn-action", "cms-form-post-method", "cms-versioning-js-edit-btn"],
)
self.toolbar.add_item(item)
@@ -125,8 +123,8 @@ def _add_unlock_button(self):
if LOCK_VERSIONS and self._is_versioned():
item = ButtonList(side=self.toolbar.RIGHT)
proxy_model = self._get_proxy_model()
- version = Version.objects.get_for_content(self.toolbar.obj)
- if version.check_unlock.as_bool(self.request.user):
+ version = Version.objects.filter_by_content_grouping_values(self.toolbar.obj).filter(state=DRAFT).first()
+ if version and version.check_unlock.as_bool(self.request.user):
unlock_url = reverse(
f"admin:{proxy_model._meta.app_label}_{proxy_model.__name__.lower()}_unlock",
args=(version.pk,),
@@ -135,7 +133,6 @@ def _add_unlock_button(self):
if can_unlock:
extra_classes = [
"cms-btn-action",
- "js-action",
"cms-form-post-method",
"cms-versioning-js-unlock-btn",
]
@@ -216,7 +213,7 @@ def _add_versioning_menu(self):
url += "?" + urlencode({
"compare_to": version.pk,
- "back": self.request.get_full_path(),
+ "back": self.toolbar.request_path,
})
versioning_menu.add_link_item(name, url=url)
# Discard changes menu entry (wrt to source)
@@ -292,15 +289,31 @@ class VersioningPageToolbar(PageToolbar):
Overriding the original Page toolbar to ensure that draft and published pages
can be accessed and to allow full control over the Page toolbar for versioned pages.
"""
- def get_page_content(self, language=None):
+
+ def __init__(self, *args, **kwargs):
+ self.page_content: Optional[PageContent] = None
+ super().__init__(*args, **kwargs)
+
+ def get_page_content(self, language: Optional[str] = None) -> PageContent:
+ # This method overwrites the method in django CMS core. Not necessary
+ # for django CMS 4.2+
if not language:
language = self.current_lang
- return get_latest_admin_viewable_content(self.page, language=language)
+ if isinstance(self.page_content, PageContent) and self.page_content.language == language:
+ # Already known - no need to query it again
+ return self.page_content
+ toolbar_obj = self.toolbar.get_object()
+ if isinstance(toolbar_obj, PageContent) and toolbar_obj.language == language:
+ # Already in the toolbar, then use it!
+ return toolbar_obj
+ else:
+ # Get it from the DB
+ return get_latest_admin_viewable_content(self.page, language=language)
def populate(self):
- self.page = self.request.current_page or getattr(self.toolbar.obj, "page", None)
- self.title = self.get_page_content() if self.page else None
+ self.page = self.request.current_page
+ self.page_content = self.get_page_content() if self.page else None
self.permissions_activated = get_cms_setting("PERMISSION")
self.override_language_menu()
@@ -316,7 +329,7 @@ def override_language_menu(self):
# Only override the menu if it exists and a page can be found
language_menu = self.toolbar.get_menu(LANGUAGE_MENU_IDENTIFIER, _("Language"))
if settings.USE_I18N and language_menu and self.page:
- # remove_item uses `items` attribute so we have to copy object
+ # remove_item uses `items` attribute, so we have to copy object
for _item in copy(language_menu.items):
language_menu.remove_item(item=_item)
diff --git a/djangocms_versioning/conditions.py b/djangocms_versioning/conditions.py
index c73a8c14..fd76a007 100644
--- a/djangocms_versioning/conditions.py
+++ b/djangocms_versioning/conditions.py
@@ -76,3 +76,22 @@ def inner(version, user):
else:
raise ConditionFailed(message)
return inner
+
+def user_can_unlock(message: str) -> callable:
+ def inner(version, user):
+ if not user.has_perm("djangocms_versioning.delete_versionlock"):
+ raise ConditionFailed(message)
+ return inner
+
+def user_can_publish(message: str) -> callable:
+ def inner(version, user):
+ if not version.has_publish_permission(user):
+ raise ConditionFailed(message)
+ return inner
+
+
+def user_can_change(message: str) -> callable:
+ def inner(version, user):
+ if not version.has_change_permission(user):
+ raise ConditionFailed(message)
+ return inner
diff --git a/djangocms_versioning/conf.py b/djangocms_versioning/conf.py
index 67634898..1188780e 100644
--- a/djangocms_versioning/conf.py
+++ b/djangocms_versioning/conf.py
@@ -1,7 +1,8 @@
+from cms import __version__ as CMS_VERSION
from django.conf import settings
ENABLE_MENU_REGISTRATION = getattr(
- settings, "DJANGOCMS_VERSIONING_ENABLE_MENU_REGISTRATION", True
+ settings, "DJANGOCMS_VERSIONING_ENABLE_MENU_REGISTRATION", CMS_VERSION <= "4.1.0"
)
USERNAME_FIELD = getattr(
@@ -27,3 +28,8 @@
EMAIL_NOTIFICATIONS_FAIL_SILENTLY = getattr(
settings, "EMAIL_NOTIFICATIONS_FAIL_SILENTLY", False
)
+
+ON_PUBLISH_REDIRECT = getattr(
+ settings, "DJANGOCMS_VERISONING_ON_PUBLISH_REDIRECT", "published"
+)
+#: Allowed values: "versions", "published", "preview"
diff --git a/djangocms_versioning/helpers.py b/djangocms_versioning/helpers.py
index e58770af..9afa7140 100644
--- a/djangocms_versioning/helpers.py
+++ b/djangocms_versioning/helpers.py
@@ -15,6 +15,7 @@
from django.db import models
from django.template.loader import render_to_string
from django.utils.encoding import force_str
+from django.utils.translation import get_language
from . import versionables
from .conf import EMAIL_NOTIFICATIONS_FAIL_SILENTLY
@@ -26,6 +27,13 @@
emit_content_change = None
+def is_editable(content_obj, request):
+ """Check of content_obj is editable"""
+ from .models import Version
+
+ return Version.objects.get_for_content(content_obj).check_modify.as_bool(request.user)
+
+
def versioning_admin_factory(admin_class, mixin):
"""A class factory returning admin class with overriden
versioning functionality.
@@ -147,6 +155,8 @@ def inject_generic_relation_to_version(model):
related_query_name = f"{model._meta.app_label}_{model._meta.model_name}"
model.add_to_class("versions", GenericRelation(
Version, related_query_name=related_query_name))
+ if not hasattr(model, "is_editable"):
+ model.add_to_class("is_editable", is_editable)
def _set_default_manager(model, manager):
@@ -223,11 +233,11 @@ def is_content_editable(placeholder, user):
return version.state == DRAFT
-def get_editable_url(content_obj):
+def get_editable_url(content_obj, force_admin=False):
"""If the object is editable the cms editable view should be used, with the toolbar.
- This method is provides the URL for it.
+ This method provides the URL for it.
"""
- if is_editable_model(content_obj.__class__):
+ if is_editable_model(content_obj.__class__) and not force_admin:
language = getattr(content_obj, "language", None)
url = get_object_edit_url(content_obj, language)
# Or else, the standard edit view should be used
@@ -263,9 +273,12 @@ def get_preview_url(content_obj: models.Model, language: typing.Union[str, None]
if versionable.preview_url:
return versionable.preview_url(content_obj)
if is_editable_model(content_obj.__class__):
+ if not language:
+ # Use language field is content object has one to determine the language
+ language = getattr(content_obj, "language", get_language())
url = get_object_preview_url(content_obj, language=language)
- # Or else, the standard change view should be used
else:
+ # Or else, the standard change view should be used
url = admin_reverse(
f"{content_obj._meta.app_label}_{content_obj._meta.model_name}_change",
args=[content_obj.pk],
@@ -291,7 +304,7 @@ def remove_published_where(queryset):
def get_latest_admin_viewable_content(
- grouper: type,
+ grouper: models.Model,
include_unpublished_archived: bool = False,
**extra_grouping_fields,
) -> models.Model:
@@ -374,7 +387,11 @@ def content_is_unlocked_for_user(content: models.Model, user: settings.AUTH_USER
"""Check if lock doesn't exist or object is locked to provided user.
"""
try:
- return version_is_unlocked_for_user(content.versions.first(), user)
+ if hasattr(content, "prefetched_versions"):
+ version = content.prefetched_versions[0]
+ else:
+ version = content.versions.first()
+ return version_is_unlocked_for_user(version, user)
except AttributeError:
return True
@@ -412,15 +429,16 @@ def send_email(
def get_latest_draft_version(version):
- """Get latest draft version of version object
- """
+ """Get latest draft version of version object and caches it in the
+ content object"""
from djangocms_versioning.constants import DRAFT
from djangocms_versioning.models import Version
- drafts = (
- Version.objects
- .filter_by_content_grouping_values(version.content)
- .filter(state=DRAFT)
- )
-
- return drafts.first()
+ if not hasattr(version.content, "_latest_draft_version"):
+ drafts = (
+ Version.objects
+ .filter_by_content_grouping_values(version.content)
+ .filter(state=DRAFT)
+ )
+ version.content._latest_draft_version = drafts.first()
+ return version.content._latest_draft_version
diff --git a/djangocms_versioning/indicators.py b/djangocms_versioning/indicators.py
index 2e8380fb..a23ebd13 100644
--- a/djangocms_versioning/indicators.py
+++ b/djangocms_versioning/indicators.py
@@ -1,5 +1,8 @@
+import typing
+
from cms.utils.urlutils import admin_reverse
from django.contrib.auth import get_permission_codename
+from django.db import models
from django.utils.http import urlencode
from django.utils.translation import gettext_lazy as _
@@ -87,45 +90,42 @@ def content_indicator_menu(request, status, versions, back=""):
return menu
-def content_indicator(content_obj):
+def content_indicator(
+ content_obj: models.Model,
+ versions: typing.Optional[list[Version]] = None
+) -> typing.Optional[str]:
"""Translates available versions into status to be reflected by the indicator.
Function caches the result in the page_content object"""
if not content_obj:
return None # pragma: no cover
elif not hasattr(content_obj, "_indicator_status"):
- versions = Version.objects.filter_by_content_grouping_values(
- content_obj
- ).order_by("-pk")
+ if versions is None:
+ # Get all versions for the content object if not available
+ versions = Version.objects.filter_by_content_grouping_values(
+ content_obj
+ ).order_by("-pk")
+ version_states = dict(VERSION_STATES)
signature = {
- state: versions.filter(state=state)
- for state, name in VERSION_STATES
+ version.state: version
+ for version in versions if version.state in version_states
}
- if signature[DRAFT] and not signature[PUBLISHED]:
+ if DRAFT in signature and PUBLISHED not in signature:
content_obj._indicator_status = "draft"
- content_obj._version = signature[DRAFT]
- elif signature[DRAFT] and signature[PUBLISHED]:
+ content_obj._version = signature[DRAFT],
+ elif DRAFT in signature and PUBLISHED in signature:
content_obj._indicator_status = "dirty"
- content_obj._version = (signature[DRAFT][0], signature[PUBLISHED][0])
- elif signature[PUBLISHED]:
+ content_obj._version = (signature[DRAFT], signature[PUBLISHED])
+ elif PUBLISHED in signature:
content_obj._indicator_status = "published"
- content_obj._version = signature[PUBLISHED]
+ content_obj._version = signature[PUBLISHED],
elif versions[0].state == UNPUBLISHED:
content_obj._indicator_status = "unpublished"
- content_obj._version = signature[UNPUBLISHED]
+ content_obj._version = signature[UNPUBLISHED],
elif versions[0].state == ARCHIVED:
content_obj._indicator_status = "archived"
- content_obj._version = signature[ARCHIVED]
+ content_obj._version = signature[ARCHIVED],
else: # pragma: no cover
content_obj._indicator_status = None
content_obj._version = [None]
return content_obj._indicator_status
-
-
-def is_editable(content_obj, request):
- """Check of content_obj is editable"""
- if not content_obj.content_indicator():
- # Something's wrong: content indicator not identified. Maybe no version?
- return False
- versions = content_obj._version
- return versions[0].check_modify.as_bool(request.user)
diff --git a/djangocms_versioning/locale/ar/LC_MESSAGES/django.mo b/djangocms_versioning/locale/ar/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..15abc8cd
Binary files /dev/null and b/djangocms_versioning/locale/ar/LC_MESSAGES/django.mo differ
diff --git a/djangocms_versioning/locale/ar/LC_MESSAGES/django.po b/djangocms_versioning/locale/ar/LC_MESSAGES/django.po
new file mode 100644
index 00000000..7de35d10
--- /dev/null
+++ b/djangocms_versioning/locale/ar/LC_MESSAGES/django.po
@@ -0,0 +1,502 @@
+# 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.
+#
+# Translators:
+# Seraj Adden Baltu, 2024
+# Fabian Braun , 2024
+# Mohammad Alsakhawy, 2024
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2023-10-02 09:37+0200\n"
+"PO-Revision-Date: 2023-01-10 15:29+0000\n"
+"Last-Translator: Mohammad Alsakhawy, 2024\n"
+"Language-Team: Arabic (https://app.transifex.com/divio/teams/58664/ar/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: ar\n"
+"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
+
+#: admin.py:164 admin.py:301 admin.py:377
+msgid "State"
+msgstr "الحالة"
+
+#: admin.py:192 constants.py:27
+msgid "Empty"
+msgstr "فارغ"
+
+#: admin.py:315 admin.py:387
+msgid "Author"
+msgstr "المؤلف"
+
+#: admin.py:329 admin.py:401 models.py:87
+msgid "Modified"
+msgstr "تعديل"
+
+#: admin.py:437 admin.py:667
+#: templates/djangocms_versioning/admin/icons/preview.html:3
+#: templates/djangocms_versioning/admin/preview.html:3
+msgid "Preview"
+msgstr "معاينة"
+
+#: admin.py:470 admin.py:758 cms_toolbars.py:115
+#: templates/djangocms_versioning/admin/icons/edit_icon.html:3
+msgid "Edit"
+msgstr "تحرير"
+
+#: admin.py:482
+#: templates/djangocms_versioning/admin/icons/manage_versions.html:3
+msgid "Manage versions"
+msgstr "إدارة الإصدارات "
+
+#: admin.py:631
+msgid "Content"
+msgstr "المحتوى"
+
+#: admin.py:647
+msgid "locked"
+msgstr "مقفول"
+
+#: admin.py:683 templates/djangocms_versioning/admin/icons/archive_icon.html:3
+msgid "Archive"
+msgstr "أرشيف"
+
+#: admin.py:701 cms_toolbars.py:79 indicators.py:34
+#: templates/djangocms_versioning/admin/icons/publish_icon.html:3
+msgid "Publish"
+msgstr "نشر"
+
+#: admin.py:721 indicators.py:54 indicators.py:60
+#: templates/djangocms_versioning/admin/icons/unpublish_icon.html:3
+msgid "Unpublish"
+msgstr "الغاء النشر "
+
+#: admin.py:758 cms_toolbars.py:115
+msgid "New Draft"
+msgstr "مسودة جديدة "
+
+#: admin.py:779 cms_toolbars.py:177
+#: templates/djangocms_versioning/admin/icons/revert_icon.html:3
+msgid "Revert"
+msgstr "استرجاع"
+
+#: admin.py:798 templates/djangocms_versioning/admin/icons/discard_icon.html:3
+msgid "Discard"
+msgstr "تجاهل"
+
+#: admin.py:821 cms_toolbars.py:145
+msgid "Unlock"
+msgstr "إلغاء القفل "
+
+#: admin.py:856
+msgid "Compare versions"
+msgstr "مقارنة الإصدارات "
+
+#: admin.py:866
+msgid "Exactly two versions need to be selected."
+msgstr "يجب تحديد إثنين من الإصدارات بالضبط"
+
+#: admin.py:903
+msgid "Version cannot be archived"
+msgstr "لا يمكن أرشفة الإصدار "
+
+#: admin.py:929
+msgid "Version archived"
+msgstr "تمت أرشفة الإصدار "
+
+#: admin.py:940 admin.py:1059 admin.py:1235
+msgid "This view only supports POST method."
+msgstr "هذا العرض يدعم فقط طريقة POST"
+
+#: admin.py:951
+msgid "Version cannot be published"
+msgstr "لا يمكن نشر الإصدار "
+
+#: admin.py:962
+msgid "Version published"
+msgstr "تم نشر الإصدار "
+
+#: admin.py:979
+msgid "Version cannot be unpublished"
+msgstr "لا يمكن إلغاء نشر الإصدار"
+
+#: admin.py:1017
+msgid "Version unpublished"
+msgstr "تم إلغاء نشر الإصدار"
+
+#: admin.py:1163
+msgid "The last version has been deleted"
+msgstr "تم حذف الإصدار السابق"
+
+#: admin.py:1249
+msgid "You do not have permission to remove the version lock"
+msgstr "ليس لديك صلاحيات لحذف قفل الإصدار"
+
+#: admin.py:1254
+msgid "Version unlocked"
+msgstr "تم إلغاء قفل الإصدار"
+
+#: admin.py:1303
+#, python-brace-format
+msgid "Displaying versions of \"{grouper}\""
+msgstr "عرض إصدارات \"{grouper}\""
+
+#: apps.py:8
+msgid "django CMS Versioning"
+msgstr "إصدارات ن.إ.م. چانجو"
+
+#: cms_config.py:246
+msgid "No available title"
+msgstr "بدون عنوان"
+
+#: cms_config.py:248 constants.py:12 constants.py:25
+msgid "Unpublished"
+msgstr "غير منشور"
+
+#: cms_config.py:342
+msgid "Language must be set to a supported language!"
+msgstr "يجب تحديد لغة ضمن اللغات المدعومة!"
+
+#: cms_config.py:360
+msgid "You do not have permission to copy these plugins."
+msgstr "ليس لديك صلاحيات لنسخ هذه الملحقات."
+
+#: cms_toolbars.py:207
+msgid "Manage Versions"
+msgstr "إدارة الإصدارات"
+
+#: cms_toolbars.py:210
+#, python-brace-format
+msgid "Compare to {source}"
+msgstr "قارن ب {source}"
+
+#: cms_toolbars.py:226 indicators.py:66
+msgid "Discard Changes"
+msgstr " تجاهل التغييرات"
+
+#: cms_toolbars.py:262
+msgid "View Published"
+msgstr "عرض المنشور"
+
+#: cms_toolbars.py:317
+msgid "Language"
+msgstr "اللغة"
+
+#: cms_toolbars.py:364
+msgid "Add Translation"
+msgstr "إضافة ترجمة"
+
+#: cms_toolbars.py:377
+msgid "Copy all plugins"
+msgstr "نسخ كل الملحقات"
+
+#: cms_toolbars.py:379
+#, python-format
+msgid "from %s"
+msgstr "من %s"
+
+#: cms_toolbars.py:380
+#, python-format
+msgid "Are you sure you want to copy all plugins from %s?"
+msgstr "هل أنت متأكد من أنك تريد نسخ كل الملحقات من %s؟"
+
+#: cms_toolbars.py:395
+msgid "No other language available"
+msgstr "لا توجد لغة أخرى متوفرة"
+
+#: constants.py:10 constants.py:24
+msgid "Draft"
+msgstr "مسودة"
+
+#: constants.py:11 constants.py:22
+msgid "Published"
+msgstr "منشور"
+
+#: constants.py:13 constants.py:26
+msgid "Archived"
+msgstr "مُؤرشَف"
+
+#: constants.py:23
+msgid "Changed"
+msgstr "مُعدّل"
+
+#: emails.py:39
+msgid "Unlocked"
+msgstr "أُلغيَ القفل"
+
+#: indicators.py:28
+#, python-format
+msgid "Unlock (%(message)s)"
+msgstr "إلغاء قفل (%(message)s)"
+
+#: indicators.py:40
+msgid "Create new draft"
+msgstr "إنشاء مسودة جديدة"
+
+#: indicators.py:46
+msgid "Revert from Unpublish"
+msgstr "تراجع عن إلغاء النشر"
+
+#: indicators.py:66
+msgid "Delete Draft"
+msgstr "حذف المسودة"
+
+#: indicators.py:72
+msgid "Compare Draft to Published..."
+msgstr "قارن المسودة بالمنشور..."
+
+#: indicators.py:82
+msgid "Manage Versions..."
+msgstr "إدارة الإصدارات..."
+
+#: models.py:29
+msgid "Version is not a draft"
+msgstr "الإصدار ليس مسودة"
+
+#: models.py:30
+#, python-brace-format
+msgid "Action Denied. The latest version is locked by {user}"
+msgstr "تم رفض الإجراء. أحدث إصدار مقفول بواسطة {user}"
+
+#: models.py:31
+#, python-brace-format
+msgid "Action Denied. The draft version is locked by {user}"
+msgstr "تم رفض الإجراء. إصدار المسودة مقفول بواسطة {user}"
+
+#: models.py:86
+msgid "Created"
+msgstr "أُنشئ"
+
+#: models.py:89
+msgid "author"
+msgstr "المؤلف"
+
+#: models.py:102
+msgid "status"
+msgstr "الحالة"
+
+#: models.py:110
+msgid "locked by"
+msgstr "مقفول بواسطة"
+
+#: models.py:119
+msgid "source"
+msgstr "المصدر"
+
+#: models.py:133
+#, python-brace-format
+msgid "Version #{number} ({state} {date})"
+msgstr "الإصدار #{number} ({state} {date})"
+
+#: models.py:140
+#, python-brace-format
+msgid "Version #{number} ({state})"
+msgstr "الإصدار #{number} ({state})"
+
+#: models.py:146
+#, python-format
+msgid "Locked by %(user)s"
+msgstr "مقفول بواسطة %(user)s"
+
+#: models.py:278 models.py:327
+msgid "Version is not in draft state"
+msgstr "الإصدار ليس في حالة مسودة"
+
+#: models.py:387
+msgid "Version is not in published state"
+msgstr "الإصدار ليس في حالة منشور"
+
+#: models.py:444
+msgid "Version is not in archived or unpublished state"
+msgstr "الإصدار ليس في حالة أرشفة أو حالة غير منشور"
+
+#: models.py:459
+msgid "Version is not in draft or published state"
+msgstr "الإصدار ليس في حالة مسودة أو حالة منشور"
+
+#: models.py:467
+msgid "Version is already locked"
+msgstr "تم قفل الإصدار بالفعل"
+
+#: models.py:473
+msgid "Draft version is not locked"
+msgstr "إصدار المسودة غير مقفول"
+
+#: templates/admin/djangocms_versioning/versioning_breadcrumbs.html:3
+#: templates/djangocms_versioning/admin/grouper_form.html:9
+msgid "Home"
+msgstr "الرئيسية"
+
+#: templates/admin/djangocms_versioning/versioning_breadcrumbs.html:7
+#: templates/djangocms_versioning/admin/mixin/change_form.html:7
+msgid "Versions"
+msgstr "الإصدارات"
+
+#: templates/djangocms_versioning/admin/archive_confirmation.html:3
+msgid "Archive Confirmation"
+msgstr "تأكيد الأرشفة"
+
+#: templates/djangocms_versioning/admin/archive_confirmation.html:15
+msgid "Are you sure you want to archive the following version?"
+msgstr "هل أنت متأكد أنك تريد أرشفة الإصدار التالي؟"
+
+#: templates/djangocms_versioning/admin/archive_confirmation.html:17
+#: templates/djangocms_versioning/admin/unpublish_confirmation.html:17
+#, python-format
+msgid " Version number: %(version_number)s"
+msgstr "إصدار رقم: %(version_number)s"
+
+#: templates/djangocms_versioning/admin/archive_confirmation.html:22
+#: templates/djangocms_versioning/admin/discard_confirmation.html:23
+#: templates/djangocms_versioning/admin/revert_confirmation.html:40
+#: templates/djangocms_versioning/admin/unpublish_confirmation.html:27
+msgid "Yes, I'm sure"
+msgstr "نعم، أنا متأكد"
+
+#: templates/djangocms_versioning/admin/archive_confirmation.html:26
+#: templates/djangocms_versioning/admin/discard_confirmation.html:27
+#: templates/djangocms_versioning/admin/revert_confirmation.html:45
+#: templates/djangocms_versioning/admin/unpublish_confirmation.html:31
+msgid "No, take me back"
+msgstr "لا، تراجع للخلف"
+
+#: templates/djangocms_versioning/admin/compare.html:8
+#, python-format
+msgid ""
+"\n"
+" Compare %(left)s to %(right)s\n"
+" "
+msgstr ""
+"\n"
+"قارن %(left)s بـ %(right)s"
+
+#: templates/djangocms_versioning/admin/compare.html:12
+#, python-format
+msgid ""
+"\n"
+" Compare %(left)s\n"
+" "
+msgstr ""
+"\n"
+"قارن %(left)s"
+
+#: templates/djangocms_versioning/admin/compare.html:16
+#, python-format
+msgid ""
+"\n"
+" Compare %(right)s\n"
+" "
+msgstr ""
+"\n"
+"قارن %(right)s"
+
+#: templates/djangocms_versioning/admin/compare.html:37
+msgid "Back"
+msgstr "رجوع"
+
+#: templates/djangocms_versioning/admin/compare.html:40
+#, python-format
+msgid ""
+"\n"
+" Comparing %(left)s with\n"
+" "
+msgstr ""
+"\n"
+"مقارنة %(left)s بـ"
+
+#: templates/djangocms_versioning/admin/compare.html:45
+msgid "Pick a version to compare to"
+msgstr "اختر إصدار للمقارنة"
+
+#: templates/djangocms_versioning/admin/compare.html:56
+msgid "Visual"
+msgstr "مرئي"
+
+#: templates/djangocms_versioning/admin/compare.html:59
+msgid "Source"
+msgstr "مصدر"
+
+#: templates/djangocms_versioning/admin/discard_confirmation.html:3
+msgid "Discard Confirmation"
+msgstr "تأكيد التجاهل"
+
+#: templates/djangocms_versioning/admin/discard_confirmation.html:15
+msgid "Are you sure you want to discard following version?"
+msgstr "هل أنت متأكد أنك تريد تجاهل الإصدار التالي؟"
+
+#: templates/djangocms_versioning/admin/discard_confirmation.html:17
+#: templates/djangocms_versioning/admin/revert_confirmation.html:24
+#, python-format
+msgid "Version number: %(version_number)s"
+msgstr "إصدار رقم: %(version_number)s"
+
+#: templates/djangocms_versioning/admin/grouper_form.html:27
+#, python-format
+msgid "Add %(name)s"
+msgstr "إضافة %(name)s"
+
+#: templates/djangocms_versioning/admin/grouper_form.html:37
+msgid "Submit"
+msgstr "تأكيد"
+
+#: templates/djangocms_versioning/admin/icons/view.html:3
+msgid "View on site"
+msgstr "عرض على الموقع"
+
+#: templates/djangocms_versioning/admin/revert_confirmation.html:3
+#: templates/djangocms_versioning/admin/unpublish_confirmation.html:3
+msgid "Revert Confirmation"
+msgstr "تأكيد الاسترجاع"
+
+#: templates/djangocms_versioning/admin/revert_confirmation.html:18
+msgid ""
+"Reverting to this version may cause loss of an existing draft version. "
+"Please select an option to continue"
+msgstr ""
+"الاسترجاع لهذا الإصدار قد يتسبب في خسارة إصدار مسودة متواجدة. يرجى تحديد "
+"اختيار للإستمرار"
+
+#: templates/djangocms_versioning/admin/revert_confirmation.html:20
+msgid "Are you sure you want to revert to the following version?"
+msgstr "هل أنت متأكد أنك تريد استرجاع الإصدار التالي؟"
+
+#: templates/djangocms_versioning/admin/revert_confirmation.html:31
+msgid "Discard existing draft and Revert"
+msgstr "تجاهل المسودة المتواجدة واسترجع"
+
+#: templates/djangocms_versioning/admin/revert_confirmation.html:35
+msgid "Archive existing draft and Revert"
+msgstr "أرشف المسودة المتواجدة واسترجع"
+
+#: templates/djangocms_versioning/admin/unpublish_confirmation.html:15
+msgid ""
+"Unpublishing will remove this version from live. Are you sure you want to "
+"unpublish?"
+msgstr ""
+"إلغاء النشر سيؤدي إلى حذف هذا الإصدار من مباشر الموقع. هل أنت متأكد أنك تريد"
+" إلغاء النشر؟"
+
+#: templates/djangocms_versioning/emails/unlock-notification.txt:2
+#, python-format
+msgid ""
+"\n"
+"The following draft version has been unlocked by %(by_user)s for their use.\n"
+"%(version_link)s\n"
+"\n"
+"Please note you will not be able to further edit this draft. Kindly reach out to %(by_user)s in case of any concerns.\n"
+"\n"
+"This is an automated notification from Django CMS.\n"
+msgstr ""
+"\n"
+"تم إلغاء قفل المسودة التالية بواسطة %(by_user)s لاستخدامهم.\n"
+"%(version_link)s\n"
+"\n"
+"برجاء العلم أنك لن تستطيع إجراء المزيد من التعديلات على هذه المسودة. يرجى التواصل مع %(by_user)s في حالة وجود أي استفسارات.\n"
+"\n"
+"هذا إشعار تلقائي من ن.إ.م. چانجو.\n"
diff --git a/djangocms_versioning/locale/de/LC_MESSAGES/django.mo b/djangocms_versioning/locale/de/LC_MESSAGES/django.mo
index 27db87fb..b14902d4 100644
Binary files a/djangocms_versioning/locale/de/LC_MESSAGES/django.mo and b/djangocms_versioning/locale/de/LC_MESSAGES/django.mo differ
diff --git a/djangocms_versioning/locale/de/LC_MESSAGES/django.po b/djangocms_versioning/locale/de/LC_MESSAGES/django.po
index 939a0fc6..095da32e 100644
--- a/djangocms_versioning/locale/de/LC_MESSAGES/django.po
+++ b/djangocms_versioning/locale/de/LC_MESSAGES/django.po
@@ -2,124 +2,125 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
-#
+#
# Translators:
# Fabian Braun , 2023
-#
+#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-05-05 17:59+0200\n"
+"POT-Creation-Date: 2023-10-02 09:37+0200\n"
"PO-Revision-Date: 2023-01-10 15:29+0000\n"
"Last-Translator: Fabian Braun , 2023\n"
"Language-Team: German (https://app.transifex.com/divio/teams/58664/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"Language: de\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: admin.py:164 admin.py:301 admin.py:383
+#: admin.py:164 admin.py:301 admin.py:377
msgid "State"
msgstr "Status"
-#: admin.py:192 constants.py:28
+#: admin.py:192 constants.py:27
msgid "Empty"
msgstr "Leer"
-#: admin.py:315 admin.py:395
+#: admin.py:315 admin.py:387
msgid "Author"
msgstr "Autor"
-#: admin.py:329 admin.py:406 models.py:89
+#: admin.py:329 admin.py:401 models.py:87
msgid "Modified"
msgstr "Geändert"
-#: admin.py:433 admin.py:661
+#: admin.py:437 admin.py:667
#: templates/djangocms_versioning/admin/icons/preview.html:3
#: templates/djangocms_versioning/admin/preview.html:3
msgid "Preview"
msgstr "Vorschau"
-#: admin.py:468 admin.py:760 cms_toolbars.py:121
+#: admin.py:470 admin.py:758 cms_toolbars.py:115
#: templates/djangocms_versioning/admin/icons/edit_icon.html:3
msgid "Edit"
msgstr "Bearbeiten"
-#: admin.py:480
+#: admin.py:482
#: templates/djangocms_versioning/admin/icons/manage_versions.html:3
msgid "Manage versions"
msgstr "Versionen verwalten"
-#: admin.py:639
+#: admin.py:631
msgid "Content"
msgstr "Inhalt"
-#: admin.py:649
+#: admin.py:647
msgid "locked"
msgstr "gesperrt"
-#: admin.py:679 templates/djangocms_versioning/admin/icons/archive_icon.html:3
+#: admin.py:683 templates/djangocms_versioning/admin/icons/archive_icon.html:3
msgid "Archive"
msgstr "Archivieren"
-#: admin.py:699 cms_toolbars.py:83 indicators.py:41
+#: admin.py:701 cms_toolbars.py:79 indicators.py:34
#: templates/djangocms_versioning/admin/icons/publish_icon.html:3
msgid "Publish"
msgstr "Veröffentlichen"
-#: admin.py:721 indicators.py:61 indicators.py:67
+#: admin.py:721 indicators.py:54 indicators.py:60
#: templates/djangocms_versioning/admin/icons/unpublish_icon.html:3
msgid "Unpublish"
msgstr "Veröffentlichung aufheben"
-#: admin.py:760 cms_toolbars.py:121
+#: admin.py:758 cms_toolbars.py:115
msgid "New Draft"
msgstr "Neuer Entwurf"
-#: admin.py:783 cms_toolbars.py:188
+#: admin.py:779 cms_toolbars.py:177
#: templates/djangocms_versioning/admin/icons/revert_icon.html:3
msgid "Revert"
msgstr "Zurückholen"
-#: admin.py:804 templates/djangocms_versioning/admin/icons/discard_icon.html:3
+#: admin.py:798 templates/djangocms_versioning/admin/icons/discard_icon.html:3
msgid "Discard"
msgstr "Verwerfen"
-#: admin.py:829 cms_toolbars.py:153
+#: admin.py:821 cms_toolbars.py:145
msgid "Unlock"
msgstr "Entsperren"
#: admin.py:856
-msgid "Exactly two versions need to be selected."
-msgstr "Genau zwei Versionen müssen ausgewählt werden."
-
-#: admin.py:870
msgid "Compare versions"
msgstr "Versionen vergleichen"
-#: admin.py:897
+#: admin.py:866
+msgid "Exactly two versions need to be selected."
+msgstr "Genau zwei Versionen müssen ausgewählt werden."
+
+#: admin.py:903
msgid "Version cannot be archived"
msgstr "Version kann nicht archiviert werden"
-#: admin.py:926
+#: admin.py:929
msgid "Version archived"
msgstr "Version archiviert"
-#: admin.py:937 admin.py:1059 admin.py:1241
+#: admin.py:940 admin.py:1059 admin.py:1235
msgid "This view only supports POST method."
msgstr "Dieser View unterstützt nur die POST-Methode."
-#: admin.py:948
+#: admin.py:951
msgid "Version cannot be published"
msgstr "Version kann nicht veröffentlicht werden"
-#: admin.py:959
+#: admin.py:962
msgid "Version published"
msgstr "Version veröffentlicht"
-#: admin.py:976
+#: admin.py:979
msgid "Version cannot be unpublished"
msgstr "Die Veröffentlichung kann nicht aufgehoben werden"
@@ -127,19 +128,19 @@ msgstr "Die Veröffentlichung kann nicht aufgehoben werden"
msgid "Version unpublished"
msgstr "Veröffentlichung aufgehoben"
-#: admin.py:1169
+#: admin.py:1163
msgid "The last version has been deleted"
msgstr "Die neueste Version wurde gelöscht"
-#: admin.py:1255
+#: admin.py:1249
msgid "You do not have permission to remove the version lock"
msgstr "Keine Berechtigung vorhanden, um die Sperrung der Version aufzuheben."
-#: admin.py:1260
+#: admin.py:1254
msgid "Version unlocked"
msgstr "Version entsperrt"
-#: admin.py:1309
+#: admin.py:1303
#, python-brace-format
msgid "Displaying versions of \"{grouper}\""
msgstr "Zeige Versionen von \"{grouper}\""
@@ -148,180 +149,180 @@ msgstr "Zeige Versionen von \"{grouper}\""
msgid "django CMS Versioning"
msgstr "django CMS Versioning"
-#: cms_config.py:262
+#: cms_config.py:246
msgid "No available title"
msgstr "Kein Titel verfügbar"
-#: cms_config.py:264 constants.py:13 constants.py:26
+#: cms_config.py:248 constants.py:12 constants.py:25
msgid "Unpublished"
msgstr "Veröffentlichung aufgehoben"
-#: cms_config.py:358
+#: cms_config.py:342
msgid "Language must be set to a supported language!"
msgstr "Eine unterstützte Sprache muss ausgewählt sein!"
-#: cms_config.py:376
+#: cms_config.py:360
msgid "You do not have permission to copy these plugins."
msgstr "Keine Erlaubnis, diese Plugins zu kopieren."
-#: cms_toolbars.py:218
+#: cms_toolbars.py:207
msgid "Manage Versions"
msgstr "Versionen verwalten"
-#: cms_toolbars.py:221
+#: cms_toolbars.py:210
#, python-brace-format
msgid "Compare to {source}"
msgstr "Mit {source} vergleichen"
-#: cms_toolbars.py:236 indicators.py:73
+#: cms_toolbars.py:226 indicators.py:66
msgid "Discard Changes"
msgstr "Änderungen verwerfen"
-#: cms_toolbars.py:271
+#: cms_toolbars.py:262
msgid "View Published"
msgstr "Veröffentlichung ansehen"
-#: cms_toolbars.py:327
+#: cms_toolbars.py:317
msgid "Language"
msgstr "Sprache"
-#: cms_toolbars.py:374
+#: cms_toolbars.py:364
msgid "Add Translation"
msgstr "Übersetzung hinzufügen"
-#: cms_toolbars.py:387
+#: cms_toolbars.py:377
msgid "Copy all plugins"
msgstr "Alle Plugins kopieren"
-#: cms_toolbars.py:389
+#: cms_toolbars.py:379
#, python-format
msgid "from %s"
msgstr "von %s"
-#: cms_toolbars.py:390
+#: cms_toolbars.py:380
#, python-format
msgid "Are you sure you want to copy all plugins from %s?"
msgstr "Sind Sie sicher, dass sie alle Plugins von %s kopieren wollen?"
-#: cms_toolbars.py:405
+#: cms_toolbars.py:395
msgid "No other language available"
msgstr "Keine andere Sprache verfügbar"
-#: constants.py:11 constants.py:25
+#: constants.py:10 constants.py:24
msgid "Draft"
msgstr "Entwurf"
-#: constants.py:12 constants.py:23
+#: constants.py:11 constants.py:22
msgid "Published"
msgstr "Veröffentlicht"
-#: constants.py:14 constants.py:27
+#: constants.py:13 constants.py:26
msgid "Archived"
msgstr "Archiviert"
-#: constants.py:24
+#: constants.py:23
msgid "Changed"
msgstr "Verändert"
-#: emails.py:38
+#: emails.py:39
msgid "Unlocked"
msgstr "Entsperrt"
-#: indicators.py:35
+#: indicators.py:28
#, python-format
msgid "Unlock (%(message)s)"
msgstr "Entsperren (%(message)s)"
-#: indicators.py:47
+#: indicators.py:40
msgid "Create new draft"
msgstr "Neuen Entwurf erstellen"
-#: indicators.py:53
+#: indicators.py:46
msgid "Revert from Unpublish"
msgstr "Zurückholen"
-#: indicators.py:73
+#: indicators.py:66
msgid "Delete Draft"
msgstr "Entwurf löschen"
-#: indicators.py:79
+#: indicators.py:72
msgid "Compare Draft to Published..."
msgstr "Entwurf mit Veröffentlichung vergleichen..."
-#: indicators.py:89
+#: indicators.py:82
msgid "Manage Versions..."
msgstr "Versionen verwalten..."
-#: models.py:31
+#: models.py:29
msgid "Version is not a draft"
msgstr "Version ist kein Entwurf"
-#: models.py:32
+#: models.py:30
#, python-brace-format
msgid "Action Denied. The latest version is locked by {user}"
msgstr "Aktion verweigert. Die aktuelle Version ist von {user} gesperrt."
-#: models.py:33
+#: models.py:31
#, python-brace-format
msgid "Action Denied. The draft version is locked by {user}"
msgstr "Aktion verweigert. Der Entwurf ist von {user} gesperrt."
-#: models.py:88
+#: models.py:86
msgid "Created"
msgstr "Erstellt"
-#: models.py:91
+#: models.py:89
msgid "author"
msgstr "Autor"
-#: models.py:100
+#: models.py:102
msgid "status"
msgstr "Status"
-#: models.py:108
+#: models.py:110
msgid "locked by"
msgstr "gesperrt von"
-#: models.py:117
+#: models.py:119
msgid "source"
msgstr "Ursprung"
-#: models.py:131
+#: models.py:133
#, python-brace-format
msgid "Version #{number} ({state} {date})"
msgstr "Version #{number} ({state} {date}) "
-#: models.py:138
+#: models.py:140
#, python-brace-format
msgid "Version #{number} ({state})"
msgstr "Version #{number} ({state})"
-#: models.py:144
+#: models.py:146
#, python-format
msgid "Locked by %(user)s"
msgstr "Gesperrt von %(user)s"
-#: models.py:276 models.py:325
+#: models.py:278 models.py:327
msgid "Version is not in draft state"
msgstr "Version ist kein Entwurf"
-#: models.py:385
+#: models.py:387
msgid "Version is not in published state"
msgstr "Version ist nicht veröffentlicht"
-#: models.py:442
+#: models.py:444
msgid "Version is not in archived or unpublished state"
msgstr "Version ist weder archiviert noch eine Veröffentlichung aufgehoben"
-#: models.py:457
+#: models.py:459
msgid "Version is not in draft or published state"
msgstr "Version ist weder ein Entwurf noch veröffentlicht"
-#: models.py:465
+#: models.py:467
msgid "Version is already locked"
msgstr "Version bereits gesperrt"
-#: models.py:471
+#: models.py:473
msgid "Draft version is not locked"
msgstr "Entwurf ist nicht gesperrt"
diff --git a/djangocms_versioning/locale/en/LC_MESSAGES/django.po b/djangocms_versioning/locale/en/LC_MESSAGES/django.po
index f5352e8b..05666e41 100644
--- a/djangocms_versioning/locale/en/LC_MESSAGES/django.po
+++ b/djangocms_versioning/locale/en/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-05-05 17:59+0200\n"
+"POT-Creation-Date: 2023-10-02 09:37+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -18,106 +18,106 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: admin.py:164 admin.py:301 admin.py:383
+#: admin.py:164 admin.py:301 admin.py:377
msgid "State"
msgstr ""
-#: admin.py:192 constants.py:28
+#: admin.py:192 constants.py:27
msgid "Empty"
msgstr ""
-#: admin.py:315 admin.py:395
+#: admin.py:315 admin.py:387
msgid "Author"
msgstr ""
-#: admin.py:329 admin.py:406 models.py:89
+#: admin.py:329 admin.py:401 models.py:87
msgid "Modified"
msgstr ""
-#: admin.py:433 admin.py:661
+#: admin.py:437 admin.py:667
#: templates/djangocms_versioning/admin/icons/preview.html:3
#: templates/djangocms_versioning/admin/preview.html:3
msgid "Preview"
msgstr ""
-#: admin.py:468 admin.py:760 cms_toolbars.py:121
+#: admin.py:470 admin.py:758 cms_toolbars.py:115
#: templates/djangocms_versioning/admin/icons/edit_icon.html:3
msgid "Edit"
msgstr ""
-#: admin.py:480
+#: admin.py:482
#: templates/djangocms_versioning/admin/icons/manage_versions.html:3
msgid "Manage versions"
msgstr ""
-#: admin.py:639
+#: admin.py:631
msgid "Content"
msgstr ""
-#: admin.py:649
+#: admin.py:647
msgid "locked"
msgstr ""
-#: admin.py:679 templates/djangocms_versioning/admin/icons/archive_icon.html:3
+#: admin.py:683 templates/djangocms_versioning/admin/icons/archive_icon.html:3
msgid "Archive"
msgstr ""
-#: admin.py:699 cms_toolbars.py:83 indicators.py:41
+#: admin.py:701 cms_toolbars.py:79 indicators.py:34
#: templates/djangocms_versioning/admin/icons/publish_icon.html:3
msgid "Publish"
msgstr ""
-#: admin.py:721 indicators.py:61 indicators.py:67
+#: admin.py:721 indicators.py:54 indicators.py:60
#: templates/djangocms_versioning/admin/icons/unpublish_icon.html:3
msgid "Unpublish"
msgstr ""
-#: admin.py:760 cms_toolbars.py:121
+#: admin.py:758 cms_toolbars.py:115
msgid "New Draft"
msgstr ""
-#: admin.py:783 cms_toolbars.py:188
+#: admin.py:779 cms_toolbars.py:177
#: templates/djangocms_versioning/admin/icons/revert_icon.html:3
msgid "Revert"
msgstr ""
-#: admin.py:804 templates/djangocms_versioning/admin/icons/discard_icon.html:3
+#: admin.py:798 templates/djangocms_versioning/admin/icons/discard_icon.html:3
msgid "Discard"
msgstr ""
-#: admin.py:829 cms_toolbars.py:153
+#: admin.py:821 cms_toolbars.py:145
msgid "Unlock"
msgstr ""
#: admin.py:856
-msgid "Exactly two versions need to be selected."
+msgid "Compare versions"
msgstr ""
-#: admin.py:870
-msgid "Compare versions"
+#: admin.py:866
+msgid "Exactly two versions need to be selected."
msgstr ""
-#: admin.py:897
+#: admin.py:903
msgid "Version cannot be archived"
msgstr ""
-#: admin.py:926
+#: admin.py:929
msgid "Version archived"
msgstr ""
-#: admin.py:937 admin.py:1059 admin.py:1241
+#: admin.py:940 admin.py:1059 admin.py:1235
msgid "This view only supports POST method."
msgstr ""
-#: admin.py:948
+#: admin.py:951
msgid "Version cannot be published"
msgstr ""
-#: admin.py:959
+#: admin.py:962
msgid "Version published"
msgstr ""
-#: admin.py:976
+#: admin.py:979
msgid "Version cannot be unpublished"
msgstr ""
@@ -125,19 +125,19 @@ msgstr ""
msgid "Version unpublished"
msgstr ""
-#: admin.py:1169
+#: admin.py:1163
msgid "The last version has been deleted"
msgstr ""
-#: admin.py:1255
+#: admin.py:1249
msgid "You do not have permission to remove the version lock"
msgstr ""
-#: admin.py:1260
+#: admin.py:1254
msgid "Version unlocked"
msgstr ""
-#: admin.py:1309
+#: admin.py:1303
#, python-brace-format
msgid "Displaying versions of \"{grouper}\""
msgstr ""
@@ -146,180 +146,180 @@ msgstr ""
msgid "django CMS Versioning"
msgstr ""
-#: cms_config.py:262
+#: cms_config.py:246
msgid "No available title"
msgstr ""
-#: cms_config.py:264 constants.py:13 constants.py:26
+#: cms_config.py:248 constants.py:12 constants.py:25
msgid "Unpublished"
msgstr ""
-#: cms_config.py:358
+#: cms_config.py:342
msgid "Language must be set to a supported language!"
msgstr ""
-#: cms_config.py:376
+#: cms_config.py:360
msgid "You do not have permission to copy these plugins."
msgstr ""
-#: cms_toolbars.py:218
+#: cms_toolbars.py:207
msgid "Manage Versions"
msgstr ""
-#: cms_toolbars.py:221
+#: cms_toolbars.py:210
#, python-brace-format
msgid "Compare to {source}"
msgstr ""
-#: cms_toolbars.py:236 indicators.py:73
+#: cms_toolbars.py:226 indicators.py:66
msgid "Discard Changes"
msgstr ""
-#: cms_toolbars.py:271
+#: cms_toolbars.py:262
msgid "View Published"
msgstr ""
-#: cms_toolbars.py:327
+#: cms_toolbars.py:317
msgid "Language"
msgstr ""
-#: cms_toolbars.py:374
+#: cms_toolbars.py:364
msgid "Add Translation"
msgstr ""
-#: cms_toolbars.py:387
+#: cms_toolbars.py:377
msgid "Copy all plugins"
msgstr ""
-#: cms_toolbars.py:389
+#: cms_toolbars.py:379
#, python-format
msgid "from %s"
msgstr ""
-#: cms_toolbars.py:390
+#: cms_toolbars.py:380
#, python-format
msgid "Are you sure you want to copy all plugins from %s?"
msgstr ""
-#: cms_toolbars.py:405
+#: cms_toolbars.py:395
msgid "No other language available"
msgstr ""
-#: constants.py:11 constants.py:25
+#: constants.py:10 constants.py:24
msgid "Draft"
msgstr ""
-#: constants.py:12 constants.py:23
+#: constants.py:11 constants.py:22
msgid "Published"
msgstr ""
-#: constants.py:14 constants.py:27
+#: constants.py:13 constants.py:26
msgid "Archived"
msgstr ""
-#: constants.py:24
+#: constants.py:23
msgid "Changed"
msgstr ""
-#: emails.py:38
+#: emails.py:39
msgid "Unlocked"
msgstr ""
-#: indicators.py:35
+#: indicators.py:28
#, python-format
msgid "Unlock (%(message)s)"
msgstr ""
-#: indicators.py:47
+#: indicators.py:40
msgid "Create new draft"
msgstr ""
-#: indicators.py:53
+#: indicators.py:46
msgid "Revert from Unpublish"
msgstr ""
-#: indicators.py:73
+#: indicators.py:66
msgid "Delete Draft"
msgstr ""
-#: indicators.py:79
+#: indicators.py:72
msgid "Compare Draft to Published..."
msgstr ""
-#: indicators.py:89
+#: indicators.py:82
msgid "Manage Versions..."
msgstr ""
-#: models.py:31
+#: models.py:29
msgid "Version is not a draft"
msgstr ""
-#: models.py:32
+#: models.py:30
#, python-brace-format
msgid "Action Denied. The latest version is locked by {user}"
msgstr ""
-#: models.py:33
+#: models.py:31
#, python-brace-format
msgid "Action Denied. The draft version is locked by {user}"
msgstr ""
-#: models.py:88
+#: models.py:86
msgid "Created"
msgstr ""
-#: models.py:91
+#: models.py:89
msgid "author"
msgstr ""
-#: models.py:100
+#: models.py:102
msgid "status"
msgstr ""
-#: models.py:108
+#: models.py:110
msgid "locked by"
msgstr ""
-#: models.py:117
+#: models.py:119
msgid "source"
msgstr ""
-#: models.py:131
+#: models.py:133
#, python-brace-format
msgid "Version #{number} ({state} {date})"
msgstr ""
-#: models.py:138
+#: models.py:140
#, python-brace-format
msgid "Version #{number} ({state})"
msgstr ""
-#: models.py:144
+#: models.py:146
#, python-format
msgid "Locked by %(user)s"
msgstr ""
-#: models.py:276 models.py:325
+#: models.py:278 models.py:327
msgid "Version is not in draft state"
msgstr ""
-#: models.py:385
+#: models.py:387
msgid "Version is not in published state"
msgstr ""
-#: models.py:442
+#: models.py:444
msgid "Version is not in archived or unpublished state"
msgstr ""
-#: models.py:457
+#: models.py:459
msgid "Version is not in draft or published state"
msgstr ""
-#: models.py:465
+#: models.py:467
msgid "Version is already locked"
msgstr ""
-#: models.py:471
+#: models.py:473
msgid "Draft version is not locked"
msgstr ""
diff --git a/djangocms_versioning/locale/fr/LC_MESSAGES/django.mo b/djangocms_versioning/locale/fr/LC_MESSAGES/django.mo
index 92cd88bb..1315279b 100644
Binary files a/djangocms_versioning/locale/fr/LC_MESSAGES/django.mo and b/djangocms_versioning/locale/fr/LC_MESSAGES/django.mo differ
diff --git a/djangocms_versioning/locale/fr/LC_MESSAGES/django.po b/djangocms_versioning/locale/fr/LC_MESSAGES/django.po
index 2fa5d9fa..02035cba 100644
--- a/djangocms_versioning/locale/fr/LC_MESSAGES/django.po
+++ b/djangocms_versioning/locale/fr/LC_MESSAGES/django.po
@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-05-05 17:59+0200\n"
+"POT-Creation-Date: 2023-10-02 09:37+0200\n"
"PO-Revision-Date: 2023-01-10 15:29+0000\n"
"Last-Translator: Frédéric Roland, 2023\n"
"Language-Team: French (https://app.transifex.com/divio/teams/58664/fr/)\n"
@@ -22,106 +22,106 @@ msgstr ""
"Language: fr\n"
"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
-#: admin.py:164 admin.py:301 admin.py:383
+#: admin.py:164 admin.py:301 admin.py:377
msgid "State"
msgstr "État"
-#: admin.py:192 constants.py:28
+#: admin.py:192 constants.py:27
msgid "Empty"
msgstr "Vide"
-#: admin.py:315 admin.py:395
+#: admin.py:315 admin.py:387
msgid "Author"
msgstr "Auteur"
-#: admin.py:329 admin.py:406 models.py:89
+#: admin.py:329 admin.py:401 models.py:87
msgid "Modified"
msgstr "Modifié"
-#: admin.py:433 admin.py:661
+#: admin.py:437 admin.py:667
#: templates/djangocms_versioning/admin/icons/preview.html:3
#: templates/djangocms_versioning/admin/preview.html:3
msgid "Preview"
msgstr "Pré-visualisation"
-#: admin.py:468 admin.py:760 cms_toolbars.py:121
+#: admin.py:470 admin.py:758 cms_toolbars.py:115
#: templates/djangocms_versioning/admin/icons/edit_icon.html:3
msgid "Edit"
msgstr "Éditer"
-#: admin.py:480
+#: admin.py:482
#: templates/djangocms_versioning/admin/icons/manage_versions.html:3
msgid "Manage versions"
msgstr "Gérer les versions"
-#: admin.py:639
+#: admin.py:631
msgid "Content"
msgstr "Contenu"
-#: admin.py:649
+#: admin.py:647
msgid "locked"
msgstr "verrouillé"
-#: admin.py:679 templates/djangocms_versioning/admin/icons/archive_icon.html:3
+#: admin.py:683 templates/djangocms_versioning/admin/icons/archive_icon.html:3
msgid "Archive"
msgstr "Archiver"
-#: admin.py:699 cms_toolbars.py:83 indicators.py:41
+#: admin.py:701 cms_toolbars.py:79 indicators.py:34
#: templates/djangocms_versioning/admin/icons/publish_icon.html:3
msgid "Publish"
msgstr "Publier"
-#: admin.py:721 indicators.py:61 indicators.py:67
+#: admin.py:721 indicators.py:54 indicators.py:60
#: templates/djangocms_versioning/admin/icons/unpublish_icon.html:3
msgid "Unpublish"
msgstr "Dépublier"
-#: admin.py:760 cms_toolbars.py:121
+#: admin.py:758 cms_toolbars.py:115
msgid "New Draft"
msgstr "Nouveau Brouillon"
-#: admin.py:783 cms_toolbars.py:188
+#: admin.py:779 cms_toolbars.py:177
#: templates/djangocms_versioning/admin/icons/revert_icon.html:3
msgid "Revert"
msgstr "Rétablir"
-#: admin.py:804 templates/djangocms_versioning/admin/icons/discard_icon.html:3
+#: admin.py:798 templates/djangocms_versioning/admin/icons/discard_icon.html:3
msgid "Discard"
msgstr "Rejeter"
-#: admin.py:829 cms_toolbars.py:153
+#: admin.py:821 cms_toolbars.py:145
msgid "Unlock"
msgstr "Déverrouiller"
#: admin.py:856
-msgid "Exactly two versions need to be selected."
-msgstr "Il faut sélectionner exactement deux versions."
-
-#: admin.py:870
msgid "Compare versions"
msgstr "Comparer les versions"
-#: admin.py:897
+#: admin.py:866
+msgid "Exactly two versions need to be selected."
+msgstr "Il faut sélectionner exactement deux versions."
+
+#: admin.py:903
msgid "Version cannot be archived"
msgstr "La version ne peut pas être archivée"
-#: admin.py:926
+#: admin.py:929
msgid "Version archived"
msgstr "Version archivée"
-#: admin.py:937 admin.py:1059 admin.py:1241
+#: admin.py:940 admin.py:1059 admin.py:1235
msgid "This view only supports POST method."
msgstr "Cette vue ne prend en charge que la méthode POST."
-#: admin.py:948
+#: admin.py:951
msgid "Version cannot be published"
msgstr "La version ne peut pas être publiée"
-#: admin.py:959
+#: admin.py:962
msgid "Version published"
msgstr "Version publiée"
-#: admin.py:976
+#: admin.py:979
msgid "Version cannot be unpublished"
msgstr "La version ne peut pas être dépubliée"
@@ -129,19 +129,19 @@ msgstr "La version ne peut pas être dépubliée"
msgid "Version unpublished"
msgstr "Version non publiée"
-#: admin.py:1169
+#: admin.py:1163
msgid "The last version has been deleted"
msgstr "La dernière version a été supprimée"
-#: admin.py:1255
+#: admin.py:1249
msgid "You do not have permission to remove the version lock"
msgstr "Vous n'avez pas la permission de retirer le verrouillage de version"
-#: admin.py:1260
+#: admin.py:1254
msgid "Version unlocked"
msgstr "Version déverrouillée"
-#: admin.py:1309
+#: admin.py:1303
#, python-brace-format
msgid "Displaying versions of \"{grouper}\""
msgstr "Afficher les versions de \"{grouper}\""
@@ -150,180 +150,180 @@ msgstr "Afficher les versions de \"{grouper}\""
msgid "django CMS Versioning"
msgstr "django CMS Versioning"
-#: cms_config.py:262
+#: cms_config.py:246
msgid "No available title"
msgstr "Aucun titre disponible"
-#: cms_config.py:264 constants.py:13 constants.py:26
+#: cms_config.py:248 constants.py:12 constants.py:25
msgid "Unpublished"
msgstr "Non publié"
-#: cms_config.py:358
+#: cms_config.py:342
msgid "Language must be set to a supported language!"
msgstr "La langue doit être définie comme une langue prise en charge !"
-#: cms_config.py:376
+#: cms_config.py:360
msgid "You do not have permission to copy these plugins."
msgstr "Vous n'avez pas la permission de copier ces plugins."
-#: cms_toolbars.py:218
+#: cms_toolbars.py:207
msgid "Manage Versions"
msgstr "Gérer les versions"
-#: cms_toolbars.py:221
+#: cms_toolbars.py:210
#, python-brace-format
msgid "Compare to {source}"
msgstr "Comparer à {source}"
-#: cms_toolbars.py:236 indicators.py:73
+#: cms_toolbars.py:226 indicators.py:66
msgid "Discard Changes"
msgstr "Abandonner les modifications"
-#: cms_toolbars.py:271
+#: cms_toolbars.py:262
msgid "View Published"
msgstr "Vue publiée"
-#: cms_toolbars.py:327
+#: cms_toolbars.py:317
msgid "Language"
msgstr "Langue"
-#: cms_toolbars.py:374
+#: cms_toolbars.py:364
msgid "Add Translation"
msgstr "Ajouter une traduction"
-#: cms_toolbars.py:387
+#: cms_toolbars.py:377
msgid "Copy all plugins"
msgstr "Copier tous les plugins"
-#: cms_toolbars.py:389
+#: cms_toolbars.py:379
#, python-format
msgid "from %s"
msgstr "de %s"
-#: cms_toolbars.py:390
+#: cms_toolbars.py:380
#, python-format
msgid "Are you sure you want to copy all plugins from %s?"
msgstr "Êtes-vous sûr de vouloir copier tous les plugins de %s?"
-#: cms_toolbars.py:405
+#: cms_toolbars.py:395
msgid "No other language available"
msgstr "Aucune autre langue disponible"
-#: constants.py:11 constants.py:25
+#: constants.py:10 constants.py:24
msgid "Draft"
msgstr "Brouillon"
-#: constants.py:12 constants.py:23
+#: constants.py:11 constants.py:22
msgid "Published"
msgstr "Publié"
-#: constants.py:14 constants.py:27
+#: constants.py:13 constants.py:26
msgid "Archived"
msgstr "Archivé"
-#: constants.py:24
+#: constants.py:23
msgid "Changed"
msgstr "Modifié"
-#: emails.py:38
+#: emails.py:39
msgid "Unlocked"
msgstr "Déverrouillée"
-#: indicators.py:35
+#: indicators.py:28
#, python-format
msgid "Unlock (%(message)s)"
msgstr "Déverrouiller (%(message)s)"
-#: indicators.py:47
+#: indicators.py:40
msgid "Create new draft"
msgstr "Créer un brouillon"
-#: indicators.py:53
+#: indicators.py:46
msgid "Revert from Unpublish"
msgstr "Annulation de la publication"
-#: indicators.py:73
+#: indicators.py:66
msgid "Delete Draft"
msgstr "Supprimer le brouillon"
-#: indicators.py:79
+#: indicators.py:72
msgid "Compare Draft to Published..."
msgstr "Comparer le brouillon à la version publiée..."
-#: indicators.py:89
+#: indicators.py:82
msgid "Manage Versions..."
msgstr "Gérer les versions..."
-#: models.py:31
+#: models.py:29
msgid "Version is not a draft"
msgstr "La version n'est pas un brouillon"
-#: models.py:32
+#: models.py:30
#, python-brace-format
msgid "Action Denied. The latest version is locked by {user}"
msgstr "Action Refusée. La dernière version est verrouillée par {user}"
-#: models.py:33
+#: models.py:31
#, python-brace-format
msgid "Action Denied. The draft version is locked by {user}"
msgstr "Action Refusée. Le brouillon est verrouillé par {user}"
-#: models.py:88
+#: models.py:86
msgid "Created"
msgstr "Créée"
-#: models.py:91
+#: models.py:89
msgid "author"
msgstr "auteur"
-#: models.py:100
+#: models.py:102
msgid "status"
msgstr "statut"
-#: models.py:108
+#: models.py:110
msgid "locked by"
msgstr "verrouillée par"
-#: models.py:117
+#: models.py:119
msgid "source"
msgstr "source"
-#: models.py:131
+#: models.py:133
#, python-brace-format
msgid "Version #{number} ({state} {date})"
msgstr "Version #{number} ({state} {date})"
-#: models.py:138
+#: models.py:140
#, python-brace-format
msgid "Version #{number} ({state})"
msgstr "Version #{number} ({state})"
-#: models.py:144
+#: models.py:146
#, python-format
msgid "Locked by %(user)s"
msgstr "Verrouillée par %(user)s"
-#: models.py:276 models.py:325
+#: models.py:278 models.py:327
msgid "Version is not in draft state"
msgstr "La version n'est pas à l'état de brouillon"
-#: models.py:385
+#: models.py:387
msgid "Version is not in published state"
msgstr "La version n'est pas dans l'état publié"
-#: models.py:442
+#: models.py:444
msgid "Version is not in archived or unpublished state"
msgstr "La version n'est pas archivé ou non publié"
-#: models.py:457
+#: models.py:459
msgid "Version is not in draft or published state"
msgstr "La version n'est pas en brouillon ou publiée"
-#: models.py:465
+#: models.py:467
msgid "Version is already locked"
msgstr "La version est déjà verrouillée"
-#: models.py:471
+#: models.py:473
msgid "Draft version is not locked"
msgstr "Le brouillon n'est pas verrouillé"
diff --git a/djangocms_versioning/locale/nl/LC_MESSAGES/django.mo b/djangocms_versioning/locale/nl/LC_MESSAGES/django.mo
index a1ffd899..7bc611d5 100644
Binary files a/djangocms_versioning/locale/nl/LC_MESSAGES/django.mo and b/djangocms_versioning/locale/nl/LC_MESSAGES/django.mo differ
diff --git a/djangocms_versioning/locale/nl/LC_MESSAGES/django.po b/djangocms_versioning/locale/nl/LC_MESSAGES/django.po
index fec58ac3..fd9c6b65 100644
--- a/djangocms_versioning/locale/nl/LC_MESSAGES/django.po
+++ b/djangocms_versioning/locale/nl/LC_MESSAGES/django.po
@@ -2,126 +2,126 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
-#
+#
# Translators:
# Fabian Braun , 2023
# Stefan van den Eertwegh , 2023
-#
+#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-05-05 17:59+0200\n"
+"POT-Creation-Date: 2023-10-02 09:37+0200\n"
"PO-Revision-Date: 2023-01-10 15:29+0000\n"
"Last-Translator: Stefan van den Eertwegh , 2023\n"
"Language-Team: Dutch (https://app.transifex.com/divio/teams/58664/nl/)\n"
-"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"Language: nl\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: admin.py:164 admin.py:301 admin.py:383
+#: admin.py:164 admin.py:301 admin.py:377
msgid "State"
msgstr "Status"
-#: admin.py:192 constants.py:28
+#: admin.py:192 constants.py:27
msgid "Empty"
msgstr "Leeg"
-#: admin.py:315 admin.py:395
+#: admin.py:315 admin.py:387
msgid "Author"
msgstr "Auteur"
-#: admin.py:329 admin.py:406 models.py:89
+#: admin.py:329 admin.py:401 models.py:87
msgid "Modified"
msgstr "Gewijzigd"
-#: admin.py:433 admin.py:661
+#: admin.py:437 admin.py:667
#: templates/djangocms_versioning/admin/icons/preview.html:3
#: templates/djangocms_versioning/admin/preview.html:3
msgid "Preview"
msgstr "Voorbeeld"
-#: admin.py:468 admin.py:760 cms_toolbars.py:121
+#: admin.py:470 admin.py:758 cms_toolbars.py:115
#: templates/djangocms_versioning/admin/icons/edit_icon.html:3
msgid "Edit"
msgstr "Bewerk"
-#: admin.py:480
+#: admin.py:482
#: templates/djangocms_versioning/admin/icons/manage_versions.html:3
msgid "Manage versions"
msgstr "Beheer versies"
-#: admin.py:639
+#: admin.py:631
msgid "Content"
msgstr "Content"
-#: admin.py:649
+#: admin.py:647
msgid "locked"
msgstr "gesloten"
-#: admin.py:679 templates/djangocms_versioning/admin/icons/archive_icon.html:3
+#: admin.py:683 templates/djangocms_versioning/admin/icons/archive_icon.html:3
msgid "Archive"
msgstr "Archiveer"
-#: admin.py:699 cms_toolbars.py:83 indicators.py:41
+#: admin.py:701 cms_toolbars.py:79 indicators.py:34
#: templates/djangocms_versioning/admin/icons/publish_icon.html:3
msgid "Publish"
msgstr "Publiceer"
-#: admin.py:721 indicators.py:61 indicators.py:67
+#: admin.py:721 indicators.py:54 indicators.py:60
#: templates/djangocms_versioning/admin/icons/unpublish_icon.html:3
msgid "Unpublish"
msgstr "Gedepubliceerd"
-#: admin.py:760 cms_toolbars.py:121
+#: admin.py:758 cms_toolbars.py:115
msgid "New Draft"
msgstr "Nieuw concept"
-#: admin.py:783 cms_toolbars.py:188
+#: admin.py:779 cms_toolbars.py:177
#: templates/djangocms_versioning/admin/icons/revert_icon.html:3
msgid "Revert"
msgstr "Terugdraaien"
-#: admin.py:804 templates/djangocms_versioning/admin/icons/discard_icon.html:3
+#: admin.py:798 templates/djangocms_versioning/admin/icons/discard_icon.html:3
msgid "Discard"
msgstr "Annuleer"
-#: admin.py:829 cms_toolbars.py:153
+#: admin.py:821 cms_toolbars.py:145
msgid "Unlock"
msgstr "Ongesloten"
#: admin.py:856
-msgid "Exactly two versions need to be selected."
-msgstr "Precies twee versies moeten zijn geselecteerd."
-
-#: admin.py:870
msgid "Compare versions"
msgstr "Vergelijk versies"
-#: admin.py:897
+#: admin.py:866
+msgid "Exactly two versions need to be selected."
+msgstr "Precies twee versies moeten zijn geselecteerd."
+
+#: admin.py:903
msgid "Version cannot be archived"
msgstr "Versie kan niet worden gearchiveerd"
-#: admin.py:926
+#: admin.py:929
msgid "Version archived"
msgstr "Versie gearchiveerd"
-#: admin.py:937 admin.py:1059 admin.py:1241
+#: admin.py:940 admin.py:1059 admin.py:1235
msgid "This view only supports POST method."
msgstr "Deze weergave ondersteunt alleen de POST methode"
-#: admin.py:948
+#: admin.py:951
msgid "Version cannot be published"
msgstr "Versie kan niet worden gepubliceerd"
-#: admin.py:959
+#: admin.py:962
msgid "Version published"
msgstr "Versie gepubliceerd"
-#: admin.py:976
+#: admin.py:979
msgid "Version cannot be unpublished"
msgstr "Versie kan niet worden gedepubliceerd"
@@ -129,19 +129,19 @@ msgstr "Versie kan niet worden gedepubliceerd"
msgid "Version unpublished"
msgstr "Versie ongepubliceerd"
-#: admin.py:1169
+#: admin.py:1163
msgid "The last version has been deleted"
msgstr "De laatste versie is verwijderd"
-#: admin.py:1255
+#: admin.py:1249
msgid "You do not have permission to remove the version lock"
msgstr "Je hebt geen rechten om de versie van t slot te halen"
-#: admin.py:1260
+#: admin.py:1254
msgid "Version unlocked"
msgstr "Versie ongesloten"
-#: admin.py:1309
+#: admin.py:1303
#, python-brace-format
msgid "Displaying versions of \"{grouper}\""
msgstr "Weergave versies van \"{grouper}\""
@@ -150,180 +150,180 @@ msgstr "Weergave versies van \"{grouper}\""
msgid "django CMS Versioning"
msgstr "django CMS Versionering"
-#: cms_config.py:262
+#: cms_config.py:246
msgid "No available title"
msgstr "Geen beschikbare titel"
-#: cms_config.py:264 constants.py:13 constants.py:26
+#: cms_config.py:248 constants.py:12 constants.py:25
msgid "Unpublished"
msgstr "Ongepubliceerd"
-#: cms_config.py:358
+#: cms_config.py:342
msgid "Language must be set to a supported language!"
msgstr "Taal moet gespecificeerd worden binnen de ondersteunde talen!"
-#: cms_config.py:376
+#: cms_config.py:360
msgid "You do not have permission to copy these plugins."
msgstr "Je hebt geen rechten om deze plugin te kopieëren."
-#: cms_toolbars.py:218
+#: cms_toolbars.py:207
msgid "Manage Versions"
msgstr "Beheer versies"
-#: cms_toolbars.py:221
+#: cms_toolbars.py:210
#, python-brace-format
msgid "Compare to {source}"
msgstr "Vergelijk met {source}"
-#: cms_toolbars.py:236 indicators.py:73
+#: cms_toolbars.py:226 indicators.py:66
msgid "Discard Changes"
msgstr "Annuleer wijzigingen"
-#: cms_toolbars.py:271
+#: cms_toolbars.py:262
msgid "View Published"
msgstr "Bekijk live versie"
-#: cms_toolbars.py:327
+#: cms_toolbars.py:317
msgid "Language"
msgstr "Taal"
-#: cms_toolbars.py:374
+#: cms_toolbars.py:364
msgid "Add Translation"
msgstr "Voeg vertaling toe"
-#: cms_toolbars.py:387
+#: cms_toolbars.py:377
msgid "Copy all plugins"
msgstr "Kopieer alle plugins"
-#: cms_toolbars.py:389
+#: cms_toolbars.py:379
#, python-format
msgid "from %s"
msgstr "van %s"
-#: cms_toolbars.py:390
+#: cms_toolbars.py:380
#, python-format
msgid "Are you sure you want to copy all plugins from %s?"
msgstr "Ben je er zeker van om alle plugins te kopiëren van %s?"
-#: cms_toolbars.py:405
+#: cms_toolbars.py:395
msgid "No other language available"
msgstr "Geen andere taal beschikbaar"
-#: constants.py:11 constants.py:25
+#: constants.py:10 constants.py:24
msgid "Draft"
msgstr "Concept"
-#: constants.py:12 constants.py:23
+#: constants.py:11 constants.py:22
msgid "Published"
msgstr "Gepubliceerd"
-#: constants.py:14 constants.py:27
+#: constants.py:13 constants.py:26
msgid "Archived"
msgstr "Gearchiveerd"
-#: constants.py:24
+#: constants.py:23
msgid "Changed"
msgstr "Gewijzigd"
-#: emails.py:38
+#: emails.py:39
msgid "Unlocked"
msgstr "Ongesloten"
-#: indicators.py:35
+#: indicators.py:28
#, python-format
msgid "Unlock (%(message)s)"
msgstr "Ongesloten (%(message)s)"
-#: indicators.py:47
+#: indicators.py:40
msgid "Create new draft"
msgstr "Maakt een nieuw concept"
-#: indicators.py:53
+#: indicators.py:46
msgid "Revert from Unpublish"
msgstr "Gedepubliceerde terugdraaien"
-#: indicators.py:73
+#: indicators.py:66
msgid "Delete Draft"
msgstr "Verwijder concept"
-#: indicators.py:79
+#: indicators.py:72
msgid "Compare Draft to Published..."
msgstr "Vergelijk Concept met Gepubliceerde..."
-#: indicators.py:89
+#: indicators.py:82
msgid "Manage Versions..."
msgstr "Beheer versies..."
-#: models.py:31
+#: models.py:29
msgid "Version is not a draft"
msgstr "Versie is niet een concept"
-#: models.py:32
+#: models.py:30
#, python-brace-format
msgid "Action Denied. The latest version is locked by {user}"
msgstr "Actie niet geldig. De laatste versie is gesloten door {user}"
-#: models.py:33
+#: models.py:31
#, python-brace-format
msgid "Action Denied. The draft version is locked by {user}"
msgstr "Actie niet geldig. De concept versie is gesloten door {user}"
-#: models.py:88
+#: models.py:86
msgid "Created"
msgstr "Aangemaakt"
-#: models.py:91
+#: models.py:89
msgid "author"
msgstr "auteur"
-#: models.py:100
+#: models.py:102
msgid "status"
msgstr "status"
-#: models.py:108
+#: models.py:110
msgid "locked by"
msgstr "gesloten door"
-#: models.py:117
+#: models.py:119
msgid "source"
msgstr "bron"
-#: models.py:131
+#: models.py:133
#, python-brace-format
msgid "Version #{number} ({state} {date})"
msgstr "Versie #{number} ({state} {date})"
-#: models.py:138
+#: models.py:140
#, python-brace-format
msgid "Version #{number} ({state})"
msgstr "Versie #{number} ({state})"
-#: models.py:144
+#: models.py:146
#, python-format
msgid "Locked by %(user)s"
msgstr "Gesloten door %(user)s"
-#: models.py:276 models.py:325
+#: models.py:278 models.py:327
msgid "Version is not in draft state"
msgstr "Versie is niet in concept staat"
-#: models.py:385
+#: models.py:387
msgid "Version is not in published state"
msgstr "Versie is niet in gepubliceerde staat"
-#: models.py:442
+#: models.py:444
msgid "Version is not in archived or unpublished state"
msgstr "Versie is niet gearchiveerd en niet gepubliceerd"
-#: models.py:457
+#: models.py:459
msgid "Version is not in draft or published state"
msgstr "Versie is niet een concept of gepubliceerde staat"
-#: models.py:465
+#: models.py:467
msgid "Version is already locked"
msgstr "Versie is al gesloten"
-#: models.py:471
+#: models.py:473
msgid "Draft version is not locked"
msgstr "Concept versie is niet gesloten"
diff --git a/djangocms_versioning/locale/sq/LC_MESSAGES/django.mo b/djangocms_versioning/locale/sq/LC_MESSAGES/django.mo
index 625df067..1929104d 100644
Binary files a/djangocms_versioning/locale/sq/LC_MESSAGES/django.mo and b/djangocms_versioning/locale/sq/LC_MESSAGES/django.mo differ
diff --git a/djangocms_versioning/locale/sq/LC_MESSAGES/django.po b/djangocms_versioning/locale/sq/LC_MESSAGES/django.po
index 2c740ce4..9a638674 100644
--- a/djangocms_versioning/locale/sq/LC_MESSAGES/django.po
+++ b/djangocms_versioning/locale/sq/LC_MESSAGES/django.po
@@ -2,127 +2,125 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
-#
+#
# Translators:
# Besnik Bleta , 2023
-#
+#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-05-05 17:59+0200\n"
+"POT-Creation-Date: 2023-10-02 09:37+0200\n"
"PO-Revision-Date: 2023-01-10 15:29+0000\n"
"Last-Translator: Besnik Bleta , 2023\n"
-"Language-Team: Albanian (https://www.transifex.com/divio/teams/58664/sq/)\n"
-"Language: sq\n"
+"Language-Team: Albanian (https://app.transifex.com/divio/teams/58664/sq/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"Language: sq\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: admin.py:164 admin.py:301 admin.py:383
+#: admin.py:164 admin.py:301 admin.py:377
msgid "State"
msgstr "Gjendje"
-#: admin.py:192 constants.py:28
+#: admin.py:192 constants.py:27
msgid "Empty"
msgstr "I zbrazët"
-#: admin.py:315 admin.py:395
+#: admin.py:315 admin.py:387
msgid "Author"
msgstr "Autor"
-#: admin.py:329 admin.py:406 models.py:89
+#: admin.py:329 admin.py:401 models.py:87
msgid "Modified"
msgstr "E ndryshuar"
-#: admin.py:433 admin.py:661
+#: admin.py:437 admin.py:667
#: templates/djangocms_versioning/admin/icons/preview.html:3
#: templates/djangocms_versioning/admin/preview.html:3
msgid "Preview"
msgstr "Paraparje"
-#: admin.py:468 admin.py:760 cms_toolbars.py:121
+#: admin.py:470 admin.py:758 cms_toolbars.py:115
#: templates/djangocms_versioning/admin/icons/edit_icon.html:3
msgid "Edit"
msgstr "Përpunojeni"
-#: admin.py:480
+#: admin.py:482
#: templates/djangocms_versioning/admin/icons/manage_versions.html:3
msgid "Manage versions"
msgstr "Administroni versione"
-#: admin.py:639
+#: admin.py:631
msgid "Content"
msgstr "Lëndë"
-#: admin.py:649
+#: admin.py:647
msgid "locked"
msgstr ""
-#: admin.py:679 templates/djangocms_versioning/admin/icons/archive_icon.html:3
+#: admin.py:683 templates/djangocms_versioning/admin/icons/archive_icon.html:3
msgid "Archive"
msgstr "Arkiv"
-#: admin.py:699 cms_toolbars.py:83 indicators.py:41
+#: admin.py:701 cms_toolbars.py:79 indicators.py:34
#: templates/djangocms_versioning/admin/icons/publish_icon.html:3
msgid "Publish"
msgstr "Botoje"
-#: admin.py:721 indicators.py:61 indicators.py:67
+#: admin.py:721 indicators.py:54 indicators.py:60
#: templates/djangocms_versioning/admin/icons/unpublish_icon.html:3
msgid "Unpublish"
msgstr "Hiqe nga të botuar"
-#: admin.py:760 cms_toolbars.py:121
-#, fuzzy
-#| msgid "Draft"
+#: admin.py:758 cms_toolbars.py:115
msgid "New Draft"
-msgstr "Skicë"
+msgstr ""
-#: admin.py:783 cms_toolbars.py:188
+#: admin.py:779 cms_toolbars.py:177
#: templates/djangocms_versioning/admin/icons/revert_icon.html:3
msgid "Revert"
msgstr "Riktheje"
-#: admin.py:804 templates/djangocms_versioning/admin/icons/discard_icon.html:3
+#: admin.py:798 templates/djangocms_versioning/admin/icons/discard_icon.html:3
msgid "Discard"
msgstr "Hidhe tej"
-#: admin.py:829 cms_toolbars.py:153
+#: admin.py:821 cms_toolbars.py:145
msgid "Unlock"
msgstr ""
#: admin.py:856
-msgid "Exactly two versions need to be selected."
-msgstr "Lypset të përzgjidhen saktësisht dy versione."
-
-#: admin.py:870
msgid "Compare versions"
msgstr "Krahasoni versione"
-#: admin.py:897
+#: admin.py:866
+msgid "Exactly two versions need to be selected."
+msgstr "Lypset të përzgjidhen saktësisht dy versione."
+
+#: admin.py:903
msgid "Version cannot be archived"
msgstr "Versioni s’mund të arkivohet"
-#: admin.py:926
+#: admin.py:929
msgid "Version archived"
msgstr "Versioni u arkivua"
-#: admin.py:937 admin.py:1059 admin.py:1241
+#: admin.py:940 admin.py:1059 admin.py:1235
msgid "This view only supports POST method."
msgstr "Kjo pamje mbulon vetëm metodën POST."
-#: admin.py:948
+#: admin.py:951
msgid "Version cannot be published"
msgstr "Versioni s’mund të botohet"
-#: admin.py:959
+#: admin.py:962
msgid "Version published"
msgstr "Versioni u botua"
-#: admin.py:976
+#: admin.py:979
msgid "Version cannot be unpublished"
msgstr "Versioni s’mund të shbotohet"
@@ -130,23 +128,19 @@ msgstr "Versioni s’mund të shbotohet"
msgid "Version unpublished"
msgstr "Versioni u shbotua"
-#: admin.py:1169
+#: admin.py:1163
msgid "The last version has been deleted"
msgstr "Versioni i fundit është fshirë"
-#: admin.py:1255
-#, fuzzy
-#| msgid "You do not have permission to copy these plugins."
+#: admin.py:1249
msgid "You do not have permission to remove the version lock"
-msgstr "S’keni leje të kopjoni këto shtojca."
+msgstr ""
-#: admin.py:1260
-#, fuzzy
-#| msgid "Version unpublished"
+#: admin.py:1254
msgid "Version unlocked"
-msgstr "Versioni u shbotua"
+msgstr ""
-#: admin.py:1309
+#: admin.py:1303
#, python-brace-format
msgid "Displaying versions of \"{grouper}\""
msgstr "Po shfaqen versione të “{grouper}”"
@@ -155,191 +149,182 @@ msgstr "Po shfaqen versione të “{grouper}”"
msgid "django CMS Versioning"
msgstr "Versione në django CMS"
-#: cms_config.py:262
+#: cms_config.py:246
msgid "No available title"
msgstr "S’ka titull"
-#: cms_config.py:264 constants.py:13 constants.py:26
+#: cms_config.py:248 constants.py:12 constants.py:25
msgid "Unpublished"
msgstr "I pabotuar"
-#: cms_config.py:358
+#: cms_config.py:342
msgid "Language must be set to a supported language!"
msgstr "Si gjuhë duhet të caktoni një gjuhë të mbuluar!"
-#: cms_config.py:376
+#: cms_config.py:360
msgid "You do not have permission to copy these plugins."
msgstr "S’keni leje të kopjoni këto shtojca."
-#: cms_toolbars.py:218
+#: cms_toolbars.py:207
msgid "Manage Versions"
msgstr "Administroni Versione"
-#: cms_toolbars.py:221
-#, fuzzy, python-brace-format
-#| msgid "Compare to {state} source"
+#: cms_toolbars.py:210
+#, python-brace-format
msgid "Compare to {source}"
-msgstr "Krahasoje me burimin {state}"
+msgstr ""
-#: cms_toolbars.py:236 indicators.py:73
-#, fuzzy
-#| msgid "Discard"
+#: cms_toolbars.py:226 indicators.py:66
msgid "Discard Changes"
-msgstr "Hidhe tej"
+msgstr ""
-#: cms_toolbars.py:271
+#: cms_toolbars.py:262
msgid "View Published"
msgstr "Shihni të Botuarin"
-#: cms_toolbars.py:327
+#: cms_toolbars.py:317
msgid "Language"
msgstr "Gjuhë"
-#: cms_toolbars.py:374
+#: cms_toolbars.py:364
msgid "Add Translation"
msgstr "Shtoni Përkthim"
-#: cms_toolbars.py:387
+#: cms_toolbars.py:377
msgid "Copy all plugins"
msgstr "Kopjo krejt shtojcat"
-#: cms_toolbars.py:389
+#: cms_toolbars.py:379
#, python-format
msgid "from %s"
msgstr "prej %s"
-#: cms_toolbars.py:390
+#: cms_toolbars.py:380
#, python-format
msgid "Are you sure you want to copy all plugins from %s?"
msgstr "Jeni i sigurt se doni të kopjohen krejt shtojcat prej %s?"
-#: cms_toolbars.py:405
+#: cms_toolbars.py:395
msgid "No other language available"
msgstr "S’ka gjuhë të tjera"
-#: constants.py:11 constants.py:25
+#: constants.py:10 constants.py:24
msgid "Draft"
msgstr "Skicë"
-#: constants.py:12 constants.py:23
+#: constants.py:11 constants.py:22
msgid "Published"
msgstr "I botuar"
-#: constants.py:14 constants.py:27
+#: constants.py:13 constants.py:26
msgid "Archived"
msgstr "I arkivuar"
-#: constants.py:24
+#: constants.py:23
msgid "Changed"
msgstr "I ndryshur"
-#: emails.py:38
+#: emails.py:39
msgid "Unlocked"
msgstr ""
-#: indicators.py:35
+#: indicators.py:28
#, python-format
msgid "Unlock (%(message)s)"
msgstr ""
-#: indicators.py:47
+#: indicators.py:40
msgid "Create new draft"
msgstr "Krijoni një skicë të re"
-#: indicators.py:53
+#: indicators.py:46
msgid "Revert from Unpublish"
msgstr "Riktheje nga Shbotoje"
-#: indicators.py:73
+#: indicators.py:66
msgid "Delete Draft"
msgstr "Fshije Skicën"
-#: indicators.py:79
+#: indicators.py:72
msgid "Compare Draft to Published..."
msgstr "Krahaso Skicë me të Pabotuar…"
-#: indicators.py:89
+#: indicators.py:82
msgid "Manage Versions..."
msgstr "Administroni Versione…"
-#: models.py:31
+#: models.py:29
msgid "Version is not a draft"
msgstr "Versioni s’është skicë"
-#: models.py:32
+#: models.py:30
#, python-brace-format
msgid "Action Denied. The latest version is locked by {user}"
msgstr ""
-#: models.py:33
+#: models.py:31
#, python-brace-format
msgid "Action Denied. The draft version is locked by {user}"
msgstr ""
-#: models.py:88
-#, fuzzy
-#| msgid "Create new draft"
+#: models.py:86
msgid "Created"
-msgstr "Krijoni një skicë të re"
+msgstr ""
-#: models.py:91
+#: models.py:89
msgid "author"
msgstr "autor"
-#: models.py:100
+#: models.py:102
msgid "status"
msgstr "gjendje"
-#: models.py:108
+#: models.py:110
msgid "locked by"
msgstr ""
-#: models.py:117
+#: models.py:119
msgid "source"
msgstr "burim"
-#: models.py:131
+#: models.py:133
#, python-brace-format
msgid "Version #{number} ({state} {date})"
msgstr "Version #{number} ({state} {date})"
-#: models.py:138
+#: models.py:140
#, python-brace-format
msgid "Version #{number} ({state})"
msgstr "Version #{number} ({state})"
-#: models.py:144
+#: models.py:146
#, python-format
msgid "Locked by %(user)s"
msgstr ""
-#: models.py:276 models.py:325
+#: models.py:278 models.py:327
msgid "Version is not in draft state"
msgstr "Versioni s’është nën gjendjen “skicë”"
-#: models.py:385
+#: models.py:387
msgid "Version is not in published state"
msgstr "Versioni s’është nën gjendjen “i botuar”"
-#: models.py:442
+#: models.py:444
msgid "Version is not in archived or unpublished state"
msgstr "Versioni s’është nën gjendjen “i arkivuar” ose “i pabotuar”"
-#: models.py:457
+#: models.py:459
msgid "Version is not in draft or published state"
msgstr "Versioni s’është nën gjendjen “skicë” ose “i botuar”"
-#: models.py:465
-#, fuzzy
-#| msgid "Version archived"
+#: models.py:467
msgid "Version is already locked"
-msgstr "Versioni u arkivua"
+msgstr ""
-#: models.py:471
-#, fuzzy
-#| msgid "Version is not a draft"
+#: models.py:473
msgid "Draft version is not locked"
-msgstr "Versioni s’është skicë"
+msgstr ""
#: templates/admin/djangocms_versioning/versioning_breadcrumbs.html:3
#: templates/djangocms_versioning/admin/grouper_form.html:9
@@ -506,17 +491,7 @@ msgid ""
"The following draft version has been unlocked by %(by_user)s for their use.\n"
"%(version_link)s\n"
"\n"
-"Please note you will not be able to further edit this draft. Kindly reach "
-"out to %(by_user)s in case of any concerns.\n"
+"Please note you will not be able to further edit this draft. Kindly reach out to %(by_user)s in case of any concerns.\n"
"\n"
"This is an automated notification from Django CMS.\n"
msgstr ""
-
-#~ msgid "actions"
-#~ msgstr "veprime"
-
-#~ msgid "version number"
-#~ msgstr "numër versioni"
-
-#~ msgid "Delete Changes"
-#~ msgstr "Fshiji Ndryshimet"
diff --git a/djangocms_versioning/management/__init__.py b/djangocms_versioning/management/__init__.py
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/djangocms_versioning/management/__init__.py
@@ -0,0 +1 @@
+
diff --git a/djangocms_versioning/managers.py b/djangocms_versioning/managers.py
index 55d615e8..5d323b76 100644
--- a/djangocms_versioning/managers.py
+++ b/djangocms_versioning/managers.py
@@ -60,23 +60,16 @@ def _chain(self):
clone._group_by_key = self._group_by_key
return clone
- def current_content_iterator(self, **kwargs):
- """Returns generator (not a queryset) over current content versions. Current versions are either draft
- versions or published versions (in that order)"""
- warnings.warn("current_content_iterator is deprecated in favour of current_conent",
- DeprecationWarning, stacklevel=2)
- return iter(self.current_content(**kwargs))
-
def current_content(self, **kwargs):
"""Returns a queryset current content versions. Current versions are either draft
versions or published versions (in that order). This optimized query assumes that
draft versions always have a higher pk than any other version type. This is true as long as
no other version type can be converted to draft without creating a new version."""
- qs = self.filter(versions__state__in=(constants.DRAFT, constants.PUBLISHED), **kwargs)
- pk_filter = qs.values(*self._group_by_key)\
+ pk_filter = self.filter(versions__state__in=(constants.DRAFT, constants.PUBLISHED))\
+ .values(*self._group_by_key)\
.annotate(vers_pk=models.Max("versions__pk"))\
- .values_list("vers_pk", flat=True)
- return qs.filter(versions__pk__in=pk_filter)
+ .values("vers_pk")
+ return self.filter(versions__pk__in=pk_filter, **kwargs)
def latest_content(self, **kwargs):
"""Returns the "latest" content object which is in this order
diff --git a/djangocms_versioning/models.py b/djangocms_versioning/models.py
index b36cc5bd..08ac8079 100644
--- a/djangocms_versioning/models.py
+++ b/djangocms_versioning/models.py
@@ -16,6 +16,9 @@
draft_is_not_locked,
in_state,
is_not_locked,
+ user_can_change,
+ user_can_publish,
+ user_can_unlock,
)
from .conf import ALLOW_DELETING_VERSIONS, LOCK_VERSIONS
from .operations import send_post_version_operation, send_pre_version_operation
@@ -29,7 +32,7 @@
not_draft_error = _("Version is not a draft")
lock_error_message = _("Action Denied. The latest version is locked by {user}")
lock_draft_error_message = _("Action Denied. The draft version is locked by {user}")
-
+permission_error_message = _("You do not have permission to perform this action")
def allow_deleting_versions(collector, field, sub_objs, using):
if ALLOW_DELETING_VERSIONS:
@@ -71,8 +74,8 @@ def filter_by_grouping_values(self, versionable, **kwargs):
def filter_by_content_grouping_values(self, content):
"""Returns a list of Version objects for grouping values taken
- from provided content object. In other words:
- it uses the content instance property values as filter parameters
+ from provided content object. In other words:
+ it uses the content instance property values as filter parameters
"""
versionable = versionables.for_content(content)
content_objects = versionable.for_content_grouping_values(content)
@@ -257,7 +260,7 @@ def copy(self, created_by):
Allows customization of how the content object will be copied
when specified in cms_config.py
- This method needs to be ran in a transaction due to the fact that if
+ This method needs to be run in a transaction due to the fact that if
models are partially created in the copy method a version is not attached.
It needs to be that if anything goes wrong we should roll back the entire task.
We shouldn't leave this to package developers to know to add this feature
@@ -275,6 +278,7 @@ def copy(self, created_by):
check_archive = Conditions(
[
+ user_can_change(permission_error_message),
in_state([constants.DRAFT], _("Version is not in draft state")),
is_not_locked(lock_error_message),
]
@@ -324,7 +328,10 @@ def _set_archive(self, user):
pass
check_publish = Conditions(
- [in_state([constants.DRAFT], _("Version is not in draft state"))]
+ [
+ user_can_publish(permission_error_message),
+ in_state([constants.DRAFT], _("Version is not in draft state")),
+ ]
)
def can_be_published(self):
@@ -357,13 +364,16 @@ def publish(self, user):
content_type=self.content_type,
)
for version in to_unpublish:
- version.unpublish(user)
+ version.unpublish(user, to_be_published=self)
on_publish = self.versionable.on_publish
if on_publish:
on_publish(self)
# trigger post operation signal
send_post_version_operation(
- constants.OPERATION_PUBLISH, version=self, token=action_token
+ constants.OPERATION_PUBLISH,
+ version=self,
+ token=action_token,
+ unpublished=list(to_unpublish),
)
if emit_content_change:
emit_content_change(self.content)
@@ -384,6 +394,7 @@ def _set_publish(self, user):
pass
check_unpublish = Conditions([
+ user_can_publish(permission_error_message),
in_state([constants.PUBLISHED], _("Version is not in published state")),
draft_is_not_locked(lock_draft_error_message),
])
@@ -391,11 +402,11 @@ def _set_publish(self, user):
def can_be_unpublished(self):
return can_proceed(self._set_unpublish)
- def unpublish(self, user):
+ def unpublish(self, user, to_be_published=None):
"""Change state to UNPUBLISHED"""
# trigger pre operation signal
action_token = send_pre_version_operation(
- constants.OPERATION_UNPUBLISH, version=self
+ constants.OPERATION_UNPUBLISH, version=self, to_be_published=to_be_published
)
self._set_unpublish(user)
self.modified = timezone.now()
@@ -411,7 +422,10 @@ def unpublish(self, user):
on_unpublish(self)
# trigger post operation signal
send_post_version_operation(
- constants.OPERATION_UNPUBLISH, version=self, token=action_token
+ constants.OPERATION_UNPUBLISH,
+ version=self,
+ token=action_token,
+ to_be_published=to_be_published,
)
if emit_content_change:
emit_content_change(self.content)
@@ -431,14 +445,60 @@ def _set_unpublish(self, user):
possible to be left with inconsistent data)"""
pass
+ def has_publish_permission(self, user) -> bool:
+ """
+ Check if the given user has permission to publish.
+
+ Args:
+ user (User): The user to check for permission.
+
+ Returns:
+ bool: True if the user has publish permission, False otherwise.
+ """
+ return self._has_permission("publish", user)
+
+ def has_change_permission(self, user) -> bool:
+ """
+ Check whether the given user has permission to change the object.
+
+ Parameters:
+ user (User): The user for which permission needs to be checked.
+
+ Returns:
+ bool: True if the user has permission to change the object, False otherwise.
+ """
+ return self._has_permission("change", user)
+
+ def _has_permission(self, perm: str, user) -> bool:
+ """
+ Check if the user has the specified permission for the content by
+ checking the content's has_publish_permission, has_placeholder_change_permission,
+ or has_change_permission methods.
+
+ Falls back to Djangos change permission for the content object.
+ """
+ if perm == "publish" and hasattr(self.content, "has_publish_permission"):
+ # First try explicit publish permission
+ return self.content.has_publish_permission(user)
+ if hasattr(self.content, "has_change_permission"):
+ # First fallback: change permissions
+ return self.content.has_change_permission(user)
+ if hasattr(self.content, "has_placeholder_change_permission"):
+ # Second fallback: placeholder change permissions - works for PageContent
+ return self.content.has_placeholder_change_permission(user)
+ # final fallback: Django perms
+ return user.has_perm(f"{self.content_type.app_label}.change_{self.content_type.model}")
+
check_modify = Conditions(
[
in_state([constants.DRAFT], not_draft_error),
draft_is_not_locked(lock_draft_error_message),
+ user_can_unlock(permission_error_message),
]
)
check_revert = Conditions(
[
+ user_can_change(permission_error_message),
in_state(
[constants.ARCHIVED, constants.UNPUBLISHED],
_("Version is not in archived or unpublished state"),
@@ -471,6 +531,7 @@ def _set_unpublish(self, user):
[
in_state([constants.DRAFT, constants.PUBLISHED], not_draft_error),
draft_is_locked(_("Draft version is not locked"))
+
]
)
diff --git a/djangocms_versioning/plugin_rendering.py b/djangocms_versioning/plugin_rendering.py
index 7571bbaf..2ed05652 100644
--- a/djangocms_versioning/plugin_rendering.py
+++ b/djangocms_versioning/plugin_rendering.py
@@ -1,5 +1,6 @@
from functools import lru_cache
+from cms import __version__ as cms_version
from cms.plugin_rendering import ContentRenderer, StructureRenderer
from cms.utils.placeholder import rescan_placeholders_for_obj
@@ -41,29 +42,32 @@ def render_plugin(self, instance, context, placeholder=None, editable=False):
prefetch_versioned_related_objects(instance, self.toolbar)
return super().render_plugin(instance, context, placeholder, editable)
- def render_obj_placeholder(
- self, slot, context, inherit, nodelist=None, editable=True
- ):
- # FIXME This is an ad-hoc solution for page-specific rendering
- # code, which by default doesn't work well with versioning.
- # Remove this method once the issue is fixed.
- from cms.models import Placeholder
+ if cms_version in ("4.1.0", "4.1.1"):
+ # Only needed for CMS 4.1.0 and 4.1.1 which have fix #7924 not merged
+ # With #7924, page-specific rendering works well with versioning.
+ def render_obj_placeholder(
+ self, slot, context, inherit, nodelist=None, editable=True
+ ):
+ # FIXME This is an ad-hoc solution for page-specific rendering
+ # code, which by default doesn't work well with versioning.
+ # Remove this method once the issue is fixed.
+ from cms.models import Placeholder
- current_obj = self.toolbar.get_object()
+ current_obj = self.toolbar.get_object()
- # Not page, therefore we will use toolbar object as
- # the current object and render the placeholder
- rescan_placeholders_for_obj(current_obj)
- placeholder = Placeholder.objects.get_for_obj(current_obj).get(slot=slot)
- content = self.render_placeholder(
- placeholder,
- context=context,
- page=current_obj,
- editable=editable,
- use_cache=True,
- nodelist=None,
- )
- return content
+ # Not page, therefore we will use toolbar object as
+ # the current object and render the placeholder
+ rescan_placeholders_for_obj(current_obj)
+ placeholder = Placeholder.objects.get_for_obj(current_obj).get(slot=slot)
+ content = self.render_placeholder(
+ placeholder,
+ context=context,
+ page=current_obj,
+ editable=editable,
+ use_cache=True,
+ nodelist=None,
+ )
+ return content
class VersionStructureRenderer(StructureRenderer):
diff --git a/djangocms_versioning/static/djangocms_versioning/css/object-tools.css b/djangocms_versioning/static/djangocms_versioning/css/object-tools.css
new file mode 100644
index 00000000..6b7b671e
--- /dev/null
+++ b/djangocms_versioning/static/djangocms_versioning/css/object-tools.css
@@ -0,0 +1,8 @@
+.object-tools a.accent {
+ background-color: var(--accent) !important;
+}
+.object-tools a.accent:hover,
+.object-tools a.accent:active,
+.object-tools a.accent:hover:active {
+ background-color: color-mix(in srgb, var(--accent) 90%, var(--dca-black)) !important;
+}
diff --git a/djangocms_versioning/static/djangocms_versioning/css/versioning.css b/djangocms_versioning/static/djangocms_versioning/css/versioning.css
index f66b94a0..bc19b68b 100644
--- a/djangocms_versioning/static/djangocms_versioning/css/versioning.css
+++ b/djangocms_versioning/static/djangocms_versioning/css/versioning.css
@@ -207,6 +207,3 @@ ins.cms-diff img {
.cms-select::-ms-expand {
opacity: 0;
}
-input.button.revert-button {
- margin: 5px;
-}
diff --git a/djangocms_versioning/static/djangocms_versioning/js/admin/versioning-actions.js b/djangocms_versioning/static/djangocms_versioning/js/admin/versioning-actions.js
new file mode 100644
index 00000000..632032fa
--- /dev/null
+++ b/djangocms_versioning/static/djangocms_versioning/js/admin/versioning-actions.js
@@ -0,0 +1,22 @@
+(function () {
+ "use strict";
+
+ function closeSideFrame() {
+ try {
+ window.top.CMS.API.Sideframe.close();
+ } catch (err) {}
+ }
+
+ document.addEventListener('DOMContentLoaded', function () {
+ document.querySelectorAll('form.js-close-sideframe').forEach(el => {
+ el.addEventListener("submit", (ev) => {
+ ev.preventDefault();
+ ev.target.action = ev.target.action; // save action url
+ closeSideFrame();
+ const form = window.top.document.body.appendChild(ev.target); // move to top window
+ form.style.display = 'none';
+ form.submit(); // submit form
+ });
+ });
+ });
+})();
diff --git a/djangocms_versioning/static/djangocms_versioning/js/object-tools.js b/djangocms_versioning/static/djangocms_versioning/js/object-tools.js
new file mode 100644
index 00000000..ca7e1e44
--- /dev/null
+++ b/djangocms_versioning/static/djangocms_versioning/js/object-tools.js
@@ -0,0 +1,13 @@
+(function($) {
+ $(document).ready(function() {
+ $('.cms-form-post-method').on('click', function(e) {
+ e.preventDefault();
+ var csrf_token = document.querySelector('form input[name="csrfmiddlewaretoken"]').value;
+ var url = this.href;
+ var $form = $('');
+ var $csrf = $(``);
+ $form.append($csrf);
+ $form.appendTo('body').submit();
+ });
+ });
+})(django.jQuery);
diff --git a/djangocms_versioning/static/djangocms_versioning/js/versioning.js b/djangocms_versioning/static/djangocms_versioning/js/versioning.js
new file mode 100644
index 00000000..0704f537
--- /dev/null
+++ b/djangocms_versioning/static/djangocms_versioning/js/versioning.js
@@ -0,0 +1,37 @@
+(function() {
+ var firstChecked, lastChecked;
+
+ function handleVersionSelection(event) {
+ if (firstChecked instanceof HTMLInputElement && firstChecked.checked) {
+ firstChecked.checked = false;
+ firstChecked.closest('tr').classList.remove('selected');
+ firstChecked = lastChecked;
+ }
+ if (event.target instanceof HTMLInputElement) {
+ if (event.target.checked) {
+ firstChecked = lastChecked;
+ lastChecked = event.target;
+ } else if (firstChecked === event.target) {
+ firstChecked = null;
+ } else {
+ lastChecked = null;
+ }
+ }
+ }
+
+ document.addEventListener('DOMContentLoaded', function(){
+ var selectedVersions = document.querySelectorAll('#result_list input[type="checkbox"].action-select');
+ var selectElement = document.querySelector('#changelist-form select[name="action"]');
+ if (selectElement instanceof HTMLSelectElement) {
+ for (var i = 0; i < selectElement.options.length; i++) {
+ if (selectElement.options[i].value && selectElement.options[i].value !== 'compare_versions') {
+ // for future safety: do not restrict on two selected versions, since there might be other actions
+ return;
+ }
+ }
+ }
+ selectedVersions.forEach(function(selectedVersion){
+ selectedVersion.addEventListener('change', handleVersionSelection);
+ });
+ });
+ })();
diff --git a/djangocms_versioning/templates/admin/djangocms_versioning/page/change_form.html b/djangocms_versioning/templates/admin/djangocms_versioning/page/change_form.html
new file mode 100644
index 00000000..43039a22
--- /dev/null
+++ b/djangocms_versioning/templates/admin/djangocms_versioning/page/change_form.html
@@ -0,0 +1,102 @@
+{% extends "admin/cms/page/change_form.html" %}
+{% load static admin_urls admin_modify djangocms_versioning i18n cms_admin %}
+
+{% block extrahead %}
+ {{ block.super }}
+
+{% endblock %}
+{% block extrastyle %}
+ {{ block.super }}
+
+{% endblock %}
+
+{% block content_title %}
+ {% if title %}{{ title }}{% if original %} - {{ original.versions.first.short_name }}{% endif %}
{% endif %}
+ {% block object-tools %}
+ {% if not popup and not add %}
+
+ {% endif %}
+ {% endblock %}
+{% endblock %}
+
+{% block content %}
+
+
+{% block admin_change_form_document_ready %}
+{{ block.super }}
+{% endblock %}
+
+{# JavaScript for prepopulated fields #}
+{% prepopulated_fields_js %}
+
+{% endblock %}
diff --git a/djangocms_versioning/templates/admin/djangocms_versioning/versioning_buttons.html b/djangocms_versioning/templates/admin/djangocms_versioning/versioning_buttons.html
new file mode 100644
index 00000000..f08c1d12
--- /dev/null
+++ b/djangocms_versioning/templates/admin/djangocms_versioning/versioning_buttons.html
@@ -0,0 +1,30 @@
+{% load djangocms_versioning i18n %}
+{% with url=original|url_publish_version:request.user %}
+ {% if url %}
+
+ {% trans "Publish" %}
+
+ {% endif %}
+{% endwith %}
+{% with url=original|url_new_draft:request.user %}
+ {% if url %}
+
+ {% trans "New Draft" %}
+
+ {% endif %}
+{% endwith %}
+{% with url=original|url_revert_version:request.user %}
+ {% if url %}
+
+ {% trans "Revert" %}
+
+ {% endif %}
+{% endwith %}
+
+{% with url=original|url_version_list %}
+ {% if url %}
+
+ {% trans "Versions" %}
+
+ {% endif %}
+{% endwith %}
diff --git a/djangocms_versioning/templates/djangocms_versioning/admin/archive_confirmation.html b/djangocms_versioning/templates/djangocms_versioning/admin/archive_confirmation.html
index 40ab5cd3..4a70749c 100644
--- a/djangocms_versioning/templates/djangocms_versioning/admin/archive_confirmation.html
+++ b/djangocms_versioning/templates/djangocms_versioning/admin/archive_confirmation.html
@@ -6,6 +6,7 @@
{{ block.super }}
{{ media }}
+
{% endblock %}
{% block breadcrumbs %}{% endblock %}
@@ -15,15 +16,17 @@
{% translate "Are you sure you want to archive the following version?" %}
{{ object_name }}
{% blocktrans %} Version number: {{ version_number }}{% endblocktrans %}
-
{% endblock %}
diff --git a/djangocms_versioning/templates/djangocms_versioning/admin/mixin/change_form.html b/djangocms_versioning/templates/djangocms_versioning/admin/mixin/change_form.html
index 6fab19b2..32908d92 100644
--- a/djangocms_versioning/templates/djangocms_versioning/admin/mixin/change_form.html
+++ b/djangocms_versioning/templates/djangocms_versioning/admin/mixin/change_form.html
@@ -1,12 +1,18 @@
{% extends versioning_fallback_change_form_template|default:"admin/change_form.html" %}
-{% load i18n admin_urls djangocms_versioning %}
+{% load static %}
+
+{% block extrahead %}
+ {{ block.super }}
+
+{% endblock %}
+
+{% block extrastyle %}
+ {{ block.super }}
+
+{% endblock %}
{% block object-tools-items %}
-
-
- {% translate "Versions" %}
-
-
+ {% include "admin/djangocms_versioning/versioning_buttons.html" %}
{{ block.super }}
{% endblock %}
diff --git a/djangocms_versioning/templates/djangocms_versioning/admin/revert_confirmation.html b/djangocms_versioning/templates/djangocms_versioning/admin/revert_confirmation.html
index 3de7d687..7bbbb168 100644
--- a/djangocms_versioning/templates/djangocms_versioning/admin/revert_confirmation.html
+++ b/djangocms_versioning/templates/djangocms_versioning/admin/revert_confirmation.html
@@ -6,6 +6,7 @@
{{ media }}
+
{% endblock %}
{% block breadcrumbs %}{% endblock %}
@@ -22,20 +23,20 @@ {% block title %}{% translate "Revert Confirmation" %}{% endblock %}
{{ object_name }}
{% blocktrans %}Version number: {{ version_number }}{% endblocktrans %}
-