Source code for chrysalio.lib.form
"""Form validation and rendering library."""
# pylint: disable = too-many-lines
from __future__ import annotations
from re import sub as re_sub
from webob.compat import cgi_FieldStorage as FieldStorage
import colander
from pyramid.csrf import get_csrf_token, new_csrf_token
from pyramid.request import Request
from ..helpers.literal import Literal
from ..helpers.builder import Builder
from ..helpers import tags
from .i18n import _
# =============================================================================
[docs]
def get_action(request: Request, silent: bool = False) -> tuple[str, tuple]:
"""Return a tuple such as ``(action, targets)`` where ``action`` is a
string such as ``<act><?|!><target_id>`` and ``targets`` is a list of
selected targets in a list form.
:type request: pyramid.request.Request
:param request:
Current request.
:param bool silent: (default=False)
If ``True``, no alert emitted.
:rtype: tuple
:return: (tuple)
A tuple such as ``(action, targets)``.
Each submit button returns a string such as ``<act><?|!><target_id>.x``
where ``<target_id>`` is the target identifier or ``#`` for all selected
targets, ``<?|!>`` means respectively *confirm* or *proceed* and ``<act>``
is the action to do.
Checkbox inputs return string such as ``#<target_id>``.
For instance, ``del!#`` and ``['#user1', '#user2']`` means "delete
``user1`` and ``user2``". ``del!user1`` means "delete ``user1`` and only
this one".
"""
action = ''
targets = []
for param in request.POST:
if param[0] == '#':
targets.append(param[1:])
elif param[-2:] == '.x':
if param[-3:-2] == '#':
action = param[0:-2]
else:
return param[0:-2], (param[4:-2], )
if '#' in action and not targets:
if not silent:
request.session.flash(_('Select items!'), 'alert')
action = ''
return action, tuple(targets)
# =============================================================================
[docs]
class SameAs():
# pylint: disable = too-few-public-methods
"""This class implements a ``colander`` validator to check if to fields are
identical.
:type request: pyramid.request.Request
:param request:
Current request.
:param str reference:
Name of the field to compare with.
:type message: :class:`str` or :class:`~pyramid.i18n.TranslationString`
:param message: (optional)
Error message.
"""
# -------------------------------------------------------------------------
def __init__(
self,
request: Request,
reference: str,
message: str | None = None):
"""Constructor method."""
self._request = request
self._reference = reference
self._message = message or _('The two fields are not identical.')
# -------------------------------------------------------------------------
def __call__(self, node: colander.SchemaNode, value: str):
"""This method raises a :class:`colander.Invalid` instance as an
exception value is not same as ``self.reference``.
:type node: colander.SchemaNode
:type value: cstruct
"""
if self._request.POST.get(self._reference) != value:
raise colander.Invalid(node, self._message)
# =============================================================================
[docs]
def button(
url: str,
label: str = '',
src: str | None = None,
title: str | None = None,
class_: str | None = 'cioButton') -> str:
"""Output a link on a label and an image with a button aspect.
:param str url:
Target URL.
:param str label: (optional)
Label for roll over and ``alt``.
:param str src: (optional)
Image path.
:param str title: (optional)
Label for roll over.
:param str class_: (default='cioButton')
The class attribute.
:rtype: str
"""
if class_ == 'cioButton' and not label and src:
class_ = None
return Literal(
'<a href="{0}"{1}{2}>{3}{4}</a> '.format(
Literal.escape(url), f' title="{title}"' if title else '',
f' class="{class_}"' if class_ else '',
f'<img src="{src}" alt="{label or title}"/>' if src else '',
label))
# =============================================================================
[docs]
def button_icon(url: str, title: str, class_: str = 'cioIconButton') -> str:
"""Output a link which looks like an icon.
:param str url:
Target URL.
:param str title:
Label for roll over.
:param str class_:
CSS class to give the aspect.
:rtype: str
"""
if 'cioIconButton' not in class_:
class_ = f'cioIconButton {class_}'
return Literal(
f'<a href="{Literal.escape(url)}" title="{title}"'
f' class="{class_}"></a> ')
# =============================================================================
[docs]
def grid_item(
name: str | None,
label: str,
content: str,
required: bool = False,
hint: str | None = None,
error: str | None = None,
title: str | None = None,
clear: bool = False,
class_: str | None = None) -> str:
"""Display an item with label, hint and error message.
:param str name:
Input ID.
:param str label:
Label.
:param str content:
HTML content.
:param bool required: (default=False)
Indicate if this field is required.
:param str hint: (optional)
Help message.
:param str error: (optional)
Error message.
:param str title: (optional)
Title for the hover effect.
:param bool clear: (default=False)
If ``True``, add a ``<div class="cioClear"/>`` at the end.
:param str class_: (optional)
The class attribute.
:rtype: str
This ouputs a structure such as:
.. code-block:: html
<div class="[class_]">
<label for="[name]"><strong>[label]<span>*</span></strong></label>
<tag title="[title]">
[content]
<em> [hint]</em>
<strong> [form.error(name)]</strong>
</tag>
<div class="cioClear"></div>
</div>
"""
# pylint: disable = too-many-arguments, too-many-positional-arguments
if not content:
return ''
if error:
class_ = f'{class_} cioError' if class_ else 'cioError'
return Literal(
'<div{class_}>'
'<label{name}><strong>{label}{required}</strong></label>'
'<div{title}>{content}{hint}{error}</div>{clear}</div>'.format(
class_=' class="{0}"'.format(class_) if class_ else '',
name=' for="{0}"'.format(name.replace(':', '')) if name else '',
label=label or '',
required=Builder().span('*') if required else '',
title=f' title="{title}"' if title else '',
content=content,
hint=Builder().em(' {0}'.format(hint)) if hint else '',
error=Builder().strong(f' {error}') if error else '',
clear=clear and '<div class="cioClear"></div>' or ''))
# =============================================================================
[docs]
class Form():
"""Form validation class."""
# pylint: disable = too-many-public-methods
# -------------------------------------------------------------------------
def __init__(
self,
request: Request,
schema: colander.SchemaNode | None = None,
defaults: dict | None = None,
obj=None,
force_defaults: bool = False):
"""Constructor method."""
# pylint: disable = too-many-arguments, too-many-positional-arguments
self.values = defaults \
if defaults and (not request.POST or force_defaults) else {}
self._request = request
self._schema = schema
self._errors: dict = {}
self._special: tuple[list, list] = ([], [])
self._validated = False
if obj is not None and schema is not None and not request.POST:
for field in [k.name for k in schema]:
if hasattr(obj, field):
self.values[field] = getattr(obj, field)
# -------------------------------------------------------------------------
[docs]
def validate(self, obj=None) -> bool:
"""Check if the form is validated.
:param object obj: (optional)
Object to fill.
:rtype: bool
:return:
``True`` if validated.
"""
# Something to do?
if not self._request.POST:
return False
if self._validated:
return not self._errors
# Schema validation
params = dict(self._request.POST.items())
if self._schema:
try:
self.values = self._schema.deserialize(params)
except colander.Invalid as err:
self._errors = {}
for child in err.children:
self._errors[child.node.name] = child.messages()
else:
self.values.update(params)
# Fill object
if obj is not None and not self._errors:
for field in self.values:
if hasattr(obj, field):
setattr(obj, field, self.values[field])
self._validated = True
return len(self._errors) == 0
# -------------------------------------------------------------------------
[docs]
def has_error(self, name: str | None = None) -> bool:
"""Return ``True`` if field ``name`` has an error.
:param str name: (optional)
Input ID.
:rtype: bool
"""
return bool(name is None and self._errors) or name in self._errors
# -------------------------------------------------------------------------
[docs]
def set_error(self, name: str, message: str):
"""Set an error message for field ``name``.
:param str name:
Input ID.
:param str message:
Error message.
"""
if name in self._errors:
self._errors[name].append(message)
else:
self._errors[name] = [message]
# -------------------------------------------------------------------------
[docs]
def error(self, name: str) -> str:
"""Return error message for field ``name``.
:param str name:
Input ID.
:rtype: str
:return:
Translated error message.
"""
if name not in self._errors:
return ''
return ' ; '.join([ # yapf: disable
self._request.localizer.translate(error)
for error in self._errors[name]])
# -------------------------------------------------------------------------
[docs]
def static(self, name: str):
"""The field ``name`` will not be updated by the form.
:param str name:
Name of field to set static.
"""
if name not in self._special[0]:
self._special[0].append(name)
# -------------------------------------------------------------------------
[docs]
def forget(self, prefix: str):
"""Fields beginning by ``prefix`` are forgotten when the page is
refreshed.
:param str prefix:
Prefix to select fields.
"""
if prefix not in self._special[1]:
self._special[1].append(prefix)
# -------------------------------------------------------------------------
[docs]
@classmethod
def make_safe_id(cls, name: str) -> str:
"""Make a string safe for including in an id attribute
:param str name:
String to transform.
:rtype: str
"""
return re_sub(r'(?!-)\W', '', re_sub( # yapf: disable
r'\s', '_', name.replace('?', '0').replace('!', '1'), )).lower()
# -------------------------------------------------------------------------
[docs]
def begin(
self,
url: str | None = None,
multipart: bool = False,
class_: str | None = 'cioForm',
**attrs) -> str:
"""Ouput the ``<form>`` tag.
:param str url: (optional)
URL to submit form, by default, the current URL.
:param bool multipart: (default=False)
If set to ``True``, the enctype is set to ``multipart/form-data``.
:param dict attrs:
Keyworded arguments for ``helpers.tags`` object.
:rtype: str
"""
token = get_csrf_token(self._request) or new_csrf_token(self._request)
html = tags.form(
url or self._request.path_qs,
'post',
multipart,
class_=class_,
**attrs)
html += Builder().div(
self.hidden('csrf_token', token), class_="cioHidden")
return html
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
[docs]
@classmethod
def submit(
cls,
name: str,
label: str | None = None,
class_: str = 'cioButton',
**attrs) -> str:
"""Output a submit button with the label as the caption.
:param str name:
Input ID.
:param str label: (optional)
Button caption.
:param str class_: (default='cioButton')
The class attribute.
:param dict attrs:
Keyworded arguments for ``helpers.tags`` object.
:rtype: str
"""
return tags.submit(
name, label, cls.make_safe_id(name), class_=class_, **attrs)
# -------------------------------------------------------------------------
[docs]
@classmethod
def submit_image(cls, name: str, label: str, src: str, **attrs) -> str:
"""Output an image submit button.
:param str name:
Input ID.
:param str label:
Label for roll over and ``alt``.
:param str src:
Image path.
:param dict attrs:
Keyworded arguments for ``helpers.tags`` object.
:rtype: str
"""
return Builder().tag(
'input',
type='image',
name=name,
id=cls.make_safe_id(name),
src=src,
title=label or name,
alt=label or name,
**attrs)
# -------------------------------------------------------------------------
[docs]
@classmethod
def submit_icon(
cls,
name: str,
title: str,
class_: str = 'cioIconButton',
**attrs) -> str:
"""Output a submit button which looks like an icon.
:param str name:
Input ID.
:param str title:
Label for roll over.
:param str class_:
CSS class to give the aspect.
:param dict attrs:
Keyworded arguments for ``helpers.tags`` object.
:rtype: str
"""
if 'cioIconButton' not in class_:
class_ = f'cioIconButton {class_}'
return Builder().tag(
'button',
type='submit',
name=f'{name}.x',
id=cls.make_safe_id(name),
title=title,
class_=class_,
**attrs)
# -------------------------------------------------------------------------
[docs]
@classmethod
def submit_cancel_icon(cls, title: str, **attrs) -> str:
"""Output a cancel submit button which looks like an icon.
:param str title:
Label for roll over and ``alt``.
:param dict attrs:
Keyworded arguments for ``helpers.tags`` object.
:rtype: str
"""
return Builder().tag(
'button',
type='submit',
name='ccl!.x',
id='ccl',
title=title,
class_='cioIconButton cioButtonCancel',
**attrs)
# -------------------------------------------------------------------------
[docs]
@classmethod
def submit_cancel(cls, label: str, src: str, **attrs) -> str:
"""Output a cancel submit button.
:param str label:
Label for roll over and ``alt``.
:param str src:
Image path.
:param dict attrs:
Keyworded arguments for ``helpers.tags`` object.
:rtype: str
:return:
HTML tag.
"""
return Builder().tag(
'input',
type='image',
name='ccl!',
id='ccl',
src=src,
title=label,
alt=label,
**attrs)
# -------------------------------------------------------------------------
[docs]
def toggle_flag(
self,
name: str,
title: str,
active: bool,
class_: str = 'cioFlag',
**attrs) -> str:
"""Output a submit button simulating a toggle flag.
:param str name:
Input ID.
:param str title:
Flag title.
:param bool active:
State of the flag._
:param str class_: (default='cioFlag')
The class attribute.
:param dict attrs:
Keyworded arguments for ``helpers.tags`` object.
:rtype: str
"""
active_str = self._request.localizer.translate(
_('active') if active else _('not active'))
title = self._request.localizer.translate( # yapf: disable
_('${l}: ${a} – click to toggle', {'l': title, 'a': active_str}))
class_ = f"cioFlag {class_}{'On' if active else 'Off'}"
return tags.submit(
f'{name}.x',
None,
self.make_safe_id(name),
title=title,
class_=class_,
**attrs)
# -------------------------------------------------------------------------
[docs]
@classmethod
def status(cls, label: str, status: str) -> str:
"""Output a span showing a status.
:param str label:
Status label.
:param str status:
ID of the current status.
:rtype: str
"""
return Literal(
f'<span title="{label}" class="cioStatus cioStatus-{status}">'
'</span>')
# -------------------------------------------------------------------------
[docs]
@classmethod
def button(
cls,
url: str,
label: str = '',
src: str | None = None,
title: str | None = None,
class_: str = 'cioButton') -> str:
"""Output a link on a label and an image with a button aspect.
See :func:`button`.
"""
return button(url, label, src, title, class_)
# -------------------------------------------------------------------------
[docs]
@classmethod
def button_icon(
cls, url: str, title: str, class_: str = 'cioButton') -> str:
"""Output a link which looks like an icon.
See :func:`button_icon`.
"""
return button_icon(url, title, class_)
# -------------------------------------------------------------------------
[docs]
@classmethod
def default_button(cls, name: str):
"""Create an invisible button to catch the Enter signal for the input
submit we want to be the default one.
:param str name:
ID of the input submit to activate.
"""
return Literal(
f'<button type="submit" name="{name}" class="cioDefaultButton"'
' aria-label="Default button"></button>')
# -------------------------------------------------------------------------
[docs]
@classmethod
def grid_item(
cls,
label: str,
content: str,
required: bool = False,
hint: str | None = None,
error: str | None = None,
title: str | None = None,
clear: bool = False,
class_: str = 'cioFormItem') -> str:
"""Output an item with label, hint and error message.
See :func:`grid_item`.
"""
# pylint: disable = too-many-arguments, too-many-positional-arguments
return grid_item(
None, label, content, required, hint, error, title, clear, class_)
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
[docs]
def text(self, name: str, value: str | None = None, **attrs) -> str:
"""Output a standard text field.
:param str name:
Input ID.
:param str value: (optional)
Default value.
:param dict attrs:
Keyworded arguments for ``helpers.tags`` object.
:rtype: str
"""
return tags.text(name, self._value(name, value), **attrs)
# -------------------------------------------------------------------------
[docs]
def password(self, name: str, value: str | None = None, **attrs) -> str:
"""Output a password field.
This method takes the same options as text().
"""
return tags.password(name, self._value(name, value), **attrs)
# -------------------------------------------------------------------------
[docs]
def checkbox(
self,
name: str,
value: str = '1',
checked: bool = False,
autosubmit: bool = False,
**attrs) -> str:
"""Output a check box.
:param str name:
Input ID.
:param str value: (default='1')
The value to return to the application if the box is checked.
:param bool checked: (default=False)
``True`` if the box should be initially checked.
:param bool autosubmit: (default=False)
If ``True``, it adds ``class="cioAutoSubmit"`` attribute.
:param dict attrs:
Keyworded arguments for ``helpers.tags`` object.
:rtype: str
"""
if autosubmit:
attrs['class_'] = f"{attrs['class_']} cioAutoSubmit" \
if 'class_' in attrs else 'cioAutoSubmit'
return tags.checkbox(
name, value, checked or self._value(name), **attrs)
# -------------------------------------------------------------------------
[docs]
def custom_checkbox(
self,
name: str,
value: str = '1',
checked: bool = False,
autosubmit: bool = False,
class_: str | None = None,
**attrs) -> str:
"""Output a check box followed by an empty label to customize the
aspect of the box.
:param str name:
Input ID.
:param str value: (default='1')
The value to return to the application if the box is checked.
:param bool checked: (default=False)
``True`` if the box should be initially checked.
:param bool autosubmit: (default=False)
If ``True``, it adds ``class="cioAutoSubmit"`` attribute.
:param str class_: (default='cioCustomCheckbox')
The class attribute.
:param dict attrs:
Keyworded arguments for ``helpers.tags`` object.
:rtype: str
"""
if class_ is None:
class_ = 'cioCustomCheckbox'
attrs['class_'] = class_
if autosubmit:
attrs['class_'] = f"{attrs['class_']} cioAutoSubmit"
uid = attrs.get('uid') or self.make_safe_id(name)
return Literal(
'{0}{1}'.format(
tags.checkbox(
name, value, checked or bool(self._value(name)), **attrs),
f'<label for="{uid}" class="{class_}"> </label>'))
# -------------------------------------------------------------------------
[docs]
def radio(
self,
name: str,
value: str,
checked: bool = False,
autosubmit: bool = False,
**attrs) -> str:
"""Output a radio button.
:param str name:
Input ID.
:param str value:
The value to return to the application if the radio is checked.
:param bool checked: (default=False)
``True`` if the box should be initially checked.
:param bool autosubmit: (default=False)
If ``True``, it adds ``class="cioAutoSubmit"`` attribute.
:param dict attrs:
Keyworded arguments for ``helpers.tags`` object.
:rtype: str
"""
if autosubmit:
attrs['class_'] = '{0} cioAutoSubmit'.format(attrs['class_']) \
if 'class_' in attrs else 'cioAutoSubmit'
return tags.radio(
name, value, checked or value == self._value(name), **attrs)
# -------------------------------------------------------------------------
[docs]
def select(
self,
name: str,
selected_values: str | list[str | int] | None,
options: list[str | int | tuple],
autosubmit: bool = False,
**attrs) -> str:
"""Output a dropdown selection box.
:param str name:
Input ID.
:type selected_value: :class:`str` or :class:`list`
:param selected_value:
A string or list of strings or integers giving the value(s) that
should be preselected.
:type options:
(list of :class:`str`, :class:`int` or ``(value, label)`` pairs)
:param options:
The label will be shown on the form; the option will be returned to
the application if that option is chosen. If you pass a ``string``
or ``int`` instead of a ``2-tuple``, it will be used for both the
value and the label.
:param bool autosubmit: (default=False)
If ``True``, it adds ``class="cioAutoSubmit"`` attribute.
:param dict attrs:
Keyworded arguments for ``helpers.tags`` object.
:rtype: str
"""
if not options:
return ''
opts = []
translate = self._request.localizer.translate
for opt in options:
if isinstance(opt, tuple):
opts.append(tags.Option(translate(opt[1]), f'{opt[0]}'))
else:
opts.append(tags.Option(f'{opt}'))
if autosubmit:
attrs['class_'] = f"{attrs['class_']} cioAutoSubmit" \
if 'class_' in attrs else 'cioAutoSubmit'
return tags.select(
name, f'{self._value(name, selected_values)}', tags.Options(opts),
**attrs)
# -------------------------------------------------------------------------
[docs]
def upload(self, name: str, value=None, **attrs) -> str:
"""Output a file upload field.
:param str name:
Input ID.
:param str value: (optional)
Default value.
:param dict attrs:
Keyworded arguments for ``helpers.tags`` object.
:rtype: str
"""
value = self._value(name, value)
return tags.file(
name, value if isinstance(value, FieldStorage) else None, **attrs)
# -------------------------------------------------------------------------
[docs]
def textarea(self, name: str, content: str = '', **attrs) -> str:
"""Output a text input area.
:param str name:
Input ID.
:param str content: (optional)
Default value.
:param dict attrs:
Keyworded arguments for ``helpers.tags`` object.
:rtype: str
"""
return tags.textarea(name, self._value(name, content), **attrs)
# -------------------------------------------------------------------------
[docs]
def grid_text(
self,
name: str,
label: str,
required: bool = False,
hint: str | None = None,
title: str | None = None,
clear: bool = False,
class_: str = 'cioFormItem',
**attrs) -> str:
"""Output a standard text field in a CSS grid layout.
:param str name:
Input ID.
:param str label:
Label.
:param bool required: (default=False)
Indicate if this field is required.
:param str hint: (optional)
Help message.
:param str title: (optional)
Title for the hover effect.
:param bool clear: (default=False)
If ``True``, add a ``<div class="cioClear"/>`` at the end.
:param str class_: (default='cioFormItem')
The class attribute.
:param dict attrs:
Keyworded arguments for ``helpers.tags`` object.
:rtype: str
"""
# pylint: disable = too-many-arguments, too-many-positional-arguments
return grid_item(
name,
label,
self.text(name, **attrs),
required,
hint,
self.error(name),
title=title,
clear=clear,
class_=class_)
# -------------------------------------------------------------------------
[docs]
def grid_password(
self,
name: str,
label: str,
required: bool = False,
hint: str | None = None,
title: str | None = None,
clear: bool = False,
class_: str = 'cioFormItem',
**attrs) -> str:
"""Output a password field in a CSS grid layout.
This method takes the same options as grid_text().
"""
# pylint: disable = too-many-arguments, too-many-positional-arguments
return grid_item(
name,
label,
self.password(name, **attrs),
required,
hint,
self.error(name),
title=title,
clear=clear,
class_=class_)
# -------------------------------------------------------------------------
[docs]
def grid_checkbox(
self,
name: str,
label: str,
required: bool = False,
hint: str | None = None,
title: str | None = None,
clear: bool = False,
class_: str = 'cioFormItem',
**attrs) -> str:
"""Output a check box in a CSS grid layout.
:param str name:
Input ID.
:param str label:
Label.
:param bool required: (default=False)
Indicate if this field is required.
:param str hint: (optional)
Help message.
:param str title: (optional)
Title for the hover effect.
:param bool clear: (default=False)
If ``True``, add a ``<div class="cioClear"/>`` at the end.
:param str class_: (default='cioFormItem')
The class attribute.
:param dict attrs:
Keyworded arguments for ``helpers.tags`` object.
:rtype: str
"""
# pylint: disable = too-many-arguments, too-many-positional-arguments
return grid_item(
name,
label,
self.checkbox(name, **attrs),
required,
hint,
self.error(name),
title=title,
clear=clear,
class_=class_)
# -------------------------------------------------------------------------
[docs]
def grid_custom_checkbox(
self,
name: str,
label: str,
required: bool = False,
hint: str | None = None,
title: str | None = None,
clear: bool = False,
class_: str = 'cioFormItem',
**attrs) -> str:
"""Output a custom check box in a CSS grid layout.
:param str name:
Input ID.
:param str label:
Label.
:param bool required: (default=False)
Indicate if this field is required.
:param str hint: (optional)
Help message.
:param str title: (optional)
Title for the hover effect.
:param bool clear: (default=False)
If ``True``, add a ``<div class="cioClear"/>`` at the end.
:param str class_: (default='cioFormItem')
The class attribute.
:param dict attrs:
Keyworded arguments for ``helpers.tags`` object.
:rtype: str
"""
# pylint: disable = too-many-arguments, too-many-positional-arguments
return grid_item(
name,
label,
self.custom_checkbox(name, **attrs),
required,
hint,
self.error(name),
title=title,
clear=clear,
class_=class_)
# -------------------------------------------------------------------------
[docs]
def grid_select(
self,
name: str,
label: str,
options: list[str | int | tuple],
autosubmit: bool = False,
required: bool = False,
hint: str | None = None,
title: str | None = None,
clear: bool = False,
class_='cioFormItem',
**attrs) -> str:
"""Output a dropdown selection box in a CSS grid layout.
:param str name:
Input ID.
:param str label:
Label.
:type options:
(list of :class:`str`, :class:`int` or ``(value, label)`` pairs)
:param options:
Values in the dropdown list.
:param bool autosubmit: (default=False)
If ``True``, it adds ``onchange="submit()"`` attribute.
:param bool required: (default=False)
Indicate if this field is required.
:param str hint: (optional)
Help message.
:param str title: (optional)
Title for the hover effect.
:param bool clear: (default=False)
If ``True``, add a ``<div class="cioClear"/>`` at the end.
:param str class_: (default='cioFormItem')
The class attribute.
:param dict attrs:
Keyworded arguments for ``helpers.tags`` object.
:rtype: str
"""
# pylint: disable = too-many-arguments, too-many-positional-arguments
if not options:
return ''
return grid_item(
name,
label,
self.select(name, None, options, autosubmit, **attrs),
required,
hint,
self.error(name),
title=title,
clear=clear,
class_=class_)
# -------------------------------------------------------------------------
[docs]
def grid_upload(
self,
name: str,
label: str,
required: bool = False,
hint: str | None = None,
title: str | None = None,
clear: bool = False,
class_: str = 'cioFormItem',
**attrs) -> str:
"""Output a file upload field in a CSS grid layout.
:param str name:
Input ID.
:param str label:
Label.
:param bool required: (default=False)
Indicate if this field is required.
:param str hint: (optional)
Help message.
:param str title: (optional)
Title for the hover effect.
:param bool clear: (default=False)
If ``True``, add a ``<div class="cioClear"/>`` at the end.
:param str class_: (default='cioFormItem')
The class attribute.
:param dict attrs:
Keyworded arguments for ``helpers.tags`` object.
:rtype: str
"""
# pylint: disable = too-many-arguments, too-many-positional-arguments
return grid_item(
name,
label,
self.upload(name, **attrs),
required,
hint,
self.error(name),
title=title,
clear=clear,
class_=class_)
# -------------------------------------------------------------------------
[docs]
def grid_textarea(
self,
name: str,
label: str,
required: bool = False,
hint: str | None = None,
title: str | None = None,
clear: bool = False,
class_: str = 'cioFormItem',
**attrs) -> str:
"""Output a text input area in a CSS grid layout.
:param str name:
Input ID.
:param str label:
Label.
:param bool required: (default=False)
Indicate if this field is required.
:param str hint: (optional)
Help message.
:param str title: (optional)
Title for the hover effect.
:param bool clear: (default=False)
If ``True``, add a ``<div class="cioClear"/>`` at the end.
:param str class_: (default='cioFormItem')
The class attribute.
:param dict attrs:
Keyworded arguments for ``helpers.tags`` object.
:rtype: str
"""
# pylint: disable = too-many-arguments, too-many-positional-arguments
return grid_item(
name,
label,
self.textarea(name, **attrs),
required,
hint,
self.error(name),
title=title,
clear=clear,
class_=class_)
# -------------------------------------------------------------------------
def _value(
self,
name: str,
default: str | list[str | int] | None = None
) -> str | list[str | int] | None:
"""Return the best value for the field ``name``.
:param str name:
Input ID.
:param str default: (optional)
Default value.
:rtype: str
"""
special = name in self._special[0] or [
True for k in self._special[1] if name.startswith(k)
]
if not special and name in self._request.POST:
return self._request.POST[name]
if name in self.values:
return self.values[name]
return default