created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
The problem I ran into was that I wanted to keep the legacy timestamp from the old database when I loaded the record for the first time then have the auto_now kick in. The problem is that auto_now is very stupid (as it should be) and doesn't understand the concept of insert or update and whether your new record is coming in with pre-populated DateTimeField attribute. It gladly throws away any value away and always replaces the value with the current time. I didn't notice it on first import but soon realized auto_now wouldn't meet my needs.
After a quick search, I found this Stack Overflow question that had the perfect fix for my problem. It suggested implementing my own save that would allow me to pass in values for created or modified but would fall back to the current time. I changed the code a bit because I didn't need a created timestamp for my model, but this should give you an idea.
Note: you have to remove any additional values from kwargs before passing to the parent class or super.save will complain that created and modified aren't valid arguments.
import datetime
class User(models.Model):
created = models.DateTimeField(editable=False)
modified = models.DateTimeField()
def save(self, *args, **kwargs):
# original Code: called datetime.datetime.today()
# multiple places. Modified kwargs
#
# created = kwargs.pop('created', None)
# if created:
# self.created = created
# elif not self.id:
# self.created = datetime.datetime.today()
#
# modified = kwargs.pop('modified', None)
# if modified:
# self.modified = modified
# else:
# self.modified = datetime.datetime.today()
# Edit #1: Changed example to more efficient
# version from blog comments.
# single today()
#
# today = datetime.datetime.today()
#
# if not self.id
# self.created = kwargs.pop('created', today)
# elif kwargs.get('created', None)
# self.created = kwargs.pop('created')
#
# self.modified = kwargs.pop('modified', today)
# Edit #2: no longer pass arguments via save
# was orignially how I wanted to solve
# the problem and how I felt auto_now
# should have worked. Thanks to
# indosauros from reddit. :)
#
# On inital creation of the object
# only set today if values are not
# already populated. auto_now*
# wipes them out regardless
today = datetime.datetime.today()
if not self.id:
if not self.created:
self.created = today
if not self.modified:
self.modified = today
else:
self.modified = today
super(User, self).save(*args, **kwargs)
If you see any glaring python or style related issues, please let me know. I'm still finding my way pythonwise and would love any best practices feedback.
Instead of using None as the default arg for kwargs.pop just use datetime.datetime.today()!
ReplyDeletetoday = datetime.datetime.today()
self.created = kwargs.pop('modified',today)
self.modified = kwargs.pop('modified',today)
note I use the same value of today for both created and modified. If you call datetime.datetime.today() each for created and modified you may get slightly different values.
Aw, perfect. I think because python is so foreign to me, I'm not seeing those simple patterns. Thanks! Small bug that created gets set with today if or if created is passed in then it can be overridden anytime.
ReplyDeletetoday = datetime.datetime.today()
if not self.id:
self.created = kwargs.pop('created', today)
elif kwargs.get('created',None)
self.created = kwargs.pop('created')
self.modified = kwargs.pop('modified', today)
Thanks for taking time to respond.
Updated the post to reflect the changes.
DeleteHello, but what to do when you use diferrent field as primary_key and you don't have access to self.id ?
ReplyDeleteAt this moment I do:
try:
p = Product.objects.get(reference=self.reference)
self.date_created = p.date_created
except self.DoesNotExist:
pass
but I'm not sure if this is the correct method