If you don’t do so allready, you should really get into the habit of looking at the validation errors your users are seeing. If you’re using the idiomatic Django view code this is very easy to do. After you get a POST request, you’ll verify the data:
# validate the form errors = sform.get_validation_errors(new_data)
then all you have to do is write the errors dict to a log (a database table with a big message field works as well as most other approaches). When I did this with a project where I’d used the date widget from a couple of posts ago, users were having big problems entering dates correctly (it was a quick project for users that shouldn’t have any problems entering dates in new formats, but still there were problems).
The first solution I wanted to try was to allow users to enter dates in their normal format. Normal for these users meant that October 30th, 2006 would either be entered as 30.10.2006 or 30/10/2006. You can do this in Django by simply writing a validator for this pattern, but since I knew I needed to write custom field classes for other things I wanted to do, this is the solution I ended up with:
import re, datetime from django import forms from django.core import validators class BirthDay(forms.FormField): @staticmethod def validator(field_data, all_data): # split on . or / and expect to get three items fields = re.split(r'[\./]', field_data) if len(fields) != 3: errtxt = "Date must look like: 30.10.2006." raise validators.ValidationError(errtxt) try: fields = map(int, fields) except ValueError: raise validators.ValidationError("Only use numerical date parts.") try: d = datetime.date(fields, fields, fields) except ValueError: raise validators.ValidationError("Invalid date.") today = datetime.date.today() if d> today: raise validators.ValidationError("You're not born yet.") if today.year - d.year> 120: errtxt = "Invalid date: did you enter a 4 digit year?" raise validators.ValidationError(errtxt) return True @staticmethod def html2python(data): # data is allready validated fields = map(int, re.split(r'[\./]', data)) return datetime.date(fields, fields, fields) def __init__(self, field_name='', is_required=False): self.field_name = field_name self.validator_list = [BirthDay.validator] self.is_required = is_required def render(self, data): widget = '<input id="%s" name="%s" type=text maxlength=10 size=10 value="%s" />' return widget % (self.get_id(), self.field_name, forms.escape(data))
If you look closely it’s all almost trivial code. I try to be as thorough as possible in the validator so that I can issue relevant error messages. The html2python method relies on validation allready being done, which it would be when following the standard Django idiom (you cannot in general call the validator from here since the validator might need other fields in order to validate…) The magic happens in the render method, by making sure you provide the correct id and name for the input tag, and feed it data that the user has allready provided.
After this is done, you can use the new class in the custom manipulator we wrote last time:
class SuggestionForm(forms.Manipulator): def __init__(self): self.fields = [ forms.TextField('name', is_required=True), BirthDay('dob', is_required=False), forms.LargeTextField('comment', is_required=True), ]
This was perhaps not the best documented corner of Django, but the code in the forms module was exceedingly easy to follow.