]> git.ipfire.org Git - people/ms/westferry.git/blame - src/westferry/ui/forms.py
forms: Add store callback and automatic value setting
[people/ms/westferry.git] / src / westferry / ui / forms.py
CommitLineData
f33e1d2f
MT
1#!/usr/bin/python3
2###############################################################################
3# #
4# Westferry - The IPFire web user interface #
5# Copyright (C) 2015 IPFire development team #
6# #
7# This program is free software: you can redistribute it and/or modify #
8# it under the terms of the GNU General Public License as published by #
9# the Free Software Foundation, either version 3 of the License, or #
10# (at your option) any later version. #
11# #
12# This program is distributed in the hope that it will be useful, #
13# but WITHOUT ANY WARRANTY; without even the implied warranty of #
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15# GNU General Public License for more details. #
16# #
17# You should have received a copy of the GNU General Public License #
18# along with this program. If not, see <http://www.gnu.org/licenses/>. #
19# #
20###############################################################################
21
f33e1d2f
MT
22from . import base
23
208738dd 24import logging
edf0b75a
MT
25log = logging.getLogger(__name__)
26
b57004d7 27class Form(object):
edf0b75a 28 def __init__(self, handler, id, method="POST", action="",
b57004d7
MT
29 form=None, label=None, help=None, disabled=False):
30 self.handler = handler
edf0b75a 31 self.id = id
b57004d7
MT
32 self.method = method
33 self.action = action
ac38be45 34 self.form = form
b57004d7
MT
35 self.label = label
36 self.help = help
37
38 self.elements = []
ac38be45
MT
39
40 # Is this form disabled?
b57004d7 41 self.disabled = disabled
ac38be45 42
acef41fe
MT
43 def __repr__(self):
44 return "<%s id=%s>" % (self.__class__.__name__, self.id)
45
ac38be45
MT
46 def __iter__(self):
47 """
48 Iterating over a form will iterate over its elements.
49 """
b57004d7
MT
50 for element in self.elements:
51 yield element
ac38be45 52
b57004d7
MT
53 if isinstance(element, Form):
54 for e in element:
55 yield e
56
57 def export(self):
58 """
59 Returns a dictionary with all keys and values
60 of the form.
61 """
62 ret = {}
ac38be45 63
b57004d7
MT
64 for element in self:
65 # Skip any sub-form elements
66 if isinstance(element, Form):
67 continue
ac38be45 68
b57004d7
MT
69 ret[element.name] = element.value
70
71 return ret
72
edf0b75a
MT
73 def execute(self):
74 """
75 Executes the form action
76 """
77 log.debug("Executing form %s" % self)
78
4d13129f
MT
79 for element in self:
80 log.debug(" Processing element %s" % element)
81
82 # Call the store callback if available
83 if element.store and callable(element.store):
84 element.store(element.value)
edf0b75a 85
4d13129f
MT
86 # Otherwise try setting attribute to object
87 elif element.object and element.attribute:
88 setattr(element.object, element.attribute, element.value)
edf0b75a 89
4d13129f
MT
90 else:
91 log.warning("Value of %s could not be stored" % element)
edf0b75a 92
b57004d7 93 # Sub-Forms
ac38be45 94
b57004d7 95 def _add_subform(self, cls, *args, **kwargs):
edf0b75a 96 form = cls(self.handler, self.id, *args, form=self, **kwargs)
b57004d7 97 self.elements.append(form)
ac38be45 98
b57004d7
MT
99 return form
100
101 def add_group(self, *args, **kwargs):
102 return self._add_subform(Group, *args, **kwargs)
103
104 def add_fieldset(self, *args, **kwargs):
105 return self._add_subform(Fieldset, *args, **kwargs)
106
107 # Elements
108
109 def _add_element(self, cls, *args, **kwargs):
ac38be45
MT
110 """
111 Adds a new form element to this form.
112 """
b57004d7
MT
113 element = cls(self, *args, **kwargs)
114 self.elements.append(element)
ac38be45 115
b57004d7 116 return element
ac38be45
MT
117
118 def get_element_by_name(self, name):
119 """
120 Returns the element with the given name (if any).
121 """
b57004d7
MT
122 for element in self:
123 if element.name == name:
124 return element
ac38be45
MT
125
126 # Have a shortcut to find elements by their name quickly.
b57004d7 127 __getattr__ = get_element_by_name
ac38be45
MT
128
129 def add_text_input(self, *args, **kwargs):
130 """
b57004d7 131 Creates a new text input
ac38be45 132 """
b57004d7 133 return self._add_element(TextInput, *args, **kwargs)
ac38be45
MT
134
135 def add_multiline_text_input(self, *args, **kwargs):
136 """
b57004d7 137 Creates a new multiline text input
ac38be45 138 """
b57004d7 139 return self._add_element(MultilineTextInput, *args, **kwargs)
ac38be45
MT
140
141 def add_password_input(self, *args, **kwargs):
142 """
b57004d7 143 Creates a new password input
ac38be45 144 """
b57004d7 145 return self._add_element(PasswordInput, *args, **kwargs)
ac38be45
MT
146
147 def add_yesno_input(self, *args, **kwargs):
148 """
b57004d7 149 Creates a new yes/no input
ac38be45 150 """
b57004d7 151 return self._add_element(YesNoInput, *args, **kwargs)
ac38be45
MT
152
153 def add_single_choice_input(self, *args, **kwargs):
154 """
b57004d7 155 Creates a new single choice input
ac38be45 156 """
b57004d7 157 return self._add_element(SingleChoiceInput, *args, **kwargs)
ac38be45
MT
158
159 def add_multiple_choice_input(self, *args, **kwargs):
160 """
b57004d7 161 Creates a new multiple choice input
ac38be45 162 """
b57004d7 163 return self._add_element(MultipleChoiceInput, *args, **kwargs)
ac38be45
MT
164
165
b57004d7
MT
166class Fieldset(Form):
167 pass
ac38be45
MT
168
169
170class FormFieldsetModule(base.BaseUIModule):
171 def render(self, fieldset):
172 return self.render_string("modules/forms/fieldset.html", fs=fieldset)
173
174
b57004d7
MT
175class Group(Form):
176 pass
ac38be45
MT
177
178
179class Element(object):
180 # The element type
181 type = None
182
b57004d7 183 def __init__(self, form, name=None, label=None, default=None,
4d13129f 184 disabled=False, readonly=False, object=None, attribute=None, store=None, **kwargs):
ac38be45
MT
185 assert self.type
186
187 self.form = form
188 self.name = name
189 self.label = label
190 self.default = default
191
192 # Is this element editable?
b57004d7
MT
193 self.disabled = disabled
194 self.readonly = readonly
ac38be45 195
4d13129f
MT
196 # Object & Attribute
197 self.object = object
198 self.attribute = attribute or name
199
200 # Import default value from object and attribute if none set
201 if self.default is None and self.object and self.attribute:
202 self.default = getattr(self.object, self.attribute)
203
204 # Callbacks
205 self.store = store
206
ac38be45
MT
207 # Help text
208 self.help = None
209
b57004d7
MT
210 # Call initializer for inheriting classes
211 self.initialize(**kwargs)
ac38be45 212
acef41fe
MT
213 def __repr__(self):
214 return "<%s name=\"%s\">" % (self.__class__.__name__, self.name)
215
b57004d7 216 def initialize(self):
ac38be45
MT
217 pass
218
219 @property
220 def value(self):
221 """
222 Returns the value that the user entered for this element.
223 """
b57004d7 224 return self.form.handler.get_argument(self.name, self.default)
ac38be45
MT
225
226
227class SingleChoiceInput(Element):
228 type = "single-choice"
229
230
231class MultipleChoiceInput(Element):
232 type = "multiple-choice"
233
cdcc050f 234
f33e1d2f
MT
235class FormModule(base.BaseUIModule):
236 def render(self, form):
b57004d7 237 return self.render_string("modules/forms/index.html", form=form)
f33e1d2f
MT
238
239
1b19941d
MT
240class FormElementsModule(base.BaseUIModule):
241 def render(self, elements):
242 return self.render_string("modules/forms/elements.html", elements=elements)
f33e1d2f
MT
243
244
ac38be45
MT
245class TextInput(Element):
246 type = "text"
247
b57004d7
MT
248 def initialize(self, prefix=None, suffix=None, placeholder=None):
249 self.prefix = prefix
250 self.suffix = suffix
ac38be45 251
b57004d7 252 self.placeholder = placeholder or self.label
f33e1d2f
MT
253
254
f33e1d2f
MT
255class TextInputModule(base.BaseUIModule):
256 def render(self, input):
ac38be45 257 assert isinstance(input, TextInput)
f33e1d2f
MT
258
259 return self.render_string("modules/forms/inputs/text.html", input=input)
260
261
ac38be45
MT
262class PasswordInput(TextInput):
263 type = "password"
264
265
266class MultilineTextInput(TextInput):
267 type = "multiline-text"
268
b57004d7
MT
269 DEFAULT_LINES = 3
270
271 def initialize(self, lines=DEFAULT_LINES, **kwargs):
272 super().initialize(**kwargs)
273
274 self.lines = lines
ac38be45 275
ac38be45
MT
276
277
f33e1d2f
MT
278class MultilineTextInputModule(base.BaseUIModule):
279 def render(self, input):
ac38be45 280 assert isinstance(input, MultilineTextInput)
f33e1d2f
MT
281
282 return self.render_string("modules/forms/inputs/textarea.html", input=input)
283
284
ac38be45
MT
285class YesNoInput(Element):
286 type = "yesno"
287
b57004d7
MT
288 def initialize(self, description=None):
289 self.description = description
ac38be45
MT
290
291 def get_value(self):
292 value = Element.get_value(self)
293
294 # This only returns true or false
295 if value == "on":
296 return True
297
298 return False
299
300
f33e1d2f
MT
301class YesNoInputModule(base.BaseUIModule):
302 def render(self, input):
ac38be45 303 assert isinstance(input, YesNoInput)
f33e1d2f
MT
304
305 return self.render_string("modules/forms/inputs/checkbox.html", input=input)