Added django-bootstrap3 to requirements.txt; removed local copy of django-bootstrap3.

- Legacy-Id: 9372
This commit is contained in:
Henrik Levkowetz 2015-04-03 17:36:00 +00:00
parent 70b4ecf7b8
commit 0301056c9f
19 changed files with 1 additions and 2128 deletions

View file

@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-
__version__ = '5.1.1'

View file

@ -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)

View file

@ -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">&times;</button>'
return '<div class="{css_classes}">{button}{content}</div>'.format(
css_classes=' '.join(css_classes),
button=button,
content=text_value(content),
)

View file

@ -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

View file

@ -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 = '&#160;'
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))

View file

@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-
# Empty models.py, required file for Django tests

View file

@ -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 '&#160;'
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')

View file

@ -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,
}

View file

@ -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>

View file

@ -1 +0,0 @@
{{ help_text_and_errors|join:' ' }}

View file

@ -1,6 +0,0 @@
<div class="alert alert-danger alert-dismissable alert-link">
<button class="close" type="button" data-dismiss="alert" aria-hidden="true">&#215;</button>
{% for error in errors %}
{{ error }}{% if not forloop.last %}<br>{% endif %}
{% endfor %}
</div>

View file

@ -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">&#215;</button>
{{ message|safe }}
</div>
{% endfor %}

View file

@ -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 %}">&laquo;</a>
</li>
{% if pages_back %}
<li>
<a href="{{ bootstrap_pagination_url }}{{ parameter_name }}={{ pages_back }}">&hellip;</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 }}">&hellip;</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 %}">&raquo;</a>
</li>
</ul>
{% endwith %}

View file

@ -1 +0,0 @@
# -*- coding: utf-8 -*-

View file

@ -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,
}

View file

@ -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">' +
'&times;</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">&#215;</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">&#215;</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">&#215;</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>')

View file

@ -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)

View file

@ -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),
)

View file

@ -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