Skip to content

Commit 967ab4d

Browse files
feat: Auto-add versioning mixing to GrouperAdmin (#472)
* feat: Auto-add state indicator and versioning mixin to grouper admin * add docstring * fix: add can_change_content method * add some type hints * Remove inline JS code for django CMS 5 * Fix ruff issues * update testing requirements * Add tests * Fix ruff * Fix tests for djangocms_text * Update test action * Run tests with py3.12 * Update docs * updarte docs * Update readme * Update readme 2 * Update docs * Update tests * Fix pyproject.toml * Fix tests * Update docs * Update docs * Update docs * fix readme typo * Update djangocms_versioning/helpers.py Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * typo * Fix: Remove menu registration from 5.x tests * Update conf --------- Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
1 parent cc84fec commit 967ab4d

30 files changed

+606
-368
lines changed

.github/workflows/test.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ jobs:
151151
strategy:
152152
fail-fast: false
153153
matrix:
154-
python-version: ['3.13']
154+
python-version: ['3.12']
155155
requirements-file: ['dj52_cms50.txt']
156156
cms-version: [
157157
'https://github.com/django-cms/django-cms/archive/main.tar.gz'
@@ -171,6 +171,7 @@ jobs:
171171
run: |
172172
python -m pip install --upgrade pip
173173
python -m pip install -r tests/requirements/${{ matrix.requirements-file }}
174+
python -m pip uninstall -y django-cms
174175
python -m pip install ${{ matrix.cms-version }}
175176
python setup.py install
176177
@@ -185,7 +186,7 @@ jobs:
185186
strategy:
186187
fail-fast: false
187188
matrix:
188-
python-version: [ "3.13" ]
189+
python-version: [ "3.12" ]
189190
cms-version: [
190191
'https://github.com/django-cms/django-cms/archive/main.tar.gz'
191192
]
@@ -205,6 +206,7 @@ jobs:
205206
run: |
206207
python -m pip install --upgrade pip
207208
python -m pip install -r tests/requirements/${{ matrix.requirements-file }}
209+
python -m pip uninstall -y Django django-cms
208210
python -m pip install ${{ matrix.cms-version }} ${{ matrix.django-version }}
209211
python setup.py install
210212

README.rst

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
|django| |djangocms|
1+
|PyPiVersion| |DjVersion| |CmsVersion|
22

33
*********************
44
django CMS Versioning
@@ -27,7 +27,7 @@ Add ``djangocms_versioning`` to your project's ``INSTALLED_APPS``.
2727
Run::
2828

2929
python -m manage migrate djangocms_versioning
30-
python -m manage create_versions --userid <user-id-of-migration-user>
30+
python -m manage create_versions --userid <user-id-of-migration-user>
3131

3232
to perform the application's database migrations and (only if you have an existing database) add version objects
3333
needed to mark existing versions as draft.
@@ -48,7 +48,7 @@ An example implementation can be found here:
4848
Testing
4949
=======
5050

51-
To run all the tests the only thing you need to do is run
51+
To run all the tests the only thing you need to do is run::
5252

5353
pip install -r tests/requirements.txt
5454
python setup.py test
@@ -98,8 +98,18 @@ To update transifex translation in this repo you need to download the
9898
``tx pull`` from the repo's root directory. After downloading the translations
9999
do not forget to run the ``compilemessages`` management command.
100100

101+
.. |PyPiVersion| image:: https://img.shields.io/pypi/v/djangocms-versioning.svg?style=flat-square
102+
:target: https://pypi.python.org/pypi/djangocms-versioning
103+
:alt: Latest PyPI version
101104

102-
.. |django| image:: https://img.shields.io/badge/django-4.2%2B-blue.svg
103-
:target: https://www.djangoproject.com/
104-
.. |djangocms| image:: https://img.shields.io/badge/django%20CMS-4.1%2B-blue.svg
105-
:target: https://www.django-cms.org/
105+
.. |PyVersion| image:: https://img.shields.io/pypi/pyversions/djangocms-versioning.svg?style=flat-square
106+
:target: https://pypi.python.org/pypi/djangocms-versioning
107+
:alt: Python versions
108+
109+
.. |DjVersion| image:: https://img.shields.io/pypi/frameworkversions/django/djangocms-versioning.svg?style=flat-square
110+
:target: https://pypi.python.org/pypi/djangocms-versioning
111+
:alt: Django versions
112+
113+
.. |CmsVersion| image:: https://img.shields.io/pypi/frameworkversions/django-cms/djangocms-versioning.svg?style=flat-square
114+
:target: https://pypi.python.org/pypi/djangocms-versioning
115+
:alt: django CMS versions

djangocms_versioning/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "2.3.2"
1+
__version__ = "2.4.0"

djangocms_versioning/admin.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,48 @@ def get_modified_date(self, obj: models.Model) -> typing.Union[str, None]:
344344
"""
345345
return getattr(obj, "content_modified", None)
346346

347+
def can_change_content(self, request: HttpRequest, content_obj: models.Model) -> bool:
348+
"""Returns True if user can change content_obj"""
349+
if content_obj is None:
350+
# Creating an object is never restricted by versioning
351+
return True
352+
version = Version.objects.get_for_content(content_obj)
353+
return version.check_modify.as_bool(request.user)
354+
355+
356+
357+
class DefaultGrouperVersioningAdminMixin(StateIndicatorMixin, ExtendedGrouperVersionAdminMixin):
358+
"""Default mixin for grouper model admin classes: Includes state indicator, author and modified date.
359+
Usage::
360+
class MyContentModelAdmin(DefaultGrouperAdminMixin, cms.admin.utils.GrouperModelAdmin):
361+
list_display = [
362+
...,
363+
"get_author", # Adds the author column
364+
"get_modified_date", # Adds the modified column
365+
"state_indicator", # Adds the state indicator column
366+
...]
367+
368+
If "state_indicator" is not in `list_display`, it will be added automatically before the
369+
"admin_list_actions" field, or - together with the actions - at the end of the list_display
370+
if no actions are present.
371+
"""
372+
def get_list_display(self, request):
373+
list_display = getattr(self, "list_display", ())
374+
if "state_indicator" not in list_display:
375+
if "admin_list_actions" in list_display:
376+
# If the admin_list_actions is present, we need to add the state_indicator
377+
# to the end of the list_display, so it doesn't interfere with the actions
378+
index = list_display.index("admin_list_actions")
379+
self.list_display = (
380+
*list_display[:index], # All items before admin_list_actions
381+
"state_indicator", # Add the state indicator before admin_list_actions
382+
*list_display[index:], # All items after admin_list_actions
383+
)
384+
else:
385+
# Add the state indicator and admin_list_actions to the end of the list_display
386+
self.list_display = (*list_display, "state_indicator", "admin_list_actions",)
387+
return super().get_list_display(request)
388+
347389

348390
class ExtendedVersionAdminMixin(
349391
ExtendedListDisplayMixin,

djangocms_versioning/cms_config.py

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from cms import __version__ as cms_version
44
from cms.app_base import CMSAppConfig, CMSAppExtension
5-
from cms.extensions.models import BaseExtension
65
from cms.models import PageContent
76
from cms.utils.i18n import get_language_list, get_language_tuple
87
from cms.utils.plugins import copy_plugins_to_placeholder
@@ -124,6 +123,12 @@ def handle_admin_classes(self, cms_config):
124123
for versionable in cms_config.versioning
125124
]
126125
)
126+
replace_admin_for_models(
127+
[
128+
(versionable.grouper_model, versionable.grouper_admin_mixin)
129+
for versionable in cms_config.versioning if versionable.grouper_admin_mixin is not None
130+
]
131+
)
127132

128133
def handle_version_admin(self, cms_config):
129134
"""
@@ -191,14 +196,6 @@ def copy_page_content(original_content):
191196
"""
192197
new_content = default_copy(original_content)
193198
new_content.creation_date = now()
194-
195-
# If pagecontent has an associated content or page extension, also copy this!
196-
for field in PageContent._meta.related_objects:
197-
if hasattr(original_content, field.name):
198-
extension = getattr(original_content, field.name)
199-
if isinstance(extension, BaseExtension):
200-
extension.copy(new_content, new_content.language)
201-
202199
return new_content
203200

204201

@@ -264,13 +261,6 @@ def get_queryset(self, request):
264261
.prefetch_related(Prefetch("versions", to_attr="prefetched_versions"))
265262
return queryset
266263

267-
# CAVEAT:
268-
# - PageContent contains the template, this can differ for each language,
269-
# it is assumed that templates would be the same when copying from one language to another
270-
# FIXME: The long term solution will require knowing:
271-
# - why this view is an ajax call
272-
# - where it should live going forwards (cms vs versioning)
273-
# - A better way of making the feature extensible / modifiable for versioning
274264
def copy_language(self, request, object_id):
275265
target_language = request.POST.get("target_language")
276266

djangocms_versioning/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
ENABLE_MENU_REGISTRATION = getattr(
55
settings, "DJANGOCMS_VERSIONING_ENABLE_MENU_REGISTRATION", CMS_VERSION <= "4.1.0"
66
)
7+
if CMS_VERSION.startswith("5."):
8+
ENABLE_MENU_REGISTRATION = False
79

810
USERNAME_FIELD = getattr(
911
settings, "DJANGOCMS_VERSIONING_USERNAME_FIELD", "username"

0 commit comments

Comments
 (0)