diff --git a/cmsplugin_cascade/app_settings.py b/cmsplugin_cascade/app_settings.py index 54464fca5..58162b4d6 100644 --- a/cmsplugin_cascade/app_settings.py +++ b/cmsplugin_cascade/app_settings.py @@ -124,6 +124,18 @@ def CMSPLUGIN_CASCADE(self): config.setdefault('register_page_editor', True) + + config.setdefault('fallback',{ + # default + 'path_main_scss': 'bs4demo/css/main.scss', + 'path_main_scss': 'myshop/css/default.scss', + 'image':{'color':'hsla(221.7, 57.5%, 84.3%, 0.8)', 'svg':''}, + 'picture':{'color':'hsla(0, 40%, 80.4%, 0.8)', 'svg':'' }, + 'jumbotron':{'color':'hsl(62, 90%, 90%, 0.8)', 'svg':''}, + 'img_or_pic_lost_pk': True, + }) + + for module_name in self.CASCADE_PLUGINS: try: settings_module = import_module('{}.settings'.format(module_name)) diff --git a/cmsplugin_cascade/bootstrap4/buttons.py b/cmsplugin_cascade/bootstrap4/buttons.py index 0e8e1e4c1..24a0877c3 100644 --- a/cmsplugin_cascade/bootstrap4/buttons.py +++ b/cmsplugin_cascade/bootstrap4/buttons.py @@ -117,6 +117,7 @@ class BootstrapButtonMixin(IconPluginMixin): default_css_attributes = ['button_type', 'button_size', 'button_options', 'stretched_link'] ring_plugin = 'ButtonMixin' + class Media: css = {'all': ['cascade/css/admin/bootstrap4-buttons.css', 'cascade/css/admin/iconplugin.css']} js = ['admin/js/jquery.init.js', 'cascade/js/admin/buttonmixin.js'] @@ -147,6 +148,7 @@ class BootstrapButtonPlugin(BootstrapButtonMixin, LinkPluginBase): form = BootstrapButtonFormMixin ring_plugin = 'ButtonPlugin' DEFAULT_BUTTON_ATTRIBUTES = {'role': 'button'} +# render_template_fallback = "cascade/generic/fallback_button.html" # mode stride gallery class Media: js = ['admin/js/jquery.init.js', 'cascade/js/admin/buttonplugin.js'] diff --git a/cmsplugin_cascade/bootstrap4/jumbotron.py b/cmsplugin_cascade/bootstrap4/jumbotron.py index be349bce6..91d2d16f9 100644 --- a/cmsplugin_cascade/bootstrap4/jumbotron.py +++ b/cmsplugin_cascade/bootstrap4/jumbotron.py @@ -195,6 +195,7 @@ class BootstrapJumbotronPlugin(BootstrapPluginBase): form = JumbotronFormMixin raw_id_fields = ['image_file'] render_template = 'cascade/bootstrap4/jumbotron.html' + render_template_fallback = "cascade/generic/fallback_jumbotron.html" # mode stride gallery ring_plugin = 'JumbotronPlugin' footnote_html = """
For more information about the Jumbotron please read the
diff --git a/cmsplugin_cascade/bootstrap4/utils.py b/cmsplugin_cascade/bootstrap4/utils.py
index 66817c67b..8568a3b9b 100644
--- a/cmsplugin_cascade/bootstrap4/utils.py
+++ b/cmsplugin_cascade/bootstrap4/utils.py
@@ -31,6 +31,9 @@ def get_image_tags(instance):
aspect_ratio = compute_aspect_ratio(instance.image)
elif 'image' in instance.glossary and 'width' in instance.glossary['image']:
aspect_ratio = compute_aspect_ratio_with_glossary(instance.glossary)
+ # fallback logic
+ elif 'image_properties' in instance.glossary and 'width' in instance.glossary['image_properties']:
+ aspect_ratio = compute_aspect_ratio_with_glossary(instance.glossary)
else:
# if accessing the image file fails or fake image fails, abort here
raise FileNotFoundError("Unable to compute aspect ratio of image")
@@ -52,7 +55,11 @@ def get_image_tags(instance):
else:
image_width = parse_responsive_length(instance.glossary['image_width_fixed'])
if not image_width[0]:
- image_width = (instance.image.width, image_width[1])
+ if hasattr(instance,'image' ) and hasattr(instance.image,'width' ) :
+ image_width = (instance.image.width, image_width[1])
+ # logic fallback
+ else:
+ image_width = (instance.glossary['image_properties']['width'],image_width[1] )
try:
image_height = parse_responsive_length(instance.glossary['image_height'])
except KeyError:
@@ -99,7 +106,10 @@ def get_picture_elements(instance):
if hasattr(instance, 'image') and hasattr(instance.image, 'exif'):
aspect_ratio = compute_aspect_ratio(instance.image)
- elif 'image' in instance.glossary and 'width' in instance.glossary['image']:
+ # fallback logic picture
+ elif 'image' in instance.glossary and 'width' in instance.glossary['image']:
+ aspect_ratio = compute_aspect_ratio_with_glossary(instance.glossary)
+ elif 'image_properties' in instance.glossary and 'width' in instance.glossary['image_properties']:
aspect_ratio = compute_aspect_ratio_with_glossary(instance.glossary)
else:
# if accessing the image file fails or fake image fails, abort here
diff --git a/cmsplugin_cascade/clipboard/admin.py b/cmsplugin_cascade/clipboard/admin.py
index ce3c57491..b02f2dea6 100644
--- a/cmsplugin_cascade/clipboard/admin.py
+++ b/cmsplugin_cascade/clipboard/admin.py
@@ -10,9 +10,9 @@
from jsonfield.fields import JSONField
from cms.models.placeholderpluginmodel import PlaceholderReference
+from cms.admin.placeholderadmin import PlaceholderAdminMixin
from cmsplugin_cascade.clipboard.utils import deserialize_to_clipboard, serialize_from_placeholder
-from cmsplugin_cascade.models import CascadeClipboard
-
+from cmsplugin_cascade.models import CascadeClipboard, CascadeClipboardGroup
class JSONAdminWidget(widgets.Textarea):
def __init__(self):
@@ -36,10 +36,13 @@ def render(self, name, value, attrs=None, renderer=None):
_("Successfully pasted JSON data"),
_("Successfully copied JSON data"))
+@admin.register(CascadeClipboardGroup)
+class GroupModelAdmin(PlaceholderAdminMixin, admin.ModelAdmin):
+ pass
@admin.register(CascadeClipboard)
class CascadeClipboardAdmin(admin.ModelAdmin):
- fields = ['identifier', ('created_by', 'created_at', 'last_accessed_at'), 'save_clipboard', 'restore_clipboard', 'data']
+ fields = ['identifier', 'group', ('created_by', 'created_at', 'last_accessed_at'), 'save_clipboard', 'restore_clipboard', 'data']
readonly_fields = ['created_by', 'created_at', 'last_accessed_at', 'save_clipboard', 'restore_clipboard']
formfield_overrides = {
JSONField: {'widget': JSONAdminWidget},
diff --git a/cmsplugin_cascade/clipboard/cms_plugins.py b/cmsplugin_cascade/clipboard/cms_plugins.py
index 96de225c2..25569093a 100644
--- a/cmsplugin_cascade/clipboard/cms_plugins.py
+++ b/cmsplugin_cascade/clipboard/cms_plugins.py
@@ -1,10 +1,11 @@
import json
-
+from ast import literal_eval
from django.conf.urls import url
from django.contrib.admin import site as default_admin_site
from django.contrib.admin.helpers import AdminForm
from django.core.exceptions import PermissionDenied
-from django.forms import CharField, ModelChoiceField
+from django.forms import CharField, ModelChoiceField, ModelMultipleChoiceField, ChoiceField, MultipleChoiceField
+from django.http import HttpResponse
from django.shortcuts import render
from django.template.response import TemplateResponse
from django.urls import reverse
@@ -18,7 +19,81 @@
from cms.utils import get_language_from_request
from cmsplugin_cascade.clipboard.forms import ClipboardBaseForm
from cmsplugin_cascade.clipboard.utils import deserialize_to_clipboard, serialize_from_placeholder
-from cmsplugin_cascade.models import CascadeClipboard
+from cmsplugin_cascade.models import CascadeClipboard, CascadeClipboardGroup
+from cmsplugin_cascade.clipboard.forms import ClipboardBaseForm
+from django.forms import widgets
+from django.contrib.admin.widgets import RelatedFieldWidgetWrapper
+from django.conf import settings
+
+
+def FormViewClipboard(request, form, context):
+ group_selected=request.GET.get('group', 'Clipboard Home')
+ widget = form['clipboard'].field.widget
+ widget.attrs['id']= '1'
+ widget.attrs['pk']= '1'
+ # widget.choices, len_ungroup = merder()
+ form['clipboard'].field.widget.get_context('clipboards', '', widget.attrs)
+ widget.optgroups = form['clipboard'].field.widget.optgroups_result
+ tpl_basedir = settings.CMSPLUGIN_CASCADE['bootstrap4'].get('template_basedir', None)
+ context.update( {
+ 'img_or_pic_lost_pk': settings.CMSPLUGIN_CASCADE['fallback']['img_or_pic_lost_pk'],
+ 'tpl_basedir' :tpl_basedir,
+ 'len_ungroup' :form.len_ungroup,
+ 'group_selected' : group_selected,
+ 'widget': widget ,
+ 'placeholder_ref_id': request.GET['placeholder'],
+ 'language_ref': request.GET['language'],
+ 'main_scss': settings.CMSPLUGIN_CASCADE['fallback']['path_main_scss'],
+ 'qs_clipboards': CascadeClipboard.objects.all(),
+ 'groups_exclude_home':list(CascadeClipboardGroup.objects.all().exclude( name='Clipboard Home').values_list('name',flat=True,)),
+ 'widget_optgroups': form['clipboard'].field.widget.optgroups_result,
+ 'form': form })
+ return render(request, "cascade/admin/clipboard_import.html", context)
+
+
+def tree_group_clipboards():
+ queryset=CascadeClipboard.objects.all().prefetch_related('group')
+ clipboards_groupby={}
+
+ def treegroup( groups, index2):
+ groups_clipboard=list(groups.group.values_list('name', flat=True))
+ if len(groups_clipboard) >= 1:
+ for index, key in enumerate(groups_clipboard, start=1):
+ clipboards_groupby.setdefault(key, [])
+ clipboards_groupby[key].append(( groups.identifier ,groups.identifier,))
+ else:
+ clipboards_groupby.setdefault('ungroup', [])
+ clipboards_groupby['ungroup'].append(( groups.identifier ,groups.identifier,))
+
+ [treegroup( groups, index) for index, groups in enumerate(queryset , start=1)]
+ if 'ungroup' in clipboards_groupby :
+ len_ungroup = len(clipboards_groupby['ungroup'])
+ else:
+ len_ungroup = 0
+ if not 'Clipboard Home' in clipboards_groupby:
+ group ='Clipboard Home'
+ clipboard_home = CascadeClipboardGroup.objects.get_or_create(name=group)
+ CHOICES = (list(clipboards_groupby.items(),))
+ return CHOICES, len_ungroup
+
+
+class ClipboardWidget(widgets.Select):
+
+ def __init__(self, *args, **kwargs):
+ self.req= None
+ self.optgroups_result= None
+ super().__init__(*args, **kwargs)
+
+ def get_context(self, name, value, attrs):
+ context = super(ClipboardWidget, self).get_context(name, value, attrs)
+ context['request'] = self.req
+ self.optgroups_result = self.optgroups(name, context['widget']['value'], attrs)
+ groups=list(CascadeClipboardGroup.objects.all().exclude( name='Clipboard Home').values_list('name',flat=True))
+ context['groups_exclude_home'] = groups
+ context['qs_clipboards'] = CascadeClipboard.objects.all()
+ context['main_scss'] = settings.CMSPLUGIN_CASCADE['fallback']['path_main_scss']
+
+ return context
class CascadeClipboardPlugin(CMSPluginBase):
@@ -40,7 +115,7 @@ def get_extra_placeholder_menu_items(cls, request, placeholder):
})
return [
PluginMenuItem(
- _("Export to Clipboard"),
+ _("Export from Clipboard"),
reverse('admin:export_clipboard_plugins') + '?' + data,
data={},
action='modal',
@@ -51,15 +126,14 @@ def get_extra_placeholder_menu_items(cls, request, placeholder):
PluginMenuItem(
_("Import from Clipboard"),
reverse('admin:import_clipboard_plugins') + '?' + data,
- data={},
action='modal',
attributes={
'icon': 'import',
},
- )
+ ),
]
- def render_modal_window(self, request, form):
+ def render_modal_window(self, request,form):
"""
Render a modal popup window with a select box to edit the form
"""
@@ -70,52 +144,99 @@ def render_modal_window(self, request, form):
**default_admin_site.each_context(request),
'title': form.title,
'adminform': adminForm,
- 'add': False,
+ 'add': True,
'change': True,
- 'save_as': False,
- 'has_add_permission': False,
+ 'save_as': True,
+ 'has_add_permission': True,
'has_change_permission': True,
+ 'can_change_related':True,
+ 'can_add_related':True,
'opts': opts,
'root_path': reverse('admin:index'),
'is_popup': True,
'app_label': opts.app_label,
'media': self.media + form.media,
}
- return TemplateResponse(request, self.change_form_template, context)
+ if not 'ClipboardExportForm' in str(type(form)):
+ context.update({
+ 'main_scss': settings.CMSPLUGIN_CASCADE['fallback']['path_main_scss'],
+ 'qs_clipboards': CascadeClipboard.objects.all(),
+ 'groups_exclude_home':list(CascadeClipboardGroup.objects.all().exclude( name='Clipboard Home').values_list('name',flat=True)),
+ 'group_selected' : 'Clipboard Home',
+ })
+ response = FormViewClipboard(request, form , context )
+ return response
+ else:
+ return TemplateResponse(request, self.change_form_template, context )
+
+ return TemplateResponse(request, self.change_form_template, context )
- def import_plugins_view(self, request):
+
+ def import_plugins_view(self, request, *args, **kwargs):
# TODO: check for permissions
+ view_breakdown = request.session.get('view_breakdown', "lg")
+ placeholder_ref_id = None
+ if request.GET.get('placeholder'):
+ placeholder_ref_id = request.GET.get('placeholder')
+ if request.GET.get('group'):
+ req_parameter_group = request.GET.get('group')
+ title = ": {}".format(req_parameter_group)
+ else:
+ req_parameter_group = "Clipboard Home"
+ title = _("Import to Clipboard")
+ CHOICES, len_ungroup = tree_group_clipboards()
- title = _("Import from Clipboard")
+ language= get_language_from_request(request)
+
if request.method == 'GET':
Form = type('ClipboardImportForm', (ClipboardBaseForm,), {
- 'clipboard': ModelChoiceField(
- queryset=CascadeClipboard.objects.all(),
- label=_("Select Clipboard Content"),
+ 'clipboard':ChoiceField(
+ choices=CHOICES,
+ label=_("Select Clipboard"),
required=False,
+ widget=ClipboardWidget(attrs={"placeholder_ref_id": placeholder_ref_id, "language": language, 'count_target':len_ungroup ,'view_breakdown':view_breakdown }),
),
'title': title,
})
+
+ Form.Media = type("Media",(), {'css' : { 'all': [ ''] }})
+ Form.base_fields['clipboard'].widget.req = request
form = Form(request.GET)
+ form.len_ungroup = len_ungroup
assert form.is_valid()
+
elif request.method == 'POST':
Form = type('ClipboardImportForm', (ClipboardBaseForm,), {
- 'clipboard': ModelChoiceField(
- queryset=CascadeClipboard.objects.all(),
- label=_("Select Clipboard Content"),
+ 'clipboard': ChoiceField(
+ choices=CHOICES,
+ label=_("Select Clipboard"),
+ widget=ClipboardWidget(),
),
'title': title,
})
- form = Form(request.POST)
+
+ complete_form_dict = {'placeholder':request.GET['placeholder'], 'language':request.GET['language'] }
+ request_post_dict = request.POST.dict()
+ request_post_dict.update(complete_form_dict)
+ form = Form(request_post_dict)
+
+ form.len_ungroup = len_ungroup
if form.is_valid():
return self.paste_from_clipboard(request, form)
return self.render_modal_window(request, form)
def paste_from_clipboard(self, request, form):
+ # request.toolbar.clipboard.clear()
placeholder = form.cleaned_data['placeholder']
language = form.cleaned_data['language']
cascade_clipboard = form.cleaned_data['clipboard']
+
tree_order = placeholder.get_plugin_tree_order(language)
+ if not hasattr(cascade_clipboard, 'data'):
+ cascade_clipboard = CascadeClipboard.objects.get(identifier=cascade_clipboard)
+ if settings.CMSPLUGIN_CASCADE.get('fallback', None ).get('img_or_pic_lost_pk', None):
+ tree_data_lost_ref_img = str(cascade_clipboard.data).replace("'image_file': {'model': 'filer.image', 'pk': ", "'image_file': {'model': 'filer.image', 'pk': 10000")
+ cascade_clipboard.data= literal_eval(tree_data_lost_ref_img)
deserialize_to_clipboard(request, cascade_clipboard.data)
cascade_clipboard.last_accessed_at = now()
cascade_clipboard.save(update_fields=['last_accessed_at'])
@@ -123,16 +244,22 @@ def paste_from_clipboard(self, request, form):
# detach plugins from clipboard and reattach them to current placeholder
cb_placeholder_plugin = request.toolbar.clipboard.cmsplugin_set.first()
cb_placeholder_instance, _ = cb_placeholder_plugin.get_plugin_instance()
- new_plugins = cb_placeholder_instance.placeholder_ref.get_plugins()
+
+ # bug if cb_placeholder_instance.plugin_type == 'Alias or Text,
+ # they don't have a 'placeholder ref' attribute.
+ if cb_placeholder_instance.plugin_type == 'AliasPlugin' or cb_placeholder_instance.plugin_type == 'AliasPluginModel' or cb_placeholder_instance.plugin_type == 'TextPlugin':
+ return HttpResponse('Clipboard has AliasPlugin or TextPlugin, clear Clipboard Before')
+ else:
+ new_plugins = cb_placeholder_instance.placeholder_ref.get_plugins()
new_plugins.update(placeholder=placeholder)
# reorder root plugins in placeholder
root_plugins = placeholder.get_plugins(language).filter(parent__isnull=True).order_by('changed_date')
for position, plugin in enumerate(root_plugins.iterator()):
plugin.update(position=position)
- placeholder.mark_as_dirty(language, clear_cache=False)
- # create a list of pasted plugins to be added to the structure view
+ placeholder.mark_as_dirty(language, clear_cache=False)
+ # create a list of pasted plugins to be added to the soptgroups_resultture view
all_plugins = placeholder.get_plugins(language)
if all_plugins.exists():
new_plugins = placeholder.get_plugins(language).exclude(pk__in=tree_order)
@@ -141,27 +268,46 @@ def paste_from_clipboard(self, request, form):
else:
return render(request, 'cascade/admin/clipboard_reload_page.html')
data['target_placeholder_id'] = placeholder.pk
- context = {'structure_data': json.dumps(data)}
+ context = {'soptgroups_resultture_data': json.dumps(data)}
return render(request, 'cascade/admin/clipboard_paste_plugins.html', context)
def export_plugins_view(self, request):
if not request.user.is_staff:
raise PermissionDenied
+ qs_clipboards=CascadeClipboardGroup.objects.all()
+ if not'Clipboard Home' in list(qs_clipboards.values_list( 'name' , flat=True)):
+ qs_clipboards.get_or_create(name="Clipboard Home")
+
title = _("Export to Clipboard")
if request.method == 'GET':
Form = type('ClipboardExportForm', (ClipboardBaseForm,), {
'identifier': CharField(required=False),
'title': title,
+ 'group' : ModelMultipleChoiceField(
+ queryset=qs_clipboards,
+ required=False,
+ ),
})
form = Form(request.GET)
+ form.fields['group'].widget = RelatedFieldWidgetWrapper(
+ form.fields['group'].widget,CascadeClipboard.group.rel,
+ default_admin_site, can_change_related=True)
assert form.is_valid()
elif request.method == 'POST':
Form = type('ClipboardExportForm', (ClipboardBaseForm,), {
'identifier': CharField(),
'title': title,
+ 'group' : ModelMultipleChoiceField(
+ queryset=qs_clipboards,
+ required=False,
+ ),
})
form = Form(request.POST)
+ form.fields['group'].widget = RelatedFieldWidgetWrapper(
+ form.fields['group'].widget,CascadeClipboard.group.rel,
+ default_admin_site, can_change_related=True)
+
if form.is_valid():
return self.add_to_clipboard(request, form)
return self.render_modal_window(request, form)
@@ -170,12 +316,14 @@ def add_to_clipboard(self, request, form):
placeholder = form.cleaned_data['placeholder']
language = form.cleaned_data['language']
identifier = form.cleaned_data['identifier']
- data = serialize_from_placeholder(placeholder)
- CascadeClipboard.objects.create(
+ group = form.cleaned_data['group']
+ data = serialize_from_placeholder(placeholder,language=language)
+ cascade_clipboard = CascadeClipboard.objects.create(
identifier=identifier,
data=data,
created_by=request.user,
)
+ cascade_clipboard.group.set(group)
return render(request, 'cascade/admin/clipboard_close_frame.html', {})
plugin_pool.register_plugin(CascadeClipboardPlugin)
diff --git a/cmsplugin_cascade/clipboard/utils.py b/cmsplugin_cascade/clipboard/utils.py
index d74783050..65b7f3200 100644
--- a/cmsplugin_cascade/clipboard/utils.py
+++ b/cmsplugin_cascade/clipboard/utils.py
@@ -12,7 +12,7 @@
from cmsplugin_cascade.models import CascadeElement
-def serialize_from_placeholder(placeholder, admin_site=default_admin_site):
+def serialize_from_placeholder(placeholder, admin_site=default_admin_site, language=None):
"""
Create a serialized representation of all the plugins belonging to the clipboard.
"""
@@ -31,7 +31,7 @@ def populate_data(parent, data):
populate_data(child, entry[2])
data = {'plugins': []}
- plugin_qs = placeholder.cmsplugin_set.all()
+ plugin_qs = placeholder.cmsplugin_set.filter(language=language)
populate_data(None, data['plugins'])
return data
diff --git a/cmsplugin_cascade/image.py b/cmsplugin_cascade/image.py
index 16b4f7f93..d547576e0 100644
--- a/cmsplugin_cascade/image.py
+++ b/cmsplugin_cascade/image.py
@@ -20,26 +20,31 @@ class ImageFormMixin(EntangledModelFormMixin):
help_text=_("Textual description of the image added to the 'alt' tag of the element."),
)
- _image_properties = EntangledField()
+ image_properties = EntangledField()
class Meta:
- entangled_fields = {'glossary': ['image_file', 'image_title', 'alt_tag', '_image_properties']}
+ entangled_fields = {'glossary': ['image_file', 'image_title', 'alt_tag', 'image_properties']}
def __init__(self, *args, **kwargs):
if not getattr(self, 'require_image', True):
self.base_fields['image_file'].required = False
super().__init__(*args, **kwargs)
- def clean_image_file(self):
- image_file = self.cleaned_data['image_file']
- # _image_properties are just a cached representation, maybe useless
- if image_file:
- self.cleaned_data['_image_properties'] = {
+
+ def clean(self):
+ cleaned_data = super().clean()
+ if 'image_file' in self.cleaned_data:
+ image_file = self.cleaned_data['image_file']
+ if image_file.mime_type == 'image/svg+xml':
+ image_file_orientation = 1
+ else:
+ image_file_orientation = image_file.exif.get('Orientation', 1)
+ cleaned_data['image_properties'] = {
'width': image_file._width,
'height': image_file._height,
- 'exif_orientation': image_file.exif.get('Orientation', 1),
+ 'exif_orientation': image_file_orientation,
}
- return image_file
+ return cleaned_data
class ImagePropertyMixin:
diff --git a/cmsplugin_cascade/migrations/0027_version_1.py b/cmsplugin_cascade/migrations/0027_version_1.py
index a21f06754..31d38fc58 100644
--- a/cmsplugin_cascade/migrations/0027_version_1.py
+++ b/cmsplugin_cascade/migrations/0027_version_1.py
@@ -58,7 +58,7 @@ def migrate_image(glossary):
})
if 'width' in image and 'height' in image and 'exif_orientation' in image:
glossary.update({
- '_image_properties': {'width': image['width'], 'height': image['height'],
+ 'image_properties': {'width': image['width'], 'height': image['height'],
'exif_orientation': image['exif_orientation']},
})
return True
diff --git a/cmsplugin_cascade/migrations/0028_cascade_clipboard.py b/cmsplugin_cascade/migrations/0028_cascade_clipboard.py
index d19fdea29..dfb74e100 100644
--- a/cmsplugin_cascade/migrations/0028_cascade_clipboard.py
+++ b/cmsplugin_cascade/migrations/0028_cascade_clipboard.py
@@ -32,4 +32,16 @@ class Migration(migrations.Migration):
name='last_accessed_at',
field=models.DateTimeField(default=None, editable=False, null=True, verbose_name='Last accessed at'),
),
+ migrations.CreateModel(
+ name='CascadeClipboardGroup',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=50)),
+ ],
+ ),
+ migrations.AddField(
+ model_name='cascadeclipboard',
+ name='group',
+ field=models.ManyToManyField(blank=True, to='cmsplugin_cascade.CascadeClipboardGroup'),
+ ),
]
diff --git a/cmsplugin_cascade/models.py b/cmsplugin_cascade/models.py
index 78b45bfaf..de679d618 100644
--- a/cmsplugin_cascade/models.py
+++ b/cmsplugin_cascade/models.py
@@ -228,6 +228,11 @@ class Meta:
managed = False # it's a dummy model
db_table = None
+class CascadeClipboardGroup(models.Model):
+ name = models.CharField(max_length=50)
+
+ def __str__(self):
+ return str(self.name)
class CascadeClipboard(models.Model):
"""
@@ -239,6 +244,8 @@ class CascadeClipboard(models.Model):
unique=True,
)
+ group = models.ManyToManyField(CascadeClipboardGroup,blank=True)
+
data = JSONField(
null=True,
blank=True,
diff --git a/cmsplugin_cascade/static/cascade/admin/clipboards/demo/demo_carousel-plugin.json b/cmsplugin_cascade/static/cascade/admin/clipboards/demo/demo_carousel-plugin.json
new file mode 100644
index 000000000..54a948d7c
--- /dev/null
+++ b/cmsplugin_cascade/static/cascade/admin/clipboards/demo/demo_carousel-plugin.json
@@ -0,0 +1,232 @@
+{
+ "plugins": [
+ [
+ "BootstrapContainerPlugin",
+ {
+ "glossary": {
+ "hide_plugin": false,
+ "breakpoints": [
+ "xs",
+ "sm",
+ "md",
+ "lg",
+ "xl"
+ ],
+ "fluid": false
+ },
+ "pk": 1295
+ },
+ [
+ [
+ "BootstrapRowPlugin",
+ {
+ "glossary": {
+ "hide_plugin": false,
+ "padding_xs": "",
+ "padding_sm": "",
+ "padding_md": "",
+ "padding_lg": "",
+ "padding_xl": ""
+ },
+ "pk": 1296
+ },
+ [
+ [
+ "BootstrapColumnPlugin",
+ {
+ "glossary": {
+ "xs-column-width": "col"
+ },
+ "pk": 1297
+ },
+ [
+ [
+ "BootstrapCarouselPlugin",
+ {
+ "glossary": {
+ "hide_plugin": false,
+ "margins_xs": "",
+ "margins_sm": "",
+ "margins_md": "",
+ "margins_lg": "",
+ "margins_xl": "",
+ "interval": 5,
+ "options": [
+ "slide",
+ "pause",
+ "wrap"
+ ],
+ "container_max_heights": {
+ "xs": "100px",
+ "sm": "150px",
+ "md": "200px",
+ "lg": "250px",
+ "xl": "300px"
+ },
+ "resize_options": [
+ "upscale",
+ "crop",
+ "subject_location",
+ "high_resolution"
+ ]
+ },
+ "pk": 1316
+ },
+ [
+ [
+ "BootstrapCarouselSlidePlugin",
+ {
+ "glossary": {
+ "hide_plugin": false,
+ "image_file": {
+ "model": "filer.image",
+ "pk": 234
+ },
+ "image_title": "",
+ "alt_tag": "",
+ "image_properties": {
+ "width": 1368,
+ "height": 768,
+ "exif_orientation": 1
+ },
+ "resize_options": [
+ "upscale",
+ "crop",
+ "subject_location",
+ "high_resolution"
+ ],
+ "media_queries": {
+ "xs": {
+ "width": 572,
+ "media": "(max-width: 575.98px)"
+ },
+ "sm": {
+ "width": 540,
+ "media": "(min-width: 576px) and (max-width: 767.98px)"
+ },
+ "md": {
+ "width": 720,
+ "media": "(min-width: 768px) and (max-width: 991.98px)"
+ },
+ "lg": {
+ "width": 960,
+ "media": "(min-width: 992px) and (max-width: 1199.98px)"
+ },
+ "xl": {
+ "width": 1140,
+ "media": "(min-width: 1200px)"
+ }
+ }
+ },
+ "pk": 1317
+ },
+ []
+ ],
+ [
+ "BootstrapCarouselSlidePlugin",
+ {
+ "glossary": {
+ "hide_plugin": false,
+ "image_file": {
+ "model": "filer.image",
+ "pk": 235
+ },
+ "image_title": "",
+ "alt_tag": "",
+ "image_properties": {
+ "width": 1280,
+ "height": 720,
+ "exif_orientation": 1
+ },
+ "resize_options": [
+ "upscale",
+ "crop",
+ "subject_location",
+ "high_resolution"
+ ],
+ "media_queries": {
+ "xs": {
+ "width": 572,
+ "media": "(max-width: 575.98px)"
+ },
+ "sm": {
+ "width": 540,
+ "media": "(min-width: 576px) and (max-width: 767.98px)"
+ },
+ "md": {
+ "width": 720,
+ "media": "(min-width: 768px) and (max-width: 991.98px)"
+ },
+ "lg": {
+ "width": 960,
+ "media": "(min-width: 992px) and (max-width: 1199.98px)"
+ },
+ "xl": {
+ "width": 1140,
+ "media": "(min-width: 1200px)"
+ }
+ }
+ },
+ "pk": 1318
+ },
+ []
+ ],
+ [
+ "BootstrapCarouselSlidePlugin",
+ {
+ "glossary": {
+ "hide_plugin": false,
+ "image_file": {
+ "model": "filer.image",
+ "pk": 236
+ },
+ "image_title": "",
+ "alt_tag": "",
+ "image_properties": {
+ "width": 1367,
+ "height": 344,
+ "exif_orientation": 1
+ },
+ "resize_options": [
+ "upscale",
+ "crop",
+ "subject_location",
+ "high_resolution"
+ ],
+ "media_queries": {
+ "xs": {
+ "width": 572,
+ "media": "(max-width: 575.98px)"
+ },
+ "sm": {
+ "width": 540,
+ "media": "(min-width: 576px) and (max-width: 767.98px)"
+ },
+ "md": {
+ "width": 720,
+ "media": "(min-width: 768px) and (max-width: 991.98px)"
+ },
+ "lg": {
+ "width": 960,
+ "media": "(min-width: 992px) and (max-width: 1199.98px)"
+ },
+ "xl": {
+ "width": 1140,
+ "media": "(min-width: 1200px)"
+ }
+ }
+ },
+ "pk": 1319
+ },
+ []
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+}
diff --git a/cmsplugin_cascade/static/cascade/css/admin/clipboard.css b/cmsplugin_cascade/static/cascade/css/admin/clipboard.css
index aaf2dec94..e799adccc 100644
--- a/cmsplugin_cascade/static/cascade/css/admin/clipboard.css
+++ b/cmsplugin_cascade/static/cascade/css/admin/clipboard.css
@@ -1,3 +1,6 @@
+body {
+padding-top:0px !important!;
+}
.pull-left {
float: left !important;
}
@@ -40,4 +43,114 @@ div.cms .cms-structure .cms-submenu-item a[data-icon=import]:focus:before {
div.cms .cms-structure.cms-structure-condensed .cms-submenu-item a[data-icon=export]:before,
div.cms .cms-structure.cms-structure-condensed .cms-submenu-item a[data-icon=import]:before {
top: 12px !important;
-}
\ No newline at end of file
+}
+
+
+
+.container_clipboard {
+ display: flex;
+ justify-content: space-between;
+}
+
+.cascade_clipboard_admin {
+ padding: 1rem;
+ z-index: 4;
+}
+
+.button_clipboard {
+ background-color:#ffffff;
+ -webkit-border-radius:6px;
+ -moz-border-radius:6px;
+ border-radius:6px;
+ border:1px solid #dcdcdc;
+ cursor:pointer;
+ color:#666666;
+ font-family:Arial;
+ font-size:15px;
+ font-weight:bold;
+ padding:6px 24px;
+ text-decoration:none;
+}
+.button_clipboard:hover {
+ background-color:#f6f6f6;
+}
+.button_clipboard:active {
+ position:relative;
+ top:1px;
+}
+
+.cms-admin-modal{
+background-color: white;
+}
+.field-placeholder, .field-language, #footer {
+ display:none;
+}
+
+.field-clipboard {
+ border-bottom: none;
+}
+
+.select-box__current {
+ display: inline-flex;
+ flex-wrap: wrap;
+ position:absolute;
+ justify-content: center;
+ width: 100%;
+}
+
+.cms-cascade-clipboard {
+ width:400px;
+ margin-right:1rem;
+ margin-bottom:1rem;
+}
+
+.clipboard-base .clipboard-fullscreen:checked ~ .cms-cascade-clipboard {
+ width:100%;
+}
+
+.clipboard-fullscreen {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ -o-appearance: none;
+ appearance: none;
+ position: relative;
+ right: 3Opx;
+ left: 50%;
+ top: 26px;
+}
+
+.form-clipboard {
+position: absolute;
+width: 100%;
+top: 3rem;
+}
+
+.clipboard-fullscreen:after{
+ content:'fullscreen';
+ cursor: pointer;
+}
+
+.clipboard-fullscreen:checked:after {
+ content:'back';
+ cursor: pointer;
+}
+
+.clipboard-base .clipboard-fullscreen:checked {
+ display: inline-block;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ -o-appearance: none;
+ appearance: none;
+}
+
+.clipboard-base .clipboard-fullscreen:not(:checked), .clipboard-base .clipboard-fullscreen:not(:checked) ~ .cms-cascade-clipboard {
+ display:none;
+}
+
+.clipboard-base .select-box__current {
+ display:inherit;
+}
+
+.shortcuts-admin{
+display:none;
+}
diff --git a/cmsplugin_cascade/static/cascade/js/admin/clipboard_gallery.js b/cmsplugin_cascade/static/cascade/js/admin/clipboard_gallery.js
new file mode 100644
index 000000000..62684d180
--- /dev/null
+++ b/cmsplugin_cascade/static/cascade/js/admin/clipboard_gallery.js
@@ -0,0 +1,45 @@
+const div_select_box = document.querySelector(".select-box");
+const svg_stride = document.querySelectorAll(".cms-cascade-svg-viewer");
+const input_fullscreen = document.querySelectorAll(".clipboard-fullscreen");
+const xs = document.getElementById("js-cascade-btn-xs");
+const sm = document.getElementById("js-cascade-btn-sm");
+const md = document.getElementById("js-cascade-btn-md");
+const lg = document.getElementById("js-cascade-btn-lg");
+const xl = document.getElementById("js-cascade-btn-xl");
+const max = document.getElementById("js-cascade-btn-max");
+
+input_fullscreen.forEach(element => {
+ element.addEventListener('click', function(event) {
+ if ( div_select_box.classList.contains('clipboard-base')){
+ div_select_box.classList.remove('clipboard-base');
+ }
+ else {
+ div_select_box.classList.add('clipboard-base');
+ }
+ });
+});
+
+function parser_clipboards(elements, viewbox){
+ elements.forEach(element => {
+ element.setAttribute("viewBox", viewbox);
+ });
+}
+
+xs.addEventListener('click', function(event) {
+ parser_clipboards(svg_stride ,'0 0 475 500');
+ });
+sm.addEventListener('click', function(event) {
+ parser_clipboards(svg_stride ,'0 0 767 800');
+ });
+md.addEventListener('click', function(event) {
+ parser_clipboards(svg_stride ,'0 0 991 900');
+ });
+lg.addEventListener('click', function(event) {
+ parser_clipboards(svg_stride ,'0 0 1199 1400');
+ });
+xl.addEventListener('click', function(event) {
+ parser_clipboards(svg_stride ,'0 0 2000 4000');
+ });
+max.addEventListener('click', function(event) {
+ parser_clipboards(svg_stride ,'0 0 5000 10000');
+ });
diff --git a/cmsplugin_cascade/strides.py b/cmsplugin_cascade/strides.py
index e0c11991b..d6d9c6d70 100644
--- a/cmsplugin_cascade/strides.py
+++ b/cmsplugin_cascade/strides.py
@@ -11,7 +11,9 @@
from cmsplugin_cascade import app_settings
from cmsplugin_cascade.mixins import CascadePluginMixin
-
+from collections import defaultdict
+from sekizai.data import UniqueSequence
+from random import randint
__all__ = ['register_stride', 'StrideContentRenderer']
@@ -30,6 +32,8 @@ class StrideElementBase(object):
"""
def __init__(self, plugin, data, children_data, parent=None):
self.plugin = plugin
+ #pass args radom id
+ data['pk'] = randint(0, 20000)
self.id = data.get('pk')
self.glossary = data.get('glossary', {})
self.sortinline_elements = self.inline_elements = EmulateQuerySet(data.get('inlines', []))
@@ -151,6 +155,10 @@ def _get_render_template(self, context, instance, placeholder):
if not template:
raise TemplateDoesNotExist("plugin {} has no render_template".format(self.__class__))
+
+ if hasattr(instance.plugin, 'render_template_fallback'):
+ template = instance.plugin.render_template_fallback
+
return template
def in_edit_mode(self, request, placeholder):
@@ -205,7 +213,9 @@ def render_plugin(self, instance, context, placeholder=None, editable=False):
from sekizai.helpers import get_varname as get_sekizai_context_key
sekizai_context_key = get_sekizai_context_key()
+
if app_settings.CMSPLUGIN_CASCADE['cache_strides'] and getattr(instance.plugin, 'cache', not editable):
+
cache = caches['default']
key = 'cascade_element-{}'.format(instance.pk)
content = cache.get(key)
@@ -218,8 +228,9 @@ def render_plugin(self, instance, context, placeholder=None, editable=False):
context = instance.plugin.render(context, instance, placeholder)
context = flatten_context(context)
- template = instance.plugin._get_render_template(context, instance, placeholder)
+ template = instance.plugin._get_render_template( context, instance, placeholder)
template = self.get_cached_template(template)
+
content = template.render(context)
if context['cms_cachable_plugins'].value:
cache.set(key, content)
diff --git a/cmsplugin_cascade/templates/cascade/admin/clipboard_import.html b/cmsplugin_cascade/templates/cascade/admin/clipboard_import.html
new file mode 100644
index 000000000..04e8c7d9b
--- /dev/null
+++ b/cmsplugin_cascade/templates/cascade/admin/clipboard_import.html
@@ -0,0 +1,102 @@
+{% load static cms_tags cascade_tags sekizai_tags djng_tags i18n sass_tags %}
+
+
+