class RevealImagePlugin(CascadePluginBase): name = _("Image") module = 'Reveal' model_mixins = (ImagePropertyMixin,) render_template = 'reveal/image.html' require_parent = True parent_classes = ('RevealSectionPlugin', 'SimpleWrapperPlugin',) allow_children = False raw_id_fields = ('image_file',) text_enabled = True admin_preview = False form = ImageForm glossary_fields = ( PartialFormField('image-width', CascadingSizeWidget(allowed_units=['px', '%'], required=False), label=_("Image Width"), help_text=_("Set a fixed image width in pixels."), ), PartialFormField('image-height', CascadingSizeWidget(allowed_units=['px', '%'], required=False), label=_("Image Height"), help_text=_("Set a fixed height in pixels, or percent relative to the image width."), ), ) def render(self, context, instance, placeholder): glossary = dict(instance.get_complete_glossary()) # extra_styles = tags.pop('extra_styles') inline_styles = instance.glossary.get('inline_styles', {}) # inline_styles.update(extra_styles) # instance.glossary['inline_styles'] = inline_styles size = (960, 600) src = {'size': size, 'crop': False} context.update(dict(instance=instance, src=src, placeholder=placeholder)) return context
class BootstrapGalleryPlugin(CascadePluginBase): name = _("Gallery") module = 'Bootstrap' parent_classes = ['BootstrapColumnPlugin'] require_parent = True allow_children = False raw_id_fields = ('image_file', ) text_enabled = True admin_preview = False render_template = 'cascade/bootstrap3/gallery.html' default_css_attributes = ('image_shapes', ) html_tag_attributes = {'image_title': 'title', 'alt_tag': 'tag'} inlines = (GalleryPluginInline, ) SHAPE_CHOICES = (('img-responsive', _("Responsive")), ) RESIZE_OPTIONS = ( ('upscale', _("Upscale image")), ('crop', _("Crop image")), ('subject_location', _("With subject location")), ('high_resolution', _("Optimized for Retina")), ) image_shapes = GlossaryField( widgets.CheckboxSelectMultiple(choices=SHAPE_CHOICES), label=_("Image Responsiveness"), initial=['img-responsive'], ) image_width_responsive = GlossaryField( CascadingSizeWidget(allowed_units=['%'], required=False), label=_("Responsive Image Width"), initial='100%', help_text=_( "Set the image width in percent relative to containing element."), ) image_width_fixed = GlossaryField( CascadingSizeWidget(allowed_units=['px'], required=False), label=_("Fixed Image Width"), help_text=_("Set a fixed image width in pixels."), ) image_height = GlossaryField( CascadingSizeWidget(allowed_units=['px', '%'], required=False), label=_("Adapt Image Height"), help_text= _("Set a fixed height in pixels, or percent relative to the image width." ), ) thumbnail_width = GlossaryField( CascadingSizeWidget(allowed_units=['px']), label=_("Thumbnail Width"), help_text=_("Set a fixed thumbnail width in pixels."), ) thumbnail_height = GlossaryField( CascadingSizeWidget(allowed_units=['px', '%']), label=_("Thumbnail Height"), help_text= _("Set a fixed height in pixels, or percent relative to the thumbnail width." ), ) resize_options = GlossaryField( widgets.CheckboxSelectMultiple(choices=RESIZE_OPTIONS), label=_("Resize Options"), help_text=_("Options to use when resizing the image."), initial=['crop', 'subject_location', 'high_resolution'], ) class Media: js = resolve_dependencies('cascade/js/admin/imageplugin.js') def get_form(self, request, obj=None, **kwargs): utils.reduce_breakpoints(self, 'responsive_heights') form = super(BootstrapGalleryPlugin, self).get_form(request, obj, **kwargs) return form def render(self, context, instance, placeholder): gallery_instances = [] options = dict(instance.get_complete_glossary()) for inline_element in instance.sortinline_elements.all(): # since inline_element requires the property `image`, add ImagePropertyMixin # to its class during runtime try: ProxyModel = create_proxy_model('GalleryImage', (ImagePropertyMixin, ), SortableInlineCascadeElement, module=__name__) inline_element.__class__ = ProxyModel options.update( inline_element.glossary, **{ 'image_width_fixed': options['thumbnail_width'], 'image_height': options['thumbnail_height'], 'is_responsive': False, }) thumbnail_tags = utils.get_image_tags(context, inline_element, options) for key, val in thumbnail_tags.items(): setattr(inline_element, key, val) gallery_instances.append(inline_element) except (KeyError, AttributeError): pass inline_styles = instance.glossary.get('inline_styles', {}) inline_styles.update(width=options['thumbnail_width']) instance.glossary['inline_styles'] = inline_styles context.update( dict(instance=instance, placeholder=placeholder, gallery_instances=gallery_instances)) return context @classmethod def get_css_classes(cls, obj): css_classes = super(BootstrapGalleryPlugin, cls).get_css_classes(obj) css_class = obj.glossary.get('css_class') if css_class: css_classes.append(css_class) return css_classes @classmethod def get_identifier(cls, obj): identifier = super(BootstrapGalleryPlugin, cls).get_identifier(obj) num_elems = obj.sortinline_elements.count() content = ungettext_lazy("with {0} image", "with {0} images", num_elems).format(num_elems) 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 BootstrapImagePlugin(ImageAnnotationMixin, LinkPluginBase): name = _("Image") model_mixins = (ImagePropertyMixin, LinkElementMixin,) module = 'Bootstrap' parent_classes = ['BootstrapColumnPlugin'] require_parent = True allow_children = False raw_id_fields = LinkPluginBase.raw_id_fields + ['image_file'] admin_preview = False ring_plugin = 'ImagePlugin' render_template = 'cascade/bootstrap4/linked-image.html' default_css_attributes = ['image_shapes', 'image_alignment'] html_tag_attributes = {'image_title': 'title', 'alt_tag': 'tag'} html_tag_attributes.update(LinkPluginBase.html_tag_attributes) fields = ['image_file'] + list(LinkPluginBase.fields) SHAPE_CHOICES = [ ('img-fluid', _("Responsive")), ('rounded', _('Rounded')), ('rounded-circle', _('Circle')), ('img-thumbnail', _('Thumbnail')), ] RESIZE_OPTIONS = [ ('upscale', _("Upscale image")), ('crop', _("Crop image")), ('subject_location', _("With subject location")), ('high_resolution', _("Optimized for Retina")), ] ALIGNMENT_OPTIONS = [ ('float-left', _("Left")), ('float-right', _("Right")), ('mx-auto', _("Center")), ] image_shapes = GlossaryField( widgets.CheckboxSelectMultiple(choices=SHAPE_CHOICES), label=_("Image Shapes"), initial=['img-fluid'] ) image_width_responsive = GlossaryField( CascadingSizeWidget(allowed_units=['%'], required=False), label=_("Responsive Image Width"), initial='100%', help_text=_("Set the image width in percent relative to containing element."), ) image_width_fixed = GlossaryField( CascadingSizeWidget(allowed_units=['px'], required=False), label=_("Fixed Image Width"), help_text=_("Set a fixed image width in pixels."), ) image_height = GlossaryField( CascadingSizeWidget(allowed_units=['px', '%'], required=False), label=_("Adapt Image Height"), help_text=_("Set a fixed height in pixels, or percent relative to the image width."), ) resize_options = GlossaryField( widgets.CheckboxSelectMultiple(choices=RESIZE_OPTIONS), label=_("Resize Options"), help_text=_("Options to use when resizing the image."), initial=['subject_location', 'high_resolution'], ) image_alignment = GlossaryField( widgets.RadioSelect(choices=ALIGNMENT_OPTIONS), label=_("Image Alignment"), help_text=_("How to align a non-responsive image."), ) class Media: js = ['cascade/js/admin/imageplugin.js'] def get_form(self, request, obj=None, **kwargs): image_file = ModelChoiceField(queryset=Image.objects.all(), required=False, label=_("Image")) LinkForm = getattr(VoluntaryLinkForm, 'get_form_class')() Form = type(str('ImageForm'), (ImageFormMixin, LinkForm), {'image_file': image_file}) kwargs.update(form=Form) return super(BootstrapImagePlugin, self).get_form(request, obj, **kwargs) def render(self, context, instance, placeholder): try: tags = get_image_tags(instance) except Exception as exc: logger.warning("Unable generate image tags. Reason: {}".format(exc)) tags = tags if tags else {} if 'extra_styles' in tags: extra_styles = tags.pop('extra_styles') inline_styles = instance.glossary.get('inline_styles', {}) inline_styles.update(extra_styles) instance.glossary['inline_styles'] = inline_styles context.update(dict(instance=instance, placeholder=placeholder, **tags)) return context @classmethod def get_css_classes(cls, obj): css_classes = cls.super(BootstrapImagePlugin, cls).get_css_classes(obj) css_class = obj.glossary.get('css_class') if css_class: css_classes.append(css_class) return css_classes @classmethod def get_identifier(cls, obj): identifier = super(BootstrapImagePlugin, cls).get_identifier(obj) try: content = force_text(obj.image) except AttributeError: content = _("No Image") return format_html('{0}{1}', identifier, content) @classmethod def sanitize_model(cls, obj): sanitized = False parent = obj.parent try: while parent.plugin_type != 'BootstrapColumnPlugin': parent = parent.parent grid_column = parent.get_bound_plugin().get_grid_instance() min_max_bounds = grid_column.get_min_max_bounds() if obj.glossary.get('column_bounds') != min_max_bounds: obj.glossary['column_bounds'] = min_max_bounds sanitized = True obj.glossary.setdefault('media_queries', {}) for bp in Breakpoint: media_query = '{} {:.2f}px'.format(bp.media_query, grid_column.get_bound(bp).max) if obj.glossary['media_queries'].get(bp.name) != media_query: obj.glossary['media_queries'][bp.name] = media_query sanitized = True except AttributeError: logger.warning("ImagePlugin(pk={}) has no ColumnPlugin as ancestor.".format(obj.pk)) return return sanitized
class TextImagePlugin(ImageAnnotationMixin, LinkPluginBase): name = _("Image in text") text_enabled = True ring_plugin = 'TextImagePlugin' render_template = 'cascade/plugins/textimage.html' parent_classes = ('TextPlugin', ) model_mixins = (ImagePropertyMixin, LinkElementMixin) allow_children = False require_parent = False html_tag_attributes = {'image_title': 'title', 'alt_tag': 'tag'} html_tag_attributes.update(LinkPluginBase.html_tag_attributes) fields = ['image_file'] + list(LinkPluginBase.fields) RESIZE_OPTIONS = [('upscale', _("Upscale image")), ('crop', _("Crop image")), ('subject_location', _("With subject location")), ('high_resolution', _("Optimized for Retina"))] image_width = GlossaryField( CascadingSizeWidget(allowed_units=['px'], required=True), label=_("Image Width"), help_text=_("Set the image width in pixels."), ) image_height = GlossaryField( CascadingSizeWidget(allowed_units=['px'], required=False), label=_("Image Height"), help_text=_("Set the image height in pixels."), ) resize_options = GlossaryField( widgets.CheckboxSelectMultiple(choices=RESIZE_OPTIONS), label=_("Resize Options"), help_text=_("Options to use when resizing the image."), initial=['subject_location', 'high_resolution']) alignement = GlossaryField( widgets.RadioSelect( choices=[('', _("Not aligned")), ('left', _("Left")), ('right', _("Right"))]), initial='', label=_("Alignement"), ) class Media: js = ['cascade/js/admin/textimageplugin.js'] def get_form(self, request, obj=None, **kwargs): LINK_TYPE_CHOICES = (('none', _("No Link")),) + \ tuple(t for t in getattr(LinkForm, 'LINK_TYPE_CHOICES') if t[0] != 'email') image_file = ModelChoiceField(queryset=Image.objects.all(), required=False, label=_("Image")) Form = type(str('ImageForm'), ( ImageFormMixin, getattr(LinkForm, 'get_form_class')(), ), { 'LINK_TYPE_CHOICES': LINK_TYPE_CHOICES, 'image_file': image_file }) kwargs.update(form=Form) return super(TextImagePlugin, self).get_form(request, obj, **kwargs) @classmethod def requires_parent_plugin(cls, slot, page): """ Workaround for `PluginPool.get_all_plugins()`, otherwise TextImagePlugin is not allowed as a child of a `TextPlugin`. """ return False @classmethod def get_inline_styles(cls, instance): inline_styles = cls.super(TextImagePlugin, cls).get_inline_styles(instance) alignement = instance.glossary.get('alignement') if alignement: inline_styles['float'] = alignement return inline_styles def render(self, context, instance, placeholder): try: aspect_ratio = compute_aspect_ratio(instance.image) except Exception: # if accessing the image file fails, abort here return context resize_options = instance.glossary.get('resize_options', {}) crop = 'crop' in resize_options upscale = 'upscale' in resize_options subject_location = instance.image.subject_location if 'subject_location' in resize_options else False high_resolution = 'high_resolution' in resize_options image_width = instance.glossary.get('image_width', '') if not image_width.endswith('px'): return context image_width = int(image_width.rstrip('px')) image_height = instance.glossary.get('image_height', '') if image_height.endswith('px'): image_height = int(image_height.rstrip('px')) else: image_height = int(round(image_width * aspect_ratio)) src = { 'size': (image_width, image_height), 'size2x': (image_width * 2, image_height * 2), 'crop': crop, 'upscale': upscale, 'subject_location': subject_location, 'high_resolution': high_resolution, } context.update( dict(instance=instance, placeholder=placeholder, src=src)) return context
class BootstrapImagePlugin(LinkPluginBase): name = _("Image") model_mixins = ( ImagePropertyMixin, LinkElementMixin, ) module = 'Bootstrap' parent_classes = ['BootstrapColumnPlugin'] require_parent = True allow_children = False raw_id_fields = ('image_file', ) text_enabled = True admin_preview = False render_template = 'cascade/bootstrap3/linked-image.html' default_css_attributes = ('image-shapes', ) html_tag_attributes = {'image-title': 'title', 'alt-tag': 'tag'} fields = ( 'image_file', getattr(LinkPluginBase, 'glossary_field_map')['link'], 'glossary', ) LINK_TYPE_CHOICES = (('none', _("No Link")),) + \ tuple(t for t in getattr(LinkForm, 'LINK_TYPE_CHOICES') if t[0] != 'email') SHAPE_CHOICES = ( ('img-responsive', _("Responsive")), ('img-rounded', _('Rounded')), ('img-circle', _('Circle')), ('img-thumbnail', _('Thumbnail')), ) RESIZE_OPTIONS = ( ('upscale', _("Upscale image")), ('crop', _("Crop image")), ('subject_location', _("With subject location")), ('high_resolution', _("Optimized for Retina")), ) glossary_fields = ( PartialFormField( 'image-title', widgets.TextInput(), label=_('Image Title'), help_text= _("Caption text added to the 'title' attribute of the <img> element." ), ), PartialFormField( 'alt-tag', widgets.TextInput(), label=_('Alternative Description'), help_text= _("Textual description of the image added to the 'alt' tag of the <img> element." ), ), ) + getattr(LinkPluginBase, 'glossary_fields', ()) + ( PartialFormField('image-shapes', widgets.CheckboxSelectMultiple(choices=SHAPE_CHOICES), label=_("Image Shapes"), initial=['img-responsive']), PartialFormField( 'image-width-responsive', CascadingSizeWidget(allowed_units=['%'], required=False), label=_("Responsive Image Width"), initial='100%', help_text=_( "Set the image width in percent relative to containing element." ), ), PartialFormField( 'image-width-fixed', CascadingSizeWidget(allowed_units=['px'], required=False), label=_("Fixed Image Width"), help_text=_("Set a fixed image width in pixels."), ), PartialFormField( 'image-height', CascadingSizeWidget(allowed_units=['px', '%'], required=False), label=_("Adapt Image Height"), help_text= _("Set a fixed height in pixels, or percent relative to the image width." ), ), PartialFormField( 'resize-options', widgets.CheckboxSelectMultiple(choices=RESIZE_OPTIONS), label=_("Resize Options"), help_text=_("Options to use when resizing the image."), initial=['subject_location', 'high_resolution']), ) class Media: js = resolve_dependencies('cascade/js/admin/imageplugin.js') def get_form(self, request, obj=None, **kwargs): utils.reduce_breakpoints(self, 'responsive-heights') image_file = ModelChoiceField(queryset=Image.objects.all(), required=False, label=_("Image")) Form = type(str('ImageForm'), ( ImageFormMixin, getattr(LinkForm, 'get_form_class')(), ), { 'LINK_TYPE_CHOICES': self.LINK_TYPE_CHOICES, 'image_file': image_file }) kwargs.update(form=Form) return super(BootstrapImagePlugin, self).get_form(request, obj, **kwargs) def render(self, context, instance, placeholder): is_responsive = 'img-responsive' in instance.glossary.get( 'image-shapes', []) options = dict(instance.get_complete_glossary(), is_responsive=is_responsive) tags = utils.get_image_tags(context, instance, options) if tags: extra_styles = tags.pop('extra_styles') inline_styles = instance.glossary.get('inline_styles', {}) inline_styles.update(extra_styles) instance.glossary['inline_styles'] = inline_styles context.update( dict(instance=instance, placeholder=placeholder, **tags)) return context @classmethod def get_css_classes(cls, obj): css_classes = super(BootstrapImagePlugin, cls).get_css_classes(obj) css_class = obj.glossary.get('css_class') if css_class: css_classes.append(css_class) return css_classes @classmethod def get_identifier(cls, obj): identifier = super(BootstrapImagePlugin, cls).get_identifier(obj) try: content = force_text(obj.image) except AttributeError: content = _("No Image") return format_html('{0}{1}', identifier, content)
class FramedIconPlugin(IconPluginMixin, CascadePluginBase): name = _("Icon with frame") 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 = 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
class MarkerForm(ModelForm): title = CharField( label=_("Marker Title"), widget=widgets.TextInput(attrs={'size': 60}), help_text=_( "Please choose a title, then go to the map to set a marker pin")) use_icon = BooleanField( label=_("Use customized marker icon"), initial=False, required=False, ) marker_image = ModelChoiceField( queryset=Image.objects.all(), label=_("Marker Image"), required=False, ) marker_width = GlossaryFormField( widget=CascadingSizeWidget(allowed_units=['px'], required=False), label=_("Marker Width"), required=False, help_text=_("Width of the marker icon in pixels."), ) marker_anchor = GlossaryFormField( widget=MultipleCascadingSizeWidget(['left', 'top'], allowed_units=['px', '%'], required=False), required=False, label=_("Marker Anchor"), help_text= _("The coordinates of the icon's anchor (relative to its top left corner)." ), ) popup_text = HTMLFormField( required=False, help_text=_("Optional rich text to display in popup."), ) position = Field(widget=widgets.HiddenInput) glossary_field_order = [ 'title', 'marker_width', 'marker_anchor', 'popup_text' ] class Meta: exclude = ['glossary'] def __init__(self, *args, **kwargs): try: initial = dict(kwargs['instance'].glossary) has_original = True except (KeyError, AttributeError): initial = {} has_original = False initial.update(kwargs.pop('initial', {})) self.base_fields['position'].initial = json.dumps( initial.pop('position', {})) for key in self.glossary_field_order: self.base_fields[key].initial = initial.get(key) try: self.base_fields['marker_image'].initial = initial['image']['pk'] except KeyError: self.base_fields['marker_image'].initial = None self.base_fields['use_icon'].initial = False else: self.base_fields['use_icon'].initial = True self.base_fields['marker_image'].widget = AdminFileWidget( ManyToOneRel(FilerImageField, Image, 'file_ptr'), site) super(MarkerForm, self).__init__(*args, **kwargs) if has_original: self.fields['title'].help_text = None def clean(self): try: position = self.cleaned_data['position'] if isinstance(position, six.string_types): position = json.loads(position) elif not isinstance(position, dict): raise ValueError except (ValueError, KeyError): raise ValidationError( "Invalid internal position data. Check your Javascript imports." ) else: if 'lat' not in position or 'lng' not in position: # place the marker in the center of the current map position = { k: v for k, v in self.instance.cascade_element. glossary['map_position'].items() if k in ['lat', 'lng'] } self.instance.glossary.update(position=position) marker_image = self.cleaned_data.pop('marker_image', None) if marker_image: image_data = {'pk': marker_image.pk, 'model': 'filer.Image'} self.instance.glossary.update(image=image_data) else: self.instance.glossary.pop('image', None) popup_text = self.cleaned_data.pop('popup_text', None) if strip_tags(popup_text): popup_text = strip_spaces_between_tags(popup_text) self.cleaned_data.update(popup_text=popup_text) for key in self.glossary_field_order: self.instance.glossary.update({key: self.cleaned_data.get(key)})
class LeafletPlugin(CascadePluginBase): name = _("Map") parent_classes = None require_parent = False allow_children = False change_form_template = 'cascade/admin/leaflet_plugin_change_form.html' ring_plugin = 'LeafletPlugin' admin_preview = False render_template = 'cascade/plugins/leaflet.html' inlines = (MarkerInline, ) glossary_field_order = ('map_width', 'map_height') model_mixins = (LeafletModelMixin, ) form = LeafletForm settings = mark_safe(json.dumps(app_settings.CMSPLUGIN_CASCADE['leaflet'])) map_width = GlossaryField( CascadingSizeWidget(allowed_units=['px', '%'], required=True), label=_("Map Width"), initial='100%', help_text=_( "Set the map width in percent relative to containing element."), ) map_height = GlossaryField( CascadingSizeWidget(allowed_units=['px', '%'], required=True), label=_("Adapt Map Height"), initial='400px', help_text= _("Set a fixed height in pixels, or percent relative to the map width." ), ) class Media: css = { 'all': [ 'node_modules/leaflet/dist/leaflet.css', 'node_modules/leaflet-easybutton/src/easy-button.css', 'cascade/css/admin/leafletplugin.css', ] } js = [ 'node_modules/leaflet/dist/leaflet.js', 'node_modules/leaflet-easybutton/src/easy-button.js', 'cascade/js/admin/leafletplugin.js', ] def add_view(self, request, form_url='', extra_context=None): extra_context = dict(extra_context or {}, settings=self.settings) return super(LeafletPlugin, self).add_view(request, form_url, extra_context) def change_view(self, request, object_id, form_url='', extra_context=None): extra_context = dict(extra_context or {}, settings=self.settings) return super(LeafletPlugin, self).change_view(request, object_id, form_url, extra_context) def render(self, context, instance, placeholder): marker_instances = [] for inline_element in instance.inline_elements.all(): try: ProxyModel = create_proxy_model( 'LeafletMarker', (ImagePropertyMixin, MarkerModelMixin), InlineCascadeElement, module=__name__) marker = ProxyModel(id=inline_element.id, glossary=inline_element.glossary) try: aspect_ratio = compute_aspect_ratio(marker.image) width = parse_responsive_length( marker.glossary.get('marker_width') or '25px') marker.size = list( get_image_size(width[0], (None, None), aspect_ratio)) marker.size2x = 2 * marker.size[0], 2 * marker.size[1] except Exception: # if accessing the image file fails, skip size computations pass else: try: marker_anchor = marker.glossary['marker_anchor'] top = parse_responsive_length(marker_anchor['top']) left = parse_responsive_length(marker_anchor['left']) if top[0] is None or left[0] is None: left = width[0] * left[1] top = width[0] * aspect_ratio * top[1] else: left, top = left[0], top[0] marker.anchor = [left, top] except Exception: pass marker_instances.append(marker) except (KeyError, AttributeError): pass context.update( dict(instance=instance, placeholder=placeholder, settings=self.settings, config=app_settings.CMSPLUGIN_CASCADE['leaflet'], markers=marker_instances)) return context @classmethod def get_css_classes(cls, obj): css_classes = cls.super(LeafletPlugin, cls).get_css_classes(obj) css_class = obj.glossary.get('css_class') if css_class: css_classes.append(css_class) return css_classes @classmethod def get_identifier(cls, obj): identifier = super(LeafletPlugin, cls).get_identifier(obj) num_elems = obj.inline_elements.count() content = ungettext_lazy("with {0} marker", "with {0} markers", num_elems).format(num_elems) return format_html('{0}{1}', identifier, content) @classmethod def get_data_representation(cls, instance): data = super(LeafletPlugin, cls).get_data_representation(instance) data.update( inlines=[ie.glossary for ie in instance.inline_elements.all()]) return data
class BootstrapImagePlugin(ImageAnnotationMixin, LinkPluginBase): name = _("Image") model_mixins = ( ImagePropertyMixin, LinkElementMixin, ) module = 'Bootstrap' parent_classes = ('BootstrapColumnPlugin', ) require_parent = True allow_children = False raw_id_fields = LinkPluginBase.raw_id_fields + ['image_file'] admin_preview = False ring_plugin = 'ImagePlugin' render_template = 'cascade/bootstrap3/linked-image.html' default_css_attributes = ('image_shapes', ) html_tag_attributes = {'image_title': 'title', 'alt_tag': 'tag'} html_tag_attributes.update(LinkPluginBase.html_tag_attributes) fields = ['image_file'] + list(LinkPluginBase.fields) SHAPE_CHOICES = [ ('img-responsive', _("Responsive")), ('img-rounded', _('Rounded')), ('img-circle', _('Circle')), ('img-thumbnail', _('Thumbnail')), ] RESIZE_OPTIONS = [ ('upscale', _("Upscale image")), ('crop', _("Crop image")), ('subject_location', _("With subject location")), ('high_resolution', _("Optimized for Retina")), ] image_shapes = GlossaryField( widgets.CheckboxSelectMultiple(choices=SHAPE_CHOICES), label=_("Image Shapes"), initial=['img-responsive']) image_width_responsive = GlossaryField( CascadingSizeWidget(allowed_units=['%'], required=False), label=_("Responsive Image Width"), initial='100%', help_text=_( "Set the image width in percent relative to containing element."), ) image_width_fixed = GlossaryField( CascadingSizeWidget(allowed_units=['px'], required=False), label=_("Fixed Image Width"), help_text=_("Set a fixed image width in pixels."), ) image_height = GlossaryField( CascadingSizeWidget(allowed_units=['px', '%'], required=False), label=_("Adapt Image Height"), help_text= _("Set a fixed height in pixels, or percent relative to the image width." ), ) resize_options = GlossaryField( widgets.CheckboxSelectMultiple(choices=RESIZE_OPTIONS), label=_("Resize Options"), help_text=_("Options to use when resizing the image."), initial=['subject_location', 'high_resolution']) class Media: js = ['cascade/js/admin/imageplugin.js'] def get_form(self, request, obj=None, **kwargs): utils.reduce_breakpoints(self, 'responsive_heights', request=request, obj=obj) LinkForm = getattr(VoluntaryLinkForm, 'get_form_class')() image_file = ModelChoiceField(queryset=Image.objects.all(), required=False, label=_("Image")) Form = type(str('ImageForm'), ( ImageFormMixin, LinkForm, ), {'image_file': image_file}) kwargs.update(form=Form) return super(BootstrapImagePlugin, self).get_form(request, obj, **kwargs) def render(self, context, instance, placeholder): is_responsive = 'img-responsive' in instance.glossary.get( 'image_shapes', []) options = dict(instance.get_complete_glossary(), is_responsive=is_responsive) tags = utils.get_image_tags(context, instance, options) if tags: extra_styles = tags.pop('extra_styles') inline_styles = instance.glossary.get('inline_styles', {}) inline_styles.update(extra_styles) instance.glossary['inline_styles'] = inline_styles context.update( dict(instance=instance, placeholder=placeholder, **tags)) return context @classmethod def get_css_classes(cls, obj): css_classes = cls.super(BootstrapImagePlugin, cls).get_css_classes(obj) css_class = obj.glossary.get('css_class') if css_class: css_classes.append(css_class) return css_classes @classmethod def get_identifier(cls, obj): identifier = super(BootstrapImagePlugin, cls).get_identifier(obj) try: content = force_text(obj.image) except AttributeError: content = _("No Image") return format_html('{0}{1}', identifier, content)