combo/combo/apps/assets/views.py

249 lines
8.5 KiB
Python

# 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 <http://www.gnu.org/licenses/>.
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()