From 9fc6f3e1dcb8ff5712398cd346bb241a40f87781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Tue, 12 Jun 2018 08:35:35 +0200 Subject: [PATCH] general: move asset management to its own app (#24450) --- combo/apps/assets/__init__.py | 34 +++++ combo/apps/assets/forms.py | 22 +++ .../combo/manager_asset_confirm_delete.html | 0 .../combo/manager_asset_overwrite.html | 0 .../templates/combo/manager_asset_upload.html | 0 .../templates/combo/manager_assets.html | 0 combo/apps/assets/urls.py | 33 ++++ combo/apps/assets/views.py | 144 ++++++++++++++++++ combo/manager/forms.py | 4 - .../manager/templates/combo/manager_home.html | 1 - combo/manager/urls.py | 4 - combo/manager/views.py | 123 +-------------- combo/settings.py | 1 + 13 files changed, 236 insertions(+), 130 deletions(-) create mode 100644 combo/apps/assets/__init__.py create mode 100644 combo/apps/assets/forms.py rename combo/{manager => apps/assets}/templates/combo/manager_asset_confirm_delete.html (100%) rename combo/{manager => apps/assets}/templates/combo/manager_asset_overwrite.html (100%) rename combo/{manager => apps/assets}/templates/combo/manager_asset_upload.html (100%) rename combo/{manager => apps/assets}/templates/combo/manager_assets.html (100%) create mode 100644 combo/apps/assets/urls.py create mode 100644 combo/apps/assets/views.py diff --git a/combo/apps/assets/__init__.py b/combo/apps/assets/__init__.py new file mode 100644 index 0000000..6098ca3 --- /dev/null +++ b/combo/apps/assets/__init__.py @@ -0,0 +1,34 @@ +# combo - content management system +# Copyright (C) 2017-2018 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import django.apps +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ + + +class AppConfig(django.apps.AppConfig): + name = 'combo.apps.assets' + verbose_name = _('Assets') + + def get_before_urls(self): + from . import urls + return urls.urlpatterns + + def get_extra_manager_actions(self): + return [{'href': reverse('combo-manager-assets'), + 'text': _('Assets')}] + +default_app_config = 'combo.apps.assets.AppConfig' diff --git a/combo/apps/assets/forms.py b/combo/apps/assets/forms.py new file mode 100644 index 0000000..aba7a54 --- /dev/null +++ b/combo/apps/assets/forms.py @@ -0,0 +1,22 @@ +# combo - content management system +# Copyright (C) 2017-2018 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django import forms +from django.utils.translation import ugettext_lazy as _ + + +class AssetUploadForm(forms.Form): + upload = forms.FileField(label=_('File')) diff --git a/combo/manager/templates/combo/manager_asset_confirm_delete.html b/combo/apps/assets/templates/combo/manager_asset_confirm_delete.html similarity index 100% rename from combo/manager/templates/combo/manager_asset_confirm_delete.html rename to combo/apps/assets/templates/combo/manager_asset_confirm_delete.html diff --git a/combo/manager/templates/combo/manager_asset_overwrite.html b/combo/apps/assets/templates/combo/manager_asset_overwrite.html similarity index 100% rename from combo/manager/templates/combo/manager_asset_overwrite.html rename to combo/apps/assets/templates/combo/manager_asset_overwrite.html diff --git a/combo/manager/templates/combo/manager_asset_upload.html b/combo/apps/assets/templates/combo/manager_asset_upload.html similarity index 100% rename from combo/manager/templates/combo/manager_asset_upload.html rename to combo/apps/assets/templates/combo/manager_asset_upload.html diff --git a/combo/manager/templates/combo/manager_assets.html b/combo/apps/assets/templates/combo/manager_assets.html similarity index 100% rename from combo/manager/templates/combo/manager_assets.html rename to combo/apps/assets/templates/combo/manager_assets.html diff --git a/combo/apps/assets/urls.py b/combo/apps/assets/urls.py new file mode 100644 index 0000000..8936c87 --- /dev/null +++ b/combo/apps/assets/urls.py @@ -0,0 +1,33 @@ +# combo - content management system +# Copyright (C) 2017-2018 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.conf.urls import url, include + +from combo.urls_utils import decorated_includes, manager_required + +from . import views + +assets_manager_urls = [ + url(r'^$', views.assets, name='combo-manager-assets'), + url(r'^delete$', views.asset_delete, name='combo-manager-asset-delete'), + url(r'^overwrite/$', views.asset_overwrite, name='combo-manager-asset-overwrite'), + url(r'^upload/$', views.asset_upload, name='combo-manager-asset-upload'), +] + +urlpatterns = [ + url(r'^manage/assets/', decorated_includes(manager_required, + include(assets_manager_urls))), +] diff --git a/combo/apps/assets/views.py b/combo/apps/assets/views.py new file mode 100644 index 0000000..5748f42 --- /dev/null +++ b/combo/apps/assets/views.py @@ -0,0 +1,144 @@ +# combo - content management system +# Copyright (C) 2017-2018 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import os + +from django.conf import settings +from django.core.exceptions import PermissionDenied +from django.core.files.storage import default_storage +from django.core.urlresolvers import reverse, reverse_lazy +from django.shortcuts import redirect +from django.views.generic import TemplateView, ListView, FormView + +import ckeditor + +from .forms import AssetUploadForm + + +class Asset(object): + def __init__(self, filepath): + self.filepath = filepath + self.filename = os.path.basename(filepath) + self.src = ckeditor.utils.get_media_url(filepath) + + def css_classes(self): + extension = os.path.splitext(self.filepath)[-1].strip('.') + if extension: + return 'asset-ext-%s' % extension + return '' + + def size(self): + return os.stat(default_storage.path(self.filepath)).st_size + + def thumb(self): + if getattr(settings, 'CKEDITOR_IMAGE_BACKEND', None): + thumb = ckeditor.utils.get_media_url( + ckeditor.utils.get_thumb_filename(self.filepath)) + else: + thumb = self.src + return thumb + + def is_image(self): + return ckeditor.views.is_image(self.src) + + +class Assets(ListView): + template_name = 'combo/manager_assets.html' + paginate_by = 10 + + def get_queryset(self): + files = [Asset(x) for x in ckeditor.views.get_image_files(self.request.user)] + q = self.request.GET.get('q') + if q: + files = [x for x in files if q.lower() in x.filename.lower()] + files.sort(key=lambda x: getattr(x, 'filename')) + return files + + def get_context_data(self, **kwargs): + context = super(Assets, self).get_context_data(**kwargs) + context['query'] = self.request.GET.get('q') or '' + return context + +assets = Assets.as_view() + + +class AssetUpload(FormView): + form_class = AssetUploadForm + template_name = 'combo/manager_asset_upload.html' + success_url = reverse_lazy('combo-manager-assets') + + def form_valid(self, form): + # use native ckeditor view so it's available from ckeditor file/image + # dialogs. + ckeditor_upload_view = ckeditor.views.ImageUploadView() + self.request.GET = {'CKEditorFuncNum': '-'} # hack + ckeditor_upload_view.post(self.request) + return super(AssetUpload, self).form_valid(form) + +asset_upload = AssetUpload.as_view() + + +class AssetOverwrite(FormView): + form_class = AssetUploadForm + template_name = 'combo/manager_asset_overwrite.html' + success_url = reverse_lazy('combo-manager-assets') + + def form_valid(self, form): + img_orig = self.request.GET['img'] + if '..' in img_orig: + raise PermissionDenied() # better safe than sorry + base_path = settings.CKEDITOR_UPLOAD_PATH + if getattr(settings, 'CKEDITOR_RESTRICT_BY_USER', False): + base_path = os.path.join(base_path, self.request.user.username) + if not img_orig.startswith(base_path): + raise PermissionDenied() + + upload = self.request.FILES['upload'] + default_storage.delete(img_orig) + if getattr(settings, 'CKEDITOR_IMAGE_BACKEND', None): + thumb = ckeditor.utils.get_thumb_filename(img_orig) + default_storage.delete(thumb) + saved_path = default_storage.save(img_orig, upload) + backend = ckeditor.image_processing.get_backend() + upload.seek(0) # rewind file to be sure + try: + backend.image_verify(upload) + except ckeditor.utils.NotAnImageException: + pass + else: + if backend.should_create_thumbnail(saved_path): + backend.create_thumbnail(saved_path) + return super(AssetOverwrite, self).form_valid(form) + +asset_overwrite = AssetOverwrite.as_view() + + +class AssetDelete(TemplateView): + template_name = 'combo/manager_asset_confirm_delete.html' + + def post(self, request): + img_orig = request.GET['img'] + if '..' in img_orig: + raise PermissionDenied() # better safe than sorry + base_path = settings.CKEDITOR_UPLOAD_PATH + if getattr(settings, 'CKEDITOR_RESTRICT_BY_USER', False): + base_path = os.path.join(base_path, request.user.username) + if not img_orig.startswith(base_path): + raise PermissionDenied() + default_storage.delete(img_orig) + return redirect(reverse('combo-manager-assets')) + +asset_delete = AssetDelete.as_view() diff --git a/combo/manager/forms.py b/combo/manager/forms.py index c6b9a1b..fac2bca 100644 --- a/combo/manager/forms.py +++ b/combo/manager/forms.py @@ -101,7 +101,3 @@ class PageEditExcludeFromNavigationForm(forms.ModelForm): class SiteImportForm(forms.Form): site_json = forms.FileField(label=_('Site Export File')) - - -class AssetUploadForm(forms.Form): - upload = forms.FileField(label=_('File')) diff --git a/combo/manager/templates/combo/manager_home.html b/combo/manager/templates/combo/manager_home.html index d763eef..508312b 100644 --- a/combo/manager/templates/combo/manager_home.html +++ b/combo/manager/templates/combo/manager_home.html @@ -6,7 +6,6 @@ {% trans 'New' %}
    -
  • {% trans 'Assets' %}
  • {% trans 'Export Site' %}
  • {% trans 'Import Site' %}
  • {% for extra_action in extra_actions %} diff --git a/combo/manager/urls.py b/combo/manager/urls.py index 28759f6..23c1a8d 100644 --- a/combo/manager/urls.py +++ b/combo/manager/urls.py @@ -76,10 +76,6 @@ urlpatterns = [ name='combo-manager-cell-order'), url(r'^pages/order$', views.page_order, name='combo-manager-page-order'), - url(r'^assets/$', views.assets, name='combo-manager-assets'), - url(r'^assets/delete$', views.asset_delete, name='combo-manager-asset-delete'), - url(r'^assets/overwrite/$', views.asset_overwrite, name='combo-manager-asset-overwrite'), - url(r'^assets/upload/$', views.asset_upload, name='combo-manager-asset-upload'), url(r'^ckeditor/upload/', staff_member_required(ckeditor_views.upload), name='ckeditor_upload'), url(r'^ckeditor/browse/', never_cache(staff_member_required(ckeditor_views.browse)), diff --git a/combo/manager/views.py b/combo/manager/views.py index fb9b2f4..f7aa341 100644 --- a/combo/manager/views.py +++ b/combo/manager/views.py @@ -17,11 +17,8 @@ import json import os -import ckeditor - from django.conf import settings from django.contrib import messages -from django.core.files.storage import default_storage from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django.core.urlresolvers import reverse, reverse_lazy from django.http import HttpResponse, HttpResponseRedirect, Http404 @@ -31,7 +28,7 @@ from django.utils.encoding import force_text from django.utils.formats import date_format from django.utils.timezone import localtime from django.views.decorators.csrf import requires_csrf_token -from django.views.generic import (TemplateView, RedirectView, DetailView, +from django.views.generic import (RedirectView, DetailView, CreateView, UpdateView, ListView, DeleteView, FormView) from combo.data.models import Page, CellBase, ParentContentCell, PageSnapshot @@ -42,7 +39,7 @@ from combo import plugins from .forms import (PageEditTitleForm, PageVisibilityForm, SiteImportForm, PageEditRedirectionForm, PageSelectTemplateForm, PageEditSlugForm, PageEditPictureForm, PageEditExcludeFromNavigationForm, - AssetUploadForm, PageEditDescriptionForm) + PageEditDescriptionForm) class HomepageView(ListView): @@ -473,122 +470,6 @@ def page_get_additional_label(request, page_pk, cell_reference): return response -class Asset(object): - def __init__(self, filepath): - self.filepath = filepath - self.filename = os.path.basename(filepath) - self.src = ckeditor.utils.get_media_url(filepath) - - def css_classes(self): - extension = os.path.splitext(self.filepath)[-1].strip('.') - if extension: - return 'asset-ext-%s' % extension - return '' - - def size(self): - return os.stat(default_storage.path(self.filepath)).st_size - - def thumb(self): - if getattr(settings, 'CKEDITOR_IMAGE_BACKEND', None): - thumb = ckeditor.utils.get_media_url( - ckeditor.utils.get_thumb_filename(self.filepath)) - else: - thumb = self.src - return thumb - - def is_image(self): - return ckeditor.views.is_image(self.src) - - -class Assets(ListView): - template_name = 'combo/manager_assets.html' - paginate_by = 10 - - def get_queryset(self): - files = [Asset(x) for x in ckeditor.views.get_image_files(self.request.user)] - q = self.request.GET.get('q') - if q: - files = [x for x in files if q.lower() in x.filename.lower()] - files.sort(key=lambda x: getattr(x, 'filename')) - return files - - def get_context_data(self, **kwargs): - context = super(Assets, self).get_context_data(**kwargs) - context['query'] = self.request.GET.get('q') or '' - return context - -assets = Assets.as_view() - - -class AssetUpload(FormView): - form_class = AssetUploadForm - template_name = 'combo/manager_asset_upload.html' - success_url = reverse_lazy('combo-manager-assets') - - def form_valid(self, form): - # use native ckeditor view so it's available from ckeditor file/image - # dialogs. - ckeditor_upload_view = ckeditor.views.ImageUploadView() - self.request.GET = {'CKEditorFuncNum': '-'} # hack - ckeditor_upload_view.post(self.request) - return super(AssetUpload, self).form_valid(form) - -asset_upload = AssetUpload.as_view() - - -class AssetOverwrite(FormView): - form_class = AssetUploadForm - template_name = 'combo/manager_asset_overwrite.html' - success_url = reverse_lazy('combo-manager-assets') - - def form_valid(self, form): - img_orig = self.request.GET['img'] - if '..' in img_orig: - raise PermissionDenied() # better safe than sorry - base_path = settings.CKEDITOR_UPLOAD_PATH - if getattr(settings, 'CKEDITOR_RESTRICT_BY_USER', False): - base_path = os.path.join(base_path, self.request.user.username) - if not img_orig.startswith(base_path): - raise PermissionDenied() - - upload = self.request.FILES['upload'] - default_storage.delete(img_orig) - if getattr(settings, 'CKEDITOR_IMAGE_BACKEND', None): - thumb = ckeditor.utils.get_thumb_filename(img_orig) - default_storage.delete(thumb) - saved_path = default_storage.save(img_orig, upload) - backend = ckeditor.image_processing.get_backend() - upload.seek(0) # rewind file to be sure - try: - backend.image_verify(upload) - except ckeditor.utils.NotAnImageException: - pass - else: - if backend.should_create_thumbnail(saved_path): - backend.create_thumbnail(saved_path) - return super(AssetOverwrite, self).form_valid(form) - -asset_overwrite = AssetOverwrite.as_view() - - -class AssetDelete(TemplateView): - template_name = 'combo/manager_asset_confirm_delete.html' - - def post(self, request): - img_orig = request.GET['img'] - if '..' in img_orig: - raise PermissionDenied() # better safe than sorry - base_path = settings.CKEDITOR_UPLOAD_PATH - if getattr(settings, 'CKEDITOR_RESTRICT_BY_USER', False): - base_path = os.path.join(base_path, request.user.username) - if not img_orig.startswith(base_path): - raise PermissionDenied() - default_storage.delete(img_orig) - return redirect(reverse('combo-manager-assets')) - -asset_delete = AssetDelete.as_view() - - def menu_json(request): if settings.TEMPLATE_VARS.get('site_title'): label = _('Editing "%(site_title)s"') % settings.TEMPLATE_VARS diff --git a/combo/settings.py b/combo/settings.py index 3421e8f..ed07675 100644 --- a/combo/settings.py +++ b/combo/settings.py @@ -62,6 +62,7 @@ INSTALLED_APPS = ( 'combo.profile', 'combo.manager', 'combo.public', + 'combo.apps.assets', 'combo.apps.dashboard', 'combo.apps.wcs', 'combo.apps.publik', -- 2.17.1