Tuesday, October 26, 2010

USPhoneNumberWidget

This discussion on Stack Overflow focused on how to build a multi <input> phone number fields in Django. The use case is a US Phone number (i.e. 650-555-1212) in which you want to create separate input fields for each part of the phone number. The proposed solutions are discussed in this thread:

http://stackoverflow.com/questions/1777435/django-multiwidget-phone-number-field

Django provides a widget class called MultiValueWidget.py in django.forms.widgets MultiValueWidget.py for this very purpose. We can look at SplitDateTimeWidget in this file for an example on how to use this class. Basically it comes down to implementing a decompress() method, which will take the string-encoded value and return back a list.
def decompress(self, value):
        if value:
            return value.split('-')
        return [None,None,None]
The above implementation implies that our data will be stored in the database with dash-separated fields. We also need to implement the value_from_datadict() method, which is what is used to return the value of the widget.

Normally, the MultiValueWidget class will retrieve each individual text field and concatenate the result back into a list as follows:
def value_from_datadict(self, data, files, name):
        return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)]

We override this function and return back the data in hyphenated form:
def value_from_datadict(self, data, files, name):
        values = super(USPhoneNumberMultiWidget, self).value_from_datadict(data, files, name)
        return u'%s-%s-%s' % values
Footnote: The value_from_datadict() is used to retrieve the data submitted in the form and aggregating the data. The original method simply returns a list, but since we are not using MultiValueField objects in conjunction with MultiWidget, then we must go one extra step and return the data back into the hyphenated string form.

If we were to use MultiValueField objects with MultiWidgets, then the compress() method would be used when a form's clean() function is invoked. This would in turn would convert the list into the native Python object. Since we aren't using MultiValueField, then this clean() function is not invoked and we therefore must return the data directly. Hence, we have the " return u'%s-%s-%s' % values" inside our value_from_datadict() code.

django.forms.forms.py:
def _clean_fields(self):
        for name, field in self.fields.items():
            # value_from_datadict() gets the data from the data dictionaries.                                                                                                                                                                
            # Each widget type knows how to retrieve its own data, because some                                                                                                                                                              
            # widgets split data over several HTML fields.                                                                                                                                                                                   
            value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
            try:
                if isinstance(field, FileField):
                    initial = self.initial.get(name, field.initial)
                    value = field.clean(value, initial)
                else:
                    value = field.clean(value)
                self.cleaned_data[name] = value
                if hasattr(self, 'clean_%s' % name):

2 comments: