Thursday, May 29, 2014

Hack the SelectDateWidget of Django's form

Remember how we can fetch data from an external database to a Django's form? If not, you can check out this blog post:

http://iambusychangingtheworld.blogspot.com/2013/07/django-make-use-of-django-forms.html

Code block (1):
...
def __init__(self, *args, **kwargs):

    db = my_db_utils.MyDBUtil()
    self.fields['mydate'].initial = my_db_utils.get_data(db, 'my_date')

...


Today, I found out an issue with the SelectDateWidget for django.forms.extra.widgets module. It could not display the correct year fetched from the database if the year is in the past. It will render the current year instead. The only correct parts are the day and the month of the intial date data fetched from db.

As I invested source code of the django/forms/extra/widgets.py, I saw:

Code block (2):
...
    def __init__(self, attrs=None, years=None, required=True):
        # years is an optional list/tuple of years to use in the "year" select box.
        self.attrs = attrs or {}
        self.required = required
        if years:
            self.years = years
        else:
            this_year = datetime.date.today().year
            self.years = range(this_year, this_year+10)
...

It separates the year part of the form data to process and it only get the year which will be passed to the SelectDateWidget. So it will generate a year range from the current year to 10 years later if I don't give it my custom years range. You can assign the year range to the SelectDateWidget when indicating the widget style of a form's field, something like:

Code block (3):

my_date = forms.DateField(widget=SelectDateWidget(years=[2012, 2013, 2014]),required=True)


So, I can assign my desired year data to the form's field by creating a global year range with the year fetched from the db, and also move the data connection utils out of the init method:

Code block (4):

db = my_db_utils.MyDBUtil()
mydate = my_db_utils.get_data(db, 'my_date')
my_year_range = range(mydate.year, mydate.year+10)

class MyForm(forms.Form):
...
   
    my_date = forms.DateField(widget=SelectDateWidget(years=my_year_range),required=True)

    def __init__(self, attrs=None, years=None, required=True):
    ...

        self.fields['my_date'].initial = mydate

...




Reference: https://docs.djangoproject.com/en/dev/ref/forms/widgets/#selectdatewidget