class FramedIconPlugin(IconPluginMixin, CascadePluginBase): name = _("Icon") parent_classes = None require_parent = False allow_children = False render_template = 'cascade/plugins/icon.html' model_mixins = (IconModelMixin, ) ring_plugin = 'FramedIconPlugin' SIZE_CHOICES = [('{}em'.format(c), "{} em".format(c)) for c in range(1, 13)] RADIUS_CHOICES = [(None, _("Square"))] + \ [('{}px'.format(r), "{} px".format(r)) for r in (1, 2, 3, 5, 7, 10, 15, 20)] + \ [('50%', _("Circle"))] icon_font = GlossaryField( widgets.Select(), label=_("Font"), ) symbol = GlossaryField( widgets.HiddenInput(), label=_("Select Symbol"), ) font_size = GlossaryField( CascadingSizeWidget(allowed_units=['px', 'em']), label=_("Icon size"), initial='1em', ) color = GlossaryField( widgets.TextInput(attrs={ 'style': 'width: 5em;', 'type': 'color' }), label=_("Icon color"), ) background_color = GlossaryField( ColorPickerWidget(), label=_("Background color"), ) text_align = GlossaryField( widgets.RadioSelect(choices=(('', _("Do not align")), ('text-left', _("Left")), ('text-center', _("Center")), ('text-right', _("Right")))), label=_("Text alignment"), initial='', help_text=_("Align the icon inside the parent column.")) border = GlossaryField( SetBorderWidget(), label=_("Set border"), ) border_radius = GlossaryField( widgets.Select(choices=RADIUS_CHOICES), label=_("Border radius"), ) glossary_field_order = ('icon_font', 'symbol', 'text_align', 'font_size', 'color', 'background_color', 'border', 'border_radius') class Media: js = ['cascade/js/admin/framediconplugin.js'] @classmethod def get_tag_type(self, instance): if instance.glossary.get('text_align'): return 'div' @classmethod def get_css_classes(cls, instance): css_classes = super(FramedIconPlugin, cls).get_css_classes(instance) text_align = instance.glossary.get('text_align') if text_align: css_classes.append(text_align) return css_classes @classmethod def get_inline_styles(cls, instance): inline_styles = super(FramedIconPlugin, cls).get_inline_styles(instance) inline_styles['font-size'] = instance.glossary.get('font_size', '1em') return inline_styles
class BootstrapJumbotronPlugin(BootstrapPluginBase): name = _("Jumbotron") model_mixins = (ImagePropertyMixin, ImageBackgroundMixin) form = JumbotronPluginForm default_css_class = 'jumbotron' require_parent = False parent_classes = ('BootstrapColumnPlugin',) allow_children = True alien_child_classes = True raw_id_fields = ['image_file'] fields = ('glossary', 'image_file',) render_template = 'cascade/bootstrap3/jumbotron.html' ring_plugin = 'JumbotronPlugin' ATTACHMENT_CHOICES = ('scroll', 'fixed', 'local') VERTICAL_POSITION_CHOICES = ('top', '10%', '20%', '30%', '40%', 'center', '60%', '70%', '80%', '90%', 'bottom') HORIZONTAL_POSITION_CHOICES = ('left', '10%', '20%', '30%', '40%', 'center', '60%', '70%', '80%', '90%', 'right') REPEAT_CHOICES = ('repeat', 'repeat-x', 'repeat-y', 'no-repeat') SIZE_CHOICES = ('auto', 'width/height', 'cover', 'contain') container_glossary_fields = ( GlossaryField( ContainerBreakpointsWidget(choices=get_widget_choices()), label=_("Available Breakpoints"), name='breakpoints', initial=list(BS3_BREAKPOINT_KEYS)[::-1], help_text=_("Supported display widths for Bootstrap's grid system.") ), GlossaryField( MultipleCascadingSizeWidget(BS3_BREAKPOINT_KEYS, allowed_units=['px', '%'], required=False), label=_("Adapt Picture Heights"), name='container_max_heights', initial={'xs': '100%', 'sm': '100%', 'md': '100%', 'lg': '100%'}, help_text=_("Heights of picture in percent or pixels for distinct Bootstrap's breakpoints.") ), GlossaryField( widgets.CheckboxSelectMultiple(choices=BootstrapPicturePlugin.RESIZE_OPTIONS), label=_("Resize Options"), name='resize_options', initial=['crop', 'subject_location', 'high_resolution'], help_text=_("Options to use when resizing the image.") ), ) background_color = GlossaryField( ColorPickerWidget(), label=_("Background color"), ) background_repeat = GlossaryField( widgets.RadioSelect(choices=[(c, c) for c in REPEAT_CHOICES]), initial='no-repeat', label=_("This property specifies how an image repeates."), ) background_attachment = GlossaryField( widgets.RadioSelect(choices=[(c, c) for c in ATTACHMENT_CHOICES]), initial='local', label=_("This property specifies how to move the background relative to the viewport."), ) background_vertical_position = GlossaryField( widgets.Select(choices=[(c, c) for c in VERTICAL_POSITION_CHOICES]), initial='center', label=_("This property moves a background image vertically within its container."), ) background_horizontal_position = GlossaryField( widgets.Select(choices=[(c, c) for c in HORIZONTAL_POSITION_CHOICES]), initial='center', label=_("This property moves a background image horizontally within its container."), ) background_size = GlossaryField( widgets.RadioSelect(choices=[(c, c) for c in SIZE_CHOICES]), initial='auto', label=_("Background size"), help_text=_("This property specifies how an image is sized."), ) background_width_height = GlossaryField( MultipleCascadingSizeWidget(['width', 'height'], allowed_units=['px', '%'], required=False), label=_("Background width and height"), help_text=_("This property specifies the width and height of a background image."), ) footnote_html = """ <p>For more information about the Jumbotron please read </p> """ class Media: js = ['cascade/js/admin/jumbotronplugin.js'] def get_form(self, request, obj=None, **kwargs): if self.get_parent_instance(request, obj) is None: # we only ask for breakpoints, if the jumbotron is the root of the placeholder kwargs.update(glossary_fields=list(self.container_glossary_fields)) kwargs['glossary_fields'].extend(self.glossary_fields) form = super(BootstrapJumbotronPlugin, self).get_form(request, obj, **kwargs) return form def render(self, context, instance, placeholder): # image shall be rendered in a responsive context using the ``<picture>`` element elements = get_picture_elements(context, instance) context.update({ 'elements': [e for e in elements if 'media' in e] if elements else [], 'CSS_PREFIXES': app_settings.CSS_PREFIXES, }) return self.super(BootstrapJumbotronPlugin, self).render(context, instance, placeholder) @classmethod def sanitize_model(cls, obj): # if the jumbotron is the root of the placeholder, we consider it as "fluid" obj.glossary['fluid'] = obj.parent is None sanitized = super(BootstrapJumbotronPlugin, cls).sanitize_model(obj) compute_media_queries(obj) return sanitized @classmethod def get_identifier(cls, obj): identifier = super(BootstrapJumbotronPlugin, cls).get_identifier(obj) try: content = obj.image.name or obj.image.original_filename except AttributeError: content = _("Without background image") return format_html('{0}{1}', identifier, content)
class FramedIconPlugin(IconPluginMixin, LinkPluginBase): name = _("Icon with frame") parent_classes = None require_parent = False allow_children = False render_template = 'cascade/plugins/framedicon.html' model_mixins = (LinkElementMixin,) ring_plugin = 'FramedIconPlugin' fields = list(LinkPluginBase.fields) SIZE_CHOICES = [('{}em'.format(c), "{} em".format(c)) for c in range(1, 13)] RADIUS_CHOICES = [(None, _("Square"))] + \ [('{}px'.format(r), "{} px".format(r)) for r in (1, 2, 3, 5, 7, 10, 15, 20)] + \ [('50%', _("Circle"))] icon_font = GlossaryField( widgets.Select(), label=_("Font"), initial=get_default_icon_font, ) symbol = GlossaryField( widgets.HiddenInput(), label=_("Select Symbol"), ) font_size = GlossaryField( CascadingSizeWidget(allowed_units=['px', 'em']), label=_("Icon size"), initial='1em', ) color = GlossaryField( ColorPickerWidget(), label=_("Icon color"), ) background_color = GlossaryField( ColorPickerWidget(), label=_("Background color"), ) text_align = GlossaryField( widgets.RadioSelect(choices=[ ('', _("Do not align")), ('text-left', _("Left")), ('text-center', _("Center")), ('text-right', _("Right")) ]), label=_("Text alignment"), initial='', help_text=_("Align the icon inside the parent column.") ) border = GlossaryField( SetBorderWidget(), label=_("Set border"), ) border_radius = GlossaryField( widgets.Select(choices=RADIUS_CHOICES), label=_("Border radius"), ) glossary_field_order = ['icon_font', 'symbol', 'text_align', 'font_size', 'color', 'background_color', 'border', 'border_radius'] class Media: js = ['cascade/js/admin/framediconplugin.js'] @classmethod def get_tag_type(self, instance): if instance.glossary.get('text_align') or instance.glossary.get('font_size'): return 'div' @classmethod def get_css_classes(cls, instance): css_classes = cls.super(FramedIconPlugin, cls).get_css_classes(instance) text_align = instance.glossary.get('text_align') if text_align: css_classes.append(text_align) return css_classes @classmethod def get_inline_styles(cls, instance): inline_styles = cls.super(FramedIconPlugin, cls).get_inline_styles(instance) inline_styles['font-size'] = instance.glossary.get('font_size', '1em') return inline_styles def get_form(self, request, obj=None, **kwargs): kwargs.update(form=VoluntaryLinkForm.get_form_class()) return super(FramedIconPlugin, self).get_form(request, obj, **kwargs) def render(self, context, instance, placeholder): context = self.super(FramedIconPlugin, self).render(context, instance, placeholder) icon_font = self.get_icon_font(instance) symbol = instance.glossary.get('symbol') attrs = [] if icon_font and symbol: attrs.append(mark_safe('class="{}{}"'.format(icon_font.config_data.get('css_prefix_text', 'icon-'), symbol))) styles = {'display': 'inline-block'} disabled, color = instance.glossary.get('color', (True, '#000000')) if not disabled: styles['color'] = color disabled, background_color = instance.glossary.get('background_color', (True, '#000000')) if not disabled: styles['background-color'] = background_color border = instance.glossary.get('border') if isinstance(border, list) and border[0] and border[1] != 'none': styles.update(border='{0} {1} {2}'.format(*border)) radius = instance.glossary.get('border_radius') if radius: styles['border-radius'] = radius attrs.append(format_html('style="{}"', format_html_join('', '{0}:{1};', [(k, v) for k, v in styles.items()]))) context['icon_font_attrs'] = mark_safe(' '.join(attrs)) return context
class TextIconPlugin(IconPluginMixin, LinkPluginBase): name = _("Icon in text") text_enabled = True render_template = 'cascade/plugins/texticon.html' ring_plugin = 'IconPlugin' parent_classes = ('TextPlugin',) model_mixins = (TextIconModelMixin, LinkElementMixin,) allow_children = False require_parent = False symbol = GlossaryField( widgets.HiddenInput(), label=_("Select Symbol"), ) color = GlossaryField( ColorPickerWidget(), label=_("Icon color"), ) glossary_field_order = ['symbol', 'color'] class Media: js = ['cascade/js/admin/iconplugin.js'] @classmethod def requires_parent_plugin(cls, slot, page): return False def get_form(self, request, obj=None, **kwargs): LINK_TYPE_CHOICES = [('none', _("No Link"))] LINK_TYPE_CHOICES.extend(getattr(LinkForm, 'LINK_TYPE_CHOICES')) Form = type(str('TextIconForm'), (getattr(LinkForm, 'get_form_class')(),), {'LINK_TYPE_CHOICES': LINK_TYPE_CHOICES}) kwargs.update(form=Form) return super(TextIconPlugin, self).get_form(request, obj, **kwargs) def get_plugin_urls(self): urls = [ url(r'^wysiwig-config\.js$', self.render_wysiwig_config, name='cascade_texticon_wysiwig_config'), ] return urls def render_wysiwig_config(self, request): """Find the icon font associated to the CMS page, from which this subrequest is originating.""" context = {} # Since this request is originating from CKEditor, we have no other choice rather than using # the referer, to determine the current CMS page. referer = urlparse(request.META['HTTP_REFERER']) matches = re.match(r'.+/edit-plugin/(\d+)/$', referer.path) if matches: cms_plugin = CMSPlugin.objects.get(id=matches.group(1)) try: context['icon_font'] = cms_plugin.page.cascadepage.icon_font except CascadePage.DoesNotExist: pass javascript = render_to_string('cascade/admin/ckeditor.wysiwyg.txt', context) return HttpResponse(javascript, content_type='application/javascript') @classmethod def get_inline_styles(cls, instance): inline_styles = cls.super(TextIconPlugin, cls).get_inline_styles(instance) color = instance.glossary.get('color') if isinstance(color, list) and len(color) == 2 and not color[0]: inline_styles['color'] = color[1] return inline_styles
class BootstrapJumbotronPlugin(BootstrapPluginBase): name = _("Jumbotron") model_mixins = (ContainerGridMixin, ImagePropertyMixin, ImageBackgroundMixin) form = JumbotronPluginForm require_parent = False parent_classes = ('BootstrapColumnPlugin',) allow_children = True alien_child_classes = True raw_id_fields = ['image_file'] fields = ['glossary', 'image_file'] render_template = 'cascade/bootstrap4/jumbotron.html' ring_plugin = 'JumbotronPlugin' ATTACHMENT_CHOICES = ['scroll', 'fixed', 'local'] VERTICAL_POSITION_CHOICES = ['top', '10%', '20%', '30%', '40%', 'center', '60%', '70%', '80%', '90%', 'bottom'] HORIZONTAL_POSITION_CHOICES = ['left', '10%', '20%', '30%', '40%', 'center', '60%', '70%', '80%', '90%', 'right'] REPEAT_CHOICES = ['repeat', 'repeat-x', 'repeat-y', 'no-repeat'] SIZE_CHOICES = ['auto', 'width/height', 'cover', 'contain'] container_glossary_fields = ( GlossaryField( ContainerBreakpointsWidget(choices=get_widget_choices()), label=_("Available Breakpoints"), name='breakpoints', initial=app_settings.CMSPLUGIN_CASCADE['bootstrap4']['default_bounds'].keys(), help_text=_("Supported display widths for Bootstrap's grid system.") ), GlossaryField( MultipleCascadingSizeWidget([bp.name for bp in Breakpoint], allowed_units=['px', '%'], required=False), label=_("Adapt Picture Heights"), name='container_max_heights', initial={'xs': '100%', 'sm': '100%', 'md': '100%', 'lg': '100%', 'xl': '100%'}, help_text=_("Heights of picture in percent or pixels for distinct Bootstrap's breakpoints.") ), GlossaryField( widgets.CheckboxSelectMultiple(choices=BootstrapPicturePlugin.RESIZE_OPTIONS), label=_("Resize Options"), name='resize_options', initial=['crop', 'subject_location', 'high_resolution'], help_text=_("Options to use when resizing the image.") ), ) background_color = GlossaryField( ColorPickerWidget(), label=_("Background color"), ) background_repeat = GlossaryField( widgets.RadioSelect(choices=[(c, c) for c in REPEAT_CHOICES]), initial='no-repeat', label=_("This property specifies how an image repeates."), ) background_attachment = GlossaryField( widgets.RadioSelect(choices=[(c, c) for c in ATTACHMENT_CHOICES]), initial='local', label=_("This property specifies how to move the background relative to the viewport."), ) background_vertical_position = GlossaryField( widgets.Select(choices=[(c, c) for c in VERTICAL_POSITION_CHOICES]), initial='center', label=_("This property moves a background image vertically within its container."), ) background_horizontal_position = GlossaryField( widgets.Select(choices=[(c, c) for c in HORIZONTAL_POSITION_CHOICES]), initial='center', label=_("This property moves a background image horizontally within its container."), ) background_size = GlossaryField( widgets.RadioSelect(choices=[(c, c) for c in SIZE_CHOICES]), initial='auto', label=_("Background size"), help_text=_("This property specifies how an image is sized."), ) background_width_height = GlossaryField( MultipleCascadingSizeWidget(['width', 'height'], allowed_units=['px', '%'], required=False), label=_("Background width and height"), help_text=_("This property specifies the width and height of a background image."), ) footnote_html = """ <p>For more information about the Jumbotron please read </p> """ class Media: js = ['cascade/js/admin/jumbotronplugin.js'] def get_form(self, request, obj=None, **kwargs): if self.get_parent_instance(request, obj) is None: # we only ask for breakpoints, if the jumbotron is the root of the placeholder kwargs.update(glossary_fields=list(self.container_glossary_fields)) kwargs['glossary_fields'].extend(self.glossary_fields) form = super(BootstrapJumbotronPlugin, self).get_form(request, obj, **kwargs) return form def render(self, context, instance, placeholder): # image shall be rendered in a responsive context using the ``<picture>`` element try: elements = get_picture_elements(instance) except Exception as exc: logger.warning("Unable generate picture elements. Reason: {}".format(exc)) else: if instance.child_plugin_instances and instance.child_plugin_instances[0].plugin_type == 'BootstrapRowPlugin': padding='padding: {0}px {0}px;'.format(int( app_settings.CMSPLUGIN_CASCADE['bootstrap4']['gutter']/2)) context.update({'add_gutter_if_child_is_BootstrapRowPlugin': padding,}) context.update({ 'elements': [e for e in elements if 'media' in e] if elements else [], 'CSS_PREFIXES': app_settings.CSS_PREFIXES, }) return self.super(BootstrapJumbotronPlugin, self).render(context, instance, placeholder) @classmethod def sanitize_model(cls, obj): sanitized = False # if the jumbotron is the root of the placeholder, we consider it as "fluid" obj.glossary['fluid'] = obj.parent is None super(BootstrapJumbotronPlugin, cls).sanitize_model(obj) grid_container = obj.get_bound_plugin().get_grid_instance() obj.glossary.setdefault('media_queries', {}) for bp, bound in grid_container.bounds.items(): obj.glossary['media_queries'].setdefault(bp.name, {}) width = round(bound.max) if obj.glossary['media_queries'][bp.name].get('width') != width: obj.glossary['media_queries'][bp.name]['width'] = width sanitized = True if obj.glossary['media_queries'][bp.name].get('media') != bp.media_query: obj.glossary['media_queries'][bp.name]['media'] = bp.media_query sanitized = True return sanitized @classmethod def get_css_classes(cls, obj): css_classes = cls.super(BootstrapJumbotronPlugin, cls).get_css_classes(obj) if obj.glossary.get('fluid'): css_classes.append('jumbotron-fluid') else: css_classes.append('jumbotron') return css_classes @classmethod def get_identifier(cls, obj): identifier = super(BootstrapJumbotronPlugin, cls).get_identifier(obj) try: content = obj.image.name or obj.image.original_filename except AttributeError: content = _("Without background image") return format_html('{0}{1}', identifier, content)