def __init__(self, model=None, admin_site=None): glossary_fields = ( PartialFormField('grid', widgets.Select(choices=self.GRID_CHOICES), label=_('Column Grid'), initial='grid_4', help_text=_("Grid in column units.")), PartialFormField( 'prefix', widgets.Select(choices=self.PREFIX_CHOICES), label=_('Prefix'), ), PartialFormField( 'suffix', widgets.Select(choices=self.SUFFIX_CHOICES), label=_('Suffix'), ), PartialFormField( 'options', widgets.CheckboxSelectMultiple(choices=self.OPTION_CHOICES), label=_('Options'), ), PartialFormField('inline_styles', MultipleCascadingSizeWidget( ['min-height', 'margin-top', 'margin-bottom']), label=_('Inline Styles'), help_text=_('Minimum height for this column.')), ) super(Grid960BasePlugin, self).__init__(model, admin_site, glossary_fields=glossary_fields)
class BootstrapButtonPlugin(LinkPluginBase): module = 'Bootstrap' name = _("Button") form = TextLinkForm model_mixins = (LinkElementMixin,) parent_classes = ['BootstrapColumnPlugin'] render_template = 'cascade/bootstrap3/button.html' allow_children = False text_enabled = True tag_type = None default_css_class = 'btn' default_css_attributes = ('button-type', 'button-size', 'button-options', 'quick-float',) fields = ('link_content', ('link_type', 'cms_page', 'ext_url', 'mail_to'), 'glossary',) glossary_fields = ( PartialFormField('button-type', widgets.RadioSelect(choices=((k, v) for k, v in ButtonTypeRenderer.BUTTON_TYPES.items()), renderer=ButtonTypeRenderer), label=_('Button Type'), initial='btn-default', help_text=_("Display Link using this Button Style") ), PartialFormField('button-size', widgets.RadioSelect(choices=((k, v) for k, v in ButtonSizeRenderer.BUTTON_SIZES.items()), renderer=ButtonSizeRenderer), label=_('Button Size'), initial='', help_text=_("Display Link using this Button Size") ), PartialFormField('button-options', widgets.CheckboxSelectMultiple(choices=(('btn-block', _('Block level')), ('disabled', _('Disabled')),)), label=_('Button Options'), ), PartialFormField('quick-float', widgets.RadioSelect(choices=(('', _("Do not float")), ('pull-left', _("Pull left")), ('pull-right', _("Pull right")),)), label=_('Quick Float'), initial='', help_text=_("Float the button to the left or right.") ), ) + LinkPluginBase.glossary_fields + ( PartialFormField('inline_styles', MultipleCascadingSizeWidget(['margin-top', 'margin-right', 'margin-bottom', 'margin-left'], allowed_units=['px', 'em'], required=False), label=_('Margins'), help_text=_('Margins for this button wrapper.') ), ) class Media: css = {'all': ('cascade/css/admin/bootstrap.min.css', 'cascade/css/admin/bootstrap-theme.min.css',)} @classmethod def get_identifier(cls, obj): identifier = super(BootstrapButtonPlugin, cls).get_identifier(obj) content = obj.glossary.get('link_content') if not content: try: content = force_text(ButtonTypeRenderer.BUTTON_TYPES[obj.glossary['button-type']]) except KeyError: content = _("Empty") return format_html('{0}{1}', identifier, content)
class CarouselPlugin(BootstrapPluginBase): name = _("Carousel") form = CarouselSlidesForm default_css_class = 'carousel' default_css_attributes = ('options', ) parent_classes = ['BootstrapColumnPlugin', 'SimpleWrapperPlugin'] render_template = 'cascade/bootstrap3/{}/carousel.html' default_inline_styles = {'overflow': 'hidden'} fields = ( 'num_children', 'glossary', ) DEFAULT_CAROUSEL_ATTRIBUTES = {'data-ride': 'carousel'} OPTION_CHOICES = ( ('slide', _("Animate")), ('pause', _("Pause")), ('wrap', _("Wrap")), ) interval = GlossaryField( NumberInputWidget(attrs={ 'size': '2', 'style': 'width: 4em;', 'min': '1' }), label=_("Interval"), initial=5, help_text=_("Change slide after this number of seconds."), ) options = GlossaryField( widgets.CheckboxSelectMultiple(choices=OPTION_CHOICES), label=_('Options'), initial=['slide', 'wrap', 'pause'], help_text=_("Adjust interval for the carousel."), ) container_max_heights = GlossaryField( MultipleCascadingSizeWidget(list( tp[0] for tp in settings.CMSPLUGIN_CASCADE['bootstrap3']['breakpoints']), allowed_units=['px']), label=_("Carousel heights"), initial=dict( (bp[0], '{}px'.format(100 + 50 * i)) for i, bp in enumerate( settings.CMSPLUGIN_CASCADE['bootstrap3']['breakpoints'])), help_text= _("Heights of Carousel in pixels for distinct Bootstrap's breakpoints." ), ) resize_options = GlossaryField( widgets.CheckboxSelectMultiple( choices=BootstrapPicturePlugin.RESIZE_OPTIONS), label=_("Resize Options"), help_text=_("Options to use when resizing the image."), initial=['upscale', 'crop', 'subject_location', 'high_resolution']) def get_form(self, request, obj=None, **kwargs): utils.reduce_breakpoints(self, 'container_max_heights', request) return super(CarouselPlugin, self).get_form(request, obj, **kwargs) @classmethod def get_identifier(cls, obj): identifier = super(CarouselPlugin, cls).get_identifier(obj) num_cols = obj.get_children().count() content = ungettext_lazy('with {0} slide', 'with {0} slides', num_cols).format(num_cols) return format_html('{0}{1}', identifier, content) @classmethod def get_css_classes(cls, obj): css_classes = super(CarouselPlugin, cls).get_css_classes(obj) if 'slide' in obj.glossary.get('options', []): css_classes.append('slide') return css_classes @classmethod def get_html_tag_attributes(cls, obj): attributes = super(CarouselPlugin, cls).get_html_tag_attributes(obj) attributes.update(cls.DEFAULT_CAROUSEL_ATTRIBUTES) attributes['data-interval'] = 1000 * int( obj.glossary.get('interval', 5)) options = obj.glossary.get('options', []) attributes['data-pause'] = 'pause' in options and 'hover' or 'false' attributes['data-wrap'] = 'wrap' in options and 'true' or 'false' return attributes def save_model(self, request, obj, form, change): wanted_children = int(form.cleaned_data.get('num_children')) super(CarouselPlugin, self).save_model(request, obj, form, change) self.extend_children(obj, wanted_children, CarouselSlidePlugin) @classmethod def sanitize_model(cls, obj): sanitized = super(CarouselPlugin, cls).sanitize_model(obj) complete_glossary = obj.get_complete_glossary() # fill all invalid heights for this container to a meaningful value max_height = max(obj.glossary['container_max_heights'].values()) pattern = re.compile(r'^(\d+)px$') for bp in complete_glossary.get('breakpoints', ()): if not pattern.match(obj.glossary['container_max_heights'].get( bp, '')): obj.glossary['container_max_heights'][bp] = max_height return sanitized
class BootstrapPicturePlugin(ImageAnnotationMixin, LinkPluginBase): name = _("Picture") model_mixins = ( ImagePropertyMixin, LinkElementMixin, ) module = 'Bootstrap' parent_classes = ['Bootstrap4ColumnPlugin', 'SimpleWrapperPlugin'] require_parent = True allow_children = False raw_id_fields = ('image_file', ) admin_preview = False ring_plugin = 'PicturePlugin' render_template = 'cascade/bootstrap4/linked-picture.html' default_css_class = 'img-fluid' 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', ) + LinkPluginBase.fields RESIZE_OPTIONS = ( ('upscale', _("Upscale image")), ('crop', _("Crop image")), ('subject_location', _("With subject location")), ('high_resolution', _("Optimized for Retina")), ) responsive_heights = GlossaryField( MultipleCascadingSizeWidget(BS4_BREAKPOINT_KEYS, allowed_units=['px', '%'], required=False), label=_("Adapt Picture 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." ), ) responsive_zoom = GlossaryField( MultipleCascadingSizeWidget(BS4_BREAKPOINT_KEYS, allowed_units=['%'], required=False), label=_("Adapt Picture Zoom"), initial={ 'xs': '0%', 'sm': '0%', 'md': '0%', 'lg': '0%', 'xl': '0%' }, help_text= _("Magnification of picture in percent for distinct Bootstrap's breakpoints." ), ) 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/pictureplugin.js'] def get_form(self, request, obj=None, **kwargs): reduce_breakpoints(self, 'responsive_heights') 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(BootstrapPicturePlugin, self).get_form(request, obj, **kwargs) def render(self, context, instance, placeholder): # image shall be rendered in a responsive context using the picture element elements = get_picture_elements(context, instance) fluid = instance.get_complete_glossary().get('fluid') == 'on' context.update({ 'is_responsive': True, 'instance': instance, 'is_fluid': fluid, 'placeholder': placeholder, 'elements': elements, }) return context @classmethod def get_css_classes(cls, obj): css_classes = cls.super(BootstrapPicturePlugin, 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(BootstrapPicturePlugin, cls).get_identifier(obj) try: content = force_text(obj.image) except AttributeError: content = _("No Picture") return format_html('{0}{1}', identifier, content)
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 BootstrapPicturePlugin(LinkPluginBase): name = _("Picture") model_mixins = ( ImagePropertyMixin, LinkElementMixin, ) module = 'Bootstrap' parent_classes = ['BootstrapColumnPlugin'] require_parent = True allow_children = False raw_id_fields = ('image_file', ) text_enabled = False admin_preview = False render_template = 'cascade/bootstrap3/linked-picture.html' default_css_class = 'img-responsive' 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') 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( 'responsive-heights', MultipleCascadingSizeWidget(BS3_BREAKPOINT_KEYS, allowed_units=['px', '%'], required=False), label=_("Adapt Picture Heights"), initial={ 'xs': '100%', 'sm': '100%', 'md': '100%', 'lg': '100%' }, help_text= _("Heights of picture in percent or pixels for distinct Bootstrap's breakpoints." ), ), PartialFormField( 'responsive-zoom', MultipleCascadingSizeWidget( BS3_BREAKPOINT_KEYS, allowed_units=['%'], required=False), label=_("Adapt Picture Zoom"), initial={ 'xs': '0%', 'sm': '0%', 'md': '0%', 'lg': '0%' }, help_text= _("Magnification of picture in percent for distinct Bootstrap's breakpoints." ), ), 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/pictureplugin.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(BootstrapPicturePlugin, self).get_form(request, obj, **kwargs) def render(self, context, instance, placeholder): # image shall be rendered in a responsive context using the picture element elements = utils.get_picture_elements(context, instance) context.update({ 'is_responsive': True, 'instance': instance, 'placeholder': placeholder, 'elements': elements, }) return context @classmethod def get_css_classes(cls, obj): css_classes = super(BootstrapPicturePlugin, 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(BootstrapPicturePlugin, cls).get_identifier(obj) try: content = force_text(obj.image) except AttributeError: content = _("No Picture") return format_html('{0}{1}', identifier, content)
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)
class BootstrapJumbotronPlugin(BootstrapPluginBase): name = _("Jumbotron") model_mixins = (ImagePropertyMixin, ImageBackgroundMixin) form = JumbotronPluginForm default_css_class = 'jumbotron' parent_classes = ['BootstrapColumnPlugin'] require_parent = False allow_children = True alien_child_classes = True raw_id_fields = ('image_file', ) fields = ( 'glossary', 'image_file', ) render_template = 'cascade/bootstrap3/jumbotron.html' 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 = ( PartialFormField( 'breakpoints', widgets.CheckboxSelectMultiple( choices=get_widget_choices(), renderer=ContainerBreakpointsRenderer), label=_("Available Breakpoints"), initial=list(BS3_BREAKPOINT_KEYS)[::-1], help_text=_( "Supported display widths for Bootstrap's grid system.")), PartialFormField( 'container_max_heights', MultipleCascadingSizeWidget(BS3_BREAKPOINT_KEYS, allowed_units=['px', '%'], required=False), label=_("Adapt Picture Heights"), initial={ 'xs': '100%', 'sm': '100%', 'md': '100%', 'lg': '100%' }, help_text= _("Heights of picture in percent or pixels for distinct Bootstrap's breakpoints." ), ), PartialFormField( 'resize-options', widgets.CheckboxSelectMultiple( choices=BootstrapPicturePlugin.RESIZE_OPTIONS), label=_("Resize Options"), help_text=_("Options to use when resizing the image."), initial=['crop', 'subject_location', 'high_resolution']), ) glossary_fields = ( PartialFormField( 'background-color', ColorPickerWidget(), label=_("Background color"), ), PartialFormField( 'background-repeat', widgets.RadioSelect(choices=[(c, c) for c in REPEAT_CHOICES]), initial='no-repeat', label=_("This property specifies how an image repeates."), ), PartialFormField( 'background-attachment', 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." ), ), PartialFormField( 'background-vertical-position', widgets.Select(choices=[(c, c) for c in VERTICAL_POSITION_CHOICES]), initial='center', label= _("This property moves a background image vertically within its container." ), ), PartialFormField( 'background-horizontal-position', widgets.Select(choices=[(c, c) for c in HORIZONTAL_POSITION_CHOICES]), initial='center', label= _("This property moves a background image horizontally within its container." ), ), PartialFormField( 'background-size', 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."), ), PartialFormField( 'background-width-height', 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: css = {'all': (settings.CMSPLUGIN_CASCADE['fontawesome_css_url'], )} js = resolve_dependencies('cascade/js/admin/jumbotronplugin.js') def get_form(self, request, obj=None, **kwargs): if self.get_parent_instance(request) 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({ 'instance': instance, 'placeholder': placeholder, 'elements': [e for e in elements if 'media' in e] if elements else [], }) return 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 BootstrapPicturePlugin(ImageAnnotationMixin, LinkPluginBase): name = _("Picture") model_mixins = ( ImagePropertyMixin, LinkElementMixin, ) module = 'Bootstrap' parent_classes = ['BootstrapColumnPlugin', 'SimpleWrapperPlugin'] require_parent = True allow_children = False raw_id_fields = ('image_file', ) admin_preview = False ring_plugin = 'PicturePlugin' render_template = 'cascade/bootstrap4/linked-picture.html' default_css_class = 'img-fluid' 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) RESIZE_OPTIONS = [('upscale', _("Upscale image")), ('crop', _("Crop image")), ('subject_location', _("With subject location")), ('high_resolution', _("Optimized for Retina"))] responsive_heights = GlossaryField( MultipleCascadingSizeWidget([bp.name for bp in Breakpoint], allowed_units=['px', '%'], required=False), label=_("Adapt Picture 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." ), ) responsive_zoom = GlossaryField( MultipleCascadingSizeWidget([bp.name for bp in Breakpoint], allowed_units=['%'], required=False), label=_("Adapt Picture Zoom"), initial={ 'xs': '0%', 'sm': '0%', 'md': '0%', 'lg': '0%', 'xl': '0%' }, help_text= _("Magnification of picture in percent for distinct Bootstrap's breakpoints." ), ) 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/pictureplugin.js'] def get_form(self, request, obj=None, **kwargs): LINK_TYPE_CHOICES = [('none', _("No Link"))] LINK_TYPE_CHOICES.extend( 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(BootstrapPicturePlugin, self).get_form(request, obj, **kwargs) 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: context.update({ 'instance': instance, 'is_fluid': True, 'placeholder': placeholder, 'elements': elements, }) return context @classmethod def get_css_classes(cls, obj): css_classes = cls.super(BootstrapPicturePlugin, 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(BootstrapPicturePlugin, cls).get_identifier(obj) try: content = force_text(obj.image) except AttributeError: content = _("No Picture") return format_html('{0}{1}', identifier, content) @classmethod def sanitize_model(cls, obj): sanitized = False parent = obj.parent while parent.plugin_type != 'BootstrapColumnPlugin': parent = parent.parent if parent is None: logger.warning( "PicturePlugin(pk={}) has no ColumnPlugin as ancestor.". format(obj.pk)) return grid_column = parent.get_bound_plugin().get_grid_instance() obj.glossary.setdefault('media_queries', {}) for bp in Breakpoint: obj.glossary['media_queries'].setdefault(bp.name, {}) width = round(grid_column.get_bound(bp).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