Pylons / deform

A Python HTML form library.
Other
416 stars 160 forks source link

Values for SelectWidget/Select2Widget may not be a generator #385

Closed digitalresistor closed 3 years ago

digitalresistor commented 5 years ago

If you create a widget with a keyword argument that is a generator for values and you validate the form and render the form you will lose all your values in the rendered output as the generator won't be generating anymore.

This bit me pretty hard tonight and took me a while to debug, even though I would consider a generator a "a sequence" if one were to look at the documentation.

Maybe it should internally create a list if the incoming type is not a list? Or some other way save the result from _validate_choices so that it is computed once instead of each time.

stevepiercy commented 3 years ago

@bertjwregeer can you provide a code sample that reproduces this issue? I tried with this example modified from deformdemo, but I think I missed something.

    @view_config(renderer="templates/form.pt", name="select_with_multiple")
    @demonstrate("Select Widget (with multiple)")
    def select_with_multiple(self):

        def choices_generator():
            for value in range(3):
                yield(value, value)

        choices = choices_generator()

        class Schema(colander.Schema):
            pepper = colander.SchemaNode(
                colander.Set(),
                widget=deform.widget.SelectWidget(
                    values=choices, multiple=True
                ),
            )

        schema = Schema()
        form = deform.Form(schema, buttons=("submit",))

        return self.render_form(form)
digitalresistor commented 3 years ago

You need to submit the form at least once (and validate, but have it fail validation on another form input for instance). At that point when you render the form again the generator will no longer return any values, and all choices will be blank.

digitalresistor commented 3 years ago

Also, I think I meant _normalize_choices not _validate_choices.

stevepiercy commented 3 years ago

I modified the mini.py example.

class ExampleSchema(deform.schema.CSRFSchema):
    def choices_generator():
        for value in range(3):
            yield(value, value)

    choices = choices_generator()

    pepper = colander.SchemaNode(
        colander.Set(),
        widget=deform.widget.SelectWidget(
            values=choices, multiple=True,
        ),
    )

    name = colander.SchemaNode(colander.String(), title="Name")

Submit that without entering a value for name, and the set of pepper values does not appear. Nasty, evil, formses. I think I got a fix for this.

stevepiercy commented 3 years ago

@bertjwregeer I opened a PR for you to try out and review. https://github.com/Pylons/deform/pull/469

I couldn't figure out how Deform could save the generated values into a list so it doesn't have to regenerate them on each load/submission.