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 %} + + + + {% block title %}Djangocms-cascade Clipboard{% endblock %} + + + + + + {% block head %} + {% addtoblock "css" %}{% endaddtoblock %} + {% addtoblock "css" %} {% endaddtoblock %} + +{% addtoblock "js" %}{% endaddtoblock %} +{% addtoblock "js" %}{% endaddtoblock %} +{% addtoblock "js" %}{% endaddtoblock %} +{% addtoblock "js" %}{% endaddtoblock %} +{% addtoblock "js" %}{% endaddtoblock %} + {% add_data "ng-requires" "ui.bootstrap" %} + + + + + {% endblock head %} + {% render_block "css" %} + + + + + +

+ {% block header %}{% endblock %} +
+ {% block toast-messages %}{% include "shop/messages.html" %}{% endblock %} + + +{% spaceless %} + +{% include "cascade/admin/widgets/clipboard_menu_group.html" %} + + +
+ {% csrf_token %} + + +{% with id=widget.attrs.id %} + + + +{% endwith %} +
+ + +
+
+ +{% endspaceless %} +{# to do logic for jquery #} +{# #} +{% addtoblock "js" %} {% endaddtoblock %} + +{% render_block 'js' %} + + +{% block footer%} +{% endblock %} + diff --git a/cmsplugin_cascade/templates/cascade/admin/widgets/clipboard_menu_group.html b/cmsplugin_cascade/templates/cascade/admin/widgets/clipboard_menu_group.html new file mode 100644 index 000000000..3fe950d69 --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/admin/widgets/clipboard_menu_group.html @@ -0,0 +1,49 @@ +{% load static %} +
+
+ Clipboard Home +
+
+ + +
+ +
+ admin groups + admin clipboard + + + + + xs + + + + sm + + + + md + + + + lg + + + + xl + + + Max + + +
+
+
+ {% for group in groups_exclude_home %} + {{ group }} + {% endfor %} + {% if widget.attrs.count_target %} + Ungroup + {% endif %} +
diff --git a/cmsplugin_cascade/templates/cascade/admin/widgets/clipboard_stride.html b/cmsplugin_cascade/templates/cascade/admin/widgets/clipboard_stride.html new file mode 100644 index 000000000..25d1ce5af --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/admin/widgets/clipboard_stride.html @@ -0,0 +1,15 @@ +{% load i18n cms_admin cascade_tags %} +
+ + +{{ option.label }} +
+ + +
+ {% render_cascade data option.label %} +
+
+
+
+
diff --git a/cmsplugin_cascade/templates/cascade/bootstrap4/image.html b/cmsplugin_cascade/templates/cascade/bootstrap4/image.html index ab4e8c093..fb9492dfd 100644 --- a/cmsplugin_cascade/templates/cascade/bootstrap4/image.html +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/image.html @@ -1,15 +1,15 @@ -{% load l10n static cascade_tags thumbnail %} +{% load l10n static cascade_tags thumbnail %} {% localize off %}{% spaceless %}{% with css_classes=instance.css_classes inline_styles=instance.inline_styles %} - {% else %} - src="{% static 'cascade/fallback.svg' %}" +{% include "cascade/generic/fallback_image.html" %} {% endif %} -/> {% endwith %}{% endspaceless %}{% endlocalize %} diff --git a/cmsplugin_cascade/templates/cascade/bootstrap4/jumbotron.html b/cmsplugin_cascade/templates/cascade/bootstrap4/jumbotron.html index fdffc3a0d..db0997df1 100644 --- a/cmsplugin_cascade/templates/cascade/bootstrap4/jumbotron.html +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/jumbotron.html @@ -28,6 +28,10 @@ } } {% endfor %} +{% elif 'pk' in instance.glossary.image_file %} + #cascadeelement_id-{{ instance.pk }} { + {% fallback instance %} + } {% endif %} {% endlocalize %}{% endaddtoblock %} diff --git a/cmsplugin_cascade/templates/cascade/bootstrap4/picture.html b/cmsplugin_cascade/templates/cascade/bootstrap4/picture.html index 0db1a5cb7..a902e9e85 100644 --- a/cmsplugin_cascade/templates/cascade/bootstrap4/picture.html +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/picture.html @@ -2,6 +2,7 @@ {% localize off %}{% spaceless %} {% if instance.image|is_valid_image %} + {% for elem in elements %} {% thumbnail instance.image elem.size zoom=elem.zoom crop=elem.crop upscale=elem.upscale subject_location=elem.subject_location as thumb %} {% if elem.size2 %} @@ -14,7 +15,8 @@ {% endif %} {% endfor %} {% else %} - +
{# fallback instance #}
+ {% include "cascade/generic/fallback_picture.html" %} {% endif %} {% endspaceless %}{% endlocalize %} diff --git a/cmsplugin_cascade/templates/cascade/generic/fallback_image.html b/cmsplugin_cascade/templates/cascade/generic/fallback_image.html new file mode 100644 index 000000000..1351c52c0 --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/generic/fallback_image.html @@ -0,0 +1,28 @@ +{% load l10n static thumbnail cascade_tags %} +{% fallback_config 'image' as fallback_image %} +{% if instance.glossary.image_properties.width != src.size.0 or instance.glossary.image_properties.height != src.size.1 %} +{% if not 'img-fluid' in instance.glossary.image_shapes %} +
+ +
+{% else %} +
+ +
+{% endif %} +{% else %} +
+ +
+{% endif %} diff --git a/cmsplugin_cascade/templates/cascade/generic/fallback_jumbotron.html b/cmsplugin_cascade/templates/cascade/generic/fallback_jumbotron.html new file mode 100644 index 000000000..e19f04fd4 --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/generic/fallback_jumbotron.html @@ -0,0 +1,3 @@ +{% load l10n cascade_tags thumbnail sekizai_tags %} +{% render_block 'css' %}{% block css %}{% endblock %} +{% include 'cascade/bootstrap4/jumbotron.html' %} diff --git a/cmsplugin_cascade/templates/cascade/generic/fallback_picture.html b/cmsplugin_cascade/templates/cascade/generic/fallback_picture.html new file mode 100644 index 000000000..0cb0de245 --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/generic/fallback_picture.html @@ -0,0 +1,30 @@ +{% load l10n thumbnail cascade_tags %} +{% fallback_config 'picture' as fallback_picture %} + +
+ +
diff --git a/cmsplugin_cascade/templatetags/cascade_tags.py b/cmsplugin_cascade/templatetags/cascade_tags.py index b42839f80..d98d85d2d 100644 --- a/cmsplugin_cascade/templatetags/cascade_tags.py +++ b/cmsplugin_cascade/templatetags/cascade_tags.py @@ -1,6 +1,7 @@ import io import json import os +from ast import literal_eval from cms.toolbar.utils import get_toolbar_from_request from django import template from django.conf import settings @@ -11,6 +12,8 @@ from classytags.arguments import Argument from classytags.core import Options, Tag from cmsplugin_cascade.strides import StrideContentRenderer +from django.templatetags.static import static +from sekizai.data import UniqueSequence register = template.Library() @@ -21,30 +24,47 @@ class StrideRenderer(Tag): {% render_cascade "cascade-data.json" %} Keyword arguments: - datafile -- Filename containing the cascade tree. Must be file locatable by Django's + data_clipboard -- Filename containing the cascade tree. Must be file locatable by Django's static file finders. + identifier --- Identifier persitent clipboard db. """ name = 'render_cascade' options = Options( - Argument('datafile'), + Argument('data_clipboard'), + Argument('identifier', required=False), ) - def render_tag(self, context, datafile): + def render_tag(self, context, data_clipboard, identifier=None): from sekizai.helpers import get_varname as get_sekizai_context_key from cmsplugin_cascade.strides import StrideContentRenderer + if isinstance(data_clipboard, dict): + # qs_clipboards + identifier = identifier + datafile = False + elif isinstance(data_clipboard, str): + # relative path + datafile = data_clipboard + identifier = datafile + + tree_data_key = 'cascade-strides:' + identifier cache = caches['default'] - tree_data_key = 'cascade-strides:' + datafile tree_data = cache.get(tree_data_key) if cache else None - if tree_data is None: - jsonfile = finders.find(datafile) - if not jsonfile: - raise IOError("Unable to find file: {}".format(datafile)) - - with io.open(jsonfile) as fp: - tree_data = json.load(fp) - content_renderer = StrideContentRenderer(context['request']) + if tree_data is None: + if datafile : + jsonfile = finders.find(datafile) + if not jsonfile: + raise IOError("Unable to find file: {}".format(datafile)) + with io.open(jsonfile) as fp: + tree_data = json.load(fp) + else: + tree_data = data_clipboard + if settings.CMSPLUGIN_CASCADE.get('fallback', None ).get('img_or_pic_lost_pk', None): + tree_data_lost_ref_img = str(tree_data).replace("'image_file': {'model': 'filer.image', 'pk': ", "'image_file': {'model': 'filer.image', 'pk': 10000") + tree_data= literal_eval(tree_data_lost_ref_img) + request = context['request'] + content_renderer = StrideContentRenderer(request) with context.push(cms_content_renderer=content_renderer): content = content_renderer.render_cascade(context, tree_data) @@ -52,7 +72,12 @@ def render_tag(self, context, datafile): cache = caches['default'] if cache: sekizai_context_key = get_sekizai_context_key() - SEKIZAI_CONTENT_HOLDER = cache.get_or_set(sekizai_context_key, context.get(sekizai_context_key)) + if context.get(sekizai_context_key): + def context_sekizai(): + return context.get(sekizai_context_key) + else: + context_sekizai = UniqueSequence + SEKIZAI_CONTENT_HOLDER = cache.get_or_set(sekizai_context_key, context_sekizai) if SEKIZAI_CONTENT_HOLDER: for name in SEKIZAI_CONTENT_HOLDER: context[sekizai_context_key][name] = SEKIZAI_CONTENT_HOLDER[name] @@ -74,9 +99,6 @@ class RenderPlugin(Tag): def render_tag(self, context, plugin): if not plugin: return '' - - request = context['request'] - toolbar = get_toolbar_from_request(request) if 'cms_content_renderer' in context and isinstance(context['cms_content_renderer'], StrideContentRenderer): content_renderer = context['cms_content_renderer'] elif 'cms_renderer' in context: @@ -84,17 +106,102 @@ def render_tag(self, context, plugin): elif 'cms_content_renderer' in context: content_renderer = context['cms_content_renderer'] else: + request = context['request'] + toolbar = get_toolbar_from_request(request) content_renderer = toolbar.content_renderer + try: + toolbar_edit_mode = toolbar.edit_mode_active + except UnboundLocalError: + toolbar_edit_mode = True content = content_renderer.render_plugin( instance=plugin, context=context, - editable=toolbar.edit_mode_active, + editable=toolbar_edit_mode ) return content register.tag('render_plugin', RenderPlugin) +class FallBack(Tag): + name = 'fallback' + + options = Options( + Argument('plugin',required=False) + ) + + def render_tag(self, context, plugin): + for context_ in context: + if 'instance'in context_ : + glossary = context_['instance'].glossary + instance = context_['instance'] + fallback_plugin_type = plugin.plugin_class.__name__ + css_classes = glossary.get('css_classes','') + width = 0; height = 0; exif_orientation = 0; x = 0; y = 0; + inline_styles = glossary.get('inline_styles','') + html_tag_attributes = glossary.get('html_tag_attributes','') + + if 'image' in glossary: + image_fallback = 'image' + img = settings.CMSPLUGIN_CASCADE["fallback"]["image"]['svg'] + color = settings.CMSPLUGIN_CASCADE["fallback"]["image"]['color'] + static_fallback_svg = static(img) + elif fallback_plugin_type == 'BootstrapJumbotronPlugin': + image_fallback=None + img = settings.CMSPLUGIN_CASCADE["fallback"]["jumbotron"]['svg'] + color = settings.CMSPLUGIN_CASCADE["fallback"]["jumbotron"]['color'] + static_fallback_svg = static(img) + elif 'image_properties' in glossary: + image_fallback='image_properties' + img = settings.CMSPLUGIN_CASCADE["fallback"]["picture"]['svg'] + color = settings.CMSPLUGIN_CASCADE["fallback"]["picture"]['color'] + static_fallback_svg = static(img) + else: + image_fallback=None + img = settings.CMSPLUGIN_CASCADE["fallback"]["picture"]['svg'] + color = settings.CMSPLUGIN_CASCADE["fallback"]["picture"]['color'] + static_fallback_svg = static(img) + if image_fallback : + width = glossary[image_fallback].get('width',0) + height = glossary[image_fallback].get('height',0) + exif_orientation = glossary[image_fallback].get('exif_orientation',0) + + x = 50 + y = 50 + + if fallback_plugin_type == 'BootstrapJumbotronPlugin': + style=''' + background: url({static_fallback_svg}); + background-size: auto; + background-position-y: 20%; + background-size: 50%; + background-repeat: no-repeat; + background-position-x: 50%; + background-attachment: fixed; + background-color: {color}; + border: white solid; + '''.format( color=color, static_fallback_svg=static_fallback_svg) + + return style + else: + svg=' \ + \ + '.format( + width=width, + height=height, + color=color, + css_classes=css_classes, + inline_styles=inline_styles, + html_tag_attributes=html_tag_attributes, + static_fallback_svg=static_fallback_svg, + x=x, + y=y) + return svg + +register.tag('fallback', FallBack) + @register.filter def is_valid_image(image): try: @@ -110,3 +217,13 @@ def sphinx_docs_include(path): raise TemplateDoesNotExist("'{path}' does not exist".format(path=path)) with io.open(filename) as fh: return mark_safe(fh.read()) + + +@register.simple_tag +def cascadeclipboard_data_by_identifier(queryset, identifier ): + qs_identifier=queryset.filter(identifier=identifier) + return qs_identifier[0].data + +@register.simple_tag +def fallback_config(type_fallback): + return settings.CMSPLUGIN_CASCADE["fallback"][type_fallback] diff --git a/cmsplugin_cascade/utils.py b/cmsplugin_cascade/utils.py index 5fd348c0b..f36d3b543 100644 --- a/cmsplugin_cascade/utils.py +++ b/cmsplugin_cascade/utils.py @@ -50,11 +50,19 @@ def compute_aspect_ratio(image): def compute_aspect_ratio_with_glossary(glossary): - if glossary['image']['exif_orientation'] > 4: - # image is rotated by 90 degrees, while keeping width and height - return float(glossary['image']['width']) / float(glossary['image']['height']) - else: - return float(glossary['image']['height']) / float(glossary['image']['width']) + if 'image' in glossary : + if glossary['image']['exif_orientation'] > 4: + # image is rotated by 90 degrees, while keeping width and height + return float(glossary['image']['width']) / float(glossary['image']['height']) + else: + return float(glossary['image']['height']) / float(glossary['image']['width']) + # fallogic logic + elif 'image_properties' in glossary: + if glossary['image_properties']['exif_orientation'] > 4: + # image is rotated by 90 degrees, while keeping width and height + return float(glossary['image_properties']['width']) / float(glossary['image_properties']['height']) + else: + return float(glossary['image_properties']['height']) / float(glossary['image_properties']['width']) def get_image_size(width, image_height, aspect_ratio): diff --git a/examples/bs4demo/settings.py b/examples/bs4demo/settings.py index 30532ead0..5d01b13f4 100644 --- a/examples/bs4demo/settings.py +++ b/examples/bs4demo/settings.py @@ -62,6 +62,7 @@ 'sass_processor', 'sekizai', 'bs4demo', + 'django_extensions', ] MIDDLEWARE = [ @@ -217,7 +218,7 @@ 'plugins_with_sharables': { 'BootstrapImagePlugin': ('image_shapes', 'image_width_responsive', 'image_width_fixed', 'image_height', 'resize_options',), - 'BootstrapPicturePlugin': ('image_shapes', 'responsive_heights', 'image_size', 'resize_options',), + 'BootstrapPicturePlugin': ('image_shapes', 'responsive_heights','resize_options',), }, 'exclude_hiding_plugin': ('SegmentPlugin', 'Badge'), 'allow_plugin_hiding': True, diff --git a/examples/poetry.lock b/examples/poetry.lock new file mode 100644 index 000000000..52f905dde --- /dev/null +++ b/examples/poetry.lock @@ -0,0 +1,656 @@ +[[package]] +category = "dev" +description = "Python package for providing Mozilla's CA Bundle." +name = "certifi" +optional = false +python-versions = "*" +version = "2019.11.28" + +[[package]] +category = "dev" +description = "Universal encoding detector for Python 2 and 3" +name = "chardet" +optional = false +python-versions = "*" +version = "3.0.4" + +[[package]] +category = "dev" +description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." +name = "django" +optional = false +python-versions = ">=3.5" +version = "2.2.10" + +[package.dependencies] +pytz = "*" +sqlparse = "*" + +[package.extras] +argon2 = ["argon2-cffi (>=16.1.0)"] +bcrypt = ["bcrypt"] + +[[package]] +category = "dev" +description = "Generic drag-and-drop sorting for the List, the Stacked- and the Tabular-Inlines Views in the Django Admin" +name = "django-admin-sortable2" +optional = false +python-versions = "*" +version = "0.7.5" + +[package.dependencies] +Django = ">=1.8,<3.1" + +[[package]] +category = "dev" +description = "A helper class for handling configuration defaults of packaged apps gracefully." +name = "django-appconf" +optional = false +python-versions = "*" +version = "1.0.3" + +[package.dependencies] +django = "*" +six = "*" + +[[package]] +category = "dev" +description = "Class based template tags for Django" +name = "django-classy-tags" +optional = false +python-versions = "*" +version = "1.0.0" + +[package.dependencies] +django = ">=1.11" +six = "*" + +[[package]] +category = "dev" +description = "An Advanced Django CMS" +name = "django-cms" +optional = false +python-versions = "*" +version = "3.7.1" + +[package.dependencies] +Django = ">=1.11,<3.0" +django-classy-tags = ">=0.7.2" +django-formtools = ">=2.1" +django-sekizai = ">=0.7" +django-treebeard = ">=4.3" +djangocms-admin-style = ">=1.2" + +[[package]] +category = "dev" +description = "Compresses linked and inline JavaScript or CSS into single cached files." +name = "django-compressor" +optional = false +python-versions = "*" +version = "2.4" + +[package.dependencies] +django-appconf = ">=1.0.3" +rcssmin = "1.0.6" +rjsmin = "1.1.0" +six = ">=1.12.0" + +[[package]] +category = "dev" +description = "Dynamic global and instance settings for your django project" +name = "django-dynamic-preferences" +optional = false +python-versions = "*" +version = "1.8.1" + +[package.dependencies] +django = ">=1.11" +persisting-theory = ">=0.2.1" +six = "*" + +[[package]] +category = "dev" +description = "Edit JSON field using Django Model Form" +name = "django-entangled" +optional = false +python-versions = "*" +version = "0.3" + +[[package]] +category = "dev" +description = "A file management application for django that makes handling of files and images a breeze." +name = "django-filer" +optional = false +python-versions = "*" +version = "1.6.0" + +[package.dependencies] +Unidecode = ">=0.04,<1.1" +django = ">=1.11,<3.0" +django-mptt = ">=0.6,<1.0" +django_polymorphic = ">=0.7,<2.1" +easy-thumbnails = ">=2,<3.0" + +[[package]] +category = "dev" +description = "A set of high-level abstractions for Django forms" +name = "django-formtools" +optional = false +python-versions = "*" +version = "2.2" + +[package.dependencies] +Django = ">=1.11" + +[[package]] +category = "dev" +description = "script tag with additional attributes for django.forms.Media" +name = "django-js-asset" +optional = false +python-versions = "*" +version = "1.2.2" + +[[package]] +category = "dev" +description = "Utilities for implementing Modified Preorder Tree Traversal with your Django Models and working with trees of Model instances." +name = "django-mptt" +optional = false +python-versions = ">=3.5" +version = "0.11.0" + +[package.dependencies] +Django = ">=1.11" +django-js-asset = "*" + +[[package]] +category = "dev" +description = "Seamless polymorphic inheritance for Django models" +name = "django-polymorphic" +optional = false +python-versions = "*" +version = "2.0.3" + +[package.dependencies] +Django = ">=1.11" + +[[package]] +category = "dev" +description = "SASS processor to compile SCSS files into *.css, while rendering, or offline." +name = "django-sass-processor" +optional = false +python-versions = "*" +version = "0.8" + +[package.extras] +dev = ["libsass (>=0.13)"] +management-command = ["django-compressor (>=2.4)"] + +[[package]] +category = "dev" +description = "Django Sekizai" +name = "django-sekizai" +optional = false +python-versions = "*" +version = "1.1.0" + +[package.dependencies] +django = ">=1.11" +django-classy-tags = ">=0.9.0" +six = "*" + +[[package]] +category = "dev" +description = "Select2 option fields for Django" +name = "django-select2" +optional = false +python-versions = "*" +version = "7.2.0" + +[package.dependencies] +django = ">=2.2" +django-appconf = ">=0.6.0" + +[[package]] +category = "dev" +description = "Efficient tree implementations for Django" +name = "django-treebeard" +optional = false +python-versions = "*" +version = "4.3.1" + +[package.dependencies] +Django = ">=1.8" + +[[package]] +category = "dev" +description = "Adds pretty CSS styles for the django CMS admin interface." +name = "djangocms-admin-style" +optional = false +python-versions = "*" +version = "1.5.0" + +[[package]] +category = "dev" +description = "Templates and templatetags to be used with django-CMS with Bootstrap3 or Bootstrap4." +name = "djangocms-bootstrap" +optional = false +python-versions = "*" +version = "1.1.2" + +[package.dependencies] +django-cms = ">3.4" + +[[package]] +category = "dev" +description = "Build Single Page Applications using the Django-CMS plugin system" +name = "djangocms-cascade" +optional = false +python-versions = "*" +version = "1.2.3" + +[package.dependencies] +django = ">=1.11,<3.0" +django-classy-tags = ">=0.8" +django-cms = ">=3.5,<4" +django-entangled = "*" +djangocms-text-ckeditor = ">=3.7" +jsonfield = "*" +requests = "*" + +[[package]] +category = "dev" +description = "Adds undo/redo functionality to django CMS" +name = "djangocms-history" +optional = false +python-versions = "*" +version = "1.1.0" + +[package.dependencies] +django-cms = ">=3.4.5" + +[[package]] +category = "dev" +description = "Text Plugin for django CMS with CKEditor support" +name = "djangocms-text-ckeditor" +optional = false +python-versions = "*" +version = "3.8.0" + +[package.dependencies] +Pillow = "*" +django-cms = ">=3.4.5" +html5lib = ">=0.999999999" + +[[package]] +category = "dev" +description = "Adds import and export of plugin data." +name = "djangocms-transfer" +optional = false +python-versions = "*" +version = "0.2.0" + +[package.dependencies] +django-cms = ">=3.4.5" + +[[package]] +category = "dev" +description = "Easy thumbnails for Django" +name = "easy-thumbnails" +optional = false +python-versions = ">=3.5" +version = "2.7" + +[package.dependencies] +django = ">=1.11,<4.0" +pillow = "*" + +[[package]] +category = "dev" +description = "HTML parser based on the WHATWG HTML specification" +name = "html5lib" +optional = false +python-versions = "*" +version = "1.0.1" + +[package.dependencies] +six = ">=1.9" +webencodings = "*" + +[package.extras] +all = ["genshi", "chardet (>=2.2)", "datrie", "lxml"] +chardet = ["chardet (>=2.2)"] +datrie = ["datrie"] +genshi = ["genshi"] +lxml = ["lxml"] + +[[package]] +category = "dev" +description = "Internationalized Domain Names in Applications (IDNA)" +name = "idna" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.8" + +[[package]] +category = "dev" +description = "A reusable Django field that allows you to store validated JSON in your model." +name = "jsonfield" +optional = false +python-versions = "*" +version = "3.0.0" + +[package.dependencies] +Django = ">=2.2" + +[[package]] +category = "dev" +description = "Sass for Python: A straightforward binding of libsass for Python." +name = "libsass" +optional = false +python-versions = "*" +version = "0.19.4" + +[package.dependencies] +six = "*" + +[[package]] +category = "dev" +description = "Registries that can autodiscover values accross your project apps" +name = "persisting-theory" +optional = false +python-versions = "*" +version = "0.2.1" + +[[package]] +category = "dev" +description = "Python Imaging Library (Fork)" +name = "pillow" +optional = false +python-versions = ">=3.5" +version = "7.0.0" + +[[package]] +category = "dev" +description = "World timezone definitions, modern and historical" +name = "pytz" +optional = false +python-versions = "*" +version = "2019.3" + +[[package]] +category = "dev" +description = "CSS Minifier" +name = "rcssmin" +optional = false +python-versions = "*" +version = "1.0.6" + +[[package]] +category = "dev" +description = "Python HTTP for Humans." +name = "requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.22.0" + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<3.1.0" +idna = ">=2.5,<2.9" +urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" + +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] + +[[package]] +category = "dev" +description = "Javascript Minifier" +name = "rjsmin" +optional = false +python-versions = "*" +version = "1.1.0" + +[[package]] +category = "dev" +description = "Python 2 and 3 compatibility utilities" +name = "six" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +version = "1.14.0" + +[[package]] +category = "dev" +description = "Non-validating SQL parser" +name = "sqlparse" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.3.0" + +[[package]] +category = "dev" +description = "ASCII transliterations of Unicode text" +name = "unidecode" +optional = false +python-versions = "*" +version = "1.0.23" + +[[package]] +category = "dev" +description = "HTTP library with thread-safe connection pooling, file post, and more." +name = "urllib3" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +version = "1.25.8" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] + +[[package]] +category = "dev" +description = "Character encoding aliases for legacy web content" +name = "webencodings" +optional = false +python-versions = "*" +version = "0.5.1" + +[metadata] +content-hash = "2115171a195bcabffe11d3f5f9b97eb118bbc02c9c77c45ed3c7c68db1550ff6" +python-versions = "^3.6" + +[metadata.files] +certifi = [ + {file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"}, + {file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"}, +] +chardet = [ + {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, + {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, +] +django = [ + {file = "Django-2.2.10-py3-none-any.whl", hash = "sha256:9a4635813e2d498a3c01b10c701fe4a515d76dd290aaa792ccb65ca4ccb6b038"}, + {file = "Django-2.2.10.tar.gz", hash = "sha256:1226168be1b1c7efd0e66ee79b0e0b58b2caa7ed87717909cd8a57bb13a7079a"}, +] +django-admin-sortable2 = [ + {file = "django-admin-sortable2-0.7.5.tar.gz", hash = "sha256:1366cb159439aa28472639dc1f33d6370a56d4c3efbe3def854d953aaa6bbd24"}, +] +django-appconf = [ + {file = "django-appconf-1.0.3.tar.gz", hash = "sha256:35f13ca4d567f132b960e2cd4c832c2d03cb6543452d34e29b7ba10371ba80e3"}, + {file = "django_appconf-1.0.3-py2.py3-none-any.whl", hash = "sha256:c98a7af40062e996b921f5962a1c4f3f0c979fa7885f7be4710cceb90ebe13a6"}, +] +django-classy-tags = [ + {file = "django-classy-tags-1.0.0.tar.gz", hash = "sha256:ad6a25fc2b58a098f00d86bd5e5dad47922f5ca4e744bc3cccb7b4be5bc35eb1"}, +] +django-cms = [ + {file = "django-cms-3.7.1.tar.gz", hash = "sha256:05ea915d490562413428e04acb9ecae604c63b8dc5ed9250d0e9437b1314c996"}, + {file = "django_cms-3.7.1-py2.py3-none-any.whl", hash = "sha256:89d1e3bebd732a6bd00b0cfaf8128bfb0924429e83f1297e0b34dd9343e55b7a"}, +] +django-compressor = [ + {file = "django_compressor-2.4-py2.py3-none-any.whl", hash = "sha256:57ac0a696d061e5fc6fbc55381d2050f353b973fb97eee5593f39247bc0f30af"}, + {file = "django_compressor-2.4.tar.gz", hash = "sha256:d2ed1c6137ddaac5536233ec0a819e14009553fee0a869bea65d03e5285ba74f"}, +] +django-dynamic-preferences = [ + {file = "django-dynamic-preferences-1.8.1.tar.gz", hash = "sha256:727f71f865ff29df93c01a0a1686af0dca19776ee786f876ea0ae7e062719b24"}, + {file = "django_dynamic_preferences-1.8.1-py2.py3-none-any.whl", hash = "sha256:1d600c2f88baa9e0098e856f8a14e320a4a479305deea1afbc8722d953c44c65"}, +] +django-entangled = [ + {file = "django-entangled-0.3.tar.gz", hash = "sha256:e51cb1748f30d3dc474fd32d195f051307507a3dbbe789f0fed29b80ce2e6a35"}, +] +django-filer = [ + {file = "django-filer-1.6.0.tar.gz", hash = "sha256:3f2045cfd9e53c1a29cd8a71747e984faead630ee72baab29d6b3b45584d52e0"}, +] +django-formtools = [ + {file = "django-formtools-2.2.tar.gz", hash = "sha256:c5272c03c1cd51b2375abf7397a199a3148a9fbbf2f100e186467a84025d13b2"}, + {file = "django_formtools-2.2-py2.py3-none-any.whl", hash = "sha256:304fa777b8ef9e0693ce7833f885cb89ba46b0e46fc23b01176900a93f46742f"}, +] +django-js-asset = [ + {file = "django-js-asset-1.2.2.tar.gz", hash = "sha256:c163ae80d2e0b22d8fb598047cd0dcef31f81830e127cfecae278ad574167260"}, + {file = "django_js_asset-1.2.2-py2.py3-none-any.whl", hash = "sha256:8ec12017f26eec524cab436c64ae73033368a372970af4cf42d9354fcb166bdd"}, +] +django-mptt = [ + {file = "django-mptt-0.11.0.tar.gz", hash = "sha256:dfdb3af75ad27cdd4458b0544ec8574174f2b90f99bc2cafab6a15b4bc1895a8"}, + {file = "django_mptt-0.11.0-py2.py3-none-any.whl", hash = "sha256:90eb236eb4f1a92124bd7c37852bbe09c0d21158477cc237556d59842a91c509"}, +] +django-polymorphic = [ + {file = "django-polymorphic-2.0.3.tar.gz", hash = "sha256:1fb5505537bcaf71cfc951ff94c4e3ba83c761eaca04b7b2ce9cb63937634ea5"}, + {file = "django_polymorphic-2.0.3-py2.py3-none-any.whl", hash = "sha256:79e7df455fdc8c3d28d38b7ab8323fc21d109a162b8ca282119e0e9ce8db7bdb"}, +] +django-sass-processor = [ + {file = "django-sass-processor-0.8.tar.gz", hash = "sha256:e039551994feaaba6fcf880412b25a772dd313162a34cbb4289814988cfae340"}, +] +django-sekizai = [ + {file = "django-sekizai-1.1.0.tar.gz", hash = "sha256:e2f6e666d4dd9d3ecc27284acb85ef709e198014f5d5af8c6d54ed04c2d684d9"}, +] +django-select2 = [ + {file = "django-select2-7.2.0.tar.gz", hash = "sha256:4c531cb7e9eb4152c7e5f8ab83be386f46978b3d80e91e55ad1fb46382222a0b"}, + {file = "django_select2-7.2.0-py2.py3-none-any.whl", hash = "sha256:d17bb0e64503a7e52ba405f73a187664906cefda5f1c33971c67ab0b3891e91c"}, +] +django-treebeard = [ + {file = "django-treebeard-4.3.1.tar.gz", hash = "sha256:83aebc34a9f06de7daaec330d858d1c47887e81be3da77e3541fe7368196dd8a"}, +] +djangocms-admin-style = [ + {file = "djangocms-admin-style-1.5.0.tar.gz", hash = "sha256:47d9868aa448e593ce90059d6542a4d907c120d357cd0561c586f5557e3ba316"}, +] +djangocms-bootstrap = [ + {file = "djangocms-bootstrap-1.1.2.tar.gz", hash = "sha256:60e0802111df17298933104fdc73fc7bb74924e435d5c025b3a12a29a10e6761"}, +] +djangocms-cascade = [ + {file = "djangocms-cascade-1.2.3.tar.gz", hash = "sha256:5fb0e6519d36577469bbbd6645c41220db0abade147fe6e0dc530c02b845755a"}, +] +djangocms-history = [ + {file = "djangocms-history-1.1.0.tar.gz", hash = "sha256:783c0aea25671e14df634838660e53023d6559c66697c9d8d8cfa771842e9ff2"}, +] +djangocms-text-ckeditor = [ + {file = "djangocms-text-ckeditor-3.8.0.tar.gz", hash = "sha256:0f0291cdf305c469741a639d89c71ee77f29dfc5aada4f7a453d6dc2926ceca9"}, +] +djangocms-transfer = [ + {file = "djangocms-transfer-0.2.0.tar.gz", hash = "sha256:9e7d251a82fa13beb6bf541a157a63b8306a8ae29a90349c783f0077e439f325"}, +] +easy-thumbnails = [ + {file = "easy-thumbnails-2.7.tar.gz", hash = "sha256:e4e7a0dd4001f56bfd4058428f2c91eafe27d33ef3b8b33ac4e013b159b9ff91"}, +] +html5lib = [ + {file = "html5lib-1.0.1-py2.py3-none-any.whl", hash = "sha256:20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3"}, + {file = "html5lib-1.0.1.tar.gz", hash = "sha256:66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736"}, +] +idna = [ + {file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"}, + {file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"}, +] +jsonfield = [ + {file = "jsonfield-3.0.0-py3-none-any.whl", hash = "sha256:a6616f38db42542d1f2434f5eae1a91550e5cc15dff27edd87b4358493ef2a92"}, + {file = "jsonfield-3.0.0.tar.gz", hash = "sha256:1a17efe9a26850e9175900cb37cf30c6182fc2251989f8af44ef47fbbf48eaff"}, +] +libsass = [ + {file = "libsass-0.19.4-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:74acd9adf506142699dfa292f0e569fdccbd9e7cf619e8226f7117de73566e32"}, + {file = "libsass-0.19.4-cp27-cp27m-win32.whl", hash = "sha256:50778d4be269a021ba2bf42b5b8f6ff3704ab96a82175a052680bddf3ba7cc9f"}, + {file = "libsass-0.19.4-cp27-cp27m-win_amd64.whl", hash = "sha256:4dcfd561fb100250b89496e1362b96f2cc804f689a59731eb0f94f9a9e144f4a"}, + {file = "libsass-0.19.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:845a9573b25c141164972d498855f4ad29367c09e6d76fad12955ad0e1c83013"}, + {file = "libsass-0.19.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:81a013a4c2a614927fd1ef7a386eddabbba695cbb02defe8f31cf495106e974c"}, + {file = "libsass-0.19.4-cp35-cp35m-win32.whl", hash = "sha256:fcb7ab4dc81889e5fc99cafbc2017bc76996f9992fc6b175f7a80edac61d71df"}, + {file = "libsass-0.19.4-cp35-cp35m-win_amd64.whl", hash = "sha256:fc5f8336750f76f1bfae82f7e9e89ae71438d26fc4597e3ab4c05ca8fcd41d8a"}, + {file = "libsass-0.19.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:9b59afa0d755089c4165516400a39a289b796b5612eeef5736ab7a1ebf96a67c"}, + {file = "libsass-0.19.4-cp36-cp36m-win32.whl", hash = "sha256:c93df526eeef90b1ea4799c1d33b6cd5aea3e9f4633738fb95c1287c13e6b404"}, + {file = "libsass-0.19.4-cp36-cp36m-win_amd64.whl", hash = "sha256:0fd8b4337b3b101c6e6afda9112cc0dc4bacb9133b59d75d65968c7317aa3272"}, + {file = "libsass-0.19.4-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:003a65b4facb4c5dbace53fb0f70f61c5aae056a04b4d112a198c3c9674b31f2"}, + {file = "libsass-0.19.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:338e9ae066bf1fde874e335324d5355c52d2081d978b4f74fc59536564b35b08"}, + {file = "libsass-0.19.4-cp37-cp37m-win32.whl", hash = "sha256:e318f06f06847ff49b1f8d086ac9ebce1e63404f7ea329adab92f4f16ba0e00e"}, + {file = "libsass-0.19.4-cp37-cp37m-win_amd64.whl", hash = "sha256:a7e685466448c9b1bf98243339793978f654a1151eb5c975f09b83c7a226f4c1"}, + {file = "libsass-0.19.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6a51393d75f6e3c812785b0fa0b7d67c54258c28011921f204643b55f7355ec0"}, + {file = "libsass-0.19.4.tar.gz", hash = "sha256:8b5b6d1a7c4ea1d954e0982b04474cc076286493f6af2d0a13c2e950fbe0be95"}, +] +persisting-theory = [ + {file = "persisting-theory-0.2.1.tar.gz", hash = "sha256:00ff7dcc8f481ff75c770ca5797d968e8725b6df1f77fe0cf7d20fa1e5790c0a"}, +] +pillow = [ + {file = "Pillow-7.0.0-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:5f3546ceb08089cedb9e8ff7e3f6a7042bb5b37c2a95d392fb027c3e53a2da00"}, + {file = "Pillow-7.0.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:9d2ba4ed13af381233e2d810ff3bab84ef9f18430a9b336ab69eaf3cd24299ff"}, + {file = "Pillow-7.0.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:ff3797f2f16bf9d17d53257612da84dd0758db33935777149b3334c01ff68865"}, + {file = "Pillow-7.0.0-cp35-cp35m-win32.whl", hash = "sha256:c18f70dc27cc5d236f10e7834236aff60aadc71346a5bc1f4f83a4b3abee6386"}, + {file = "Pillow-7.0.0-cp35-cp35m-win_amd64.whl", hash = "sha256:875358310ed7abd5320f21dd97351d62de4929b0426cdb1eaa904b64ac36b435"}, + {file = "Pillow-7.0.0-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:ab76e5580b0ed647a8d8d2d2daee170e8e9f8aad225ede314f684e297e3643c2"}, + {file = "Pillow-7.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a62ec5e13e227399be73303ff301f2865bf68657d15ea50b038d25fc41097317"}, + {file = "Pillow-7.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8ac6ce7ff3892e5deaab7abaec763538ffd011f74dc1801d93d3c5fc541feee2"}, + {file = "Pillow-7.0.0-cp36-cp36m-win32.whl", hash = "sha256:91b710e3353aea6fc758cdb7136d9bbdcb26b53cefe43e2cba953ac3ee1d3313"}, + {file = "Pillow-7.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:bf598d2e37cf8edb1a2f26ed3fb255191f5232badea4003c16301cb94ac5bdd0"}, + {file = "Pillow-7.0.0-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:5bfef0b1cdde9f33881c913af14e43db69815c7e8df429ceda4c70a5e529210f"}, + {file = "Pillow-7.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:dc058b7833184970d1248135b8b0ab702e6daa833be14035179f2acb78ff5636"}, + {file = "Pillow-7.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c5ed816632204a2fc9486d784d8e0d0ae754347aba99c811458d69fcdfd2a2f9"}, + {file = "Pillow-7.0.0-cp37-cp37m-win32.whl", hash = "sha256:54ebae163e8412aff0b9df1e88adab65788f5f5b58e625dc5c7f51eaf14a6837"}, + {file = "Pillow-7.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:87269cc6ce1e3dee11f23fa515e4249ae678dbbe2704598a51cee76c52e19cda"}, + {file = "Pillow-7.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0a628977ac2e01ca96aaae247ec2bd38e729631ddf2221b4b715446fd45505be"}, + {file = "Pillow-7.0.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:62a889aeb0a79e50ecf5af272e9e3c164148f4bd9636cc6bcfa182a52c8b0533"}, + {file = "Pillow-7.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bf4003aa538af3f4205c5fac56eacaa67a6dd81e454ffd9e9f055fff9f1bc614"}, + {file = "Pillow-7.0.0-cp38-cp38-win32.whl", hash = "sha256:7406f5a9b2fd966e79e6abdaf700585a4522e98d6559ce37fc52e5c955fade0a"}, + {file = "Pillow-7.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:5f7ae9126d16194f114435ebb79cc536b5682002a4fa57fa7bb2cbcde65f2f4d"}, + {file = "Pillow-7.0.0-pp373-pypy36_pp73-win32.whl", hash = "sha256:8453f914f4e5a3d828281a6628cf517832abfa13ff50679a4848926dac7c0358"}, + {file = "Pillow-7.0.0.tar.gz", hash = "sha256:4d9ed9a64095e031435af120d3c910148067087541131e82b3e8db302f4c8946"}, +] +pytz = [ + {file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"}, + {file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"}, +] +rcssmin = [ + {file = "rcssmin-1.0.6.tar.gz", hash = "sha256:ca87b695d3d7864157773a61263e5abb96006e9ff0e021eff90cbe0e1ba18270"}, +] +requests = [ + {file = "requests-2.22.0-py2.py3-none-any.whl", hash = "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"}, + {file = "requests-2.22.0.tar.gz", hash = "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4"}, +] +rjsmin = [ + {file = "rjsmin-1.1.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:799890bd07a048892d8d3deb9042dbc20b7f5d0eb7da91e9483c561033b23ce2"}, + {file = "rjsmin-1.1.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:211c2fe8298951663bbc02acdffbf714f6793df54bfc50e1c6c9e71b3f2559a3"}, + {file = "rjsmin-1.1.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:585e75a84d9199b68056fd4a083d9a61e2a92dfd10ff6d4ce5bdb04bc3bdbfaf"}, + {file = "rjsmin-1.1.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e487a7783ac4339e79ec610b98228eb9ac72178973e3dee16eba0e3feef25924"}, + {file = "rjsmin-1.1.0-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:0ab825839125eaca57cc59581d72e596e58a7a56fbc0839996b7528f0343a0a8"}, + {file = "rjsmin-1.1.0-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:6044ca86e917cd5bb2f95e6679a4192cef812122f28ee08c677513de019629b3"}, + {file = "rjsmin-1.1.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:ecd29f1b3e66a4c0753105baec262b331bcbceefc22fbe6f7e8bcd2067bcb4d7"}, + {file = "rjsmin-1.1.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:975b69754d6a76be47c0bead12367a1ca9220d08e5393f80bab0230d4625d1f4"}, + {file = "rjsmin-1.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:466fe70cc5647c7c51b3260c7e2e323a98b2b173564247f9c89e977720a0645f"}, + {file = "rjsmin-1.1.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:e3908b21ebb584ce74a6ac233bdb5f29485752c9d3be5e50c5484ed74169232c"}, + {file = "rjsmin-1.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:714329db774a90947e0e2086cdddb80d5e8c4ac1c70c9f92436378dedb8ae345"}, + {file = "rjsmin-1.1.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:dd0f4819df4243ffe4c964995794c79ca43943b5b756de84be92b445a652fb86"}, + {file = "rjsmin-1.1.0.tar.gz", hash = "sha256:b15dc75c71f65d9493a8c7fa233fdcec823e3f1b88ad84a843ffef49b338ac32"}, +] +six = [ + {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, + {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, +] +sqlparse = [ + {file = "sqlparse-0.3.0-py2.py3-none-any.whl", hash = "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177"}, + {file = "sqlparse-0.3.0.tar.gz", hash = "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873"}, +] +unidecode = [ + {file = "Unidecode-1.0.23-py2.py3-none-any.whl", hash = "sha256:092cdf7ad9d1052c50313426a625b717dab52f7ac58f859e09ea020953b1ad8f"}, + {file = "Unidecode-1.0.23.tar.gz", hash = "sha256:8b85354be8fd0c0e10adbf0675f6dc2310e56fda43fa8fe049123b6c475e52fb"}, +] +urllib3 = [ + {file = "urllib3-1.25.8-py2.py3-none-any.whl", hash = "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc"}, + {file = "urllib3-1.25.8.tar.gz", hash = "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"}, +] +webencodings = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] diff --git a/tests/requirements.txt b/tests/requirements.txt index 0504e95cd..8bec45c9a 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -7,4 +7,4 @@ pytest-django tox django-reversion factory-boy -pytest-factoryboy +pytest-factoryboy \ No newline at end of file