hobbes3
hobbes3

Reputation: 30308

How to dynamically create a ModelForm based on James Bennett's article "So you want a dynamic form"?

In James Bennett's article "So you want a dynamic form" (Nov. 9, 2008), he wrote that to create a dynamic form you can do something like this:

def make_contact_form(user):
    fields = { 'name': forms.CharField(max_length=50),
               'email': forms.EmailField(),
               'message': forms.CharField(widget=forms.Textarea) }
    if not user.is_authenticated():
        fields['captcha'] = CaptchaField()
    return type('ContactForm', (forms.BaseForm,), { 'base_fields': fields })

But how would you do the same thing with forms.ModelForm?

So far I'm just doing something like this (I couldn't figure out how to use type with an inner class 'Meta')

def make_order_edit_form(include_fields):
    class _OrderEditForm(forms.ModelForm):
        if 'fa_date' in include_fields:
            fa_date = CustomDateTimeField(label="first appointment time")

        class Meta:
            model = Order
            fields = include_fields
            widgets = custom_widgets

    return _OrderEditForm

where include_fields is a tuple of fields I want to show.

However even if I wrote a correct make_order_edit_form, how do I use it in views.py? Specifically how do I pass both the POST request and the order instance to it? Normally I would do something like

order = Order.objects.get(pk=pk)

order_form = OrderEditForm(data=request.POST, instance=order)

Bonus question:

Why did Bennett create a ContactForm out of forms.BaseForm instead of forms.Form? (I'm assuming that's why the fields are called base_fields as well.)

Upvotes: 2

Views: 217

Answers (1)

okm
okm

Reputation: 23871

make_order_edit_form returns a ModelForm class, thus you could

form_cls = make_order_edit_form(fields)
order_form = form_cls(request.POST, instance=order)

For the bonus question, check the Form code:

class Form(BaseForm):
    "A collection of Fields, plus their associated data."
    # This is a separate class from BaseForm in order to abstract the way
    # self.fields is specified. This class (Form) is the one that does the
    # fancy metaclass stuff purely for the semantic sugar -- it allows one
    # to define a form using declarative syntax.
    # BaseForm itself has no way of designating self.fields.
    __metaclass__ = DeclarativeFieldsMetaclass

The Form has a customized meta class DeclarativeFieldsMetaclass which automatically collects form fields written in declarative syntax, if you use Form in type(), it looks like (take Bennett's example)

type('ContactForm', (forms.Form,), {
    'name': forms.CharField(max_length=50),
    'email': forms.EmailField(),
    'message': forms.CharField(widget=forms.Textarea)}
# instead of
type('ContactForm', (forms.BaseForm,), { 'base_fields': fields })

update

to build up ModelForm using type, no much different

def make_order_edit_form(include_fields):
    d = {}
    class Meta:
        model = Order
        fields = include_fields
        widgets = custom_widgets 
    d = {'Meta':Meta}
    if 'fa_date' in include_fields:
        d['fa_date'] = CustomDateTimeField(label="first appointment time")
    return type('OrderEditForm', (forms.ModelForm,), d)

Upvotes: 3

Related Questions