Coverage for .tox/coverage/lib/python3.11/site-packages/wuttaweb/forms/widgets.py: 100%
117 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-08-27 21:18 -0500
« prev ^ index » next coverage.py v7.6.1, created at 2024-08-27 21:18 -0500
1# -*- coding: utf-8; -*-
2################################################################################
3#
4# wuttaweb -- Web App for Wutta Framework
5# Copyright © 2024 Lance Edgar
6#
7# This file is part of Wutta Framework.
8#
9# Wutta Framework is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License as published by the Free
11# Software Foundation, either version 3 of the License, or (at your option) any
12# later version.
13#
14# Wutta Framework is distributed in the hope that it will be useful, but
15# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17# more details.
18#
19# You should have received a copy of the GNU General Public License along with
20# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
21#
22################################################################################
23"""
24Form widgets
26This module defines some custom widgets for use with WuttaWeb.
28However for convenience it also makes other Deform widgets available
29in the namespace:
31* :class:`deform:deform.widget.Widget` (base class)
32* :class:`deform:deform.widget.TextInputWidget`
33* :class:`deform:deform.widget.TextAreaWidget`
34* :class:`deform:deform.widget.PasswordWidget`
35* :class:`deform:deform.widget.CheckedPasswordWidget`
36* :class:`deform:deform.widget.CheckboxWidget`
37* :class:`deform:deform.widget.SelectWidget`
38* :class:`deform:deform.widget.CheckboxChoiceWidget`
39* :class:`deform:deform.widget.MoneyInputWidget`
40"""
42import os
44import colander
45import humanize
46from deform.widget import (Widget, TextInputWidget, TextAreaWidget,
47 PasswordWidget, CheckedPasswordWidget,
48 CheckboxWidget, SelectWidget, CheckboxChoiceWidget,
49 MoneyInputWidget)
50from webhelpers2.html import HTML
52from wuttaweb.db import Session
53from wuttaweb.grids import Grid
56class ObjectRefWidget(SelectWidget):
57 """
58 Widget for use with model "object reference" fields, e.g. foreign
59 key UUID => TargetModel instance.
61 While you may create instances of this widget directly, it
62 normally happens automatically when schema nodes of the
63 :class:`~wuttaweb.forms.schema.ObjectRef` (sub)type are part of
64 the form schema; via
65 :meth:`~wuttaweb.forms.schema.ObjectRef.widget_maker()`.
67 In readonly mode, this renders a ``<span>`` tag around the
68 :attr:`model_instance` (converted to string).
70 Otherwise it renders a select (dropdown) element allowing user to
71 choose from available records.
73 This is a subclass of :class:`deform:deform.widget.SelectWidget`
74 and uses these Deform templates:
76 * ``select``
77 * ``readonly/objectref``
79 .. attribute:: model_instance
81 Reference to the model record instance, i.e. the "far side" of
82 the foreign key relationship.
84 .. note::
86 You do not need to provide the ``model_instance`` when
87 constructing the widget. Rather, it is set automatically
88 when the :class:`~wuttaweb.forms.schema.ObjectRef` type
89 instance (associated with the node) is serialized.
90 """
91 readonly_template = 'readonly/objectref'
93 def __init__(self, request, url=None, *args, **kwargs):
94 super().__init__(*args, **kwargs)
95 self.request = request
96 self.url = url
98 def get_template_values(self, field, cstruct, kw):
99 """ """
100 values = super().get_template_values(field, cstruct, kw)
102 # add url, only if rendering readonly
103 readonly = kw.get('readonly', self.readonly)
104 if readonly:
105 if 'url' not in values and self.url and field.schema.model_instance:
106 values['url'] = self.url(field.schema.model_instance)
108 return values
111class NotesWidget(TextAreaWidget):
112 """
113 Widget for use with "notes" fields.
115 In readonly mode, this shows the notes with a background to make
116 them stand out a bit more.
118 Otherwise it effectively shows a ``<textarea>`` input element.
120 This is a subclass of :class:`deform:deform.widget.TextAreaWidget`
121 and uses these Deform templates:
123 * ``textarea``
124 * ``readonly/notes``
125 """
126 readonly_template = 'readonly/notes'
129class WuttaCheckboxChoiceWidget(CheckboxChoiceWidget):
130 """
131 Custom widget for :class:`python:set` fields.
133 This is a subclass of
134 :class:`deform:deform.widget.CheckboxChoiceWidget`, but adds
135 Wutta-related params to the constructor.
137 :param request: Current :term:`request` object.
139 :param session: Optional :term:`db session` to use instead of
140 :class:`wuttaweb.db.sess.Session`.
142 It uses these Deform templates:
144 * ``checkbox_choice``
145 * ``readonly/checkbox_choice``
146 """
148 def __init__(self, request, session=None, *args, **kwargs):
149 super().__init__(*args, **kwargs)
150 self.request = request
151 self.config = self.request.wutta_config
152 self.app = self.config.get_app()
153 self.session = session or Session()
156class FileDownloadWidget(Widget):
157 """
158 Widget for use with :class:`~wuttaweb.forms.schema.FileDownload`
159 fields.
161 This only supports readonly, and shows a hyperlink to download the
162 file. Link text is the filename plus file size.
164 This is a subclass of :class:`deform:deform.widget.Widget` and
165 uses these Deform templates:
167 * ``readonly/filedownload``
169 :param request: Current :term:`request` object.
171 :param url: Optional URL for hyperlink. If not specified, file
172 name/size is shown with no hyperlink.
173 """
174 readonly_template = 'readonly/filedownload'
176 def __init__(self, request, *args, **kwargs):
177 self.url = kwargs.pop('url', None)
178 super().__init__(*args, **kwargs)
179 self.request = request
180 self.config = self.request.wutta_config
181 self.app = self.config.get_app()
183 def serialize(self, field, cstruct, **kw):
184 """ """
185 # nb. readonly is the only way this rolls
186 kw['readonly'] = True
187 template = self.readonly_template
189 path = cstruct or None
190 if path:
191 kw.setdefault('filename', os.path.basename(path))
192 kw.setdefault('filesize', self.readable_size(path))
193 if self.url:
194 kw.setdefault('url', self.url)
196 else:
197 kw.setdefault('filename', None)
198 kw.setdefault('filesize', None)
200 kw.setdefault('url', None)
201 values = self.get_template_values(field, cstruct, kw)
202 return field.renderer(template, **values)
204 def readable_size(self, path):
205 """ """
206 try:
207 size = os.path.getsize(path)
208 except os.error:
209 size = 0
210 return humanize.naturalsize(size)
213class RoleRefsWidget(WuttaCheckboxChoiceWidget):
214 """
215 Widget for use with User
216 :attr:`~wuttjamaican:wuttjamaican.db.model.auth.User.roles` field.
217 This is the default widget for the
218 :class:`~wuttaweb.forms.schema.RoleRefs` type.
220 This is a subclass of :class:`WuttaCheckboxChoiceWidget`.
221 """
222 readonly_template = 'readonly/rolerefs'
224 def serialize(self, field, cstruct, **kw):
225 """ """
226 model = self.app.model
228 # special logic when field is editable
229 readonly = kw.get('readonly', self.readonly)
230 if not readonly:
232 # but does not apply if current user is root
233 if not self.request.is_root:
234 auth = self.app.get_auth_handler()
235 admin = auth.get_role_administrator(self.session)
237 # prune admin role from values list; it should not be
238 # one of the options since current user is not admin
239 values = kw.get('values', self.values)
240 values = [val for val in values
241 if val[0] != admin.uuid]
242 kw['values'] = values
244 else: # readonly
246 # roles
247 roles = []
248 if cstruct:
249 for uuid in cstruct:
250 role = self.session.get(model.Role, uuid)
251 if role:
252 roles.append(role)
253 kw['roles'] = roles
255 # url
256 url = lambda role: self.request.route_url('roles.view', uuid=role.uuid)
257 kw['url'] = url
259 # default logic from here
260 return super().serialize(field, cstruct, **kw)
263class UserRefsWidget(WuttaCheckboxChoiceWidget):
264 """
265 Widget for use with Role
266 :attr:`~wuttjamaican:wuttjamaican.db.model.auth.Role.users` field.
267 This is the default widget for the
268 :class:`~wuttaweb.forms.schema.UserRefs` type.
270 This is a subclass of :class:`WuttaCheckboxChoiceWidget`; however
271 it only supports readonly mode and does not use a template.
272 Rather, it generates and renders a
273 :class:`~wuttaweb.grids.base.Grid` showing the users list.
274 """
276 def serialize(self, field, cstruct, **kw):
277 """ """
278 readonly = kw.get('readonly', self.readonly)
279 if not readonly:
280 raise NotImplementedError("edit not allowed for this widget")
282 model = self.app.model
283 columns = ['person', 'username', 'active']
285 # generate data set for users
286 users = []
287 if cstruct:
288 for uuid in cstruct:
289 user = self.session.get(model.User, uuid)
290 if user:
291 users.append(dict([(key, getattr(user, key))
292 for key in columns + ['uuid']]))
294 # do not render if no data
295 if not users:
296 return HTML.tag('span')
298 # grid
299 grid = Grid(self.request, key='roles.view.users',
300 columns=columns, data=users)
302 # view action
303 if self.request.has_perm('users.view'):
304 url = lambda user, i: self.request.route_url('users.view', uuid=user['uuid'])
305 grid.add_action('view', icon='eye', url=url)
306 grid.set_link('person')
307 grid.set_link('username')
309 # edit action
310 if self.request.has_perm('users.edit'):
311 url = lambda user, i: self.request.route_url('users.edit', uuid=user['uuid'])
312 grid.add_action('edit', url=url)
314 # render as simple <b-table>
315 # nb. must indicate we are a part of this form
316 form = getattr(field.parent, 'wutta_form', None)
317 return grid.render_table_element(form)
320class PermissionsWidget(WuttaCheckboxChoiceWidget):
321 """
322 Widget for use with Role
323 :attr:`~wuttjamaican:wuttjamaican.db.model.auth.Role.permissions`
324 field.
326 This is a subclass of :class:`WuttaCheckboxChoiceWidget`. It uses
327 these Deform templates:
329 * ``permissions``
330 * ``readonly/permissions``
331 """
332 template = 'permissions'
333 readonly_template = 'readonly/permissions'
335 def serialize(self, field, cstruct, **kw):
336 """ """
337 kw.setdefault('permissions', self.permissions)
339 if 'values' not in kw:
340 values = []
341 for gkey, group in self.permissions.items():
342 for pkey, perm in group['perms'].items():
343 values.append((pkey, perm['label']))
344 kw['values'] = values
346 return super().serialize(field, cstruct, **kw)