Mimsy Were the Borogoves

Hacks: Articles about programming in Python, Perl, PHP, and whatever else I happen to feel like hacking at.

Django formsets and date/time fields

Jerry Stratton, December 17, 2009

Just a quick note on something I’ve run into a couple of times now. The forms created from modelformset_factory (and probably other inline forms) will detect spurious changes if you have DateFields, DateTimeFields, or TimeFields and your default value is a Python datetime object.

[toggle code]

  • class Note(models.Model):
    • title = models.CharField('Note Title', max_length=80)
    • content = models.TextField('Note', max_length=2000)
    • live = models.BooleanField(default=True)
    • livedate = models.DateTimeField('Go Live', default=datetime.datetime.now)
    • deaddate = models.DateTimeField('Go Dead', default=datetime.datetime.now)

What appears to be happening if I create an automatic formset from this model is that the form will check for changes based on whether any of the data in the submitted form differs from the data in the defaults above. But form submissions are always text, and the default for the two DateTimeFields are Python datetime objects1.

The blank line for adding new items thus always looks like it has changed—I’m guessing, because text never equals a datetime object. The is_valid method will always return invalid (unless they actually did add a new item) because it sees those two fields as having changed. They are “no longer” datetime objects. So it assumes the visitor has attempted to add a new item, but the other required items (title, content) are blank. So it generates errors of “This field is required.” for the title and content fields.

The way I fix this is to make the default be a text rendering of the date/time using strftime.

[toggle code]

  • #without setting a string format, formsets will always think the value has changed on form submission
  • def defaultLive():
    • now = datetime.datetime.now()
    • return now.strftime('%Y-%m-%d %H:%M')
  • def defaultDead():
    • then = datetime.datetime.now()+datetime.timedelta(days=3)
    • return then.strftime('%Y-%m-%d 09:00')
  • class Note(models.Model):
    • title = models.CharField('Note Title', max_length=80)
    • content = models.TextField('Note', max_length=2000)
    • live = models.BooleanField(default=True)
    • livedate = models.DateTimeField('Go Live', default=defaultLive)
    • deaddate = models.DateTimeField('Go Dead', default=defaultDead)

This issue appears to be unique to Date/Time fields. For example, the BooleanField above works fine, despite the form not sending back a Python boolean. I suspect it’s just a matter of being a lot easier to convert a text value to a boolean than to convert it to a Date/Time field.

  1. It might also be that the comparison is between text versions, but that the conversion to text isn’t the same between the automatic form and the default value check. I didn’t try very hard, but I was unable to find a text version that would match and not be flagged as a change.

  1. <- Django Dynamic Input
  2. Inkscape extension scripts ->