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/>.
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 {}
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
52 :param single_kwargs: If kwargs should be passed to the template as
53 ``input_args`` instead of being passed as
57 def __init__(self
, template
, plugin
=None, single_line
=False, single_kwargs
=False, **context
):
58 self
.template
= template
60 self
.context
= context
61 self
.single_line
= single_line
62 self
.single_kwargs
= single_kwargs
64 def __call__(self
, field
, **kwargs
):
67 if hasattr(plugin
, 'name'):
69 template
= '{}:{}'.format(plugin
, self
.template
)
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"""
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
):
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
)
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
):
149 if hasattr(field
, 'choices'):
150 choices
.extend(field
.choices
)
154 'searchOnFocus': True,
156 'mustSelectItem': True,
162 options
.update(kwargs
.pop('options', {}))
163 options
.update(self
.typeahead_options
)
164 return super(TypeaheadWidget
, self
).__call
__(field
, options
=options
)