1

I want to set attributes dynamically to a FlaskForm like this

form = ModelForm(request.form)

file form.py

class ModelForm(FlaskForm):
    def __init__(self, postData):
        super(ModelForm, self).__init__()
        for p in postData:
            setattr(ModelForm, p, StringField(p, validators=[InputRequired()]))

But it only work for the second time running, the first time running, it doesn't work.

I really don't understand how python constructor works. As this post, it said because

class A is not fully initialized when you do your setattr(A, p, v) there.

But in other languages, the object have to be created after constructor finished, and it has full class variables, properties declared in the constructor ?

For example, it works and can print a.key. So what's difference there in the flask constructor and this?

class A(object):
    def __init__(self):
        self.a = 'i am a accessor'
        setattr(self, 'key', 'value')

a = A()
print a.a
print a.key
TomSawyer
  • 3,711
  • 6
  • 44
  • 79

1 Answers1

1

class A is not fully initialized

means, when you create your first instance of your class with form=ModelForm(), you start adding attributes to the class you are currently instancing from. I don't know, how exactly this works internally in Python. But since you say this only works in the second run, I guess the object is first created with all attributes defined for the class, then __init__ is executed. So the new attributes are added to late.

In other words: Your are trying to change the class definition after you already created an instance of it. You need to add all your attributes, before you instantiate.

Now what you need to do is: first define the class without any dynamic fields. Then, after the class definition, you add the loop with your dynamic fields.

ModelForm = FlaskForm
for p in postData:
    setattr(ModelForm, p, StringField(p, validators=[InputRequired()]))

Or if you need to add some other stuff in class definition:

class ModelForm(FlaskForm):
    def foo(self):
        return 'bar'

for p in postData:
    setattr(ModelForm, p, StringField(p, validators=[InputRequired()]))

And then, somewhere in your view function, you can use form = ModelForm(request.form) as usual.

Usually you know what fields you need beforehand. The form just answers on what it got from the GET request. So this should be fine. But maybe you added some more fields with some JS on client side, which the server does not know about (yet). In that case you might try to put the class definition into the local scope of the view function which handles the POST request.

Feodoran
  • 1,752
  • 1
  • 14
  • 31
  • I've updated the question with example about constructor to clarify what's difference there. Since ` I don't know, how exactly this works internally in Python` has been spoken, it means constructor in Python doesn't work properly or Flask form did something with their library to prevent finishing object initialization. Ofcouse, we can workaround by many way with late `setattr`, but it doesn't help me to understand what's going on with Flask or Python constructor – TomSawyer Oct 10 '17 at 19:57
  • What happens if you try putting the `super().__init__` call after `setattr`? But why do you even want to use `setattr` in `__init__`? You are trying to build an object which changes its own blueprints during constructions. I don't even know whether this is supposed to work. – Feodoran Oct 10 '17 at 21:22
  • Putting `super()` after setattr doesn't work, it gives error `TypeError: 'NoneType' object is not iterable`. Setattr inside __init__ is good practice and work in every language, it helps avoiding to write more lines to declare form object, i only need: `form = MyForm(attrs)` – TomSawyer Oct 11 '17 at 04:52
  • My suggestion only requires this only line for declaring as well. Where the error is coming from is hard to tell without full traceback and MCVE. – Feodoran Oct 11 '17 at 06:56