diff --git a/pyproject.toml b/pyproject.toml index c54a1444..ce977e3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,27 +26,19 @@ classifiers=[ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", -<<<<<<< HEAD "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", -======= - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", ->>>>>>> cf6e93a (chore: Package overhaul (#140)) "Framework :: Django", "Framework :: Django :: 3.2", "Framework :: Django :: 4.2", "Framework :: Django CMS", -<<<<<<< HEAD - "Framework :: Django CMS :: 4.0", -======= "Framework :: Django CMS :: 3.8", "Framework :: Django CMS :: 3.9", "Framework :: Django CMS :: 3.10", "Framework :: Django CMS :: 3.11", ->>>>>>> cf6e93a (chore: Package overhaul (#140)) + "Framework :: Django CMS :: 4.0", + "Framework :: Django CMS :: 4.1", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Software Development", diff --git a/src/djangocms_snippet/admin.py b/src/djangocms_snippet/admin.py index f0c55536..9ade47ec 100644 --- a/src/djangocms_snippet/admin.py +++ b/src/djangocms_snippet/admin.py @@ -1,3 +1,7 @@ +from typing import ClassVar + +from cms.utils import get_current_site +from cms.utils.permissions import get_model_permission_codename from django.conf import settings from django.contrib import admin from django.contrib.admin import helpers @@ -9,17 +13,12 @@ from django.urls import path from django.utils.translation import gettext as _ -from cms.utils import get_current_site -from cms.utils.permissions import get_model_permission_codename - -from .cms_config import SnippetCMSAppConfig from .forms import SnippetForm from .models import Snippet - # Use the version mixin if djangocms-versioning is installed and enabled snippet_admin_classes = [admin.ModelAdmin] -djangocms_versioning_enabled = SnippetCMSAppConfig.djangocms_versioning_enabled +djangocms_versioning_enabled = getattr(settings, "DJANGOCMS_SNIPPET_VERSIONING_ENABLED", True) try: from djangocms_versioning.admin import ExtendedVersionAdminMixin @@ -40,16 +39,16 @@ class Media: ) list_display = ('name',) - search_fields = ['name'] + search_fields: ClassVar[list[str]] = ['name'] change_form_template = 'djangocms_snippet/admin/change_form.html' - text_area_attrs = { + text_area_attrs: ClassVar[dict] = { 'rows': 20, 'data-editor': True, 'data-mode': getattr(settings, 'DJANGOCMS_SNIPPET_THEME', 'html'), 'data-theme': getattr(settings, 'DJANGOCMS_SNIPPET_MODE', 'github'), } form = SnippetForm - formfield_overrides = { + formfield_overrides: ClassVar[dict] = { models.TextField: {'widget': Textarea(attrs=text_area_attrs)} } # This was move here from model, otherwise first() and last() return the same when handling grouper queries @@ -114,22 +113,22 @@ def preview_view(self, request, snippet_id=None, form_url='', extra_context=None return self._get_obj_does_not_exist_redirect(request, opts, str(snippet_id)) fieldsets = self.get_fieldsets(request, obj) - ModelForm = self.get_form( + model_form = self.get_form( request, obj, change=False, fields=flatten_fieldsets(fieldsets) ) - form = ModelForm(instance=obj) + form = model_form(instance=obj) formsets, inline_instances = self._create_formsets(request, obj, change=True) readonly_fields = flatten_fieldsets(fieldsets) - adminForm = helpers.AdminForm( + admin_form = helpers.AdminForm( form, list(fieldsets), # Clear prepopulated fields on a view-only form to avoid a crash. {}, readonly_fields, model_admin=self) - media = self.media + adminForm.media + media = self.media + admin_form.media inline_formsets = self.get_inline_formsets(request, formsets, inline_instances, obj) for inline_formset in inline_formsets: @@ -140,7 +139,7 @@ def preview_view(self, request, snippet_id=None, form_url='', extra_context=None **self.admin_site.each_context(request), 'title': title % opts.verbose_name, 'subtitle': str(obj) if obj else None, - 'adminform': adminForm, + 'adminform': admin_form, 'object_id': snippet_id, 'original': obj, 'is_popup': IS_POPUP_VAR in request.POST or IS_POPUP_VAR in request.GET, @@ -163,7 +162,8 @@ def get_urls(self): self.admin_site.admin_view(self.preview_view), name="{}_{}_preview".format(*info), ), - ] + super().get_urls() + *super().get_urls(), + ] def has_delete_permission(self, request, obj=None): """ diff --git a/src/djangocms_snippet/cms_config.py b/src/djangocms_snippet/cms_config.py index 9d9d4629..e45cc725 100644 --- a/src/djangocms_snippet/cms_config.py +++ b/src/djangocms_snippet/cms_config.py @@ -1,11 +1,9 @@ -from django.conf import settings - from cms.app_base import CMSAppConfig +from django.conf import settings from djangocms_snippet.models import Snippet from djangocms_snippet.rendering import render_snippet - try: from djangocms_moderation import __version__ # NOQA @@ -24,14 +22,15 @@ class SnippetCMSAppConfig(CMSAppConfig): cms_enabled = True # cms toolbar enabled to allow for versioning compare view - cms_toolbar_enabled_models = [(Snippet, render_snippet), ] + cms_toolbar_enabled_models = ((Snippet, render_snippet),) if djangocms_moderation_enabled and djangocms_moderation_installed: moderated_models = [Snippet] if djangocms_versioning_enabled: from djangocms_versioning.datastructures import ( - VersionableItem, default_copy, + VersionableItem, + default_copy, ) versioning = [ diff --git a/src/djangocms_snippet/cms_plugins.py b/src/djangocms_snippet/cms_plugins.py index 7112bbdc..58e8f7aa 100644 --- a/src/djangocms_snippet/cms_plugins.py +++ b/src/djangocms_snippet/cms_plugins.py @@ -1,12 +1,11 @@ +from cms.plugin_base import CMSPluginBase +from cms.plugin_pool import plugin_pool from django import template from django.conf import settings from django.utils.html import escape from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ -from cms.plugin_base import CMSPluginBase -from cms.plugin_pool import plugin_pool - from .forms import SnippetPluginForm from .models import SnippetPtr from .utils import show_draft_content diff --git a/src/djangocms_snippet/conf.py b/src/djangocms_snippet/conf.py index b4dc989e..e69de29b 100644 --- a/src/djangocms_snippet/conf.py +++ b/src/djangocms_snippet/conf.py @@ -1,6 +0,0 @@ -from django.conf import settings - - -DJANGOCMS_SNIPPET_VERSIONING_MIGRATION_USER_ID = getattr( - settings, "DJANGOCMS_SNIPPET_VERSIONING_MIGRATION_USER_ID", 1 -) diff --git a/src/djangocms_snippet/forms.py b/src/djangocms_snippet/forms.py index a616f265..3a315fcd 100644 --- a/src/djangocms_snippet/forms.py +++ b/src/djangocms_snippet/forms.py @@ -1,21 +1,14 @@ +from cms.utils.urlutils import admin_reverse from django import forms from django.contrib import admin from django.db import transaction from django.utils.translation import gettext_lazy as _ -from cms.utils.urlutils import admin_reverse - -from djangocms_snippet.cms_config import SnippetCMSAppConfig from djangocms_snippet.models import Snippet, SnippetGrouper, SnippetPtr - - -try: - from djangocms_versioning import __version__ # NOQA - is_versioning_installed = True -except ImportError: - is_versioning_installed = False - -djangocms_versioning_enabled = SnippetCMSAppConfig.djangocms_versioning_enabled +from djangocms_snippet.utils import ( + djangocms_versioning_enabled, + is_versioning_installed, +) class SnippetForm(forms.ModelForm): @@ -43,8 +36,7 @@ def clean(self): snippet_grouper = data.get("snippet_grouper") snippet_queryset = Snippet.objects.all() - if djangocms_versioning_enabled and is_versioning_installed: - if snippet_grouper: + if djangocms_versioning_enabled and is_versioning_installed and snippet_grouper: snippet_queryset = snippet_queryset.exclude(snippet_grouper=snippet_grouper) for snippet in snippet_queryset: diff --git a/src/djangocms_snippet/migrations/0010_alter_snippet_id.py b/src/djangocms_snippet/migrations/0010_alter_snippet_id.py deleted file mode 100644 index a16a153c..00000000 --- a/src/djangocms_snippet/migrations/0010_alter_snippet_id.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 4.2.6 on 2023-10-25 23:30 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("djangocms_snippet", "0009_alter_snippetptr_cmsplugin_ptr"), - ] - - operations = [ - migrations.AlterField( - model_name="snippet", - name="id", - field=models.BigAutoField( - auto_created=True, primary_key=True, serialize=False, verbose_name="ID" - ), - ), - ] diff --git a/src/djangocms_snippet/migrations/0010_cms4_grouper_version_data_migration.py b/src/djangocms_snippet/migrations/0010_cms4_grouper_version_data_migration.py index 17e9ecab..1354cb0a 100644 --- a/src/djangocms_snippet/migrations/0010_cms4_grouper_version_data_migration.py +++ b/src/djangocms_snippet/migrations/0010_cms4_grouper_version_data_migration.py @@ -3,11 +3,6 @@ from django.contrib.contenttypes.management import create_contenttypes from django.db import migrations -from djangocms_snippet.cms_config import SnippetCMSAppConfig -from djangocms_snippet.conf import ( - DJANGOCMS_SNIPPET_VERSIONING_MIGRATION_USER_ID, -) - try: from djangocms_versioning.constants import DRAFT, PUBLISHED @@ -16,11 +11,15 @@ except ImportError: djangocms_versioning_installed = False +djangocms_versioning_config_enabled = getattr( + settings, 'DJANGOCMS_SNIPPET_VERSIONING_ENABLED', True +) + def cms4_grouper_version_migration(apps, schema_editor): create_contenttypes(global_apps.get_app_config("djangocms_snippet")) - djangocms_versioning_config_enabled = SnippetCMSAppConfig.djangocms_versioning_enabled + ContentType = apps.get_model('contenttypes', 'ContentType') Snippet = apps.get_model('djangocms_snippet', 'Snippet') @@ -33,8 +32,7 @@ def cms4_grouper_version_migration(apps, schema_editor): # Get a migration user to create a version. if djangocms_versioning_config_enabled and djangocms_versioning_installed and len(snippet_queryset): Version = apps.get_model('djangocms_versioning', 'Version') - - migration_user = User.objects.get(id=DJANGOCMS_SNIPPET_VERSIONING_MIGRATION_USER_ID) + migration_user = User.objects.get(id=getattr(settings, "DJANGOCMS_SNIPPET_VERSIONING_MIGRATION_USER_ID", 1)) for snippet in snippet_queryset: grouper = SnippetGrouper.objects.create() @@ -55,7 +53,7 @@ def cms4_grouper_version_migration(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('cms', '0034_remove_pagecontent_placeholders'), # Run after the CMS4 migrations + # ('cms', '0034_remove_pagecontent_placeholders'), # Run after the CMS4 migrations ('djangocms_snippet', '0009_auto_20210915_0445'), ] diff --git a/src/djangocms_snippet/models.py b/src/djangocms_snippet/models.py index 1f04e886..ac9d15dc 100644 --- a/src/djangocms_snippet/models.py +++ b/src/djangocms_snippet/models.py @@ -1,27 +1,42 @@ from typing import ClassVar +from cms.models import CMSPlugin from django.conf import settings from django.contrib.sites.models import Site from django.db import models from django.shortcuts import reverse from django.utils.translation import gettext_lazy as _ -from cms.models import CMSPlugin +# Search is enabled by default to keep backwards compatibility. +SEARCH_ENABLED = getattr(settings, "DJANGOCMS_SNIPPET_SEARCH", False) -from djangocms_versioning.constants import DRAFT, PUBLISHED -# Search is enabled by default to keep backwards compatibility. -SEARCH_ENABLED = getattr(settings, "DJANGOCMS_SNIPPET_SEARCH", False) + +class AdminQuerySet(models.QuerySet): + def current_content(self, **kwargs): + """If a versioning package is installed, this returns the currently valid content + that matches the filter given in kwargs. Used to find content to be copied, e.g.. + Without versioning every page is current.""" + return self.filter(**kwargs) + + def latest_content(self, **kwargs): + """If a versioning package is installed, returns the latest version that matches the + filter given in kwargs including discarded or unpublished page content. Without versioning + every page content is the latest.""" + return self.filter(**kwargs) class SnippetGrouper(models.Model): """ The Grouper model for snippet, this is required for versioning """ + def __str__(self): + return self.name + @property def name(self): - snippet_qs = Snippet._base_manager.filter( + snippet_qs = Snippet.admin_manager.filter( snippet_grouper=self ) return snippet_qs.first().name or super().__str__ @@ -29,16 +44,12 @@ def name(self): def snippet(self, show_editable=False): if show_editable: # When in "edit" or "preview" mode we should be able to see the latest content - return Snippet._base_manager.filter( - versions__state__in=[DRAFT, PUBLISHED], + return Snippet.admin_manager.current_content().filter( snippet_grouper=self, ).order_by("-pk").first() # When in "live" mode we should only be able to see the default published version return Snippet.objects.filter(snippet_grouper=self).first() - def __str__(self): - return self.name - # Stores the actual data class Snippet(models.Model): @@ -80,6 +91,9 @@ class Snippet(models.Model): ) site = models.ForeignKey(Site, on_delete=models.CASCADE, null=True, blank=True) + objects = models.Manager() + admin_manager = AdminQuerySet.as_manager() + class Meta: ordering: ClassVar[list[str]] = ["name"] verbose_name = _("Snippet") @@ -90,16 +104,10 @@ def __str__(self): def get_preview_url(self): return reverse( - "admin:{app}_{model}_preview".format( - app=self._meta.app_label, model=self._meta.model_name, - ), + f"admin:{self._meta.app_label}_{self._meta.model_name}_preview", args=[self.id], ) - class Meta: - verbose_name = _('Snippet') - verbose_name_plural = _('Snippets') - # Plugin model - just a pointer to Snippet class SnippetPtr(CMSPlugin): diff --git a/src/djangocms_snippet/utils.py b/src/djangocms_snippet/utils.py index 13d88b7f..e1af8434 100644 --- a/src/djangocms_snippet/utils.py +++ b/src/djangocms_snippet/utils.py @@ -1,4 +1,15 @@ from cms.toolbar.utils import get_toolbar_from_request +from django.conf import settings + +try: + import djangocms_versioning + is_versioning_installed = True +except ImportError: + is_versioning_installed = False + +djangocms_versioning_enabled = is_versioning_installed and getattr( + settings, 'DJANGOCMS_SNIPPET_VERSIONING_ENABLED', True +) def show_draft_content(request=None): @@ -8,4 +19,4 @@ def show_draft_content(request=None): if not request: return False request_toolbar = get_toolbar_from_request(request) - return request_toolbar.edit_mode_active or request_toolbar.preview_mode_active + return request_toolbar.edit_mode_active or getattr(request_toolbar, "preview_mode_active", True) diff --git a/tests/settings.py b/tests/settings.py index c5ec3774..0c7ef2f2 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -1,11 +1,18 @@ #!/usr/bin/env python + +try: + import djangocms_versioning + + add_apps = ['djangocms_versioning'] +except ImportError: + add_apps = [] + HELPER_SETTINGS = { 'SECRET_KEY': "djangocmssnippetstestsuitekey", 'INSTALLED_APPS': [ 'tests.utils', - 'djangocms_versioning', 'djangocms_snippet', - ], + ] + add_apps, 'CMS_LANGUAGES': { 1: [{ 'code': 'en', diff --git a/tests/test_admin.py b/tests/test_admin.py index 8440e3e6..f93916be 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -1,17 +1,21 @@ from importlib import reload +from unittest import skipIf from django.contrib import admin from django.contrib.sites.models import Site from django.shortcuts import reverse from django.test import RequestFactory, override_settings +from cms import __version__ as cms_version from cms.test_utils.testcases import CMSTestCase from cms.utils import get_current_site -from djangocms_versioning.models import Version +try: + from djangocms_versioning.models import Version +except ImportError: + from tests.utils.models import Version from djangocms_snippet import admin as snippet_admin -from djangocms_snippet import cms_config from djangocms_snippet.forms import SnippetForm from djangocms_snippet.models import Snippet, SnippetGrouper @@ -77,7 +81,6 @@ def test_admin_list_display_without_versioning(self): Without versioning enabled, list_display should not be extended with version related items """ admin.site.unregister(Snippet) - reload(cms_config) reload(snippet_admin) # This has to be declared again, since it will now be constructed without the versioning extension self.snippet_admin = snippet_admin.SnippetAdmin(Snippet, admin) @@ -87,6 +90,7 @@ def test_admin_list_display_without_versioning(self): self.assertEqual(self.snippet_admin.__class__.__bases__, (admin.ModelAdmin, )) self.assertEqual(list_display, ('slug', 'name')) + @skipIf(cms_version < "4", "Django CMS 4 required") @override_settings(DJANGOCMS_SNIPPET_VERSIONING_ENABLED=True) def test_admin_list_display_with_versioning(self): """ @@ -113,13 +117,13 @@ def test_admin_uses_form(self): """ self.assertEqual(self.snippet_admin.form, SnippetForm) + @skipIf(cms_version < "4", "Django CMS 4 required") @override_settings(DJANGOCMS_SNIPPET_VERSIONING_ENABLED=True) def test_admin_delete_button_disabled_versioning_enabled(self): """ If versioning is enabled, the delete button should not be rendered on the change form """ admin.site.unregister(Snippet) - reload(cms_config) reload(snippet_admin) with self.login_user_context(self.superuser): @@ -135,7 +139,6 @@ def test_admin_delete_button_available_versioning_disabled(self): If versioning is disabled, the delete button should be rendered on the change form """ admin.site.unregister(Snippet) - reload(cms_config) reload(snippet_admin) with self.login_user_context(self.superuser): @@ -145,13 +148,13 @@ def test_admin_delete_button_available_versioning_disabled(self): response, 'Delete' ) + @skipIf(cms_version < "4", "Django CMS 4 required") @override_settings(DJANGOCMS_SNIPPET_VERSIONING_ENABLED=True) def test_admin_delete_endpoint_inaccessible_versioning_enabled(self): """ If versioning is enabled, the delete endpoint should not be accessible. """ admin.site.unregister(Snippet) - reload(cms_config) reload(snippet_admin) with self.login_user_context(self.superuser): @@ -166,7 +169,6 @@ def test_admin_delete_endpoint_accessible_versioning_disabled(self): If versioning is disabled, the delete endpoint should be accessible. """ admin.site.unregister(Snippet) - reload(cms_config) reload(snippet_admin) with self.login_user_context(self.superuser): @@ -190,6 +192,7 @@ def setUp(self): ) self.snippet_version = Version.objects.create(content=self.snippet, created_by=self.superuser) + @skipIf(cms_version < "4", "Django CMS 4 required") @override_settings(DJANGOCMS_SNIPPET_VERSIONING_ENABLED=True) def test_admin_form_save_method(self): with self.login_user_context(self.superuser): @@ -206,6 +209,7 @@ def test_admin_form_save_method(self): self.assertEqual(Snippet._base_manager.count(), 2) self.assertEqual(SnippetGrouper._base_manager.count(), 2) + @skipIf(cms_version < "4", "Django CMS 4 required") @override_settings(DJANGOCMS_SNIPPET_VERSIONING_ENABLED=True) def test_admin_form_edit_when_locked(self): """ @@ -228,7 +232,6 @@ def test_slug_colomn_should_hyperlinked_with_versioning_disabled(self): Slug column should be visible and hyperlinked when versioning is disabled """ admin.site.unregister(Snippet) - reload(cms_config) reload(snippet_admin) with self.login_user_context(self.get_superuser()): @@ -236,6 +239,7 @@ def test_slug_colomn_should_hyperlinked_with_versioning_disabled(self): self.assertContains(response, '