Add folder name suggestions
[cds-indico.git] / indico / web / forms / widgets.py
blob712d9249311cf9e3237c263c57c31221f75d8ed0
1 # This file is part of Indico.
2 # Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN).
4 # Indico is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License as
6 # published by the Free Software Foundation; either version 3 of the
7 # License, or (at your option) any later version.
9 # Indico is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with Indico; if not, see <http://www.gnu.org/licenses/>.
17 import re
19 from wtforms.widgets import TextInput, TextArea
20 from wtforms.widgets.core import HTMLString
22 from indico.core.auth import multipass
23 from indico.web.util import inject_js
24 from indico.web.flask.templating import get_template_module
26 html_commment_re = re.compile(r'<!--.*?-->', re.MULTILINE)
29 class ConcatWidget(object):
30 """Renders a list of fields as a simple string joined by an optional separator."""
31 def __init__(self, separator='', prefix_label=True):
32 self.separator = separator
33 self.prefix_label = prefix_label
35 def __call__(self, field, label_args=None, field_args=None, **kwargs):
36 label_args = label_args or {}
37 field_args = field_args or {}
38 html = []
39 for subfield in field:
40 fmt = u'{0} {1}' if self.prefix_label else u'{1} {0}'
41 html.append(fmt.format(subfield.label(**label_args), subfield(**field_args)))
42 return HTMLString(self.separator.join(html))
45 class JinjaWidget(object):
46 """Renders a field using a custom Jinja template
48 :param template: The template to render
49 :param plugin: The plugin or plugin name containing the template
50 :param single_line: If the field should be rendered in single-line
51 style.
52 :param single_kwargs: If kwargs should be passed to the template as
53 ``input_args`` instead of being passed as
54 separate kwargs.
55 """
57 def __init__(self, template, plugin=None, single_line=False, single_kwargs=False, **context):
58 self.template = template
59 self.plugin = plugin
60 self.context = context
61 self.single_line = single_line
62 self.single_kwargs = single_kwargs
64 def __call__(self, field, **kwargs):
65 if self.plugin:
66 plugin = self.plugin
67 if hasattr(plugin, 'name'):
68 plugin = plugin.name
69 template = '{}:{}'.format(plugin, self.template)
70 else:
71 template = self.template
72 if self.single_kwargs:
73 kwargs = {'input_args': kwargs}
74 template_module = get_template_module(template, field=field, **dict(self.context, **kwargs))
75 javascript = template_module.javascript()
76 if '<script' in javascript:
77 inject_js(template_module.javascript())
78 elif html_commment_re.sub('', javascript).strip():
79 raise ValueError("Template did not provide valid javascript")
80 return HTMLString(template_module.html())
83 class PasswordWidget(JinjaWidget):
84 """Renders a password input"""
86 single_line = True
88 def __init__(self):
89 super(PasswordWidget, self).__init__('forms/password_widget.html')
91 def __call__(self, field, **kwargs):
92 return super(PasswordWidget, self).__call__(field, input_args=kwargs)
95 class CKEditorWidget(JinjaWidget):
96 """Renders a CKEditor WYSIWYG editor"""
97 def __init__(self, simple=False):
98 super(CKEditorWidget, self).__init__('forms/ckeditor_widget.html', simple=simple)
101 class SwitchWidget(JinjaWidget):
102 """Renders a switch widget
104 :param on_label: Text to override default ON label
105 :param off_label: Text to override default OFF label
108 def __init__(self, on_label=None, off_label=None):
109 super(SwitchWidget, self).__init__('forms/switch_widget.html')
110 self.on_label = on_label
111 self.off_label = off_label
113 def __call__(self, field, **kwargs):
114 kwargs.update({
115 'checked': getattr(field, 'checked', field.data),
116 'on_label': self.on_label,
117 'off_label': self.off_label
119 return super(SwitchWidget, self).__call__(field, kwargs=kwargs)
122 class SyncedInputWidget(JinjaWidget):
123 """Renders a text input with a sync button when needed."""
125 def __init__(self, textarea=False):
126 super(SyncedInputWidget, self).__init__('forms/synced_input_widget.html', single_line=not textarea)
127 self.textarea = textarea
128 self.default_widget = TextArea() if textarea else TextInput()
130 def __call__(self, field, **kwargs):
131 # Render a sync button for fields which can be synced, if the identity provider provides a value for the field.
132 if field.short_name in multipass.synced_fields and field.synced_value is not None:
133 return super(SyncedInputWidget, self).__call__(field, textarea=self.textarea, kwargs=kwargs)
134 else:
135 return self.default_widget(field, **kwargs)
138 class TypeaheadWidget(JinjaWidget):
139 """Renders a selectizer-based widget"""
141 def __init__(self, typeahead_options=None, min_trigger_length=0):
142 super(TypeaheadWidget, self).__init__('forms/typeahead_widget.html')
143 self.typeahead_options = typeahead_options or {}
144 self.min_trigger_length = min_trigger_length
146 def __call__(self, field, **kwargs):
147 choices = []
149 if hasattr(field, 'choices'):
150 choices.extend(field.choices)
152 options = {
153 'hint': True,
154 'searchOnFocus': True,
155 'display': 'name',
156 'mustSelectItem': True,
157 'source': {
158 'data': choices
162 options.update(kwargs.pop('options', {}))
163 options.update(self.typeahead_options)
164 return super(TypeaheadWidget, self).__call__(field, options=options)