pallets-eco / pallets-eco/wtforms

No way to build a dynamic form with inheritance

Open
#736 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
Python
Stars
1.6k
Forks
409
PR merge metrics
No merged PRs in 30d

Description

Expected Behavior

We have some data like the following:

[
  {'id': 'item1', 'percentage': 20},
  {'id': 'item2', 'percentage': 50},
  {'id': 'item3', 'percentage': 30},
]

We want to make a form with an input for each item so that we can change all the percentages in one go and check they still add up to 100%. The number of items will change over time based on what's in our database, so we can't hard-code them as class-level attributes and need to specify the fields dynamically instead.

In theory we could do this with the BaseForm class, passing an unbound field for each input in the constructor. However, this isn't possible for us in practice as we're using the FlaskForm class from flask_wtf, which inherits from Form. The Form class locks the set of fields to the class-level attributes discovered by its metaclass.

Ideally we would do this:

class DynamicFieldForm(Form): 
  # item_1 = InputField(item_1_id)  # can't do this
  # code to do validation, etc.
  pass

form = DynamicFieldForm(
  extra_fields=[
    InputField(item['id']) for item in items
  ],
  data={
    item['id']: item['percentage'] for item in items
  }
)
Actual Behavior

It's possible to pass fields in the constructor with this hack:

# toy example without validation
class DynamicFieldForm(Form):
    def __init__(self, items):
        self._unbound_fields = [
          (item['id'], InputField(item['id'])
          for item in items
        ]

        super().__init__(data={
            item['id']: item['percentage'] for item in items
        })

...

# get data for an item later on
getattr(form, item['id']).data

Using the internal _unbound_fields attribute isn't great as it's not part of the public interface and might break in future versions. Would it be possible to expose it in the constructor for the Form class?

Environment
  • Python version: 3.9
  • wtforms version: 3.0.1

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read src/wtforms/form.py, especially the Form constructor and metaclass field discovery described in the issue. Compare the public constructor behavior with the current _unbound_fields workaround and determine how dynamically supplied fields should coexist with inherited fields. Done means FlaskForm-compatible forms can declare per-instance fields through a supported interface and validate their submitted data.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.