diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6cd06bdc..334faed5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,7 @@ Changelog Unreleased ========== +* feat: Preview icon renders form in read only mode 4.0.0.dev3 (2022-01-11) ======================= diff --git a/djangocms_snippet/admin.py b/djangocms_snippet/admin.py index b3c53a59..6769d9ce 100644 --- a/djangocms_snippet/admin.py +++ b/djangocms_snippet/admin.py @@ -1,15 +1,19 @@ from django.conf import settings from django.conf.urls import url from django.contrib import admin +from django.contrib.admin import helpers +from django.contrib.admin.exceptions import DisallowedModelAdminToField +from django.contrib.admin.options import IS_POPUP_VAR, TO_FIELD_VAR +from django.contrib.admin.utils import flatten_fieldsets, unquote from django.db import models from django.forms import Textarea +from django.utils.translation import gettext as _ from cms.utils.permissions import get_model_permission_codename from .cms_config import SnippetCMSAppConfig from .forms import SnippetForm from .models import Snippet -from .views import SnippetPreviewView # Use the version mixin if djangocms-versioning is installed and enabled @@ -18,6 +22,7 @@ try: from djangocms_versioning.admin import ExtendedVersionAdminMixin + if djangocms_versioning_enabled: snippet_admin_classes.insert(0, ExtendedVersionAdminMixin) except ImportError: @@ -73,15 +78,76 @@ def get_list_display_links(self, request, list_display): self.list_display_links = (None,) return self.list_display_links + def preview_view(self, request, snippet_id=None, form_url='', extra_context=None): + """ + Custom preview endpoint to display a change form in read only mode + Solution based on django changeform view implementation + https://github.com/django/django/blob/4b8e9492d9003ca357a4402f831112dd72efd2f8/django/contrib/admin/options.py#L1553 + """ + to_field = request.POST.get(TO_FIELD_VAR, request.GET.get(TO_FIELD_VAR)) + + if to_field and not self.to_field_allowed(request, to_field): + raise DisallowedModelAdminToField("The field %s cannot be referenced." % to_field) + + model = self.model + opts = model._meta + + obj = self.get_object(request, unquote(snippet_id), to_field) + + if obj is None: + return self._get_obj_does_not_exist_redirect(request, opts, snippet_id) + + fieldsets = self.get_fieldsets(request, obj) + ModelForm = self.get_form( + request, obj, change=False, fields=flatten_fieldsets(fieldsets) + ) + form = ModelForm(instance=obj) + formsets, inline_instances = self._create_formsets(request, obj, change=True) + + readonly_fields = flatten_fieldsets(fieldsets) + + adminForm = 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 + + inline_formsets = self.get_inline_formsets(request, formsets, inline_instances, obj) + for inline_formset in inline_formsets: + media = media + inline_formset.media + + title = _('View %s') + context = { + **self.admin_site.each_context(request), + 'title': title % opts.verbose_name, + 'subtitle': str(obj) if obj else None, + 'adminform': adminForm, + 'object_id': snippet_id, + 'original': obj, + 'is_popup': IS_POPUP_VAR in request.POST or IS_POPUP_VAR in request.GET, + 'to_field': to_field, + 'media': media, + 'inline_admin_formsets': inline_formsets, + 'errors': [], + 'preserved_filters': self.get_preserved_filters(request), + } + + context.update(extra_context or {}) + + return self.render_change_form(request, context, add=False, change=False, obj=obj, form_url=form_url) + def get_urls(self): info = self.model._meta.app_label, self.model._meta.model_name return [ - url( - r"^(?P\d+)/preview/$", - self.admin_site.admin_view(SnippetPreviewView.as_view()), - name="{}_{}_preview".format(*info), - ), - ] + super().get_urls() + url( + r"^(?P\d+)/preview/$", + self.admin_site.admin_view(self.preview_view), + name="{}_{}_preview".format(*info), + ), + ] + super().get_urls() def has_delete_permission(self, request, obj=None): """ diff --git a/djangocms_snippet/views.py b/djangocms_snippet/views.py deleted file mode 100644 index 26edbe5f..00000000 --- a/djangocms_snippet/views.py +++ /dev/null @@ -1,26 +0,0 @@ -from django.http import Http404 -from django.views.generic import TemplateView - -from djangocms_snippet.models import Snippet - - -class SnippetPreviewView(TemplateView): - template_name = "djangocms_snippet/admin/preview.html" - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - snippet_id = kwargs.get("snippet_id", None) - - if not snippet_id: - Http404("snippet_id must be provided.") - - try: - snippet = Snippet._base_manager.get(pk=self.kwargs.get("snippet_id")) - except Snippet.DoesNotExist: - raise Http404 - - context.update({ - "snippet": snippet, - "opts": Snippet._meta - }) - return context diff --git a/tests/test_admin.py b/tests/test_admin.py index ed121022..d03d2358 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -209,3 +209,21 @@ def test_name_colomn_should_not_be_hyperlinked_with_versioning_enabled(self): self.assertContains(response, 'Test Snippet') self.assertNotContains(response, 'test-snippet') + + def test_preview_renders_read_only_fields(self): + """ + Check that the preview endpoint is rendered in read only mode + """ + self.snippet_version.publish(user=self.superuser) + with self.login_user_context(self.superuser): + edit_url = reverse("admin:djangocms_snippet_snippet_preview", args=(self.snippet.id,),) + response = self.client.get(edit_url) + + # Snippet name + self.assertContains(response, '
Test Snippet
') + # Snippet slug + self.assertContains(response, '
test-snippet
') + # Snippet HTML + self.assertContains(response, '
<h1>This is a test</h1>
') + # Snippet template + self.assertContains(response, '
') diff --git a/tests/test_views.py b/tests/test_views.py deleted file mode 100644 index ce3c7e5a..00000000 --- a/tests/test_views.py +++ /dev/null @@ -1,38 +0,0 @@ -from cms.test_utils.testcases import CMSTestCase -from cms.utils.urlutils import admin_reverse - -from .utils.factories import SnippetWithVersionFactory - - -class PreviewViewTestCase(CMSTestCase): - def setUp(self): - self.snippet = SnippetWithVersionFactory(html="

Test Title


Test paragraph

") - self.user = self.get_superuser() - - def test_preview_renders_html(self): - """ - Check that our snippet HTML is rendered, unescaped, on the page - """ - preview_url = admin_reverse( - "djangocms_snippet_snippet_preview", - kwargs={"snippet_id": self.snippet.id}, - ) - with self.login_user_context(self.user): - response = self.client.get(preview_url) - - self.assertEqual(self.snippet.html, "

Test Title


Test paragraph

") - self.assertEqual(response.status_code, 200) - self.assertContains(response, "

Test Title


Test paragraph

") - - def test_preview_raises_404_no_snippet(self): - """ - With no Snippet to preview, a 404 will be raised - """ - preview_url = admin_reverse( - "djangocms_snippet_snippet_preview", - kwargs={"snippet_id": 999}, # Non existent PK! - ) - with self.login_user_context(self.user): - response = self.client.get(preview_url) - - self.assertEqual(response.status_code, 404)