from __future__ import unicode_literals
from random import randint
from django.template import Template
from django.template.defaultfilters import slugify
from django.template.loader import render_to_string
from .compatibility import text_type
from .layout import Div, Field, LayoutObject, TemplateNameMixin
from .utils import TEMPLATE_PACK, flatatt, render_field
[docs]class PrependedAppendedText(Field):
template = "%s/layout/prepended_appended_text.html"
def __init__(self, field, prepended_text=None, appended_text=None, *args, **kwargs):
self.field = field
self.appended_text = appended_text
self.prepended_text = prepended_text
if 'active' in kwargs:
self.active = kwargs.pop('active')
self.input_size = None
css_class = kwargs.get('css_class', '')
if 'input-lg' in css_class:
self.input_size = 'input-lg'
if 'input-sm' in css_class:
self.input_size = 'input-sm'
super(PrependedAppendedText, self).__init__(field, *args, **kwargs)
def render(self, form, form_style, context, template_pack=TEMPLATE_PACK, extra_context=None, **kwargs):
extra_context = extra_context.copy() if extra_context is not None else {}
extra_context.update({
'crispy_appended_text': self.appended_text,
'crispy_prepended_text': self.prepended_text,
'input_size': self.input_size,
'active': getattr(self, "active", False)
})
if hasattr(self, 'wrapper_class'):
extra_context['wrapper_class'] = self.wrapper_class
template = self.get_template_name(template_pack)
return render_field(
self.field, form, form_style, context,
template=template, attrs=self.attrs,
template_pack=template_pack, extra_context=extra_context, **kwargs
)
[docs]class AppendedText(PrependedAppendedText):
def __init__(self, field, text, *args, **kwargs):
kwargs.pop('appended_text', None)
kwargs.pop('prepended_text', None)
self.text = text
super(AppendedText, self).__init__(field, appended_text=text, **kwargs)
[docs]class PrependedText(PrependedAppendedText):
def __init__(self, field, text, *args, **kwargs):
kwargs.pop('appended_text', None)
kwargs.pop('prepended_text', None)
self.text = text
super(PrependedText, self).__init__(field, prepended_text=text, **kwargs)
[docs]class InlineCheckboxes(Field):
"""
Layout object for rendering checkboxes inline::
InlineCheckboxes('field_name')
"""
template = "%s/layout/checkboxselectmultiple_inline.html"
def render(self, form, form_style, context, template_pack=TEMPLATE_PACK, **kwargs):
return super(InlineCheckboxes, self).render(
form, form_style, context, template_pack=template_pack,
extra_context={'inline_class': 'inline'}
)
[docs]class InlineRadios(Field):
"""
Layout object for rendering radiobuttons inline::
InlineRadios('field_name')
"""
template = "%s/layout/radioselect_inline.html"
def render(self, form, form_style, context, template_pack=TEMPLATE_PACK, **kwargs):
return super(InlineRadios, self).render(
form, form_style, context, template_pack=template_pack,
extra_context={'inline_class': 'inline'}
)
[docs]class Container(Div):
"""
Base class used for `Tab` and `AccordionGroup`, represents a basic container concept
"""
css_class = ""
def __init__(self, name, *fields, **kwargs):
super(Container, self).__init__(*fields, **kwargs)
self.template = kwargs.pop('template', self.template)
self.name = name
self._active_originally_included = "active" in kwargs
self.active = kwargs.pop("active", False)
if not self.css_id:
self.css_id = slugify(self.name)
def __contains__(self, field_name):
"""
check if field_name is contained within tab.
"""
return field_name in map(lambda pointer: pointer[1], self.get_field_names())
def render(self, form, form_style, context, template_pack=TEMPLATE_PACK, **kwargs):
if self.active:
if not 'active' in self.css_class:
self.css_class += ' active'
else:
self.css_class = self.css_class.replace('active', '')
return super(Container, self).render(form, form_style, context, template_pack)
[docs]class ContainerHolder(Div):
"""
Base class used for `TabHolder` and `Accordion`, groups containers
"""
[docs] def first_container_with_errors(self, errors):
"""
Returns the first container with errors, otherwise returns None.
"""
for tab in self.fields:
errors_here = any(error in tab for error in errors)
if errors_here:
return tab
return None
[docs]class Tab(Container):
"""
Tab object. It wraps fields in a div whose default class is "tab-pane" and
takes a name as first argument. Example::
Tab('tab_name', 'form_field_1', 'form_field_2', 'form_field_3')
"""
css_class = 'tab-pane'
link_template = '%s/layout/tab-link.html'
[docs] def render_link(self, template_pack=TEMPLATE_PACK, **kwargs):
"""
Render the link for the tab-pane. It must be called after render so css_class is updated
with active if needed.
"""
link_template = self.link_template % template_pack
return render_to_string(link_template, {'link': self})
[docs]class TabHolder(ContainerHolder):
"""
TabHolder object. It wraps Tab objects in a container. Requires bootstrap-tab.js::
TabHolder(
Tab('form_field_1', 'form_field_2'),
Tab('form_field_3')
)
"""
template = '%s/layout/tab.html'
def render(self, form, form_style, context, template_pack=TEMPLATE_PACK, **kwargs):
for tab in self.fields:
tab.active = False
# Open the group that should be open.
self.open_target_group_for_form(form)
content = self.get_rendered_fields(form, form_style, context, template_pack)
links = ''.join(tab.render_link(template_pack) for tab in self.fields)
context.update({
'tabs': self,
'links': links,
'content': content
})
template = self.get_template_name(template_pack)
return render_to_string(template, context.flatten())
[docs]class AccordionGroup(Container):
"""
Accordion Group (pane) object. It wraps given fields inside an accordion
tab. It takes accordion tab name as first argument::
AccordionGroup("group name", "form_field_1", "form_field_2")
"""
template = "%s/accordion-group.html"
data_parent = "" # accordion parent div id.
[docs]class Accordion(ContainerHolder):
"""
Accordion menu object. It wraps `AccordionGroup` objects in a container::
Accordion(
AccordionGroup("group name", "form_field_1", "form_field_2"),
AccordionGroup("another group name", "form_field")
)
"""
template = "%s/accordion.html"
def render(self, form, form_style, context, template_pack=TEMPLATE_PACK, **kwargs):
content = ''
# accordion group needs the parent div id to set `data-parent` (I don't
# know why). This needs to be a unique id
if not self.css_id:
self.css_id = "-".join(["accordion", text_type(randint(1000, 9999))])
# Open the group that should be open.
self.open_target_group_for_form(form)
for group in self.fields:
group.data_parent = self.css_id
content += render_field(
group, form, form_style, context, template_pack=template_pack, **kwargs
)
template = self.get_template_name(template_pack)
context.update({'accordion': self, 'content': content})
return render_to_string(template, context.flatten())
[docs]class Alert(Div):
"""
`Alert` generates markup in the form of an alert dialog
Alert(content='<strong>Warning!</strong> Best check yo self, you're not looking too good.')
"""
template = "%s/layout/alert.html"
css_class = "alert"
def __init__(self, content, dismiss=True, block=False, **kwargs):
fields = []
if block:
self.css_class += ' alert-block'
Div.__init__(self, *fields, **kwargs)
self.template = kwargs.pop('template', self.template)
self.content = content
self.dismiss = dismiss
def render(self, form, form_style, context, template_pack=TEMPLATE_PACK, **kwargs):
template = self.get_template_name(template_pack)
context.update({'alert': self, 'content': self.content, 'dismiss': self.dismiss})
return render_to_string(template, context.flatten())
[docs]class UneditableField(Field):
"""
Layout object for rendering fields as uneditable in bootstrap
Example::
UneditableField('field_name', css_class="input-xlarge")
"""
template = "%s/layout/uneditable_input.html"
def __init__(self, field, *args, **kwargs):
self.attrs = {'class': 'uneditable-input'}
super(UneditableField, self).__init__(field, *args, **kwargs)
[docs]class InlineField(Field):
template = "%s/layout/inline_field.html"