diff --git a/cms/admin/pageadmin.py b/cms/admin/pageadmin.py index 608ee8c2a43..87bc2b476c8 100644 --- a/cms/admin/pageadmin.py +++ b/cms/admin/pageadmin.py @@ -76,6 +76,7 @@ get_language_tuple, get_site_language_from_request, ) +from cms.utils.permissions import clear_permission_lru_caches from cms.utils.plugins import copy_plugins_to_placeholder from cms.utils.urlutils import admin_reverse @@ -97,7 +98,6 @@ def get_site(request): @admin.register(Page) class PageAdmin(admin.ModelAdmin): - change_list_template = "admin/cms/page/tree/base.html" actions_menu_template = 'admin/cms/page/tree/actions_dropdown.html' form = AdvancedSettingsForm @@ -113,10 +113,13 @@ def has_change_permission(self, request, obj=None): Return true if the current user has permission on the page. Return the string 'All' if the user has all rights. """ - if obj is None: - return - site = get_site(request) + if obj is None: + # Checks if user can change at least one page + return page_permissions.user_can_change_at_least_one_page( + user=request.user, + site=site, + ) return page_permissions.user_can_change_page(request.user, page=obj, site=site) def has_change_advanced_settings_permission(self, request, obj=None): @@ -334,105 +337,22 @@ def get_list(self, *args, **kwargs): return HttpResponseForbidden() def changelist_view(self, request, extra_context=None): - can_change_any_page = page_permissions.user_can_change_at_least_one_page( - user=request.user, - site=get_site(request), - use_cache=False, - ) - - if not can_change_any_page: - raise Http404 - return HttpResponseRedirect(admin_reverse('cms_pagecontent_changelist')) - - @transaction.atomic - def delete_view(self, request, object_id, extra_context=None): - # This is an unfortunate copy/paste from django's delete view. - # The reason is to add the descendant pages to the deleted objects list. - opts = self.model._meta - app_label = opts.app_label - - obj = self.get_object(request, object_id=object_id) - - if not self.has_delete_permission(request, obj): - raise PermissionDenied - - if obj is None: - raise self._get_404_exception(object_id) - - # Populate deleted_objects, a data structure of all related objects that - # will also be deleted. - objs = [obj] + list(obj.get_descendant_pages()) + parameter = "?" + request.GET.urlencode() if request.GET else "" + return HttpResponseRedirect(admin_reverse('cms_pagecontent_changelist') + parameter) - get_deleted_objects_additional_kwargs = {'request': request} - (deleted_objects, model_count, perms_needed, protected) = get_deleted_objects( - objs, admin_site=self.admin_site, - **get_deleted_objects_additional_kwargs - ) - - if request.POST and not protected: # The user has confirmed the deletion. - if perms_needed: - raise PermissionDenied - obj_display = force_str(obj) - obj_id = obj.serializable_value(opts.pk.attname) - self.log_deletion(request, obj, obj_display) - self.delete_model(request, obj) - - if IS_POPUP_VAR in request.POST: - popup_response_data = json.dumps({ - 'action': 'delete', - 'value': str(obj_id), - }) - return TemplateResponse(request, self.popup_response_template or [ - f'admin/{opts.app_label}/{opts.model_name}/popup_response.html', - 'admin/%s/popup_response.html' % opts.app_label, - 'admin/popup_response.html', - ], {'popup_response_data': popup_response_data}) - - self.message_user( - request, - _('The %(name)s "%(obj)s" was deleted successfully.') % { - 'name': force_str(opts.verbose_name), - 'obj': force_str(obj_display), - }, - messages.SUCCESS, - ) - - can_change_any_page = page_permissions.user_can_change_at_least_one_page( - user=request.user, - site=get_site(request), - use_cache=False, - ) - - if can_change_any_page: - query = self.get_preserved_filters(request) - post_url = admin_reverse('cms_pagecontent_changelist') + '?' + query - else: - post_url = admin_reverse('index') - return HttpResponseRedirect(post_url) - - object_name = force_str(opts.verbose_name) + def response_delete(self, request, obj_display, obj_id): + """ + Determine the HttpResponse for the delete_view stage. Clear the user's permission + lru cache + """ + clear_permission_lru_caches(request.user) + return super().response_delete(request, obj_display, obj_id) - if perms_needed or protected: - title = _("Cannot delete %(name)s") % {"name": object_name} - else: - title = _("Are you sure?") - - context = dict( - self.admin_site.each_context(request), - title=title, - object_name=object_name, - object=obj, - deleted_objects=deleted_objects, - model_count=dict(model_count).items(), - perms_lacking=perms_needed, - protected=protected, - opts=opts, - app_label=app_label, - is_popup=(IS_POPUP_VAR in request.POST or IS_POPUP_VAR in request.GET), - to_field=None, - ) - context.update(extra_context or {}) - return self.render_delete_form(request, context) + def get_deleted_objects(self, objs, request): + deleted_objs = list(objs) + for obj in objs: + deleted_objs.extend(obj.get_descendant_pages()) + return super().get_deleted_objects(deleted_objs, request) def delete_model(self, request, obj): operation_token = send_pre_page_operation( @@ -1061,7 +981,6 @@ def has_change_permission(self, request, obj=None): can_change_page = page_permissions.user_can_change_at_least_one_page( user=request.user, site=site, - use_cache=False, ) return can_change_page diff --git a/cms/tests/test_admin.py b/cms/tests/test_admin.py index 53ea5c79723..f8e51d394ac 100644 --- a/cms/tests/test_admin.py +++ b/cms/tests/test_admin.py @@ -136,7 +136,7 @@ def test_delete(self): add_plugin(body, 'TextPlugin', 'en', body='text') with self.login_user_context(admin_user): data = {'post': 'yes'} - response = self.client.post(URL_CMS_PAGE_DELETE % page.pk, data) + response = self.client.post(URL_CMS_PAGE_DELETE % page.pk, data, follow=True) self.assertRedirects(response, self.get_pages_admin_list_uri('en')) def test_delete_diff_language(self): @@ -148,7 +148,8 @@ def test_delete_diff_language(self): add_plugin(body, 'TextPlugin', 'en', body='text') with self.login_user_context(admin_user): data = {'post': 'yes'} - response = self.client.post(URL_CMS_PAGE_DELETE % page.pk, data) + response = self.client.post(URL_CMS_PAGE_DELETE % page.pk, data, follow=True) + # follow=True, since page changelist redirects to page content changelist self.assertRedirects(response, self.get_pages_admin_list_uri('en')) def test_search_fields(self): diff --git a/cms/tests/test_page_admin.py b/cms/tests/test_page_admin.py index fb69305ee4e..72d4872e029 100644 --- a/cms/tests/test_page_admin.py +++ b/cms/tests/test_page_admin.py @@ -1876,7 +1876,8 @@ def test_user_can_delete_empty_page(self): with self.login_user_context(staff_user): data = {'post': 'yes'} - response = self.client.post(endpoint, data) + response = self.client.post(endpoint, data, follow=True) + # follow=True, since page changelist redirects to page content changelist self.assertRedirects(response, redirect_to) self.assertFalse(self._page_exists()) @@ -1932,7 +1933,8 @@ def test_user_can_delete_non_empty_page(self): with self.login_user_context(staff_user): data = {'post': 'yes'} - response = self.client.post(endpoint, data) + response = self.client.post(endpoint, data, follow=True) + # follow=True, since page changelist redirects to page content changelist self.assertRedirects(response, redirect_to) self.assertFalse(self._page_exists()) @@ -3359,7 +3361,7 @@ def test_user_can_delete_empty_page(self): with self.login_user_context(staff_user): data = {'post': 'yes'} - response = self.client.post(endpoint, data) + response = self.client.post(endpoint, data, follow=True) self.assertRedirects(response, redirect_to) self.assertFalse(self._page_exists()) @@ -3423,10 +3425,9 @@ def test_user_can_delete_non_empty_page(self): can_change=True, can_delete=True, ) - with self.login_user_context(staff_user): data = {'post': 'yes'} - response = self.client.post(endpoint, data) + response = self.client.post(endpoint, data, follow=True) self.assertRedirects(response, redirect_to) self.assertFalse(self._page_exists()) diff --git a/cms/utils/permissions.py b/cms/utils/permissions.py index afe10658306..c25d862d756 100644 --- a/cms/utils/permissions.py +++ b/cms/utils/permissions.py @@ -158,6 +158,20 @@ def cached_func(user, *args, **kwargs): return cached_func +def clear_func_cache(user, func): + func_cache_name = '_djangocms_cached_func_%s' % func.__name__ + if hasattr(user, func_cache_name): + delattr(user, func_cache_name) + + +def clear_permission_lru_caches(user): + """ + Clear all python lru caches used by the permission system + """ + clear_func_cache(user, get_global_actions_for_user) + clear_func_cache(user, get_page_actions_for_user) + + @cached_func def get_global_actions_for_user(user, site): actions = set()