Looking at a Django form

As our aim is to allow users to create dynamic forms based on parameters stored in a database, a good place to start is to look at how a Django form works under the hood and what options we have to customize it. First, let's create a basic form. Create a new main/forms.py file and add the following code to it:

from django import forms

class SampleForm(forms.Form):
    name = forms.CharField()
    age = forms.IntegerField()
    address = forms.CharField(required=False)
    gender = forms.ChoiceField(choices=(('M', 'Male'), ('F', 'Female')))

This is a pretty basic form. However, it has a variety of form fields that we can look at. Let's play around a bit in the shell, which you can start as follows:

> python manage.py shell

Tip

I usually like to install another package called ipython. When you start the Django shell with ipython installed, you get an enhanced version of the basic shell with a lot of cool features such as autocomplete and a much better looking interface. I always install it in any Django project because I almost always use the shell at the start of the project to play around. I highly advise you to install it as well when you start a new Django project.

Import our form in the shell as follows:

> from main.forms import SampleForm

In the same shell, type the following:

> form = SampleForm()
> form.fields
OrderedDict([('name', <django.forms.fields.CharField at 0x10fc79510>),
             ('age', <django.forms.fields.IntegerField at 0x10fc79490>),
             ('address', <django.forms.fields.CharField at 0x10fc79090>),
             ('gender', <django.forms.fields.ChoiceField at 0x10fc792d0>)])

The first line of code simply created an instance of our form class. It's the second line where the fun begins. However, I'll first explain the OrderedDict data structure a bit.

As the name suggests, OrderedDict is a dictionary that maintains the order in which its elements were inserted. A normal dictionary in Python has no fixed order. If you insert three elements into a dictionary with keys A, B, and C, and then you ask for the keys back using the keys() method on the dictionary instance, the order in which you will get them back is not guaranteed, which is why normal built-in dictionaries are said to be unordered.

In contrast, the keys in OrderedDict, which is from the collections library (part of the Python standard library), are guaranteed to be in the same order that you inserted them in. So if you were to iterate over the keys using either the keys() or items() method, you would always get them back in the same order that you inserted them.

Getting back to the output, you will see that the dictionary printed has the same keys as the names of the fields that we used when we created our SampleForm class. The values of these keys are the Field subclasses (CharField, IntegerField, and so on) that we used in our form.

Let's try something. In the shell, type the following and look at the output:

> form.name
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-16-1174e5d9164a> in <module>()
----> 1 form.name

AttributeError: 'SampleForm' object has no attribute 'name'

It seems that the name attribute that we defined on our SampleForm class is no longer there. Weird, huh?

So far you have learned two facts about a Django form. The first, that the fields attribute on a form instance contains a mapping of field names to the Field classes. Second, that the field attributes we defined when creating our SampleForm class are not accessible on the instance.

Putting these two facts together gives us some indication of what Django does with the SampleForm class when creating an instance of it. It removes the field attributes and adds them to the fields dictionary. After a form instance is created, the only way to find out the fields that are defined on it is the fields dictionary attribute.

So, if we wanted to add another field to a form after the class has been defined and we could not change the code of the class itself, we could add an instance of a Field subclass to the fields attribute on the form instance. Let's try it out and see if this works.

Note

I lied a little when I said that Django removes the field attributes from a SampleForm class when creating an instance of it. In reality, the SampleForm class also has its field attributes removed. If you typed SampleForm.name in the shell, you would get a similar AttributeError. However, this information is not relevant to our current task, and the reason why this happens is complicated so I won't go into it in this book. If you want all the details, check out the source for the django.forms.Form class.

Adding an extra field to a SampleForm instance

We will now try an experiment. We will create an instance of our SampleForm with some valid test data, and then we will add another field to the fields attribute on our instance. We will then call the is_valid() method to see if our form instance considers the dynamically added field when validating the data provided earlier. Let's see what happens. In the Django shell, enter the following commands:

> from main.forms import SampleForm
> test_data = {'name': 'Jibran', 'age': 27, 'gender': 'M'}
> form = SampleForm(test_data)
> from django import forms
> form.fields['country'] = forms.CharField()
> form.is_valid()
False
> form.errors
{'country': [u'This field is required.']}

Nice! It seems like our idea worked. Even though we added the country field after our SampleForm had been instantiated and provided the data, the form validation did consider our new field. As the country field was not part of the test_data dictionary and the field is required, the form validation failed and the errors list included the appropriate error.

Now that we have a technique to achieve our objective, let's create some views and templates to render a dynamic form.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.255.187