general: move asset management to its own app (#24450)

This commit is contained in:
Frédéric Péters 2018-06-12 08:35:35 +02:00
parent 95893c29fe
commit ce71720c40
13 changed files with 236 additions and 130 deletions

View File

@ -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 <http://www.gnu.org/licenses/>.
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'

View File

@ -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 <http://www.gnu.org/licenses/>.
from django import forms
from django.utils.translation import ugettext_lazy as _
class AssetUploadForm(forms.Form):
upload = forms.FileField(label=_('File'))

33
combo/apps/assets/urls.py Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
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))),
]

144
combo/apps/assets/views.py Normal file
View File

@ -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 <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.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()

View File

@ -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'))

View File

@ -6,7 +6,6 @@
<a class="extra-actions-menu-opener"></a>
<a rel="popup" href="{% url 'combo-manager-page-add' %}">{% trans 'New' %}</a>
<ul class="extra-actions-menu">
<li><a href="{% url 'combo-manager-assets' %}">{% trans 'Assets' %}</a></li>
<li><a href="{% url 'combo-manager-site-export' %}">{% trans 'Export Site' %}</a></li>
<li><a href="{% url 'combo-manager-site-import' %}">{% trans 'Import Site' %}</a></li>
{% for extra_action in extra_actions %}

View File

@ -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)),

View File

@ -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

View File

@ -62,6 +62,7 @@ INSTALLED_APPS = (
'combo.profile',
'combo.manager',
'combo.public',
'combo.apps.assets',
'combo.apps.dashboard',
'combo.apps.wcs',
'combo.apps.publik',