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
- 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.
- Django tutorial mostly ready
- My long-promised Django tutorial is pretty much ready. It’s still designed around an in-person tutorial, but you should be able to get started using it even if you’re on your own.
- Django: Beyond the SQL
- Django is a great application framework for Python and web applications. You can use it to greatly speed up your database and application development both on the web and on the command line. This tutorial is currently a very rough draft; it probably won’t be very useful without the assistance of someone who knows Django running the tutorial. If I ever run this tutorial a second time, I’ll probably update it with screenshots to make it more usable for individuals.
- Django actions as their own intermediate page
- The Django documentation recommends a complicated URL redirect to handle admin actions that need an intermediate page. But there’s no reason we can’t just piggy-back the intermediate page on the action itself.
- 22 more pages with the topic Django, and other related pages
