Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions cmsplugin_cascade/bootstrap5/accordion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from django.forms import widgets, BooleanField, CharField
from django.forms.fields import IntegerField
from django.utils.translation import gettext_lazy as _, ngettext
from django.utils.safestring import mark_safe
from django.utils.text import Truncator
from django.utils.html import escape
from entangled.forms import EntangledModelFormMixin
from cms.plugin_pool import plugin_pool
from cmsplugin_cascade.forms import ManageChildrenFormMixin
from cmsplugin_cascade.plugin_base import TransparentWrapper, TransparentContainer
from cmsplugin_cascade.widgets import NumberInputWidget
from .plugin_base import BootstrapPluginBase


class AccordionFormMixin(ManageChildrenFormMixin, EntangledModelFormMixin):
num_children = IntegerField(
min_value=1,
initial=1,
widget=NumberInputWidget(attrs={'size': '3', 'style': 'width: 5em !important;'}),
label=_("Groups"),
help_text=_("Number of groups for this accordion."),
)

close_others = BooleanField(
label=_("Close others"),
initial=True,
required=False,
help_text=_("Open only one item at a time.")
)

first_is_open = BooleanField(
label=_("First open"),
initial=True,
required=False,
help_text=_("Start with the first item open.")
)

class Meta:
untangled_fields = ['num_children']
entangled_fields = {'glossary': ['close_others', 'first_is_open']}


class BootstrapAccordionPlugin(TransparentWrapper, BootstrapPluginBase):
name = _("Accordion")
default_css_class = 'accordion'
require_parent = True
parent_classes = ['BootstrapColumnPlugin']
direct_child_classes = ['BootstrapAccordionGroupPlugin']
allow_children = True
form = AccordionFormMixin
render_template = 'cascade/bootstrap5/{}accordion.html'

@classmethod
def get_identifier(cls, obj):
num_cards = obj.get_num_children()
content = ngettext('with {0} item', 'with {0} items', num_cards).format(num_cards)
return mark_safe(content)

def render(self, context, instance, placeholder):
context = self.super(BootstrapAccordionPlugin, self).render(context, instance, placeholder)
context.update({
'close_others': instance.glossary.get('close_others', True),
'first_is_open': instance.glossary.get('first_is_open', True),
})
return context

def save_model(self, request, obj, form, change):
wanted_children = int(form.cleaned_data.get('num_children'))
super().save_model(request, obj, form, change)
self.extend_children(obj, wanted_children, BootstrapAccordionGroupPlugin)

plugin_pool.register_plugin(BootstrapAccordionPlugin)


class AccordionGroupFormMixin(EntangledModelFormMixin):
heading = CharField(
label=_("Heading"),
widget=widgets.TextInput(attrs={'size': 80}),
)

body_padding = BooleanField(
label=_("Body with padding"),
initial=True,
required=False,
help_text=_("Add standard padding to item body."),
)

class Meta:
entangled_fields = {'glossary': ['heading', 'body_padding']}

def clean_heading(self):
return escape(self.cleaned_data['heading'])


class BootstrapAccordionGroupPlugin(TransparentContainer, BootstrapPluginBase):
name = _("Accordion Group")
direct_parent_classes = parent_classes = ['BootstrapAccordionPlugin']
render_template = 'cascade/generic/naked.html'
require_parent = True
form = AccordionGroupFormMixin
alien_child_classes = True

@classmethod
def get_identifier(cls, instance):
heading = instance.glossary.get('heading', '')
return Truncator(heading).words(3, truncate=' ...')

def render(self, context, instance, placeholder):
context = self.super(BootstrapAccordionGroupPlugin, self).render(context, instance, placeholder)
context.update({
'heading': mark_safe(instance.glossary.get('heading', '')),
'no_body_padding': not instance.glossary.get('body_padding', True),
})
return context

plugin_pool.register_plugin(BootstrapAccordionGroupPlugin)
181 changes: 181 additions & 0 deletions cmsplugin_cascade/bootstrap5/buttons.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
from django.forms import widgets
from django.forms.fields import BooleanField, CharField, ChoiceField, MultipleChoiceField
from django.utils.html import format_html
from django.utils.translation import gettext, gettext_lazy as _
from entangled.forms import EntangledModelFormMixin
from cms.plugin_pool import plugin_pool
from cmsplugin_cascade.icon.plugin_base import IconPluginMixin
from cmsplugin_cascade.icon.forms import IconFormMixin
from cmsplugin_cascade.link.config import LinkPluginBase, LinkFormMixin
from cmsplugin_cascade.link.plugin_base import LinkElementMixin


class ButtonTypeWidget(widgets.RadioSelect):
"""
Render sample buttons in different colors in the button's backend editor.
"""
template_name = 'cascade/admin/widgets/button_types.html'


class ButtonSizeWidget(widgets.RadioSelect):
"""
Render sample buttons in different sizes in the button's backend editor.
"""
template_name = 'cascade/admin/widgets/button_sizes.html'


class ButtonFormMixin(EntangledModelFormMixin):
BUTTON_TYPES = [
('btn-primary', _("Primary")),
('btn-secondary', _("Secondary")),
('btn-success', _("Success")),
('btn-danger', _("Danger")),
('btn-warning', _("Warning")),
('btn-info', _("Info")),
('btn-light', _("Light")),
('btn-dark', _("Dark")),
('btn-link', _("Link")),
('btn-outline-primary', _("Primary")),
('btn-outline-secondary', _("Secondary")),
('btn-outline-success', _("Success")),
('btn-outline-danger', _("Danger")),
('btn-outline-warning', _("Warning")),
('btn-outline-info', _("Info")),
('btn-outline-light', _("Light")),
('btn-outline-dark', _("Dark")),
('btn-outline-link', _("Link")),
]

BUTTON_SIZES = [
('btn-lg', _("Large button")),
('', _("Default button")),
('btn-sm', _("Small button")),
]

link_content = CharField(
required=False,
label=_("Button Content"),
widget=widgets.TextInput(attrs={'size': 50}),
)

button_type = ChoiceField(
label=_("Button Type"),
widget=ButtonTypeWidget(choices=BUTTON_TYPES),
choices=BUTTON_TYPES,
initial='btn-primary',
help_text=_("Display Link using this Button Style")
)

button_size = ChoiceField(
label=_("Button Size"),
widget=ButtonSizeWidget(choices=BUTTON_SIZES),
choices=BUTTON_SIZES,
initial='',
required=False,
help_text=_("Display Link using this Button Size")
)

button_options = MultipleChoiceField(
label=_("Button Options"),
choices=[
('disabled', _('Disabled')),
],
required=False,
widget=widgets.CheckboxSelectMultiple,
)

stretched_link = BooleanField(
label=_("Stretched link"),
required=False,
help_text=_("Stretched-link utility to make any anchor the size of it’s nearest position: " \
"relative parent, perfect for entirely clickable cards!")
)

icon_align = ChoiceField(
label=_("Icon alignment"),
choices=[
('icon-left', _("Icon placed left")),
('icon-right', _("Icon placed right")),
],
widget=widgets.RadioSelect,
initial='icon-right',
help_text=_("Add an Icon before or after the button content."),
)

class Meta:
entangled_fields = {'glossary': ['link_content', 'button_type', 'button_size', 'button_options', 'icon_align',
'stretched_link']}


class BootstrapButtonMixin(IconPluginMixin):
require_parent = True
parent_classes = ['BootstrapColumnPlugin', 'SimpleWrapperPlugin']
render_template = 'cascade/bootstrap5/button.html'
allow_children = False
default_css_class = 'btn'
default_css_attributes = ['button_type', 'button_size', 'button_options', 'stretched_link']
ring_plugin = 'ButtonMixin'

class Media:
css = {'all': ['cascade/css/admin/iconplugin.css']}
js = ['admin/js/jquery.init.js', 'cascade/js/admin/buttonmixin.js']

def render(self, context, instance, placeholder):
context = super().render(context, instance, placeholder)
if 'icon_font_class' in context:
mini_template = '{0}<i class="{1} {2}" aria-hidden="true"></i>{3}'
icon_align = instance.glossary.get('icon_align')
if icon_align == 'icon-left':
context['icon_left'] = format_html(mini_template, '', context['icon_font_class'], 'cascade-icon-left',
' ')
elif icon_align == 'icon-right':
context['icon_right'] = format_html(mini_template, ' ', context['icon_font_class'],
'cascade-icon-right', '')
return context


class BootstrapButtonFormMixin(LinkFormMixin, IconFormMixin, ButtonFormMixin):
require_link = False
require_icon = False


class BootstrapButtonPlugin(BootstrapButtonMixin, LinkPluginBase):
module = 'Bootstrap'
name = _("Button")
model_mixins = (LinkElementMixin,)
form = BootstrapButtonFormMixin
ring_plugin = 'ButtonPlugin'
DEFAULT_BUTTON_ATTRIBUTES = {'role': 'button'}

class Media:
js = ['admin/js/jquery.init.js', 'cascade/js/admin/buttonplugin.js']

@classmethod
def get_identifier(cls, instance):
content = instance.glossary.get('link_content')
if not content:
try:
button_types = dict(ButtonFormMixin.BUTTON_TYPES)
content = str(button_types[instance.glossary['button_type']])
except KeyError:
content = gettext("Empty")
return content

@classmethod
def get_css_classes(cls, obj):
css_classes = cls.super(BootstrapButtonPlugin, cls).get_css_classes(obj)
if obj.glossary.get('stretched_link'):
css_classes.append('stretched_link')
return css_classes

@classmethod
def get_html_tag_attributes(cls, obj):
attributes = cls.super(BootstrapButtonPlugin, cls).get_html_tag_attributes(obj)
attributes.update(cls.DEFAULT_BUTTON_ATTRIBUTES)
return attributes

def render(self, context, instance, placeholder):
context = self.super(BootstrapButtonPlugin, self).render(context, instance, placeholder)
return context

plugin_pool.register_plugin(BootstrapButtonPlugin)
65 changes: 65 additions & 0 deletions cmsplugin_cascade/bootstrap5/card.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from django.utils.translation import gettext_lazy as _
from cms.plugin_pool import plugin_pool
from cmsplugin_cascade.plugin_base import TransparentContainer, TransparentWrapper
from cmsplugin_cascade.bootstrap5.plugin_base import BootstrapPluginBase


class CardChildBase(BootstrapPluginBase):
require_parent = True
parent_classes = ['BootstrapCardPlugin']
allow_children = True
render_template = 'cascade/generic/wrapper.html'
child_classes = ['BootstrapCardHeaderPlugin', 'BootstrapCardBodyPlugin', 'BootstrapCardFooterPlugin']


class BootstrapCardHeaderPlugin(TransparentContainer, CardChildBase):
name = _("Card Header")
default_css_class = 'card-header'

plugin_pool.register_plugin(BootstrapCardHeaderPlugin)


class BootstrapCardBodyPlugin(TransparentContainer, CardChildBase):
name = _("Card Body")
default_css_class = 'card-body'

plugin_pool.register_plugin(BootstrapCardBodyPlugin)


class BootstrapCardFooterPlugin(TransparentContainer, CardChildBase):
name = _("Card Footer")
default_css_class = 'card-footer'

plugin_pool.register_plugin(BootstrapCardFooterPlugin)


class BootstrapCardPlugin(TransparentWrapper, BootstrapPluginBase):
"""
Use this plugin to display a card with optional card-header and card-footer.
"""
name = _("Card")
default_css_class = 'card'
require_parent = False
parent_classes = ['BootstrapColumnPlugin']
allow_children = True
render_template = 'cascade/bootstrap5/card.html'

@classmethod
def get_identifier(cls, instance):
try:
return instance.card_header or instance.card_footer
except AttributeError:
pass
return super().get_identifier(instance)

@classmethod
def get_child_classes(cls, slot, page, instance=None):
"""Restrict child classes of Card to one of each: Header, Body and Footer"""
child_classes = super().get_child_classes(slot, page, instance)
# allow only one child of type Header, Body, Footer
for child in instance.get_children():
if child.plugin_type in child_classes:
child_classes.remove(child.plugin_type)
return child_classes

plugin_pool.register_plugin(BootstrapCardPlugin)
Loading