0001-general-move-asset-management-to-its-own-app-24450.patch
combo/apps/assets/__init__.py | ||
---|---|---|
1 |
# combo - content management system |
|
2 |
# Copyright (C) 2017-2018 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
import django.apps |
|
18 |
from django.core.urlresolvers import reverse |
|
19 |
from django.utils.translation import ugettext_lazy as _ |
|
20 | ||
21 | ||
22 |
class AppConfig(django.apps.AppConfig): |
|
23 |
name = 'combo.apps.assets' |
|
24 |
verbose_name = _('Assets') |
|
25 | ||
26 |
def get_before_urls(self): |
|
27 |
from . import urls |
|
28 |
return urls.urlpatterns |
|
29 | ||
30 |
def get_extra_manager_actions(self): |
|
31 |
return [{'href': reverse('combo-manager-assets'), |
|
32 |
'text': _('Assets')}] |
|
33 | ||
34 |
default_app_config = 'combo.apps.assets.AppConfig' |
combo/apps/assets/forms.py | ||
---|---|---|
1 |
# combo - content management system |
|
2 |
# Copyright (C) 2017-2018 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
from django import forms |
|
18 |
from django.utils.translation import ugettext_lazy as _ |
|
19 | ||
20 | ||
21 |
class AssetUploadForm(forms.Form): |
|
22 |
upload = forms.FileField(label=_('File')) |
combo/apps/assets/urls.py | ||
---|---|---|
1 |
# combo - content management system |
|
2 |
# Copyright (C) 2017-2018 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
from django.conf.urls import url, include |
|
18 | ||
19 |
from combo.urls_utils import decorated_includes, manager_required |
|
20 | ||
21 |
from . import views |
|
22 | ||
23 |
assets_manager_urls = [ |
|
24 |
url(r'^$', views.assets, name='combo-manager-assets'), |
|
25 |
url(r'^delete$', views.asset_delete, name='combo-manager-asset-delete'), |
|
26 |
url(r'^overwrite/$', views.asset_overwrite, name='combo-manager-asset-overwrite'), |
|
27 |
url(r'^upload/$', views.asset_upload, name='combo-manager-asset-upload'), |
|
28 |
] |
|
29 | ||
30 |
urlpatterns = [ |
|
31 |
url(r'^manage/assets/', decorated_includes(manager_required, |
|
32 |
include(assets_manager_urls))), |
|
33 |
] |
combo/apps/assets/views.py | ||
---|---|---|
1 |
# combo - content management system |
|
2 |
# Copyright (C) 2017-2018 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
import os |
|
18 | ||
19 |
from django.conf import settings |
|
20 |
from django.core.exceptions import PermissionDenied |
|
21 |
from django.core.files.storage import default_storage |
|
22 |
from django.core.urlresolvers import reverse, reverse_lazy |
|
23 |
from django.shortcuts import redirect |
|
24 |
from django.views.generic import TemplateView, ListView, FormView |
|
25 | ||
26 |
import ckeditor |
|
27 | ||
28 |
from .forms import AssetUploadForm |
|
29 | ||
30 | ||
31 |
class Asset(object): |
|
32 |
def __init__(self, filepath): |
|
33 |
self.filepath = filepath |
|
34 |
self.filename = os.path.basename(filepath) |
|
35 |
self.src = ckeditor.utils.get_media_url(filepath) |
|
36 | ||
37 |
def css_classes(self): |
|
38 |
extension = os.path.splitext(self.filepath)[-1].strip('.') |
|
39 |
if extension: |
|
40 |
return 'asset-ext-%s' % extension |
|
41 |
return '' |
|
42 | ||
43 |
def size(self): |
|
44 |
return os.stat(default_storage.path(self.filepath)).st_size |
|
45 | ||
46 |
def thumb(self): |
|
47 |
if getattr(settings, 'CKEDITOR_IMAGE_BACKEND', None): |
|
48 |
thumb = ckeditor.utils.get_media_url( |
|
49 |
ckeditor.utils.get_thumb_filename(self.filepath)) |
|
50 |
else: |
|
51 |
thumb = self.src |
|
52 |
return thumb |
|
53 | ||
54 |
def is_image(self): |
|
55 |
return ckeditor.views.is_image(self.src) |
|
56 | ||
57 | ||
58 |
class Assets(ListView): |
|
59 |
template_name = 'combo/manager_assets.html' |
|
60 |
paginate_by = 10 |
|
61 | ||
62 |
def get_queryset(self): |
|
63 |
files = [Asset(x) for x in ckeditor.views.get_image_files(self.request.user)] |
|
64 |
q = self.request.GET.get('q') |
|
65 |
if q: |
|
66 |
files = [x for x in files if q.lower() in x.filename.lower()] |
|
67 |
files.sort(key=lambda x: getattr(x, 'filename')) |
|
68 |
return files |
|
69 | ||
70 |
def get_context_data(self, **kwargs): |
|
71 |
context = super(Assets, self).get_context_data(**kwargs) |
|
72 |
context['query'] = self.request.GET.get('q') or '' |
|
73 |
return context |
|
74 | ||
75 |
assets = Assets.as_view() |
|
76 | ||
77 | ||
78 |
class AssetUpload(FormView): |
|
79 |
form_class = AssetUploadForm |
|
80 |
template_name = 'combo/manager_asset_upload.html' |
|
81 |
success_url = reverse_lazy('combo-manager-assets') |
|
82 | ||
83 |
def form_valid(self, form): |
|
84 |
# use native ckeditor view so it's available from ckeditor file/image |
|
85 |
# dialogs. |
|
86 |
ckeditor_upload_view = ckeditor.views.ImageUploadView() |
|
87 |
self.request.GET = {'CKEditorFuncNum': '-'} # hack |
|
88 |
ckeditor_upload_view.post(self.request) |
|
89 |
return super(AssetUpload, self).form_valid(form) |
|
90 | ||
91 |
asset_upload = AssetUpload.as_view() |
|
92 | ||
93 | ||
94 |
class AssetOverwrite(FormView): |
|
95 |
form_class = AssetUploadForm |
|
96 |
template_name = 'combo/manager_asset_overwrite.html' |
|
97 |
success_url = reverse_lazy('combo-manager-assets') |
|
98 | ||
99 |
def form_valid(self, form): |
|
100 |
img_orig = self.request.GET['img'] |
|
101 |
if '..' in img_orig: |
|
102 |
raise PermissionDenied() # better safe than sorry |
|
103 |
base_path = settings.CKEDITOR_UPLOAD_PATH |
|
104 |
if getattr(settings, 'CKEDITOR_RESTRICT_BY_USER', False): |
|
105 |
base_path = os.path.join(base_path, self.request.user.username) |
|
106 |
if not img_orig.startswith(base_path): |
|
107 |
raise PermissionDenied() |
|
108 | ||
109 |
upload = self.request.FILES['upload'] |
|
110 |
default_storage.delete(img_orig) |
|
111 |
if getattr(settings, 'CKEDITOR_IMAGE_BACKEND', None): |
|
112 |
thumb = ckeditor.utils.get_thumb_filename(img_orig) |
|
113 |
default_storage.delete(thumb) |
|
114 |
saved_path = default_storage.save(img_orig, upload) |
|
115 |
backend = ckeditor.image_processing.get_backend() |
|
116 |
upload.seek(0) # rewind file to be sure |
|
117 |
try: |
|
118 |
backend.image_verify(upload) |
|
119 |
except ckeditor.utils.NotAnImageException: |
|
120 |
pass |
|
121 |
else: |
|
122 |
if backend.should_create_thumbnail(saved_path): |
|
123 |
backend.create_thumbnail(saved_path) |
|
124 |
return super(AssetOverwrite, self).form_valid(form) |
|
125 | ||
126 |
asset_overwrite = AssetOverwrite.as_view() |
|
127 | ||
128 | ||
129 |
class AssetDelete(TemplateView): |
|
130 |
template_name = 'combo/manager_asset_confirm_delete.html' |
|
131 | ||
132 |
def post(self, request): |
|
133 |
img_orig = request.GET['img'] |
|
134 |
if '..' in img_orig: |
|
135 |
raise PermissionDenied() # better safe than sorry |
|
136 |
base_path = settings.CKEDITOR_UPLOAD_PATH |
|
137 |
if getattr(settings, 'CKEDITOR_RESTRICT_BY_USER', False): |
|
138 |
base_path = os.path.join(base_path, request.user.username) |
|
139 |
if not img_orig.startswith(base_path): |
|
140 |
raise PermissionDenied() |
|
141 |
default_storage.delete(img_orig) |
|
142 |
return redirect(reverse('combo-manager-assets')) |
|
143 | ||
144 |
asset_delete = AssetDelete.as_view() |
combo/manager/forms.py | ||
---|---|---|
101 | 101 | |
102 | 102 |
class SiteImportForm(forms.Form): |
103 | 103 |
site_json = forms.FileField(label=_('Site Export File')) |
104 | ||
105 | ||
106 |
class AssetUploadForm(forms.Form): |
|
107 |
upload = forms.FileField(label=_('File')) |
combo/manager/templates/combo/manager_home.html | ||
---|---|---|
6 | 6 |
<a class="extra-actions-menu-opener"></a> |
7 | 7 |
<a rel="popup" href="{% url 'combo-manager-page-add' %}">{% trans 'New' %}</a> |
8 | 8 |
<ul class="extra-actions-menu"> |
9 |
<li><a href="{% url 'combo-manager-assets' %}">{% trans 'Assets' %}</a></li> |
|
10 | 9 |
<li><a href="{% url 'combo-manager-site-export' %}">{% trans 'Export Site' %}</a></li> |
11 | 10 |
<li><a href="{% url 'combo-manager-site-import' %}">{% trans 'Import Site' %}</a></li> |
12 | 11 |
{% for extra_action in extra_actions %} |
combo/manager/urls.py | ||
---|---|---|
76 | 76 |
name='combo-manager-cell-order'), |
77 | 77 |
url(r'^pages/order$', views.page_order, |
78 | 78 |
name='combo-manager-page-order'), |
79 |
url(r'^assets/$', views.assets, name='combo-manager-assets'), |
|
80 |
url(r'^assets/delete$', views.asset_delete, name='combo-manager-asset-delete'), |
|
81 |
url(r'^assets/overwrite/$', views.asset_overwrite, name='combo-manager-asset-overwrite'), |
|
82 |
url(r'^assets/upload/$', views.asset_upload, name='combo-manager-asset-upload'), |
|
83 | 79 |
url(r'^ckeditor/upload/', staff_member_required(ckeditor_views.upload), |
84 | 80 |
name='ckeditor_upload'), |
85 | 81 |
url(r'^ckeditor/browse/', never_cache(staff_member_required(ckeditor_views.browse)), |
combo/manager/views.py | ||
---|---|---|
17 | 17 |
import json |
18 | 18 |
import os |
19 | 19 | |
20 |
import ckeditor |
|
21 | ||
22 | 20 |
from django.conf import settings |
23 | 21 |
from django.contrib import messages |
24 |
from django.core.files.storage import default_storage |
|
25 | 22 |
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied |
26 | 23 |
from django.core.urlresolvers import reverse, reverse_lazy |
27 | 24 |
from django.http import HttpResponse, HttpResponseRedirect, Http404 |
... | ... | |
31 | 28 |
from django.utils.formats import date_format |
32 | 29 |
from django.utils.timezone import localtime |
33 | 30 |
from django.views.decorators.csrf import requires_csrf_token |
34 |
from django.views.generic import (TemplateView, RedirectView, DetailView,
|
|
31 |
from django.views.generic import (RedirectView, DetailView, |
|
35 | 32 |
CreateView, UpdateView, ListView, DeleteView, FormView) |
36 | 33 | |
37 | 34 |
from combo.data.models import Page, CellBase, ParentContentCell, PageSnapshot |
... | ... | |
42 | 39 |
from .forms import (PageEditTitleForm, PageVisibilityForm, SiteImportForm, |
43 | 40 |
PageEditRedirectionForm, PageSelectTemplateForm, PageEditSlugForm, |
44 | 41 |
PageEditPictureForm, PageEditExcludeFromNavigationForm, |
45 |
AssetUploadForm, PageEditDescriptionForm)
|
|
42 |
PageEditDescriptionForm) |
|
46 | 43 | |
47 | 44 | |
48 | 45 |
class HomepageView(ListView): |
... | ... | |
473 | 470 |
return response |
474 | 471 | |
475 | 472 | |
476 |
class Asset(object): |
|
477 |
def __init__(self, filepath): |
|
478 |
self.filepath = filepath |
|
479 |
self.filename = os.path.basename(filepath) |
|
480 |
self.src = ckeditor.utils.get_media_url(filepath) |
|
481 | ||
482 |
def css_classes(self): |
|
483 |
extension = os.path.splitext(self.filepath)[-1].strip('.') |
|
484 |
if extension: |
|
485 |
return 'asset-ext-%s' % extension |
|
486 |
return '' |
|
487 | ||
488 |
def size(self): |
|
489 |
return os.stat(default_storage.path(self.filepath)).st_size |
|
490 | ||
491 |
def thumb(self): |
|
492 |
if getattr(settings, 'CKEDITOR_IMAGE_BACKEND', None): |
|
493 |
thumb = ckeditor.utils.get_media_url( |
|
494 |
ckeditor.utils.get_thumb_filename(self.filepath)) |
|
495 |
else: |
|
496 |
thumb = self.src |
|
497 |
return thumb |
|
498 | ||
499 |
def is_image(self): |
|
500 |
return ckeditor.views.is_image(self.src) |
|
501 | ||
502 | ||
503 |
class Assets(ListView): |
|
504 |
template_name = 'combo/manager_assets.html' |
|
505 |
paginate_by = 10 |
|
506 | ||
507 |
def get_queryset(self): |
|
508 |
files = [Asset(x) for x in ckeditor.views.get_image_files(self.request.user)] |
|
509 |
q = self.request.GET.get('q') |
|
510 |
if q: |
|
511 |
files = [x for x in files if q.lower() in x.filename.lower()] |
|
512 |
files.sort(key=lambda x: getattr(x, 'filename')) |
|
513 |
return files |
|
514 | ||
515 |
def get_context_data(self, **kwargs): |
|
516 |
context = super(Assets, self).get_context_data(**kwargs) |
|
517 |
context['query'] = self.request.GET.get('q') or '' |
|
518 |
return context |
|
519 | ||
520 |
assets = Assets.as_view() |
|
521 | ||
522 | ||
523 |
class AssetUpload(FormView): |
|
524 |
form_class = AssetUploadForm |
|
525 |
template_name = 'combo/manager_asset_upload.html' |
|
526 |
success_url = reverse_lazy('combo-manager-assets') |
|
527 | ||
528 |
def form_valid(self, form): |
|
529 |
# use native ckeditor view so it's available from ckeditor file/image |
|
530 |
# dialogs. |
|
531 |
ckeditor_upload_view = ckeditor.views.ImageUploadView() |
|
532 |
self.request.GET = {'CKEditorFuncNum': '-'} # hack |
|
533 |
ckeditor_upload_view.post(self.request) |
|
534 |
return super(AssetUpload, self).form_valid(form) |
|
535 | ||
536 |
asset_upload = AssetUpload.as_view() |
|
537 | ||
538 | ||
539 |
class AssetOverwrite(FormView): |
|
540 |
form_class = AssetUploadForm |
|
541 |
template_name = 'combo/manager_asset_overwrite.html' |
|
542 |
success_url = reverse_lazy('combo-manager-assets') |
|
543 | ||
544 |
def form_valid(self, form): |
|
545 |
img_orig = self.request.GET['img'] |
|
546 |
if '..' in img_orig: |
|
547 |
raise PermissionDenied() # better safe than sorry |
|
548 |
base_path = settings.CKEDITOR_UPLOAD_PATH |
|
549 |
if getattr(settings, 'CKEDITOR_RESTRICT_BY_USER', False): |
|
550 |
base_path = os.path.join(base_path, self.request.user.username) |
|
551 |
if not img_orig.startswith(base_path): |
|
552 |
raise PermissionDenied() |
|
553 | ||
554 |
upload = self.request.FILES['upload'] |
|
555 |
default_storage.delete(img_orig) |
|
556 |
if getattr(settings, 'CKEDITOR_IMAGE_BACKEND', None): |
|
557 |
thumb = ckeditor.utils.get_thumb_filename(img_orig) |
|
558 |
default_storage.delete(thumb) |
|
559 |
saved_path = default_storage.save(img_orig, upload) |
|
560 |
backend = ckeditor.image_processing.get_backend() |
|
561 |
upload.seek(0) # rewind file to be sure |
|
562 |
try: |
|
563 |
backend.image_verify(upload) |
|
564 |
except ckeditor.utils.NotAnImageException: |
|
565 |
pass |
|
566 |
else: |
|
567 |
if backend.should_create_thumbnail(saved_path): |
|
568 |
backend.create_thumbnail(saved_path) |
|
569 |
return super(AssetOverwrite, self).form_valid(form) |
|
570 | ||
571 |
asset_overwrite = AssetOverwrite.as_view() |
|
572 | ||
573 | ||
574 |
class AssetDelete(TemplateView): |
|
575 |
template_name = 'combo/manager_asset_confirm_delete.html' |
|
576 | ||
577 |
def post(self, request): |
|
578 |
img_orig = request.GET['img'] |
|
579 |
if '..' in img_orig: |
|
580 |
raise PermissionDenied() # better safe than sorry |
|
581 |
base_path = settings.CKEDITOR_UPLOAD_PATH |
|
582 |
if getattr(settings, 'CKEDITOR_RESTRICT_BY_USER', False): |
|
583 |
base_path = os.path.join(base_path, request.user.username) |
|
584 |
if not img_orig.startswith(base_path): |
|
585 |
raise PermissionDenied() |
|
586 |
default_storage.delete(img_orig) |
|
587 |
return redirect(reverse('combo-manager-assets')) |
|
588 | ||
589 |
asset_delete = AssetDelete.as_view() |
|
590 | ||
591 | ||
592 | 473 |
def menu_json(request): |
593 | 474 |
if settings.TEMPLATE_VARS.get('site_title'): |
594 | 475 |
label = _('Editing "%(site_title)s"') % settings.TEMPLATE_VARS |
combo/settings.py | ||
---|---|---|
62 | 62 |
'combo.profile', |
63 | 63 |
'combo.manager', |
64 | 64 |
'combo.public', |
65 |
'combo.apps.assets', |
|
65 | 66 |
'combo.apps.dashboard', |
66 | 67 |
'combo.apps.wcs', |
67 | 68 |
'combo.apps.publik', |
68 |
- |