Added django-bootstrap3 to requirements.txt; removed local copy of django-bootstrap3.
- Legacy-Id: 9372
This commit is contained in:
parent
70b4ecf7b8
commit
0301056c9f
|
@ -1,3 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
__version__ = '5.1.1'
|
|
@ -1,106 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.importlib import import_module
|
||||
|
||||
|
||||
# Default settings
|
||||
BOOTSTRAP3_DEFAULTS = {
|
||||
'jquery_url': '//code.jquery.com/jquery.min.js',
|
||||
'base_url': '//netdna.bootstrapcdn.com/bootstrap/3.3.2/',
|
||||
'css_url': None,
|
||||
'theme_url': None,
|
||||
'javascript_url': None,
|
||||
'javascript_in_head': False,
|
||||
'include_jquery': False,
|
||||
'horizontal_label_class': 'col-md-2',
|
||||
'horizontal_field_class': 'col-md-4',
|
||||
'set_required': True,
|
||||
'set_placeholder': True,
|
||||
'required_css_class': '',
|
||||
'error_css_class': 'has-error',
|
||||
'success_css_class': 'has-success',
|
||||
'formset_renderers': {
|
||||
'default': 'bootstrap3.renderers.FormsetRenderer',
|
||||
},
|
||||
'form_renderers': {
|
||||
'default': 'bootstrap3.renderers.FormRenderer',
|
||||
},
|
||||
'field_renderers': {
|
||||
'default': 'bootstrap3.renderers.FieldRenderer',
|
||||
'inline': 'bootstrap3.renderers.InlineFieldRenderer',
|
||||
},
|
||||
}
|
||||
|
||||
# Start with a copy of default settings
|
||||
BOOTSTRAP3 = BOOTSTRAP3_DEFAULTS.copy()
|
||||
|
||||
# Override with user settings from settings.py
|
||||
BOOTSTRAP3.update(getattr(settings, 'BOOTSTRAP3', {}))
|
||||
|
||||
|
||||
def get_bootstrap_setting(setting, default=None):
|
||||
"""
|
||||
Read a setting
|
||||
"""
|
||||
return BOOTSTRAP3.get(setting, default)
|
||||
|
||||
|
||||
def bootstrap_url(postfix):
|
||||
"""
|
||||
Prefix a relative url with the bootstrap base url
|
||||
"""
|
||||
return get_bootstrap_setting('base_url') + postfix
|
||||
|
||||
|
||||
def jquery_url():
|
||||
"""
|
||||
Return the full url to jQuery file to use
|
||||
"""
|
||||
return get_bootstrap_setting('jquery_url')
|
||||
|
||||
|
||||
def javascript_url():
|
||||
"""
|
||||
Return the full url to the Bootstrap JavaScript file
|
||||
"""
|
||||
return get_bootstrap_setting('javascript_url') or \
|
||||
bootstrap_url('js/bootstrap.min.js')
|
||||
|
||||
|
||||
def css_url():
|
||||
"""
|
||||
Return the full url to the Bootstrap CSS file
|
||||
"""
|
||||
return get_bootstrap_setting('css_url') or \
|
||||
bootstrap_url('css/bootstrap.min.css')
|
||||
|
||||
|
||||
def theme_url():
|
||||
"""
|
||||
Return the full url to the theme CSS file
|
||||
"""
|
||||
return get_bootstrap_setting('theme_url')
|
||||
|
||||
|
||||
def get_renderer(renderers, **kwargs):
|
||||
layout = kwargs.get('layout', '')
|
||||
path = renderers.get(layout, renderers['default'])
|
||||
mod, cls = path.rsplit(".", 1)
|
||||
return getattr(import_module(mod), cls)
|
||||
|
||||
|
||||
def get_formset_renderer(**kwargs):
|
||||
renderers = get_bootstrap_setting('formset_renderers')
|
||||
return get_renderer(renderers, **kwargs)
|
||||
|
||||
|
||||
def get_form_renderer(**kwargs):
|
||||
renderers = get_bootstrap_setting('form_renderers')
|
||||
return get_renderer(renderers, **kwargs)
|
||||
|
||||
|
||||
def get_field_renderer(**kwargs):
|
||||
renderers = get_bootstrap_setting('field_renderers')
|
||||
return get_renderer(renderers, **kwargs)
|
|
@ -1,37 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.forms.widgets import flatatt
|
||||
|
||||
from .text import text_value
|
||||
|
||||
|
||||
def render_icon(icon, title=''):
|
||||
"""
|
||||
Render a Bootstrap glyphicon icon
|
||||
"""
|
||||
attrs = {
|
||||
'class': 'glyphicon glyphicon-{icon}'.format(icon=icon),
|
||||
}
|
||||
if title:
|
||||
attrs['title'] = title
|
||||
return '<span{attrs}></span>'.format(attrs=flatatt(attrs))
|
||||
|
||||
|
||||
def render_alert(content, alert_type=None, dismissable=True):
|
||||
"""
|
||||
Render a Bootstrap alert
|
||||
"""
|
||||
button = ''
|
||||
if not alert_type:
|
||||
alert_type = 'info'
|
||||
css_classes = ['alert', 'alert-' + text_value(alert_type)]
|
||||
if dismissable:
|
||||
css_classes.append('alert-dismissable')
|
||||
button = '<button type="button" class="close" ' + \
|
||||
'data-dismiss="alert" aria-hidden="true">×</button>'
|
||||
return '<div class="{css_classes}">{button}{content}</div>'.format(
|
||||
css_classes=' '.join(css_classes),
|
||||
button=button,
|
||||
content=text_value(content),
|
||||
)
|
|
@ -1,16 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
class BootstrapException(Exception):
|
||||
"""
|
||||
Any exception from this package
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class BootstrapError(BootstrapException):
|
||||
"""
|
||||
Any exception that is an error
|
||||
"""
|
||||
pass
|
|
@ -1,173 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.admin.widgets import AdminFileWidget
|
||||
from django.forms import (
|
||||
HiddenInput, FileInput, CheckboxSelectMultiple, Textarea, TextInput
|
||||
)
|
||||
|
||||
from .bootstrap import (
|
||||
get_bootstrap_setting, get_form_renderer, get_field_renderer,
|
||||
get_formset_renderer
|
||||
)
|
||||
from .text import text_concat, text_value
|
||||
from .exceptions import BootstrapError
|
||||
from .utils import add_css_class, render_tag
|
||||
from .components import render_icon
|
||||
|
||||
|
||||
FORM_GROUP_CLASS = 'form-group'
|
||||
|
||||
|
||||
def render_formset(formset, **kwargs):
|
||||
"""
|
||||
Render a formset to a Bootstrap layout
|
||||
"""
|
||||
renderer_cls = get_formset_renderer(**kwargs)
|
||||
return renderer_cls(formset, **kwargs).render()
|
||||
|
||||
|
||||
def render_formset_errors(form, **kwargs):
|
||||
"""
|
||||
Render formset errors to a Bootstrap layout
|
||||
"""
|
||||
renderer_cls = get_formset_renderer(**kwargs)
|
||||
return renderer_cls(form, **kwargs).render_errors()
|
||||
|
||||
|
||||
def render_form(form, **kwargs):
|
||||
"""
|
||||
Render a formset to a Bootstrap layout
|
||||
"""
|
||||
renderer_cls = get_form_renderer(**kwargs)
|
||||
return renderer_cls(form, **kwargs).render()
|
||||
|
||||
|
||||
def render_form_errors(form, type='all', **kwargs):
|
||||
"""
|
||||
Render form errors to a Bootstrap layout
|
||||
"""
|
||||
renderer_cls = get_form_renderer(**kwargs)
|
||||
return renderer_cls(form, **kwargs).render_errors(type)
|
||||
|
||||
|
||||
def render_field(field, **kwargs):
|
||||
"""
|
||||
Render a formset to a Bootstrap layout
|
||||
"""
|
||||
renderer_cls = get_field_renderer(**kwargs)
|
||||
return renderer_cls(field, **kwargs).render()
|
||||
|
||||
|
||||
def render_label(content, label_for=None, label_class=None, label_title=''):
|
||||
"""
|
||||
Render a label with content
|
||||
"""
|
||||
attrs = {}
|
||||
if label_for:
|
||||
attrs['for'] = label_for
|
||||
if label_class:
|
||||
attrs['class'] = label_class
|
||||
if label_title:
|
||||
attrs['title'] = label_title
|
||||
return render_tag('label', attrs=attrs, content=content)
|
||||
|
||||
|
||||
def render_button(
|
||||
content, button_type=None, icon=None, button_class='', size='',
|
||||
href=''):
|
||||
"""
|
||||
Render a button with content
|
||||
"""
|
||||
attrs = {}
|
||||
classes = add_css_class('btn', button_class)
|
||||
size = text_value(size).lower().strip()
|
||||
if size == 'xs':
|
||||
classes = add_css_class(classes, 'btn-xs')
|
||||
elif size == 'sm' or size == 'small':
|
||||
classes = add_css_class(classes, 'btn-sm')
|
||||
elif size == 'lg' or size == 'large':
|
||||
classes = add_css_class(classes, 'btn-lg')
|
||||
elif size == 'md' or size == 'medium':
|
||||
pass
|
||||
elif size:
|
||||
raise BootstrapError(
|
||||
'Parameter "size" should be "xs", "sm", "lg" or ' +
|
||||
'empty ("{}" given).'.format(size))
|
||||
if button_type:
|
||||
if button_type == 'submit':
|
||||
classes = add_css_class(classes, 'btn-primary')
|
||||
elif button_type not in ('reset', 'button', 'link'):
|
||||
raise BootstrapError(
|
||||
'Parameter "button_type" should be "submit", "reset", ' +
|
||||
'"button", "link" or empty ("{}" given).'.format(button_type))
|
||||
attrs['type'] = button_type
|
||||
attrs['class'] = classes
|
||||
icon_content = render_icon(icon) if icon else ''
|
||||
if href:
|
||||
attrs['href'] = href
|
||||
tag = 'a'
|
||||
else:
|
||||
tag = 'button'
|
||||
return render_tag(
|
||||
tag, attrs=attrs, content=text_concat(
|
||||
icon_content, content, separator=' '))
|
||||
|
||||
|
||||
def render_field_and_label(
|
||||
field, label, field_class='', label_for=None, label_class='',
|
||||
layout='', **kwargs):
|
||||
"""
|
||||
Render a field with its label
|
||||
"""
|
||||
if layout == 'horizontal':
|
||||
if not label_class:
|
||||
label_class = get_bootstrap_setting('horizontal_label_class')
|
||||
if not field_class:
|
||||
field_class = get_bootstrap_setting('horizontal_field_class')
|
||||
if not label:
|
||||
label = ' '
|
||||
label_class = add_css_class(label_class, 'control-label')
|
||||
html = field
|
||||
if field_class:
|
||||
html = '<div class="{klass}">{html}</div>'.format(
|
||||
klass=field_class, html=html)
|
||||
if label:
|
||||
html = render_label(
|
||||
label, label_for=label_for, label_class=label_class) + html
|
||||
return html
|
||||
|
||||
|
||||
def render_form_group(content, css_class=FORM_GROUP_CLASS):
|
||||
"""
|
||||
Render a Bootstrap form group
|
||||
"""
|
||||
return '<div class="{klass}">{content}</div>'.format(
|
||||
klass=css_class,
|
||||
content=content,
|
||||
)
|
||||
|
||||
|
||||
def is_widget_required_attribute(widget):
|
||||
"""
|
||||
Is this widget required?
|
||||
"""
|
||||
if not get_bootstrap_setting('set_required'):
|
||||
return False
|
||||
if not widget.is_required:
|
||||
return False
|
||||
if isinstance(
|
||||
widget, (
|
||||
AdminFileWidget, HiddenInput, FileInput,
|
||||
CheckboxSelectMultiple)):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def is_widget_with_placeholder(widget):
|
||||
"""
|
||||
Is this a widget that should have a placeholder?
|
||||
Only text, search, url, tel, e-mail, password, number have placeholders
|
||||
These are all derived form TextInput, except for Textarea
|
||||
"""
|
||||
return isinstance(widget, (TextInput, Textarea))
|
|
@ -1,3 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Empty models.py, required file for Django tests
|
|
@ -1,485 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
from django.contrib.auth.forms import ReadOnlyPasswordHashWidget
|
||||
|
||||
from django.forms import (
|
||||
TextInput, DateInput, FileInput, CheckboxInput,
|
||||
ClearableFileInput, Select, RadioSelect, CheckboxSelectMultiple
|
||||
)
|
||||
from django.forms.extras import SelectDateWidget
|
||||
from django.forms.forms import BaseForm, BoundField
|
||||
from django.forms.formsets import BaseFormSet
|
||||
from django.utils.html import conditional_escape, strip_tags
|
||||
from django.template import Context
|
||||
from django.template.loader import get_template
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from .bootstrap import get_bootstrap_setting
|
||||
from .text import text_value
|
||||
from .exceptions import BootstrapError
|
||||
from .utils import add_css_class
|
||||
from .forms import (
|
||||
render_form, render_field, render_label, render_form_group,
|
||||
is_widget_with_placeholder, is_widget_required_attribute, FORM_GROUP_CLASS
|
||||
)
|
||||
|
||||
|
||||
class BaseRenderer(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.layout = kwargs.get('layout', '')
|
||||
self.form_group_class = kwargs.get(
|
||||
'form_group_class', FORM_GROUP_CLASS)
|
||||
self.field_class = kwargs.get('field_class', '')
|
||||
self.label_class = kwargs.get('label_class', '')
|
||||
self.show_help = kwargs.get('show_help', True)
|
||||
self.show_label = kwargs.get('show_label', True)
|
||||
self.exclude = kwargs.get('exclude', '')
|
||||
self.set_required = kwargs.get('set_required', True)
|
||||
self.size = self.parse_size(kwargs.get('size', ''))
|
||||
self.horizontal_label_class = kwargs.get(
|
||||
'horizontal_label_class',
|
||||
get_bootstrap_setting('horizontal_label_class')
|
||||
)
|
||||
self.horizontal_field_class = kwargs.get(
|
||||
'horizontal_field_class',
|
||||
get_bootstrap_setting('horizontal_field_class')
|
||||
)
|
||||
|
||||
def parse_size(self, size):
|
||||
size = text_value(size).lower().strip()
|
||||
if size in ('sm', 'small'):
|
||||
return 'small'
|
||||
if size in ('lg', 'large'):
|
||||
return 'large'
|
||||
if size in ('md', 'medium', ''):
|
||||
return 'medium'
|
||||
raise BootstrapError('Invalid value "%s" for parameter "size" (expected "sm", "md", "lg" or "").' % size)
|
||||
|
||||
def get_size_class(self, prefix='input'):
|
||||
if self.size == 'small':
|
||||
return prefix + '-sm'
|
||||
if self.size == 'large':
|
||||
return prefix + '-lg'
|
||||
return ''
|
||||
|
||||
|
||||
class FormsetRenderer(BaseRenderer):
|
||||
"""
|
||||
Default formset renderer
|
||||
"""
|
||||
|
||||
def __init__(self, formset, *args, **kwargs):
|
||||
if not isinstance(formset, BaseFormSet):
|
||||
raise BootstrapError(
|
||||
'Parameter "formset" should contain a valid Django Formset.')
|
||||
self.formset = formset
|
||||
super(FormsetRenderer, self).__init__(*args, **kwargs)
|
||||
|
||||
def render_management_form(self):
|
||||
return text_value(self.formset.management_form)
|
||||
|
||||
def render_form(self, form, **kwargs):
|
||||
return render_form(form, **kwargs)
|
||||
|
||||
def render_forms(self):
|
||||
rendered_forms = []
|
||||
for form in self.formset.forms:
|
||||
rendered_forms.append(self.render_form(
|
||||
form,
|
||||
layout=self.layout,
|
||||
form_group_class=self.form_group_class,
|
||||
field_class=self.field_class,
|
||||
label_class=self.label_class,
|
||||
show_help=self.show_help,
|
||||
exclude=self.exclude,
|
||||
set_required=self.set_required,
|
||||
size=self.size,
|
||||
horizontal_label_class=self.horizontal_label_class,
|
||||
horizontal_field_class=self.horizontal_field_class,
|
||||
))
|
||||
return '\n'.join(rendered_forms)
|
||||
|
||||
def get_formset_errors(self):
|
||||
return self.formset.non_form_errors()
|
||||
|
||||
def render_errors(self):
|
||||
formset_errors = self.get_formset_errors()
|
||||
if formset_errors:
|
||||
return get_template(
|
||||
'bootstrap3/form_errors.html').render(
|
||||
Context({
|
||||
'errors': formset_errors,
|
||||
'form': self.formset,
|
||||
'layout': self.layout,
|
||||
})
|
||||
)
|
||||
return ''
|
||||
|
||||
def render(self):
|
||||
return self.render_errors() + self.render_management_form() + \
|
||||
self.render_forms()
|
||||
|
||||
|
||||
class FormRenderer(BaseRenderer):
|
||||
"""
|
||||
Default form renderer
|
||||
"""
|
||||
|
||||
def __init__(self, form, *args, **kwargs):
|
||||
if not isinstance(form, BaseForm):
|
||||
raise BootstrapError(
|
||||
'Parameter "form" should contain a valid Django Form.')
|
||||
self.form = form
|
||||
super(FormRenderer, self).__init__(*args, **kwargs)
|
||||
# Handle form.empty_permitted
|
||||
if self.form.empty_permitted:
|
||||
self.set_required = False
|
||||
|
||||
def render_fields(self):
|
||||
rendered_fields = []
|
||||
for field in self.form:
|
||||
rendered_fields.append(render_field(
|
||||
field,
|
||||
layout=self.layout,
|
||||
form_group_class=self.form_group_class,
|
||||
field_class=self.field_class,
|
||||
label_class=self.label_class,
|
||||
show_help=self.show_help,
|
||||
exclude=self.exclude,
|
||||
set_required=self.set_required,
|
||||
size=self.size,
|
||||
horizontal_label_class=self.horizontal_label_class,
|
||||
horizontal_field_class=self.horizontal_field_class,
|
||||
))
|
||||
return '\n'.join(rendered_fields)
|
||||
|
||||
def get_fields_errors(self):
|
||||
form_errors = []
|
||||
for field in self.form:
|
||||
if field.is_hidden and field.errors:
|
||||
form_errors += field.errors
|
||||
return form_errors
|
||||
|
||||
def render_errors(self, type='all'):
|
||||
form_errors = None
|
||||
if type == 'all':
|
||||
form_errors = self.get_fields_errors() + \
|
||||
self.form.non_field_errors()
|
||||
elif type == 'fields':
|
||||
form_errors = self.get_fields_errors()
|
||||
elif type == 'non_fields':
|
||||
form_errors = self.form.non_field_errors()
|
||||
|
||||
if form_errors:
|
||||
return get_template(
|
||||
'bootstrap3/form_errors.html').render(
|
||||
Context({
|
||||
'errors': form_errors,
|
||||
'form': self.form,
|
||||
'layout': self.layout,
|
||||
})
|
||||
)
|
||||
return ''
|
||||
|
||||
def render(self):
|
||||
return self.render_errors() + self.render_fields()
|
||||
|
||||
|
||||
class FieldRenderer(BaseRenderer):
|
||||
"""
|
||||
Default field renderer
|
||||
"""
|
||||
|
||||
def __init__(self, field, *args, **kwargs):
|
||||
if not isinstance(field, BoundField):
|
||||
raise BootstrapError(
|
||||
'Parameter "field" should contain a valid Django BoundField.')
|
||||
self.field = field
|
||||
super(FieldRenderer, self).__init__(*args, **kwargs)
|
||||
|
||||
self.widget = field.field.widget
|
||||
self.initial_attrs = self.widget.attrs.copy()
|
||||
self.field_help = text_value(mark_safe(field.help_text)) \
|
||||
if self.show_help and field.help_text else ''
|
||||
self.field_errors = [conditional_escape(text_value(error))
|
||||
for error in field.errors]
|
||||
|
||||
if get_bootstrap_setting('set_placeholder'):
|
||||
self.placeholder = field.label
|
||||
else:
|
||||
self.placeholder = ''
|
||||
|
||||
self.addon_before = kwargs.get('addon_before', self.initial_attrs.pop('addon_before', ''))
|
||||
self.addon_after = kwargs.get('addon_after', self.initial_attrs.pop('addon_after', ''))
|
||||
|
||||
# These are set in Django or in the global BOOTSTRAP3 settings, and
|
||||
# they can be overwritten in the template
|
||||
error_css_class = kwargs.get('error_css_class', '')
|
||||
required_css_class = kwargs.get('required_css_class', '')
|
||||
bound_css_class = kwargs.get('bound_css_class', '')
|
||||
if error_css_class:
|
||||
self.error_css_class = error_css_class
|
||||
else:
|
||||
self.error_css_class = getattr(
|
||||
field.form, 'error_css_class',
|
||||
get_bootstrap_setting('error_css_class'))
|
||||
if required_css_class:
|
||||
self.required_css_class = required_css_class
|
||||
else:
|
||||
self.required_css_class = getattr(
|
||||
field.form, 'required_css_class',
|
||||
get_bootstrap_setting('required_css_class'))
|
||||
if bound_css_class:
|
||||
self.success_css_class = bound_css_class
|
||||
else:
|
||||
self.success_css_class = getattr(
|
||||
field.form, 'bound_css_class',
|
||||
get_bootstrap_setting('success_css_class'))
|
||||
|
||||
# Handle form.empty_permitted
|
||||
if self.field.form.empty_permitted:
|
||||
self.set_required = False
|
||||
self.required_css_class = ''
|
||||
|
||||
def restore_widget_attrs(self):
|
||||
self.widget.attrs = self.initial_attrs
|
||||
|
||||
def add_class_attrs(self):
|
||||
classes = self.widget.attrs.get('class', '')
|
||||
if isinstance(self.widget, ReadOnlyPasswordHashWidget):
|
||||
classes = add_css_class(
|
||||
classes, 'form-control-static', prepend=True)
|
||||
elif not isinstance(self.widget, (CheckboxInput,
|
||||
RadioSelect,
|
||||
CheckboxSelectMultiple,
|
||||
FileInput)):
|
||||
classes = add_css_class(classes, 'form-control', prepend=True)
|
||||
# For these widget types, add the size class here
|
||||
classes = add_css_class(classes, self.get_size_class())
|
||||
self.widget.attrs['class'] = classes
|
||||
|
||||
def add_placeholder_attrs(self):
|
||||
placeholder = self.widget.attrs.get('placeholder', self.placeholder)
|
||||
if placeholder and is_widget_with_placeholder(self.widget):
|
||||
self.widget.attrs['placeholder'] = placeholder
|
||||
|
||||
def add_help_attrs(self):
|
||||
if not isinstance(self.widget, CheckboxInput):
|
||||
self.widget.attrs['title'] = self.widget.attrs.get(
|
||||
'title', strip_tags(self.field_help))
|
||||
|
||||
def add_required_attrs(self):
|
||||
if self.set_required and is_widget_required_attribute(self.widget):
|
||||
self.widget.attrs['required'] = 'required'
|
||||
|
||||
def add_widget_attrs(self):
|
||||
self.add_class_attrs()
|
||||
self.add_placeholder_attrs()
|
||||
self.add_help_attrs()
|
||||
self.add_required_attrs()
|
||||
|
||||
def list_to_class(self, html, klass):
|
||||
classes = add_css_class(klass, self.get_size_class())
|
||||
mapping = [
|
||||
('<ul', '<div'),
|
||||
('</ul>', '</div>'),
|
||||
('<li', '<div class="{klass}"'.format(klass=classes)),
|
||||
('</li>', '</div>'),
|
||||
]
|
||||
for k, v in mapping:
|
||||
html = html.replace(k, v)
|
||||
return html
|
||||
|
||||
def put_inside_label(self, html):
|
||||
content = '{field} {label}'.format(field=html, label=self.field.label)
|
||||
return render_label(
|
||||
content=content, label_for=self.field.id_for_label,
|
||||
label_title=strip_tags(self.field_help))
|
||||
|
||||
def fix_date_select_input(self, html):
|
||||
div1 = '<div class="col-xs-4">'
|
||||
div2 = '</div>'
|
||||
html = html.replace('<select', div1 + '<select')
|
||||
html = html.replace('</select>', '</select>' + div2)
|
||||
return '<div class="row bootstrap3-multi-input">' + html + '</div>'
|
||||
|
||||
def fix_clearable_file_input(self, html):
|
||||
"""
|
||||
Fix a clearable file input
|
||||
TODO: This needs improvement
|
||||
|
||||
Currently Django returns
|
||||
Currently:
|
||||
<a href="dummy.txt">dummy.txt</a>
|
||||
<input id="file4-clear_id" name="file4-clear" type="checkbox" />
|
||||
<label for="file4-clear_id">Clear</label><br />
|
||||
Change: <input id="id_file4" name="file4" type="file" />
|
||||
<span class=help-block></span>
|
||||
</div>
|
||||
|
||||
"""
|
||||
# TODO This needs improvement
|
||||
return '<div class="row bootstrap3-multi-input">' + \
|
||||
'<div class="col-xs-12">' + html + '</div></div>'
|
||||
|
||||
def post_widget_render(self, html):
|
||||
if isinstance(self.widget, RadioSelect):
|
||||
html = self.list_to_class(html, 'radio')
|
||||
elif isinstance(self.widget, CheckboxSelectMultiple):
|
||||
html = self.list_to_class(html, 'checkbox')
|
||||
elif isinstance(self.widget, SelectDateWidget):
|
||||
html = self.fix_date_select_input(html)
|
||||
elif isinstance(self.widget, ClearableFileInput):
|
||||
html = self.fix_clearable_file_input(html)
|
||||
elif isinstance(self.widget, CheckboxInput):
|
||||
html = self.put_inside_label(html)
|
||||
return html
|
||||
|
||||
def wrap_widget(self, html):
|
||||
if isinstance(self.widget, CheckboxInput):
|
||||
checkbox_class = add_css_class('checkbox', self.get_size_class())
|
||||
html = \
|
||||
'<div class="{klass}">{content}</div>'.format(
|
||||
klass=checkbox_class, content=html
|
||||
)
|
||||
return html
|
||||
|
||||
def make_input_group(self, html):
|
||||
if (
|
||||
(self.addon_before or self.addon_after) and
|
||||
isinstance(self.widget, (TextInput, DateInput, Select))
|
||||
):
|
||||
before = '<span class="input-group-addon">{addon}</span>'.format(
|
||||
addon=self.addon_before) if self.addon_before else ''
|
||||
after = '<span class="input-group-addon">{addon}</span>'.format(
|
||||
addon=self.addon_after) if self.addon_after else ''
|
||||
html = \
|
||||
'<div class="input-group">' + \
|
||||
'{before}{html}{after}</div>'.format(
|
||||
before=before,
|
||||
after=after,
|
||||
html=html
|
||||
)
|
||||
return html
|
||||
|
||||
def append_to_field(self, html):
|
||||
help_text_and_errors = [self.field_help] + self.field_errors \
|
||||
if self.field_help else self.field_errors
|
||||
if help_text_and_errors:
|
||||
help_html = get_template(
|
||||
'bootstrap3/field_help_text_and_errors.html'
|
||||
).render(Context({
|
||||
'field': self.field,
|
||||
'help_text_and_errors': help_text_and_errors,
|
||||
'layout': self.layout,
|
||||
}))
|
||||
html += '<span class="help-block">{help}</span>'.format(
|
||||
help=help_html)
|
||||
return html
|
||||
|
||||
def get_field_class(self):
|
||||
field_class = self.field_class
|
||||
if not field_class and self.layout == 'horizontal':
|
||||
field_class = self.horizontal_field_class
|
||||
return field_class
|
||||
|
||||
def wrap_field(self, html):
|
||||
field_class = self.get_field_class()
|
||||
if field_class:
|
||||
html = '<div class="{klass}">{html}</div>'.format(
|
||||
klass=field_class, html=html)
|
||||
return html
|
||||
|
||||
def get_label_class(self):
|
||||
label_class = self.label_class
|
||||
if not label_class and self.layout == 'horizontal':
|
||||
label_class = self.horizontal_label_class
|
||||
label_class = text_value(label_class)
|
||||
if not self.show_label:
|
||||
label_class = add_css_class(label_class, 'sr-only')
|
||||
return add_css_class(label_class, 'control-label')
|
||||
|
||||
def get_label(self):
|
||||
if isinstance(self.widget, CheckboxInput):
|
||||
label = None
|
||||
else:
|
||||
label = self.field.label
|
||||
if self.layout == 'horizontal' and not label:
|
||||
return ' '
|
||||
return label
|
||||
|
||||
def add_label(self, html):
|
||||
label = self.get_label()
|
||||
if label:
|
||||
html = render_label(
|
||||
label,
|
||||
label_for=self.field.id_for_label,
|
||||
label_class=self.get_label_class()) + html
|
||||
return html
|
||||
|
||||
def get_form_group_class(self):
|
||||
form_group_class = self.form_group_class
|
||||
if self.field.errors and self.error_css_class:
|
||||
form_group_class = add_css_class(
|
||||
form_group_class, self.error_css_class)
|
||||
if self.field.field.required and self.required_css_class:
|
||||
form_group_class = add_css_class(
|
||||
form_group_class, self.required_css_class)
|
||||
if self.field_errors:
|
||||
form_group_class = add_css_class(form_group_class, 'has-error')
|
||||
elif self.field.form.is_bound:
|
||||
form_group_class = add_css_class(
|
||||
form_group_class, self.success_css_class)
|
||||
if self.layout == 'horizontal':
|
||||
form_group_class = add_css_class(
|
||||
form_group_class, self.get_size_class(prefix='form-group'))
|
||||
return form_group_class
|
||||
|
||||
def wrap_label_and_field(self, html):
|
||||
return render_form_group(html, self.get_form_group_class())
|
||||
|
||||
def render(self):
|
||||
# See if we're not excluded
|
||||
if self.field.name in self.exclude.replace(' ', '').split(','):
|
||||
return ''
|
||||
# Hidden input requires no special treatment
|
||||
if self.field.is_hidden:
|
||||
return text_value(self.field)
|
||||
# Render the widget
|
||||
self.add_widget_attrs()
|
||||
html = self.field.as_widget(attrs=self.widget.attrs)
|
||||
self.restore_widget_attrs()
|
||||
# Start post render
|
||||
html = self.post_widget_render(html)
|
||||
html = self.wrap_widget(html)
|
||||
html = self.make_input_group(html)
|
||||
html = self.append_to_field(html)
|
||||
html = self.wrap_field(html)
|
||||
html = self.add_label(html)
|
||||
html = self.wrap_label_and_field(html)
|
||||
return html
|
||||
|
||||
|
||||
class InlineFieldRenderer(FieldRenderer):
|
||||
"""
|
||||
Inline field renderer
|
||||
"""
|
||||
|
||||
def add_error_attrs(self):
|
||||
field_title = self.widget.attrs.get('title', '')
|
||||
field_title += ' ' + ' '.join(
|
||||
[strip_tags(e) for e in self.field_errors])
|
||||
self.widget.attrs['title'] = field_title.strip()
|
||||
|
||||
def add_widget_attrs(self):
|
||||
super(InlineFieldRenderer, self).add_widget_attrs()
|
||||
self.add_error_attrs()
|
||||
|
||||
def append_to_field(self, html):
|
||||
return html
|
||||
|
||||
def get_field_class(self):
|
||||
return self.field_class
|
||||
|
||||
def get_label_class(self):
|
||||
return add_css_class(self.label_class, 'sr-only')
|
|
@ -1,56 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from django.template import Variable, VariableDoesNotExist
|
||||
from django.template.base import (
|
||||
FilterExpression, kwarg_re, TemplateSyntaxError
|
||||
)
|
||||
|
||||
# Extra features for template file handling
|
||||
|
||||
QUOTED_STRING = re.compile(r'^["\'](?P<noquotes>.+)["\']$')
|
||||
|
||||
|
||||
def handle_var(value, context):
|
||||
# Resolve FilterExpression and Variable immediately
|
||||
if isinstance(value, FilterExpression) or isinstance(value, Variable):
|
||||
return value.resolve(context)
|
||||
# Return quoted strings unquotes, from djangosnippets.org/snippets/886
|
||||
stringval = QUOTED_STRING.search(value)
|
||||
if stringval:
|
||||
return stringval.group('noquotes')
|
||||
# Resolve variable or return string value
|
||||
try:
|
||||
return Variable(value).resolve(context)
|
||||
except VariableDoesNotExist:
|
||||
return value
|
||||
|
||||
|
||||
def parse_token_contents(parser, token):
|
||||
bits = token.split_contents()
|
||||
tag = bits.pop(0)
|
||||
args = []
|
||||
kwargs = {}
|
||||
asvar = None
|
||||
if len(bits) >= 2 and bits[-2] == 'as':
|
||||
asvar = bits[-1]
|
||||
bits = bits[:-2]
|
||||
if len(bits):
|
||||
for bit in bits:
|
||||
match = kwarg_re.match(bit)
|
||||
if not match:
|
||||
raise TemplateSyntaxError(
|
||||
'Malformed arguments to tag "{}"'.format(tag))
|
||||
name, value = match.groups()
|
||||
if name:
|
||||
kwargs[name] = parser.compile_filter(value)
|
||||
else:
|
||||
args.append(parser.compile_filter(value))
|
||||
return {
|
||||
'tag': tag,
|
||||
'args': args,
|
||||
'kwargs': kwargs,
|
||||
'asvar': asvar,
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
{% load bootstrap3 %}
|
||||
<html{% if request.LANGUAGE_CODE %} lang="{{ request.LANGUAGE_CODE }}"{% endif %}>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge" /><![endif]-->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block bootstrap3_title %}django-bootstrap3 template title{% endblock %}</title>
|
||||
{% bootstrap_css %}
|
||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
||||
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
{% if 'javascript_in_head'|bootstrap_setting %}{% bootstrap_javascript jquery=True %}{% endif %}
|
||||
{% block bootstrap3_extra_head %}{% endblock %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{% block bootstrap3_content %}django-bootstrap3 template content{% endblock %}
|
||||
{% if not 'javascript_in_head'|bootstrap_setting %}{% bootstrap_javascript jquery=True %}{% endif %}
|
||||
{% block bootstrap3_extra_script %}{% endblock %}
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1 +0,0 @@
|
|||
{{ help_text_and_errors|join:' ' }}
|
|
@ -1,6 +0,0 @@
|
|||
<div class="alert alert-danger alert-dismissable alert-link">
|
||||
<button class="close" type="button" data-dismiss="alert" aria-hidden="true">×</button>
|
||||
{% for error in errors %}
|
||||
{{ error }}{% if not forloop.last %}<br>{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
|
@ -1,6 +0,0 @@
|
|||
{% for message in messages %}
|
||||
<div class="alert{% if message.tags %} alert-{% if message.tags == 'error' %}danger{% else %}{{ message.tags }}{% endif %}{% endif %} alert-dismissable">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
|
||||
{{ message|safe }}
|
||||
</div>
|
||||
{% endfor %}
|
|
@ -1,33 +0,0 @@
|
|||
{% with bootstrap_pagination_url=bootstrap_pagination_url|default:"?" %}
|
||||
|
||||
<ul class="{{ pagination_css_classes }}">
|
||||
|
||||
<li class="prev{% if current_page == 1 %} disabled{% endif %}">
|
||||
<a href="{% if current_page == 1 %}#{% else %}{{ bootstrap_pagination_url }}{{ parameter_name }}=1{% endif %}">«</a>
|
||||
</li>
|
||||
|
||||
{% if pages_back %}
|
||||
<li>
|
||||
<a href="{{ bootstrap_pagination_url }}{{ parameter_name }}={{ pages_back }}">…</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% for p in pages_shown %}
|
||||
<li{% if current_page == p %} class="active"{% endif %}>
|
||||
<a href="{% if current_page == p %}#{% else %}{{ bootstrap_pagination_url }}{{ parameter_name }}={{ p }}{% endif %}">{{ p }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
{% if pages_forward %}
|
||||
<li>
|
||||
<a href="{{ bootstrap_pagination_url }}{{ parameter_name }}={{ pages_forward }}">…</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<li class="last{% if current_page == num_pages %} disabled{% endif %}">
|
||||
<a href="{% if current_page == num_pages %}#{% else %}{{ bootstrap_pagination_url }}{{ parameter_name }}={{ num_pages }}{% endif %}">»</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
{% endwith %}
|
|
@ -1 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
|
@ -1,637 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from math import floor
|
||||
|
||||
from django import template
|
||||
from django.template.loader import get_template
|
||||
|
||||
from ..bootstrap import (
|
||||
css_url, javascript_url, jquery_url, theme_url, get_bootstrap_setting
|
||||
)
|
||||
from ..utils import render_link_tag
|
||||
from ..forms import (
|
||||
render_button, render_field, render_field_and_label, render_form,
|
||||
render_form_group, render_formset,
|
||||
render_label, render_form_errors, render_formset_errors
|
||||
)
|
||||
from ..components import render_icon, render_alert
|
||||
from ..templates import handle_var, parse_token_contents
|
||||
from ..text import force_text
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
def bootstrap_setting(value):
|
||||
"""
|
||||
A simple way to read bootstrap settings in a template.
|
||||
Please consider this filter private for now, do not use it in your own
|
||||
templates.
|
||||
"""
|
||||
return get_bootstrap_setting(value)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_jquery_url():
|
||||
"""
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_jquery_url
|
||||
|
||||
Return the full url to jQuery file to use
|
||||
|
||||
Default value: ``//code.jquery.com/jquery.min.js``
|
||||
|
||||
This value is configurable, see Settings section
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_jquery_url %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_jquery_url %}
|
||||
"""
|
||||
return jquery_url()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_javascript_url():
|
||||
"""
|
||||
Return the full url to the Bootstrap JavaScript library
|
||||
|
||||
Default value: ``None``
|
||||
|
||||
This value is configurable, see Settings section
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_javascript_url
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_javascript_url %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_javascript_url %}
|
||||
"""
|
||||
return javascript_url()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_css_url():
|
||||
"""
|
||||
Return the full url to the Bootstrap CSS library
|
||||
|
||||
Default value: ``None``
|
||||
|
||||
This value is configurable, see Settings section
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_css_url
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_css_url %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_css_url %}
|
||||
"""
|
||||
return css_url()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_theme_url():
|
||||
"""
|
||||
Return the full url to a Bootstrap theme CSS library
|
||||
|
||||
Default value: ``None``
|
||||
|
||||
This value is configurable, see Settings section
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_css_url
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_css_url %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_css_url %}
|
||||
"""
|
||||
return theme_url()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_css():
|
||||
"""
|
||||
Return HTML for Bootstrap CSS
|
||||
Adjust url in settings. If no url is returned, we don't want this statement
|
||||
to return any HTML.
|
||||
This is intended behavior.
|
||||
|
||||
Default value: ``FIXTHIS``
|
||||
|
||||
This value is configurable, see Settings section
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_css
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_css %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_css %}
|
||||
"""
|
||||
urls = [url for url in [bootstrap_css_url(), bootstrap_theme_url()] if url]
|
||||
return ''.join([render_link_tag(url) for url in urls])
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_javascript(jquery=None):
|
||||
"""
|
||||
Return HTML for Bootstrap JavaScript.
|
||||
|
||||
Adjust url in settings. If no url is returned, we don't want this
|
||||
statement to return any HTML.
|
||||
This is intended behavior.
|
||||
|
||||
Default value: ``None``
|
||||
|
||||
This value is configurable, see Settings section
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_javascript
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:jquery: Truthy to include jQuery as well as Bootstrap
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_javascript %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_javascript jquery=1 %}
|
||||
"""
|
||||
|
||||
javascript = ''
|
||||
# See if we have to include jQuery
|
||||
if jquery is None:
|
||||
jquery = get_bootstrap_setting('include_jquery', False)
|
||||
# NOTE: No async on scripts, not mature enough. See issue #52 and #56
|
||||
if jquery:
|
||||
url = bootstrap_jquery_url()
|
||||
if url:
|
||||
javascript += '<script src="{url}"></script>'.format(url=url)
|
||||
url = bootstrap_javascript_url()
|
||||
if url:
|
||||
javascript += '<script src="{url}"></script>'.format(url=url)
|
||||
return javascript
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_formset(*args, **kwargs):
|
||||
"""
|
||||
Render a formset
|
||||
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_formset
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:args:
|
||||
:kwargs:
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_formset formset %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_formset formset layout='horizontal' %}
|
||||
|
||||
"""
|
||||
return render_formset(*args, **kwargs)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_formset_errors(*args, **kwargs):
|
||||
"""
|
||||
Render form errors
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_form_errors
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:args:
|
||||
:kwargs:
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_form_errors form %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_form_errors form layout='inline' %}
|
||||
"""
|
||||
return render_formset_errors(*args, **kwargs)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_form(*args, **kwargs):
|
||||
"""
|
||||
Render a form
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_form
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:args:
|
||||
:kwargs:
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_form form %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_form form layout='inline' %}
|
||||
"""
|
||||
return render_form(*args, **kwargs)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_form_errors(*args, **kwargs):
|
||||
"""
|
||||
Render form errors
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_form_errors
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:args:
|
||||
:kwargs:
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_form_errors form %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_form_errors form layout='inline' %}
|
||||
"""
|
||||
return render_form_errors(*args, **kwargs)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_field(*args, **kwargs):
|
||||
"""
|
||||
Render a field
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_field
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:args:
|
||||
:kwargs:
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_field form_field %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_field form_field %}
|
||||
"""
|
||||
return render_field(*args, **kwargs)
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
def bootstrap_label(*args, **kwargs):
|
||||
"""
|
||||
Render a label
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_label
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:args:
|
||||
:kwargs:
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_label FIXTHIS %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_label FIXTHIS %}
|
||||
"""
|
||||
return render_label(*args, **kwargs)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_button(*args, **kwargs):
|
||||
"""
|
||||
Render a button
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_button
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:args:
|
||||
:kwargs:
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_button FIXTHIS %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_button FIXTHIS %}
|
||||
"""
|
||||
return render_button(*args, **kwargs)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_icon(icon, **kwargs):
|
||||
"""
|
||||
Render an icon
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_icon
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:icon: icon name
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_icon "icon_name" %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_icon "star" %}
|
||||
|
||||
"""
|
||||
return render_icon(icon, **kwargs)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_alert(content, alert_type='info', dismissable=True):
|
||||
"""
|
||||
Render an alert
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_alert
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:content: HTML content of alert
|
||||
:alert_type: one of 'info', 'warning', 'danger' or 'success'
|
||||
:dismissable: boolean, is alert dismissable
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_alert "my_content" %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_alert "Something went wrong" alert_type='error' %}
|
||||
|
||||
"""
|
||||
return render_alert(content, alert_type, dismissable)
|
||||
|
||||
|
||||
@register.tag('buttons')
|
||||
def bootstrap_buttons(parser, token):
|
||||
"""
|
||||
Render buttons for form
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_buttons
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:parser:
|
||||
:token:
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_buttons FIXTHIS %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_buttons FIXTHIS %}
|
||||
"""
|
||||
kwargs = parse_token_contents(parser, token)
|
||||
kwargs['nodelist'] = parser.parse(('endbuttons', ))
|
||||
parser.delete_first_token()
|
||||
return ButtonsNode(**kwargs)
|
||||
|
||||
|
||||
class ButtonsNode(template.Node):
|
||||
def __init__(self, nodelist, args, kwargs, asvar, **kwargs2):
|
||||
self.nodelist = nodelist
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.asvar = asvar
|
||||
|
||||
def render(self, context):
|
||||
output_kwargs = {}
|
||||
for key in self.kwargs:
|
||||
output_kwargs[key] = handle_var(self.kwargs[key], context)
|
||||
buttons = []
|
||||
submit = output_kwargs.get('submit', None)
|
||||
reset = output_kwargs.get('reset', None)
|
||||
if submit:
|
||||
buttons.append(bootstrap_button(submit, 'submit'))
|
||||
if reset:
|
||||
buttons.append(bootstrap_button(reset, 'reset'))
|
||||
buttons = ' '.join(buttons) + self.nodelist.render(context)
|
||||
output_kwargs.update({
|
||||
'label': None,
|
||||
'field': buttons,
|
||||
})
|
||||
output = render_form_group(render_field_and_label(**output_kwargs))
|
||||
if self.asvar:
|
||||
context[self.asvar] = output
|
||||
return ''
|
||||
else:
|
||||
return output
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def bootstrap_messages(context, *args, **kwargs):
|
||||
"""
|
||||
Show django.contrib.messages Messages in Bootstrap alert containers.
|
||||
|
||||
In order to make the alerts dismissable (with the close button),
|
||||
we have to set the jquery parameter too when using the
|
||||
bootstrap_javascript tag.
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_messages
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:context:
|
||||
:args:
|
||||
:kwargs:
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_messages FIXTHIS %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_javascript jquery=1 %}
|
||||
{% bootstrap_messages FIXTHIS %}
|
||||
|
||||
"""
|
||||
return get_template('bootstrap3/messages.html').render(context)
|
||||
|
||||
|
||||
@register.inclusion_tag('bootstrap3/pagination.html')
|
||||
def bootstrap_pagination(page, **kwargs):
|
||||
"""
|
||||
Render pagination for a page
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_pagination
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:page:
|
||||
:parameter_name: Name of paging URL parameter (default: "page")
|
||||
:kwargs:
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_pagination FIXTHIS %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_pagination FIXTHIS %}
|
||||
"""
|
||||
|
||||
pagination_kwargs = kwargs.copy()
|
||||
pagination_kwargs['page'] = page
|
||||
return get_pagination_context(**pagination_kwargs)
|
||||
|
||||
|
||||
def get_pagination_context(page, pages_to_show=11,
|
||||
url=None, size=None, extra=None,
|
||||
parameter_name='page'):
|
||||
"""
|
||||
Generate Bootstrap pagination context from a page object
|
||||
"""
|
||||
pages_to_show = int(pages_to_show)
|
||||
if pages_to_show < 1:
|
||||
raise ValueError("Pagination pages_to_show should be a positive " +
|
||||
"integer, you specified {pages}".format(
|
||||
pages=pages_to_show))
|
||||
num_pages = page.paginator.num_pages
|
||||
current_page = page.number
|
||||
half_page_num = int(floor(pages_to_show / 2)) - 1
|
||||
if half_page_num < 0:
|
||||
half_page_num = 0
|
||||
first_page = current_page - half_page_num
|
||||
if first_page <= 1:
|
||||
first_page = 1
|
||||
if first_page > 1:
|
||||
pages_back = first_page - half_page_num
|
||||
if pages_back < 1:
|
||||
pages_back = 1
|
||||
else:
|
||||
pages_back = None
|
||||
last_page = first_page + pages_to_show - 1
|
||||
if pages_back is None:
|
||||
last_page += 1
|
||||
if last_page > num_pages:
|
||||
last_page = num_pages
|
||||
if last_page < num_pages:
|
||||
pages_forward = last_page + half_page_num
|
||||
if pages_forward > num_pages:
|
||||
pages_forward = num_pages
|
||||
else:
|
||||
pages_forward = None
|
||||
if first_page > 1:
|
||||
first_page -= 1
|
||||
if pages_back is not None and pages_back > 1:
|
||||
pages_back -= 1
|
||||
else:
|
||||
pages_back = None
|
||||
pages_shown = []
|
||||
for i in range(first_page, last_page + 1):
|
||||
pages_shown.append(i)
|
||||
# Append proper character to url
|
||||
if url:
|
||||
# Remove existing page GET parameters
|
||||
url = force_text(url)
|
||||
url = re.sub(r'\?{0}\=[^\&]+'.format(parameter_name), '?', url)
|
||||
url = re.sub(r'\&{0}\=[^\&]+'.format(parameter_name), '', url)
|
||||
# Append proper separator
|
||||
if '?' in url:
|
||||
url += '&'
|
||||
else:
|
||||
url += '?'
|
||||
# Append extra string to url
|
||||
if extra:
|
||||
if not url:
|
||||
url = '?'
|
||||
url += force_text(extra) + '&'
|
||||
if url:
|
||||
url = url.replace('?&', '?')
|
||||
# Set CSS classes, see http://getbootstrap.com/components/#pagination
|
||||
pagination_css_classes = ['pagination']
|
||||
if size == 'small':
|
||||
pagination_css_classes.append('pagination-sm')
|
||||
elif size == 'large':
|
||||
pagination_css_classes.append('pagination-lg')
|
||||
# Build context object
|
||||
return {
|
||||
'bootstrap_pagination_url': url,
|
||||
'num_pages': num_pages,
|
||||
'current_page': current_page,
|
||||
'first_page': first_page,
|
||||
'last_page': last_page,
|
||||
'pages_shown': pages_shown,
|
||||
'pages_back': pages_back,
|
||||
'pages_forward': pages_forward,
|
||||
'pagination_css_classes': ' '.join(pagination_css_classes),
|
||||
'parameter_name': parameter_name,
|
||||
}
|
|
@ -1,447 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from django import forms
|
||||
from django.template import Template, Context
|
||||
|
||||
from .text import text_value, text_concat
|
||||
from .exceptions import BootstrapError
|
||||
from .utils import add_css_class
|
||||
|
||||
|
||||
RADIO_CHOICES = (
|
||||
('1', 'Radio 1'),
|
||||
('2', 'Radio 2'),
|
||||
)
|
||||
|
||||
MEDIA_CHOICES = (
|
||||
('Audio', (
|
||||
('vinyl', 'Vinyl'),
|
||||
('cd', 'CD'),
|
||||
)
|
||||
),
|
||||
('Video', (
|
||||
('vhs', 'VHS Tape'),
|
||||
('dvd', 'DVD'),
|
||||
)
|
||||
),
|
||||
('unknown', 'Unknown'),
|
||||
)
|
||||
|
||||
|
||||
class TestForm(forms.Form):
|
||||
"""
|
||||
Form with a variety of widgets to test bootstrap3 rendering.
|
||||
"""
|
||||
date = forms.DateField(required=False)
|
||||
subject = forms.CharField(
|
||||
max_length=100,
|
||||
help_text='my_help_text',
|
||||
required=True,
|
||||
widget=forms.TextInput(attrs={'placeholder': 'placeholdertest'}),
|
||||
)
|
||||
message = forms.CharField(required=False, help_text='<i>my_help_text</i>')
|
||||
sender = forms.EmailField(label='Sender © unicode')
|
||||
secret = forms.CharField(initial=42, widget=forms.HiddenInput)
|
||||
cc_myself = forms.BooleanField(
|
||||
required=False, help_text='You will get a copy in your mailbox.')
|
||||
select1 = forms.ChoiceField(choices=RADIO_CHOICES)
|
||||
select2 = forms.MultipleChoiceField(
|
||||
choices=RADIO_CHOICES,
|
||||
help_text='Check as many as you like.',
|
||||
)
|
||||
select3 = forms.ChoiceField(choices=MEDIA_CHOICES)
|
||||
select4 = forms.MultipleChoiceField(
|
||||
choices=MEDIA_CHOICES,
|
||||
help_text='Check as many as you like.',
|
||||
)
|
||||
category1 = forms.ChoiceField(
|
||||
choices=RADIO_CHOICES, widget=forms.RadioSelect)
|
||||
category2 = forms.MultipleChoiceField(
|
||||
choices=RADIO_CHOICES,
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
help_text='Check as many as you like.',
|
||||
)
|
||||
category3 = forms.ChoiceField(
|
||||
widget=forms.RadioSelect, choices=MEDIA_CHOICES)
|
||||
category4 = forms.MultipleChoiceField(
|
||||
choices=MEDIA_CHOICES,
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
help_text='Check as many as you like.',
|
||||
)
|
||||
addon = forms.CharField(
|
||||
widget=forms.TextInput(attrs={'addon_before': 'before', 'addon_after': 'after'}),
|
||||
)
|
||||
|
||||
required_css_class = 'bootstrap3-req'
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(TestForm, self).clean()
|
||||
raise forms.ValidationError(
|
||||
"This error was added to show the non field errors styling.")
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class TestFormWithoutRequiredClass(TestForm):
|
||||
required_css_class = ''
|
||||
|
||||
|
||||
def render_template(text, **context_args):
|
||||
"""
|
||||
Create a template ``text`` that first loads bootstrap3.
|
||||
"""
|
||||
template = Template("{% load bootstrap3 %}" + text)
|
||||
if 'form' not in context_args:
|
||||
context_args['form'] = TestForm()
|
||||
return template.render(Context(context_args))
|
||||
|
||||
|
||||
def render_formset(formset=None, **context_args):
|
||||
"""
|
||||
Create a template that renders a formset
|
||||
"""
|
||||
context_args['formset'] = formset
|
||||
return render_template('{% bootstrap_formset formset %}', **context_args)
|
||||
|
||||
|
||||
def render_form(form=None, **context_args):
|
||||
"""
|
||||
Create a template that renders a form
|
||||
"""
|
||||
if form:
|
||||
context_args['form'] = form
|
||||
return render_template('{% bootstrap_form form %}', **context_args)
|
||||
|
||||
|
||||
def render_form_field(field, **context_args):
|
||||
"""
|
||||
Create a template that renders a field
|
||||
"""
|
||||
form_field = 'form.%s' % field
|
||||
return render_template(
|
||||
'{% bootstrap_field ' + form_field + ' %}', **context_args)
|
||||
|
||||
|
||||
def render_field(field, **context_args):
|
||||
"""
|
||||
Create a template that renders a field
|
||||
"""
|
||||
context_args['field'] = field
|
||||
return render_template('{% bootstrap_field field %}', **context_args)
|
||||
|
||||
|
||||
class SettingsTest(TestCase):
|
||||
def test_settings(self):
|
||||
from .bootstrap import BOOTSTRAP3
|
||||
self.assertTrue(BOOTSTRAP3)
|
||||
|
||||
def test_settings_filter(self):
|
||||
res = render_template(
|
||||
'{% load bootstrap3 %}' +
|
||||
'{{ "required_css_class"|bootstrap_setting }}')
|
||||
self.assertEqual(res.strip(), 'bootstrap3-req')
|
||||
res = render_template(
|
||||
'{% load bootstrap3 %}' +
|
||||
'{% if "javascript_in_head"|bootstrap_setting %}' +
|
||||
'head{% else %}body{% endif %}'
|
||||
)
|
||||
self.assertEqual(res.strip(), 'head')
|
||||
|
||||
def test_required_class(self):
|
||||
form = TestForm()
|
||||
res = render_template('{% bootstrap_form form %}', form=form)
|
||||
self.assertIn('bootstrap3-req', res)
|
||||
|
||||
def test_error_class(self):
|
||||
form = TestForm({})
|
||||
res = render_template('{% bootstrap_form form %}', form=form)
|
||||
self.assertIn('bootstrap3-err', res)
|
||||
|
||||
def test_bound_class(self):
|
||||
form = TestForm({'sender': 'sender'})
|
||||
res = render_template('{% bootstrap_form form %}', form=form)
|
||||
self.assertIn('bootstrap3-bound', res)
|
||||
|
||||
|
||||
class TemplateTest(TestCase):
|
||||
def test_empty_template(self):
|
||||
res = render_template('')
|
||||
self.assertEqual(res.strip(), '')
|
||||
|
||||
def test_text_template(self):
|
||||
res = render_template('some text')
|
||||
self.assertEqual(res.strip(), 'some text')
|
||||
|
||||
def test_bootstrap_template(self):
|
||||
template = Template((
|
||||
'{% extends "bootstrap3/bootstrap3.html" %}' +
|
||||
'{% block bootstrap3_content %}' +
|
||||
'test_bootstrap3_content' +
|
||||
'{% endblock %}'
|
||||
))
|
||||
res = template.render(Context({}))
|
||||
self.assertIn('test_bootstrap3_content', res)
|
||||
|
||||
def test_javascript_without_jquery(self):
|
||||
res = render_template('{% bootstrap_javascript jquery=0 %}')
|
||||
self.assertIn('bootstrap', res)
|
||||
self.assertNotIn('jquery', res)
|
||||
|
||||
def test_javascript_with_jquery(self):
|
||||
res = render_template('{% bootstrap_javascript jquery=1 %}')
|
||||
self.assertIn('bootstrap', res)
|
||||
self.assertIn('jquery', res)
|
||||
|
||||
|
||||
class FormSetTest(TestCase):
|
||||
def test_illegal_formset(self):
|
||||
with self.assertRaises(BootstrapError):
|
||||
render_formset(formset='illegal')
|
||||
|
||||
|
||||
class FormTest(TestCase):
|
||||
def test_illegal_form(self):
|
||||
with self.assertRaises(BootstrapError):
|
||||
render_form(form='illegal')
|
||||
|
||||
def test_field_names(self):
|
||||
form = TestForm()
|
||||
res = render_form(form)
|
||||
for field in form:
|
||||
self.assertIn('name="%s"' % field.name, res)
|
||||
|
||||
def test_field_addons(self):
|
||||
form = TestForm()
|
||||
res = render_form(form)
|
||||
self.assertIn('<div class="input-group"><span class="input-group-addon">before</span><input', res)
|
||||
self.assertIn('/><span class="input-group-addon">after</span></div>', res)
|
||||
|
||||
def test_exclude(self):
|
||||
form = TestForm()
|
||||
res = render_template(
|
||||
'{% bootstrap_form form exclude="cc_myself" %}', form=form)
|
||||
self.assertNotIn('cc_myself', res)
|
||||
|
||||
def test_layout_horizontal(self):
|
||||
form = TestForm()
|
||||
res = render_template(
|
||||
'{% bootstrap_form form layout="horizontal" %}', form=form)
|
||||
self.assertIn('col-md-2', res)
|
||||
self.assertIn('col-md-4', res)
|
||||
res = render_template(
|
||||
'{% bootstrap_form form layout="horizontal" ' +
|
||||
'horizontal_label_class="hlabel" ' +
|
||||
'horizontal_field_class="hfield" %}',
|
||||
form=form
|
||||
)
|
||||
self.assertIn('hlabel', res)
|
||||
self.assertIn('hfield', res)
|
||||
|
||||
def test_buttons_tag(self):
|
||||
form = TestForm()
|
||||
res = render_template(
|
||||
'{% buttons layout="horizontal" %}{% endbuttons %}', form=form)
|
||||
self.assertIn('col-md-2', res)
|
||||
self.assertIn('col-md-4', res)
|
||||
|
||||
|
||||
class FieldTest(TestCase):
|
||||
def test_illegal_field(self):
|
||||
with self.assertRaises(BootstrapError):
|
||||
render_field(field='illegal')
|
||||
|
||||
def test_show_help(self):
|
||||
res = render_form_field('subject')
|
||||
self.assertIn('my_help_text', res)
|
||||
self.assertNotIn('<i>my_help_text</i>', res)
|
||||
res = render_template('{% bootstrap_field form.subject show_help=0 %}')
|
||||
self.assertNotIn('my_help_text', res)
|
||||
|
||||
def test_subject(self):
|
||||
res = render_form_field('subject')
|
||||
self.assertIn('type="text"', res)
|
||||
self.assertIn('placeholder="placeholdertest"', res)
|
||||
|
||||
def test_required_field(self):
|
||||
required_field = render_form_field('subject')
|
||||
self.assertIn('required', required_field)
|
||||
self.assertIn('bootstrap3-req', required_field)
|
||||
not_required_field = render_form_field('message')
|
||||
self.assertNotIn('required', not_required_field)
|
||||
# Required field with required=0
|
||||
form_field = 'form.subject'
|
||||
rendered = render_template(
|
||||
'{% bootstrap_field ' + form_field + ' set_required=0 %}')
|
||||
self.assertNotIn('required', rendered)
|
||||
# Required settings in field
|
||||
form_field = 'form.subject'
|
||||
rendered = render_template(
|
||||
'{% bootstrap_field ' +
|
||||
form_field +
|
||||
' required_css_class="test-required" %}')
|
||||
self.assertIn('test-required', rendered)
|
||||
|
||||
def test_empty_permitted(self):
|
||||
form = TestForm()
|
||||
res = render_form_field('subject', form=form)
|
||||
self.assertIn('required', res)
|
||||
form.empty_permitted = True
|
||||
res = render_form_field('subject', form=form)
|
||||
self.assertNotIn('required', res)
|
||||
|
||||
def test_input_group(self):
|
||||
res = render_template(
|
||||
'{% bootstrap_field form.subject addon_before="$" ' +
|
||||
'addon_after=".00" %}'
|
||||
)
|
||||
self.assertIn('class="input-group"', res)
|
||||
self.assertIn('class="input-group-addon">$', res)
|
||||
self.assertIn('class="input-group-addon">.00', res)
|
||||
|
||||
def test_size(self):
|
||||
def _test_size(param, klass):
|
||||
res = render_template(
|
||||
'{% bootstrap_field form.subject size="' + param + '" %}')
|
||||
self.assertIn(klass, res)
|
||||
|
||||
def _test_size_medium(param):
|
||||
res = render_template(
|
||||
'{% bootstrap_field form.subject size="' + param + '" %}')
|
||||
self.assertNotIn('input-lg', res)
|
||||
self.assertNotIn('input-sm', res)
|
||||
self.assertNotIn('input-md', res)
|
||||
_test_size('sm', 'input-sm')
|
||||
_test_size('small', 'input-sm')
|
||||
_test_size('lg', 'input-lg')
|
||||
_test_size('large', 'input-lg')
|
||||
_test_size_medium('md')
|
||||
_test_size_medium('medium')
|
||||
_test_size_medium('')
|
||||
|
||||
|
||||
class ComponentsTest(TestCase):
|
||||
def test_icon(self):
|
||||
res = render_template('{% bootstrap_icon "star" %}')
|
||||
self.assertEqual(
|
||||
res.strip(), '<span class="glyphicon glyphicon-star"></span>')
|
||||
res = render_template(
|
||||
'{% bootstrap_icon "star" title="alpha centauri" %}')
|
||||
self.assertEqual(
|
||||
res.strip(),
|
||||
'<span class="glyphicon glyphicon-star" ' +
|
||||
'title="alpha centauri"></span>')
|
||||
|
||||
def test_alert(self):
|
||||
res = render_template(
|
||||
'{% bootstrap_alert "content" alert_type="danger" %}')
|
||||
self.assertEqual(
|
||||
res.strip(),
|
||||
'<div class="alert alert-danger alert-dismissable">' +
|
||||
'<button type="button" class="close" data-dismiss="alert" ' +
|
||||
'aria-hidden="true">' +
|
||||
'×</button>content</div>'
|
||||
)
|
||||
|
||||
|
||||
class MessagesTest(TestCase):
|
||||
def test_messages(self):
|
||||
class FakeMessage(object):
|
||||
"""
|
||||
Follows the `django.contrib.messages.storage.base.Message` API.
|
||||
"""
|
||||
|
||||
def __init__(self, message, tags):
|
||||
self.tags = tags
|
||||
self.message = message
|
||||
|
||||
def __str__(self):
|
||||
return self.message
|
||||
|
||||
pattern = re.compile(r'\s+')
|
||||
messages = [FakeMessage("hello", "warning")]
|
||||
res = render_template(
|
||||
'{% bootstrap_messages messages %}', messages=messages)
|
||||
expected = """
|
||||
<div class="alert alert-warning alert-dismissable">
|
||||
<button type="button" class="close" data-dismiss="alert"
|
||||
aria-hidden="true">×</button>
|
||||
hello
|
||||
</div>
|
||||
"""
|
||||
self.assertEqual(
|
||||
re.sub(pattern, '', res),
|
||||
re.sub(pattern, '', expected)
|
||||
)
|
||||
|
||||
messages = [FakeMessage("hello", "error")]
|
||||
res = render_template(
|
||||
'{% bootstrap_messages messages %}', messages=messages)
|
||||
expected = """
|
||||
<div class="alert alert-danger alert-dismissable">
|
||||
<button type="button" class="close" data-dismiss="alert"
|
||||
aria-hidden="true">×</button>
|
||||
hello
|
||||
</div>
|
||||
"""
|
||||
self.assertEqual(
|
||||
re.sub(pattern, '', res),
|
||||
re.sub(pattern, '', expected)
|
||||
)
|
||||
|
||||
messages = [FakeMessage("hello", None)]
|
||||
res = render_template(
|
||||
'{% bootstrap_messages messages %}', messages=messages)
|
||||
expected = """
|
||||
<div class="alert alert-dismissable">
|
||||
<button type="button" class="close" data-dismiss="alert"
|
||||
aria-hidden="true">×</button>
|
||||
hello
|
||||
</div>
|
||||
"""
|
||||
|
||||
self.assertEqual(
|
||||
re.sub(pattern, '', res),
|
||||
re.sub(pattern, '', expected)
|
||||
)
|
||||
|
||||
|
||||
class TextTest(TestCase):
|
||||
def test_add_css_class(self):
|
||||
css_classes = "one two"
|
||||
css_class = "three four"
|
||||
classes = add_css_class(css_classes, css_class)
|
||||
self.assertEqual(classes, "one two three four")
|
||||
|
||||
classes = add_css_class(css_classes, css_class, prepend=True)
|
||||
self.assertEqual(classes, "three four one two")
|
||||
|
||||
|
||||
class HtmlTest(TestCase):
|
||||
def test_text_value(self):
|
||||
self.assertEqual(text_value(''), "")
|
||||
self.assertEqual(text_value(' '), " ")
|
||||
self.assertEqual(text_value(None), "")
|
||||
self.assertEqual(text_value(1), "1")
|
||||
|
||||
def test_text_concat(self):
|
||||
self.assertEqual(text_concat(1, 2), "12")
|
||||
self.assertEqual(text_concat(1, 2, separator='='), "1=2")
|
||||
self.assertEqual(text_concat(None, 2, separator='='), "2")
|
||||
|
||||
|
||||
class ButtonTest(TestCase):
|
||||
def test_button(self):
|
||||
res = render_template(
|
||||
"{% bootstrap_button 'button' size='lg' %}")
|
||||
self.assertEqual(
|
||||
res.strip(), '<button class="btn btn-lg">button</button>')
|
||||
res = render_template(
|
||||
"{% bootstrap_button 'button' size='lg' href='#' %}")
|
||||
self.assertIn(
|
||||
res.strip(),
|
||||
'<a class="btn btn-lg" href="#">button</a><a href="#" ' +
|
||||
'class="btn btn-lg">button</a>')
|
|
@ -1,26 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
try:
|
||||
from django.utils.encoding import force_text
|
||||
except ImportError:
|
||||
from django.utils.encoding import force_unicode as force_text
|
||||
|
||||
|
||||
def text_value(value):
|
||||
"""
|
||||
Force a value to text, render None as an empty string
|
||||
"""
|
||||
if value is None:
|
||||
return ''
|
||||
return force_text(value)
|
||||
|
||||
|
||||
def text_concat(*args, **kwargs):
|
||||
"""
|
||||
Concatenate several values as a text string with an optional separator
|
||||
"""
|
||||
separator = text_value(kwargs.get('separator', ''))
|
||||
values = filter(None, [text_value(v) for v in args])
|
||||
return separator.join(values)
|
|
@ -1,65 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.forms.widgets import flatatt
|
||||
|
||||
from .text import text_value
|
||||
|
||||
|
||||
# Handle HTML and CSS manipulation
|
||||
|
||||
|
||||
def split_css_classes(css_classes):
|
||||
"""
|
||||
Turn string into a list of CSS classes
|
||||
"""
|
||||
classes_list = text_value(css_classes).split(' ')
|
||||
return [c for c in classes_list if c]
|
||||
|
||||
|
||||
def add_css_class(css_classes, css_class, prepend=False):
|
||||
"""
|
||||
Add a CSS class to a string of CSS classes
|
||||
"""
|
||||
classes_list = split_css_classes(css_classes)
|
||||
classes_to_add = [c for c in split_css_classes(css_class)
|
||||
if c not in classes_list]
|
||||
if prepend:
|
||||
classes_list = classes_to_add + classes_list
|
||||
else:
|
||||
classes_list += classes_to_add
|
||||
return ' '.join(classes_list)
|
||||
|
||||
|
||||
def remove_css_class(css_classes, css_class):
|
||||
"""
|
||||
Remove a CSS class from a string of CSS classes
|
||||
"""
|
||||
remove = set(split_css_classes(css_class))
|
||||
classes_list = [c for c in split_css_classes(css_classes)
|
||||
if c not in remove]
|
||||
return ' '.join(classes_list)
|
||||
|
||||
|
||||
def render_link_tag(url, rel='stylesheet', media='all'):
|
||||
"""
|
||||
Build a link tag
|
||||
"""
|
||||
return render_tag(
|
||||
'link',
|
||||
attrs={'href': url, 'rel': rel, 'media': media},
|
||||
close=False)
|
||||
|
||||
|
||||
def render_tag(tag, attrs=None, content=None, close=True):
|
||||
"""
|
||||
Render a HTML tag
|
||||
"""
|
||||
builder = '<{tag}{attrs}>{content}'
|
||||
if content or close:
|
||||
builder += '</{tag}>'
|
||||
return builder.format(
|
||||
tag=tag,
|
||||
attrs=flatatt(attrs) if attrs else '',
|
||||
content=text_value(content),
|
||||
)
|
|
@ -3,6 +3,7 @@ coverage>=3.7.1
|
|||
cssselect>=0.6.1
|
||||
decorator>=3.4.0
|
||||
defusedxml>=0.4.1
|
||||
django-bootstrap3>=5.1.1
|
||||
docutils>=0.12
|
||||
lxml>=3.4.0
|
||||
mimeparse>=0.1.3
|
||||
|
|
Loading…
Reference in a new issue