Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/ogc 1160 formcode warning for empty fieldsets #1444

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/onegov/form/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ def __init__(self, line: int):
self.line = line


class EmptyFieldsetError(FormError):
def __init__(self, field_name: str):
self.field_name = field_name


class FieldCompileError(FormError):
def __init__(self, field_name: str):
self.field_name = field_name
Expand Down
6 changes: 6 additions & 0 deletions src/onegov/form/locale/de_ch/LC_MESSAGES/onegov.form.po
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,12 @@ msgstr ""
"Ein Minimalpreis kann nur gesetzt werden, wenn mindestens ein "
"kostenpflichtiges Feld definiert ist."

#, python-format
msgid "The '{label}' group is empty and will not be visible. Either "
"remove the empty group or add fields to it."
msgstr "Die Gruppe '{label}' ist leer und wird nicht sichtbar sein. "
"Entweder entfernen Sie die leere Gruppe oder fügen Felder hinzu."

#, python-format
msgid ""
"Invalid field type for field '{label}'. For filters only 'select' or "
Expand Down
6 changes: 6 additions & 0 deletions src/onegov/form/locale/fr_CH/LC_MESSAGES/onegov.form.po
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,12 @@ msgstr ""
"Un total de prix minimum ne peut être paramétré que si au moins un champ "
"payant est défini."

#, python-format
msgid "The '{label}' group is empty and will not be visible. Either "
"remove the empty group or add fields to it."
msgstr "Le groupe '{label}' est vide et ne sera pas visible. Supprimez le "
"groupe vide ou ajoutez des champs."

#, python-format
msgid ""
"Invalid field type for field '{label}'. For filters only 'select' or "
Expand Down
7 changes: 7 additions & 0 deletions src/onegov/form/locale/it_CH/LC_MESSAGES/onegov.form.po
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,13 @@ msgstr ""
"È possibile impostare un prezzo minimo totale solo se è definito almeno un "
"campo prezzo."

#, python-format
msgid "The '{label}' group is empty and will not be visible. Either "
"remove the empty group or add fields to it."
msgstr ""
"Il gruppo '{label}' è vuoto e non sarà visibile. Rimuovere il gruppo vuoto "
"o aggiungere campi."

#, python-format
msgid ""
"Invalid field type for field '{label}'. For filters only 'select' or "
Expand Down
19 changes: 11 additions & 8 deletions src/onegov/form/parser/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1156,18 +1156,19 @@ class CheckboxField(OptionsField, Field):
@lru_cache(maxsize=1)
def parse_formcode(
formcode: str,
enable_indent_check: bool = False
enable_edit_checks: bool = False
) -> list[Fieldset]:
""" Takes the given formcode and returns an intermediate representation
that can be used to generate forms or do other things.

:param formcode: string representing formcode to be parsed
:param enable_indent_check: bool to activate indent check while parsing.
Should only be active originating from forms.validators.py
:param enable_edit_checks: bool to activate additional check after
editing the form. Should only be active originating from
forms.validators.py
"""
# CustomLoader is inherited from SafeLoader so no security issue here
parsed = yaml.load( # nosec B506
'\n'.join(translate_to_yaml(formcode, enable_indent_check)),
'\n'.join(translate_to_yaml(formcode, enable_edit_checks)),
CustomLoader
)

Expand All @@ -1188,6 +1189,8 @@ def parse_formcode(
parse_field_block(block, field_classes, used_ids, fs)
for block in (fieldset[label] or ())
]
if enable_edit_checks and not fs.fields:
raise errors.EmptyFieldsetError(label)

fieldsets.append(fs)

Expand Down Expand Up @@ -1328,14 +1331,14 @@ def validate_indent(indent: str) -> bool:

def translate_to_yaml(
text: str,
enable_indent_check: bool = False
enable_edit_checks: bool = False
) -> 'Iterator[str]':
""" Takes the given form text and constructs an easier to parse yaml
string.

:param text: string to be parsed
:param enable_indent_check: bool to activate indent check while parsing.
Should only be active originating from forms.validators.py
:param enable_edit_checks: bool to activate additional checks after
editing a form. Should only be active originating from forms.validators.py
"""

lines = ((ix, l) for ix, l in prepare(text))
Expand All @@ -1352,7 +1355,7 @@ def escape_double(text: str) -> str:
for ix, line in lines:

indent = ' ' * (4 + (len(line) - len(line.lstrip())))
if enable_indent_check and not validate_indent(indent):
if enable_edit_checks and not validate_indent(indent):
raise errors.InvalidIndentSyntax(line=ix + 1)

# the top level are the fieldsets
Expand Down
13 changes: 7 additions & 6 deletions src/onegov/form/parser/form.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,15 @@
@overload
def parse_form(
text: str,
enable_indent_check: bool,
enable_edit_checks: bool,
base_class: type[_FormT]
) -> type[_FormT]: ...


@overload
def parse_form(
text: str,
enable_indent_check: bool = False,
enable_edit_checks: bool = False,
*,
base_class: type[_FormT]
) -> type[_FormT]: ...
Expand All @@ -71,27 +71,28 @@ def parse_form(
@overload
def parse_form(
text: str,
enable_indent_check: bool = False,
enable_edit_checks: bool = False,
base_class: type[Form] = Form
) -> type[Form]: ...


def parse_form(
text: str,
enable_indent_check: bool = False,
enable_edit_checks: bool = False,
base_class: type[Form] = Form
) -> type[Form]:
""" Takes the given form text, parses it and returns a WTForms form
class (not an instance of it).

:type text: string form text to be parsed
:param enable_indent_check: bool to activate indent check while parsing.
:param enable_edit_checks: bool to activate additional checks after
editing a form.
:param base_class: Form base class
"""

builder = WTFormsClassBuilder(base_class)

for fieldset in parse_formcode(text, enable_indent_check):
for fieldset in parse_formcode(text, enable_edit_checks):
builder.set_current_fieldset(fieldset.label)

for field in fieldset.fields:
Expand Down
15 changes: 12 additions & 3 deletions src/onegov/form/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from dateutil.relativedelta import relativedelta
from mimetypes import types_map
from onegov.form import _
from onegov.form.errors import DuplicateLabelError, InvalidIndentSyntax
from onegov.form.errors import (DuplicateLabelError, InvalidIndentSyntax,
EmptyFieldsetError)
from onegov.form.errors import FieldCompileError
from onegov.form.errors import InvalidFormSyntax
from onegov.form.errors import MixedTypeError
Expand Down Expand Up @@ -189,6 +190,9 @@
"A minimum price total can only be set if at least one priced field "
"is defined."
)
empty_fieldset = _(
"The '{label}' group is empty and will not be visible. Either remove "
"the empty group or add fields to it.")

def __init__(
self,
Expand Down Expand Up @@ -218,6 +222,11 @@
raise ValidationError(
field.gettext(self.indent).format(line=exception.line)
) from exception
except EmptyFieldsetError as exception:
raise ValidationError(

Check warning on line 226 in src/onegov/form/validators.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/form/validators.py#L226

Added line #L226 was not covered by tests
field.gettext(self.empty_fieldset).format(
label=exception.field_name)
) from exception
except DuplicateLabelError as exception:
raise ValidationError(
field.gettext(self.duplicate).format(label=exception.label)
Expand Down Expand Up @@ -304,13 +313,13 @@
def _parse_form(
self,
field: 'Field',
enable_indent_check: bool = True
enable_edit_checks: bool = True
) -> 'Form':
# XXX circular import
from onegov.form import parse_form

return parse_form(field.data,
enable_indent_check=enable_indent_check)()
enable_edit_checks=enable_edit_checks)()


class ValidFilterFormDefinition(ValidFormDefinition):
Expand Down
54 changes: 50 additions & 4 deletions tests/onegov/form/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1046,7 +1046,7 @@ def test_normalization():
assert norm == "Ich möchte die Bestellung mittels Post erhalten"


@pytest.mark.parametrize('indent,indent_check,shall_raise', [
@pytest.mark.parametrize('indent,edit_checks,shall_raise', [
# indent check active while parsing
('', True, False),
(' ', True, True),
Expand All @@ -1055,7 +1055,7 @@ def test_normalization():
# no indent check while parsing
('', False, False),
])
def test_indentation_error_while_parsing(indent, indent_check, shall_raise):
def test_indentation_error(indent, edit_checks, shall_raise):
# wrong indent see 'Telefonnummer'
text = dedent(
"""
Expand All @@ -1069,11 +1069,57 @@ def test_indentation_error_while_parsing(indent, indent_check, shall_raise):

if shall_raise:
with pytest.raises(InvalidIndentSyntax) as excinfo:
parse_formcode(text, enable_indent_check=indent_check)
parse_formcode(text, enable_edit_checks=edit_checks)

assert excinfo.value.line == 6
else:
try:
parse_formcode(text, enable_indent_check=indent_check)
parse_formcode(text, enable_edit_checks=edit_checks)
except InvalidIndentSyntax as e:
pytest.fail('Unexpected exception {}'.format(type(e).__name__))


def test_empty_fieldset_error():
fieldsets = parse_formcode('\n'.join((
"# Section 1",
"# Section 2",
"First Name *= ___",
"Last Name *= ___",
"E-mail *= @@@"
)), enable_edit_checks=False)
assert len(fieldsets) == 2

with pytest.raises(errors.EmptyFieldsetError) as e:
parse_formcode('\n'.join((
"# Section 1",
"# Section 2",
"First Name *= ___",
"Last Name *= ___",
"E-mail *= @@@"
)), enable_edit_checks=True)

assert e.value.field_name == 'Section 1'

with pytest.raises(errors.EmptyFieldsetError) as e:
parse_formcode('\n'.join((
"# Section 1",
"First Name *= ___",
"Last Name *= ___",
"# Section 2",
"E-mail *= @@@",
"# Section 3",
)), enable_edit_checks=True)

assert e.value.field_name == 'Section 3'

with pytest.raises(errors.EmptyFieldsetError) as e:
parse_formcode('\n'.join((
"# Section 1",
"First Name *= ___",
"Last Name *= ___",
"# Section 2",
"# Section 3",
"E-mail *= @@@",
)), enable_edit_checks=True)

assert e.value.field_name == 'Section 2'