Django formsets and date/time fields
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.
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.
↑
More Django
- Django: fix_ampersands and abbreviations
- The fix_ampersands filter will miss some cases where ampersands need to be replaced.
- Custom managers for Django ForeignKeys
- I’ve got one really annoying model for keywords. There’s one category of keywords that, by default, should not show up when used as a ForeignKey for most models. Key word: most.
- Fixing Django 1.2.4’s SuspiciousOperation on filtering
- When you get the message “Filtering by keyword not allowed” in Django 1.2.4, here’s one way to fix it.
- Reusing Django’s filter_horizontal
- Just as with pop-ups, it’s possible to use the built-in JavaScript for filtering multiple-selection popups on custom forms.
- Multiple Input Fields with multiple inheritance
- We needed to display one TextField as either a TextInput or a Textarea, depending on the value in the field. Multiple inheritance makes it easy, if a bit wonky.
- 25 more pages with the topic Django, and other related pages

What version or revision of Django do you run?
Jannis Leidel at 3:21 p.m. December 29th, 2009
Av4Hr
At the time I wrote that I was using 1.1.1 on that server (still am), though I'm looking forward to 1.2.
capvideo at 8:54 p.m. December 29th, 2009
tVAhq
yes, this will be fixed in version 1.2
there was a changeset this week, which fixed this bug -> http://code.djangoproject.com/changeset/12029
Bernd at 9:44 a.m. December 31st, 2009
Ss3Dp