# 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.http import Http404 from django.shortcuts import redirect from django.views.generic import TemplateView, ListView, FormView import ckeditor from sorl.thumbnail.shortcuts import get_thumbnail from combo.data.models import CellBase from .forms import AssetUploadForm from .models import Asset class CkEditorAsset(object): def __init__(self, filepath): self.filepath = filepath self.name = os.path.basename(filepath) self.src = ckeditor.utils.get_media_url(filepath) @classmethod def get_assets(cls, request): return [cls(x) for x in ckeditor.views.get_image_files(request.user)] 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 SlotAsset(object): def __init__(self, key=None, name=None, asset_type='image', asset=None): self.key = key self.name = name self.asset_type = asset_type self.asset = asset def is_image(self): return self.asset_type == 'image' and bool(self.asset) def size(self): if self.asset: return os.stat(self.asset.asset.path).st_size return None def src(self): return self.asset.asset.url if self.asset else '' def thumb(self): if self.asset.asset.path.lower().endswith('.svg'): return self.asset.asset.url else: return get_thumbnail(self.asset.asset, '75x75').url @classmethod def get_assets(cls): assets = dict([(x.key, x) for x in Asset.objects.all()]) uniq_slots = {} uniq_slots.update(settings.COMBO_ASSET_SLOTS) for cell in CellBase.get_cells( cell_filter=lambda x: bool(x.get_asset_slots)): uniq_slots.update(cell.get_asset_slots()) for key, value in uniq_slots.items(): yield cls(key, name=value.get('label'), asset_type=value.get('asset-type', 'image'), asset=assets.get(key)) class Assets(ListView): template_name = 'combo/manager_assets.html' paginate_by = 10 def get_queryset(self): files = list(SlotAsset.get_assets()) + CkEditorAsset.get_assets(self.request) q = self.request.GET.get('q') if q: files = [x for x in files if q.lower() in x.name.lower()] files.sort(key=lambda x: getattr(x, 'name')) 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 def get_anchored_url(self, key=None, name=None): url = reverse('combo-manager-assets') for i, asset in enumerate(self.get_queryset()): if key and key != getattr(asset, 'key', None): continue if name and name > getattr(asset, 'name', None): continue return url + '?page=%s' % ((i // self.paginate_by) + 1) return url assets = Assets.as_view() class AssetUpload(FormView): form_class = AssetUploadForm template_name = 'combo/manager_asset_upload.html' 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) def get_success_url(self): return Assets(request=self.request).get_anchored_url(name=self.request.FILES['upload'].name) 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) def get_success_url(self): img_orig = self.request.GET['img'] return Assets(request=self.request).get_anchored_url(name=os.path.basename(img_orig)) 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( Assets(request=self.request).get_anchored_url( name=os.path.basename(img_orig))) asset_delete = AssetDelete.as_view() class SlotAssetUpload(FormView): form_class = AssetUploadForm template_name = 'combo/manager_asset_upload.html' success_url = reverse_lazy('combo-manager-assets') def form_valid(self, form): try: self.asset = Asset.objects.get(key=self.kwargs['key']) except Asset.DoesNotExist: self.asset = Asset(key=self.kwargs['key']) self.asset.asset = self.request.FILES['upload'] self.asset.save() return super(SlotAssetUpload, self).form_valid(form) def get_success_url(self): return Assets(request=self.request).get_anchored_url(key=self.kwargs['key']) slot_asset_upload = SlotAssetUpload.as_view() class SlotAssetDelete(TemplateView): template_name = 'combo/manager_asset_confirm_delete.html' def post(self, request, *args, **kwargs): Asset.objects.filter(key=kwargs['key']).delete() return redirect(Assets(request=self.request).get_anchored_url(key=kwargs['key'])) slot_asset_delete = SlotAssetDelete.as_view() def serve_asset(request, key): try: asset = Asset.objects.get(key=key) return redirect(asset.asset.url) except (Asset.DoesNotExist, AttributeError): raise Http404()