Dynamic forms with Django

By : Shabda Raaj

Newforms, (or forms now) are without doubt one of the coolest features of Django. (Of course after Admin, Localflavor, and many others). Here is some sample code.

class EmployeeForm(forms.Form):
    name = forms.CharField()
    age = forms.IntegerField()
    resume = forms.FileField()

Just this code gives you

  1. A form which knows how to render itself as Html.
  2. A form which knows how to validate data on the server side.
  3. A form which knows how to show the relevant errors.

However think of this scenario,

You need to customise your form depending on values in the Database.

What you want to do is

class EmployeeForm(forms.Form):
    name = forms.CharField()
    "Show more fields depending on the Values in DB for a specific employees."
    #....
    #....
    #....

You can do this without resorting to any black magic. Here is the code to do so,

#in models.py

type_mapping = {'CharField':forms.CharField(max_length = 100), 'TextField': forms.CharField(widget = forms.Textarea),
        'BooleanField':forms.BooleanField(required = False),
        'URLField': forms.URLField(), 'EmailField': forms.EmailField()
        }

class EmployeeFieldModel(models.Model):
    "Model for employee form fields for a specific Job board."
    employee = models.ForeignKey(Employee)
    name = models.CharField(max_length = 100)
    type = models.CharField(max_length = 100)
    order = models.IntegerField()

#in forms.py

def get_employee_form(employee):
    """Return the form for a specific Board."""
    employee_fields = EmployeeFieldModel.objects.filter(employee = employee).order_by('order')
    class EmployeeForm(forms.Form):
    def __init__(self, *args, **kwargs):
        forms.Form.__init__(self, *args, **kwargs)
        self.employee = employee
    def save(self):
        "Do the save"
    for field in employee_fields:
    setattr(EmployeeForm, field.name, copy(type_mapping[field.type]))
    return type('EmployeeForm', (forms.Form, ), dict(EmployeeForm.__dict__))

#in views.py

employee = Employee.objects.get(employee_slug)
get_employee_form(employee)

PS. A similar techniques works for Dynamic models.
PPS. Yes I have worked with Oracle. Yes, all my data models start with Employee and Departments.


Want to build an Web Application. Talk to Usware


Related Posts


Can we help you build amazing apps? Contact us today.

Topics : tips forms

Comments

Brian Beck

Cool. Just a note, 'dynamic form' is a bit general, I think an accurate description of what you're doing is generating a form for a "vertical" model.

commmenttor
Brian Beck

On second thought, you're not storing the values vertically so it really is just a dynamic form description.

commmenttor
Andrew Davison

Many thanks. This saved me a lot of digging through the Django source code.

commmenttor
Adam Smith

Cool. Looks pretty good and interesting.

commmenttor
chris

Do you think you could give a demo of what it looks like so I can see it?

You have "do the save". Does it need an override and you just haven't written it? What does your views.py look like if so?

Also, are the fields that are put in dynamically in the "type_mapping" variable?

commmenttor
shabda

Chris:

Chris: Cant give a demo, its a intranet only app.

>You have “do the save”. Does it need an override and you just haven’t written it? What does your views.py look like if so?

Yes, override saving the form data to the db.

> Also, are the fields that are put in dynamically in the “type_mapping” variable?

Db has `type`(bad name btw, type is a builtin, mea culpa), which decides what form filed to show using type_mapping

type_mapping = {'CharField':forms.CharField(max_length = 100), 'TextField': forms.CharField(widget = forms.Textarea),
'BooleanField':forms.BooleanField(required = False),
'URLField': forms.URLField(), 'EmailField': forms.EmailField()
}

commmenttor
aexl 30th July, 2009

in the drupal world there is a powerful pattern, where one app (they call module) can (1) extend the form of another app before rendering, and (2) catch the form values from the post.
this might be an essential part for doing this in django.
if i understand it right to do the same we just would need the form to send "before render" and "before save" signals. right?

commmenttor
Mert Nuhoglu

Thanks. This was what I was looking for exactly.

I have one small addition: James Bennett suggests to use forms.BaseForm instead of forms.Form as base class for dynamically generated Form classes due to some technical reasons.

commmenttor
brainiac 17th Sept., 2009

Thats a really useful trick, thanks for that!
Just out of curiosity, is it necessary to use copy on the field that is looked up in type_mapping?

commmenttor
shabda

Yes. Django sets ordering on fields, when they are instantaited. If you do not copy, the fileds will be out of order.

commmenttor
Ranjan Kumar 7th Sept., 2010

Hi,
How can we dynamically add a fileField to the dynamic form. I tried doing that using the following code but it is throwing errors.
from django import forms

class QuestionsForm(forms.Form):
def <strong>init</strong>(self, questions, <em>args, **kwargs):
super(QuestionsForm, self).<strong>init</strong>(</em>args, **kwargs)
for question in questions:
if question.answer_type == 'textbox':
self.fields['question_%d' % question.id] = forms.CharField(widget = forms.Textarea( attrs={'rows':5, 'cols':60}))
elif question.answer_type == 'file':
self.fields['question_%d' % question.id] = forms.FileField(upload_to='some_dir/')

The error message is:

__init__ got an unexpected keyword argument 'upload_to'

commmenttor
dor

why not put the fields' CLASSES in the mapping variable, instead of instances?
this way you could do this:
> field_class = type_mapping[field.type]
> setattr(EmployeeForm, field.name, field_class(label=field.label))

commmenttor
© Agiliq, 2009-2012