• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hasgeek / baseframe / 16218997531

11 Jul 2025 11:32AM UTC coverage: 67.889% (+1.0%) from 66.866%
16218997531

push

github

web-flow
Fix linter issues; fix ValidCoordinates; add OptionalCoordinates (#511)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

120 of 149 new or added lines in 14 files covered. (80.54%)

3 existing lines in 2 files now uncovered.

1592 of 2345 relevant lines covered (67.89%)

2.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

24.0
/src/baseframe/forms/widgets.py
1
"""Redefined WTForms widgets and some extra widgets."""
2

3
from __future__ import annotations
4✔
4

5
from typing import TYPE_CHECKING, Any
4✔
6

7
import wtforms
4✔
8
from flask import current_app, render_template
4✔
9
from furl import furl
4✔
10
from markupsafe import Markup, escape
4✔
11
from wtforms import Field as WTField, SelectFieldBase
4✔
12
from wtforms.fields import RadioField
4✔
13
from wtforms.widgets import RadioInput, Select, html_params
4✔
14

15
from ..extensions import _
4✔
16

17
__all__ = [
4✔
18
    'CoordinatesInput',
19
    'DateTimeInput',
20
    'ImgeeWidget',
21
    'InlineListWidget',
22
    'RadioInput',
23
    'RadioMatrixInput',
24
    'Select2Widget',
25
    'SelectWidget',
26
    'SubmitInput',
27
    'TinyMce4',
28
]
29

30

31
# This class borrowed from https://github.com/industrydive/wtforms_extended_selectfield
32
class SelectWidget(Select):
4✔
33
    """Add support of choices with ``optgroup`` to the ``Select`` widget."""
34

35
    def __call__(self, field: SelectFieldBase, **kwargs: Any) -> Markup:
4✔
36
        kwargs.setdefault('id', field.id)
×
37
        if self.multiple:
×
38
            kwargs['multiple'] = True
×
39
        html = [f'<select {html_params(name=field.name, **kwargs)}>']
×
40
        for item1, item2 in field.choices:
×
41
            if isinstance(item2, (list, tuple)):
×
42
                group_label = item1
×
43
                group_items = item2
×
44
                html.append(f'<optgroup {html_params(label=group_label)}>')
×
45
                for inner_val, inner_label in group_items:
×
46
                    html.append(
×
47
                        self.render_option(
48
                            inner_val,
49
                            inner_label,
50
                            field.coerce(inner_val) == field.data,
51
                        )
52
                    )
53
                html.append('</optgroup>')
×
54
            else:
55
                val = item1
×
56
                label = item2
×
57
                html.append(self.render_option(val, label, val == field.data))
×
58
        html.append('</select>')
×
NEW
59
        return Markup(''.join(html))  # nosec: B704  # noqa: S704
×
60

61

62
class Select2Widget(Select):
4✔
63
    """Add a select2 class to the rendered select widget."""
64

65
    def __call__(self, field: SelectFieldBase, **kwargs: Any) -> Markup:
4✔
66
        kwargs.setdefault('id', field.id)
×
67
        kwargs.pop('type', field.type)
×
68
        if field.multiple:
×
69
            kwargs['multiple'] = 'multiple'
×
70
        c = kwargs.pop('class', '') or kwargs.pop('class_', '')
×
71
        if c:
×
72
            kwargs['class'] = f'select2 {c}'
×
73
        else:
74
            kwargs['class'] = 'select2'
×
75
        html = [f'<select {html_params(name=field.name, **kwargs)}>']
×
76
        if field.iter_choices():
×
77
            for val, label, selected, render_kw in field.iter_choices():
×
78
                html.append(self.render_option(val, label, selected, **render_kw))
×
79
        html.append('</select>')
×
NEW
80
        return Markup(''.join(html))  # nosec: B704  # noqa: S704
×
81

82

83
class TinyMce4(wtforms.widgets.TextArea):
4✔
84
    """Rich text widget with Tiny MCE 4."""
85

86
    #: Used as an identifier in forms.html.jinja2
87
    input_type: str = 'tinymce4'
4✔
88

89
    def __call__(self, field: WTField, **kwargs: Any) -> Markup:
4✔
90
        c = kwargs.pop('class', '') or kwargs.pop('class_', '')
×
91
        if c:
×
92
            kwargs['class'] = f'richtext {c}'
×
93
        else:
94
            kwargs['class'] = 'richtext'
×
95
        return super().__call__(field, **kwargs)
×
96

97

98
class SubmitInput(wtforms.widgets.SubmitInput):
4✔
99
    """Submit input with pre-defined classes."""
100

101
    def __init__(self, *args: Any, **kwargs: Any) -> None:
4✔
102
        self.css_class = kwargs.pop('class', '') or kwargs.pop('class_', '')
×
103
        super().__init__(*args, **kwargs)
×
104

105
    def __call__(self, field: WTField, **kwargs: Any) -> Markup:
4✔
106
        c = kwargs.pop('class', '') or kwargs.pop('class_', '')
×
107
        kwargs['class'] = f'{self.css_class} {c}'
×
108
        return super().__call__(field, **kwargs)
×
109

110

111
class DateTimeInput(wtforms.widgets.Input):
4✔
112
    """Render date and time inputs."""
113

114
    input_type = 'datetime-local'
4✔
115

116
    def __call__(self, field: WTField, **kwargs: Any) -> Markup:
4✔
117
        kwargs.setdefault('id', field.id)
×
118
        field_id = kwargs.pop('id')
×
119
        kwargs.pop('type', None)
×
120
        value = kwargs.pop('value', None)
×
121
        if value is None:  # Allow blank value to override field data
×
122
            value = field._value() or ''
×
123
        class_ = kwargs.pop('class', kwargs.pop('class_', ''))
×
124
        input_attrs = html_params(name=field.name, id=field_id, value=value, **kwargs)
×
NEW
125
        return Markup(  # nosec: B704  # noqa: S704
×
126
            f'<input type="datetime-local" class="{class_}"'
127
            f' {input_attrs} /> {field.tzname}'
128
        )
129

130

131
class CoordinatesInput(wtforms.widgets.core.Input):
4✔
132
    """Render latitude and longitude coordinates."""
133

134
    input_type = 'text'
4✔
135

136
    def __call__(self, field: WTField, **kwargs: Any) -> Markup:
4✔
137
        id_ = kwargs.pop('id', field.id)
×
138
        kwargs.setdefault('type', self.input_type)
×
139
        kwargs.setdefault('size', 10)  # 9 digits precision and +/- sign
×
140
        kwargs.pop(
×
141
            'placeholder', None
142
        )  # Discard placeholder, use custom values for each input below
143
        value = kwargs.pop('value', None)
×
144
        if not value:
×
145
            value = field._value()
×
146
        if not value:
×
147
            value = ['', '']
×
148
        elif isinstance(value, str):
×
149
            value = value.split(',', 1)
×
150
        if len(value) < 2:
×
151
            value.append('')
×
152

NEW
153
        return Markup(  # nosec: B704  # noqa: S704
×
154
            # pylint: disable=consider-using-f-string
155
            '<input {}> <input {}>'.format(
156
                self.html_params(
157
                    id=id_ + '_latitude',
158
                    name=field.name,
159
                    placeholder=_("Latitude"),
160
                    value=value[0],
161
                    **kwargs,
162
                ),
163
                self.html_params(
164
                    id=id_ + '_longitude',
165
                    name=field.name,
166
                    placeholder=_("Longitude"),
167
                    value=value[1],
168
                    **kwargs,
169
                ),
170
            )
171
        )
172

173

174
class RadioMatrixInput:
4✔
175
    """Render a table with a radio matrix."""
176

177
    def __call__(self, field: RadioMatrixField, **kwargs: Any) -> Markup:
4✔
178
        rendered = []
×
179
        table_class = kwargs.pop('table_class', 'table')
×
180
        rendered.append(f'<table class="{escape(table_class)}">')
×
181
        rendered.append('<thead>')
×
182
        rendered.append('<tr>')
×
183
        rendered.append(f'<th>{escape(field.label)}</th>')
×
184
        for _value, label in field.choices:
×
185
            rendered.append(f'<th>{escape(label)}</th>')
×
186
        rendered.append('</th>')
×
187
        rendered.append('</thead>')
×
188
        rendered.append('<tbody>')
×
189
        for name, title in field.fields:
×
190
            rendered.append('<tr>')
×
191
            rendered.append(f'<td>{escape(title)}</td>')
×
192
            selected = field.data.get(name)
×
193
            for value, _label in field.choices:
×
194
                params: dict[str, Any] = {
×
195
                    'type': 'radio',
196
                    'name': name,
197
                    'value': value,
198
                }
199
                if str(selected) == str(value):
×
200
                    params['checked'] = True
×
201
                rendered.append(f'<td><input {html_params(**params)}/></td>')
×
202
            rendered.append('</tr>')
×
203
        rendered.append('</tbody>')
×
204
        rendered.append('</table>')
×
205

NEW
206
        return Markup('\n'.join(rendered))  # nosec: B704  # noqa: S704
×
207

208

209
class InlineListWidget:
4✔
210
    """
211
    Renders a list of fields as buttons.
212

213
    This is used for fields which encapsulate many inner fields as subfields.
214
    The widget will try to iterate the field to get access to the subfields and
215
    call them to render them.
216

217
    If `prefix_label` is set, the subfield's label is printed before the field,
218
    otherwise afterwards. The latter is useful for iterating radios or
219
    checkboxes.
220
    """
221

222
    def __init__(
4✔
223
        self,
224
        html_tag: str = 'div',
225
        class_: str = '',
226
        class_prefix: str = '',
227
    ) -> None:
228
        self.html_tag = html_tag
×
229
        self.class_ = class_
×
230
        self.class_prefix = class_prefix
×
231

232
    def __call__(self, field: RadioField, **kwargs: Any) -> Markup:
4✔
233
        kwargs.setdefault('id', field.id)
×
234
        kwargs['class_'] = (
×
235
            kwargs.pop('class_', kwargs.pop('class', '')).strip() + ' ' + self.class_
236
        ).strip()
237
        html = [f'<{escape(self.html_tag)} {html_params(**kwargs)}>']
×
NEW
238
        html.extend(
×
239
            f'<label for="{escape(subfield.id)}" class="{escape(self.class_prefix)}'
240
            f'{escape(subfield.data)}">{escape(subfield())}'
241
            f' {escape(subfield.label.text)}</label>'
242
            for subfield in field
243
        )
244
        html.append(f'</{escape(self.html_tag)}>')
×
NEW
245
        return Markup('\n'.join(html))  # nosec: B704  # noqa: S704
×
246

247

248
class ImgeeWidget(wtforms.widgets.Input):
4✔
249
    input_type = 'hidden'
4✔
250

251
    def __call__(self, field: WTField, **kwargs: Any) -> Markup:
4✔
252
        id_ = kwargs.pop('id', field.id)
×
253
        kwargs.setdefault('type', self.input_type)
×
254
        imgee_host = current_app.config.get('IMGEE_HOST')
×
255
        if not imgee_host:
×
256
            raise ValueError("No imgee server specified in config variable IMGEE_HOST")
×
257

258
        upload_url = f'{imgee_host}/{field.profile}/popup'
×
259

260
        value = kwargs.pop('value', None)
×
261
        if not value:
×
262
            value = field._value()
×
263
        if not value:
×
264
            value = ''
×
265
        elif isinstance(value, furl):
×
266
            value = furl.url
×
267

268
        # pylint: disable=consider-using-f-string
NEW
269
        iframe_html = Markup(  # nosec: B704  # noqa: S704
×
270
            '<iframe {} class="imgee-upload"></iframe>'.format(
271
                self.html_params(
272
                    id='iframe_' + id_ + '_upload',
273
                    input_id=id_,
274
                    src=upload_url,
275
                ),
276
            )
277
        )
278

NEW
279
        field_html = Markup(  # nosec: B704  # noqa: S704
×
280
            '<img {}> <input {}>'.format(
281
                self.html_params(id='img_' + id_, src=value, width='200', **kwargs),
282
                self.html_params(
283
                    id=id_,
284
                    name=field.name,
285
                    placeholder=_("Image URL"),
286
                    value=value,
287
                    **kwargs,
288
                ),
289
            )
290
        )
291

292
        return Markup(
×
293
            render_template(
294
                'baseframe/mui/imgeefield.html.jinja2',
295
                field=field,
296
                iframe_html=iframe_html,
297
                field_html=field_html,
298
            )
299
        )
300

301

302
if TYPE_CHECKING:
4✔
303
    from .fields import RadioMatrixField
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc