A simple site-wide, per-user, date format validation system

30 July 2008

django, forms, validation


It is important to be aware that dates are spelled differently in different countries (e.g. dd/mm/yyyy in Australia or mm/dd/yyyy in the US). This is why it is a good idea to let the user select their preferred date format and store it into their user profile. For example, you may store the values "%d/%m/%Y" or "%m/%d/%Y" in that user's profile. That way, you may display dates in the format chosen by the user throughout the site.

Now, if the site contains many forms with date fields (say, for example, you're building a calendar application), it can be a bit repetitive and annoying to check and assign the date format for every form in every view. To go around that, I came up with a simple trick. It all happens in the following class:

class FormWithFormattedDates(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        date_format = None
        if 'date_format' in kwargs:
            date_format = kwargs['date_format']
            del kwargs['date_format'] # The ModelForm's __init__ method doesn't expect to receive that argument, so take it out.
        super(FormWithFormattedDates, self).__init__(*args, **kwargs)
        if date_format is not None:
            for (field_name, field) in self.fields.items():
                if isinstance(field, forms.fields.DateField):
                    field.input_format = [date_format]
                    field.widget = forms.widgets.DateTimeInput(format=date_format)

What this class does is explore what's in the form, and make sure that all date fields will display and validate with the given date format.

Then, all you need to do is to make all your forms that contain date fields inherit from the above class. For example:

# Model
class Event(models.Model):
    title = models.CharField(max_length=100)
    start_date = models.DateField()
    end_date = models.DateField()

#Form
class EventForm(FormWithFormattedDates):
    class Meta:
        model = Event

The next step is then in the view. When you instantiate the form, just pass it the user's preferred format as parameter:

def add_new_event(request):
    if request.method == 'POST':
        form = EventForm(data=request.POST, date_format=request.user.get_profile().date_format)
        if form.is_valid():
            ...

All the validation logic is taken care of by the FormWithFormattedDates class. This allows to both keep the views' code very simple, and also to not have to worry about per-user validation as it all happens automatically.

Update: I could add that you could use the same approach to let the user select their preferred time format (e.g. "1pm" or "13:00"), or, in fact, with any kind of validation that needs to be operated on a large number of forms in your site and where that validation also depends on the user's preferences.