import copy import json from functools import update_wrapper from urllib.parse import urlparse from django.contrib import admin from django.db import transaction from django.http import ( HttpResponse, HttpResponseBadRequest, HttpResponseRedirect, ) from django.http.request import QueryDict from django.urls import Resolver404, path, re_path, resolve from django.utils.decorators import method_decorator from django.utils.html import conditional_escape from django.utils.translation import override from django.views.decorators.csrf import csrf_protect from cms.admin.forms import RequestToolbarForm from cms.models import UserSettings from cms.models.contentmodels import PageContent from cms.utils.page import get_page_from_request from cms.utils.page_permissions import user_can_view_page from cms.utils.permissions import user_can_view_placeholder_source from cms.utils.urlutils import admin_reverse @admin.register(UserSettings) class SettingsAdmin(admin.ModelAdmin): def get_urls(self): def wrap(view): def wrapper(*args, **kwargs): return self.admin_site.admin_view(view)(*args, **kwargs) return update_wrapper(wrapper, view) info = self.model._meta.app_label, self.model._meta.model_name return [ path('session_store/', self.session_store, name='%s_%s_session_store' % info), path('cms-toolbar/', wrap(self.get_toolbar), name='%s_%s_get_toolbar' % info), path('', wrap(self.change_view), name='%s_%s_change' % info), re_path(r'^(.+)/$', wrap(self.change_view), name='%s_%s_change' % info), ] @method_decorator(csrf_protect) @transaction.atomic def change_view(self, request, id=None): model = self.model try: obj = model.objects.get(user=request.user) except model.DoesNotExist: return self.add_view(request) return super().change_view(request, str(obj.pk)) def session_store(self, request): """ either POST or GET POST should have a settings parameter """ if not request.user.is_staff: return HttpResponse(json.dumps(""), content_type="application/json") if request.method == "POST": request.session['cms_settings'] = conditional_escape(request.POST['settings']) request.session.save() return HttpResponse( json.dumps(request.session.get('cms_settings', '')), content_type="application/json" ) def get_toolbar(self, request): from cms.toolbar.toolbar import CMSToolbar form = RequestToolbarForm(request.GET or None) if not form.is_valid(): return HttpResponseBadRequest('Invalid parameters') form_data = form.cleaned_data cms_path = form_data.get('cms_path') or request.path_info origin_url = urlparse(cms_path) attached_obj = form_data.get('attached_obj') if isinstance(attached_obj, PageContent): current_page = attached_obj.page else: current_page = get_page_from_request(request, use_path=origin_url.path, clean_path=True) # The object id/type come straight from the client, so enforce an # object-level view check before rendering a toolbar for it. Without # this, any staff user could read the title/breadcrumb/edit URLs of # pages on other sites or of view-restricted pages they cannot access. # Deny with the same generic response as an unknown object so this does # not become an existence oracle. if isinstance(attached_obj, PageContent): if not user_can_view_page(request.user, current_page): return HttpResponseBadRequest('Invalid parameters') elif attached_obj is not None: if not user_can_view_placeholder_source(request.user, attached_obj): return HttpResponseBadRequest('Invalid parameters') elif current_page is not None and not user_can_view_page(request.user, current_page): return HttpResponseBadRequest('Invalid parameters') data = QueryDict(query_string=origin_url.query, mutable=True) placeholders = request.GET.getlist("placeholders[]") if placeholders: data.setlist('placeholders[]', placeholders) request = copy.copy(request) request.GET = data request.current_page = current_page try: request.resolver_match = resolve(origin_url.path) except Resolver404: pass request.toolbar = CMSToolbar(request, request_path=origin_url.path, _async=True) request.toolbar.set_object(attached_obj or current_page) return HttpResponse(request.toolbar.render()) def save_model(self, request, obj, form, change): obj.user = request.user obj.save() def response_post_save_change(self, request, obj): # # When the user changes his language setting, we need to do two things: # 1. Change the language-prefix for the sideframed admin view # 2. Reload the whole window so that the new language affects the # toolbar, etc. # # To do this, we first redirect the sideframe to the correct new, URL, # but we pass a GET param 'reload_window', which instructs JS on that # page to strip (to avoid infinite redirection loops) that param then # reload the whole window again. # with override(obj.language): post_url = admin_reverse( 'cms_usersettings_change', args=[obj.id, ], current_app=self.admin_site.name ) return HttpResponseRedirect(f"{post_url}?reload_window") def has_change_permission(self, request, obj=None): if obj and obj.user == request.user: return True return False def get_model_perms(self, request): """ Return empty perms dict thus hiding the model from admin index. """ return {}