Source code for inspirehep.modules.forms.field_widgets
# -*- coding: utf-8 -*-
#
# This file is part of INSPIRE.
# Copyright (C) 2014-2017 CERN.
#
# INSPIRE is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# INSPIRE is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with INSPIRE. If not, see <http://www.gnu.org/licenses/>.
#
# In applying this license, CERN does not waive the privileges and immunities
# granted to it by virtue of its status as an Intergovernmental Organization
# or submit itself to any jurisdiction.
"""Implement custom field widgets."""
from __future__ import absolute_import, division, print_function
from inspirehep.utils.template import render_macro_from_template
from wtforms.widgets import HiddenInput, HTMLString, Input, RadioInput, \
TextInput, html_params
[docs]class ButtonWidget(object):
"""Implement Bootstrap HTML5 button."""
def __init__(self, label="", tooltip=None, icon=None, **kwargs):
"""Initialize button widget.
.. note:: the icons assume use of Twitter Bootstrap,
Font Awesome or some other icon library, that allows
inserting icons with a <i>-tag.
:param tooltip: str, Tooltip text for the button.
:param icon: str, Name of an icon, e.g. icon-barcode.
"""
self.icon = icon
self.label = label
self.default_params = kwargs
self.default_params.setdefault('type', 'button')
if tooltip:
self.default_params.setdefault('data-toggle', 'tooltip')
self.default_params.setdefault('title', tooltip)
super(ButtonWidget, self).__init__()
def __call__(self, field, **kwargs):
"""Render button widget."""
params = self.default_params.copy()
params.update(kwargs)
params.setdefault('id', field.id)
params['class_'] = params.get('class_', "") + " form-button"
icon = ""
if self.icon:
icon = '<i class="%s"></i> ' % self.icon
state = ""
if field._value():
state = ('<span class="text-success"> '
'<i class="glyphicon glyphicon-ok"></i></span>')
return HTMLString(u'<button %s>%s%s</button><span %s>%s</span>' % (
html_params(name=field.name, **params),
icon,
self.label,
html_params(id=field.name + '-loader', class_='loader'),
state,
))
[docs]class TagInput(Input):
"""Implement tag input widget."""
input_type = 'hidden'
def __call__(self, field, **kwargs):
"""Render tag input widget."""
if "__input__" in field.name:
self.input_type = 'text'
html = super(TagInput, self).__call__(field, **kwargs)
self.input_type = 'hidden'
else:
return super(TagInput, self).__call__(field, **kwargs)
return html
[docs]class WrappedInput(Input):
"""Widget to wrap text input in further markup."""
wrapper = '<div>%(field)s</div>'
wrapped_widget = TextInput()
def __init__(self, widget=None, wrapper=None, **kwargs):
"""Initialize wrapped input with widget and wrapper."""
self.wrapped_widget = widget or self.wrapped_widget
self.wrapper_args = kwargs
if wrapper is not None:
self.wrapper = wrapper
def __call__(self, field, **kwargs):
"""Render wrapped input."""
return HTMLString(self.wrapper % dict(
field=self.wrapped_widget(field, **kwargs),
**self.wrapper_args
))
[docs]class ColumnInput(WrappedInput):
"""Specialized column wrapped input."""
@property
def wrapper(self):
"""Wrapper template with description support."""
if 'description' in self.wrapper_args:
return ('<div class="%(class_)s">%(field)s'
'<p class="text-muted field-desc">'
'<small>%(description)s</small></p></div>')
return '<div class="%(class_)s">%(field)s</div>'
#
# Item widgets
#
[docs]class ItemWidget(object):
"""Render each subfield without additional markup around the subfield."""
def __call__(self, subfield, **kwargs):
"""Render given ``subfield``."""
return subfield()
[docs]class ListItemWidget(ItemWidget):
"""Render each subfield in a ExtendedListWidget as a list element.
If `with_label` is set, the fields label will be rendered. If
`prefix_label` is set, the label will be prefixed, otherwise it will be
suffixed.
"""
def __init__(self, html_tag='li', with_label=True, prefix_label=True,
class_=None):
"""Initialize list item with html tag.
:param html_tag: name of html tag can be 'li', 'div', or 'span'.
"""
assert html_tag in ('li', 'div', 'span', None)
self.html_tag = html_tag
self.prefix_label = prefix_label
self.with_label = with_label
self.class_ = class_
[docs] def render_subfield(self, subfield, **kwargs):
"""Render subfield."""
if self.with_label:
if self.prefix_label:
return '%s: %s' % (subfield.label, subfield())
else:
return '%s %s' % (subfield(), subfield.label)
else:
return subfield()
[docs] def open_tag(self, subfield, **kwargs):
"""Return open tag."""
if self.html_tag:
return '<%s %s>' % (
self.html_tag,
html_params(class_=self.class_ or kwargs.get('class_', ''))
)
return ''
[docs] def close_tag(self, subfield, **kwargs):
"""Return close tag."""
if self.html_tag:
return '</%s>' % self.html_tag
return ''
def __call__(self, subfield, **kwargs):
"""Render list item widget."""
html = [self.open_tag(subfield, **kwargs)]
html.append(self.render_subfield(subfield, **kwargs))
html.append(self.close_tag(subfield, **kwargs))
return HTMLString(''.join(html))
[docs]class DynamicItemWidget(ListItemWidget):
"""Render each subfield in a ExtendedListWidget enclosed in a div.
It adds also tag with buttons for sorting and removing the item.
I.e. something like:
.. code-block:: jinja
<div><span>"buttons</span>:field</div>
"""
def __init__(self, **kwargs):
"""Initialize dynamic item widget."""
self.icon_reorder = kwargs.pop('icon_reorder', 'fa fa-sort fa-fw')
self.icon_remove = kwargs.pop('icon_remove', 'fa fa-times fa-fw')
defaults = dict(
html_tag='div',
with_label=True,
)
defaults.update(kwargs)
super(DynamicItemWidget, self).__init__(**defaults)
def _sort_button(self):
return ("""<a class="sort-element text-muted sortlink iconlink" """
"""rel="tooltip" title="Drag to reorder"><i class="%s">"""
"""</i></a>""" % self.icon_reorder)
def _remove_button(self):
return ("""<a class="remove-element text-muted iconlink" """
"""rel="tooltip" title="Click to remove"><i class="%s">"""
"""</i></a>""" % self.icon_remove)
[docs] def render_subfield(self, subfield, **kwargs):
"""Render subfield."""
html = []
html.append("<div %s>" % html_params(class_='row'))
# Field
html.append(subfield())
# Buttons
html.append("<div %s>%s</div>" % (
html_params(class_='col-xs-2'),
self._sort_button() + self._remove_button()
))
html.append("</div>")
return ''.join(html)
def __call__(self, subfield, **kwargs):
"""Render dynamic item widget."""
kwargs.setdefault('id', 'element-' + subfield.id)
# Are we rendering an empty form element?
empty_index = kwargs.pop('empty_index', '__index__')
if subfield.name.endswith(empty_index):
kwargs['class_'] = kwargs.get('class_', '') + ' empty-element'
elif subfield.name.endswith('__input__'):
kwargs['class_'] = kwargs.get('class_', '') + ' input-element'
else:
# for deposit form
kwargs['class_'] = kwargs.get('class_', '') + ' field-list-element'
return super(DynamicItemWidget, self).__call__(subfield, **kwargs)
#
# List widgets
#
[docs]class ExtendedListWidget(object):
"""Render a list of fields as a `ul`, `ol` or `div` list.
This is used for fields which encapsulate a list of other fields as
subfields. The widget will try to iterate the field to get access to the
subfields and call them to render them.
The `item_widget` decide how subfields are rendered, and usually just
provide a thin wrapper around the subfields render method. E.g.
ExtendedListWidget renders the ul-tag, while the ListItemWidget renders
each li-tag. The content of the li-tag is rendered by the subfield's
widget.
"""
item_widget = ListItemWidget()
def __init__(self, html_tag='ul', item_widget=None,
class_=None):
"""Initialize extended list widget."""
assert html_tag in ('ol', 'ul', 'div', None)
self.html_tag = html_tag
self.class_ = class_
if item_widget:
self.item_widget = item_widget
[docs] def open_tag(self, field, **kwargs):
"""Render open tag."""
if self.html_tag:
kwargs.setdefault('id', field.id)
if self.class_:
kwargs['class_'] = kwargs.get('class_', '') + ' ' + self.class_
return '<%s %s>' % (self.html_tag, html_params(**kwargs))
return ''
[docs] def close_tag(self, field, **kwargs):
"""Render close tag."""
if self.html_tag:
return '</%s>' % self.html_tag
return ''
[docs] def item_kwargs(self, field, subfield):
"""Return keyword arguments for a field."""
return {}
def __call__(self, field, **kwargs):
"""Render extended list widget."""
html = [self.open_tag(field, **kwargs)]
hidden = []
for subfield in field:
if isinstance(subfield.widget, HiddenInput) or \
self.item_widget is None:
hidden.append(subfield)
else:
html.append(
self.item_widget(subfield, **self.item_kwargs(field,
subfield))
)
html.append(self.close_tag(field, **kwargs))
# Add hidden fields in the end.
for h in hidden:
html.append(h())
return HTMLString(''.join(html))
[docs]class DynamicListWidget(ExtendedListWidget):
"""Render a list of fields as a list of divs.
Additionally adds:
* A hidden input to keep track of the last index.
* An 'add another' item button.
Each subfield is rendered with DynamicItemWidget, which will add buttons
for each item to sort and remove the item.
"""
item_widget = DynamicItemWidget()
icon_add = "fa fa-plus"
def __init__(self, **kwargs):
"""Initialize dynamic list widget."""
self.icon_add = kwargs.pop('icon_add', self.icon_add)
self.item_widget = kwargs.pop('item_widget', self.item_widget)
defaults = dict(
html_tag='div',
class_='dynamic-field-list',
)
defaults.update(kwargs)
super(DynamicListWidget, self).__init__(**defaults)
def _add_button(self, field):
"""Render add button."""
label = getattr(field, 'add_label', None) or \
"Add %s" % field.label.text
ctx = {
"label": label,
"icon_add_class": self.icon_add
}
return render_macro_from_template(name="add_button",
template="forms/macros.html",
ctx=ctx)
[docs] def item_kwargs(self, field, subfield):
"""Return keyword arguments for a field."""
return {'empty_index': field.empty_index}
[docs] def open_tag(self, field, **kwargs):
"""Render open tag."""
html = super(DynamicListWidget, self).open_tag(field, **kwargs)
html += """<input %s>""" % html_params(
name=field.id + '-__last_index__',
id=field.id + '-__last_index__',
type="hidden",
value=field.last_index,
)
return html
[docs] def close_tag(self, field, **kwargs):
"""Render close tag."""
html = self._add_button(field)
html += super(DynamicListWidget, self).close_tag(field, **kwargs)
return html
#
# Radio input widgets
#
[docs]class BigIconRadioInput(RadioInput):
"""Render a single radio button with icon.
This widget is most commonly used in conjunction with InlineListWidget or
some other listing, as a single radio button is not very useful.
"""
input_type = 'radio'
def __init__(self, icons={}, **kwargs):
"""Initialize radio input widget with big icon."""
self.choices_icons = icons
super(BigIconRadioInput, self).__init__(**kwargs)
def __call__(self, field, **kwargs):
"""Render radio input."""
if field.checked:
kwargs['checked'] = u'checked'
html = super(BigIconRadioInput, self).__call__(field, **kwargs)
icon = self.choices_icons.get(field._value(), '')
if icon:
html = """<i class="%s"></i><br />%s</br>%s""" % (
icon, field.label.text, html
)
return html