Reputation: 50662
For Django 1.1.
I have this in my models.py:
class User(models.Model):
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
When updating a row I get:
[Sun Nov 15 02:18:12 2009] [error] /home/ptarjan/projects/twitter-meme/django/db/backends/mysql/base.py:84: Warning: Column 'created' cannot be null
[Sun Nov 15 02:18:12 2009] [error] return self.cursor.execute(query, args)
The relevant part of my database is:
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
Is this cause for concern?
Side question: in my admin tool, those two fields aren't showing up. Is that expected?
Upvotes: 369
Views: 438549
Reputation: 157
Such as is reported in docs:
Using update() also prevents a race condition wherein something might change in your database in the short period of time between loading the object and calling save(). Finally, realize that update() does an update at the SQL level and, thus, does not call any save() methods on your models, nor does it emit the pre_save or post_save signals (which are a consequence of calling Model.save()). If you want to update a bunch of records for a model that has a custom save() method, loop over them and call save(), like this:
for e in Entry.objects.filter(pub_date__year=2010):
e.comments_on = False
e.save()
Upvotes: 0
Reputation: 1092
Some comments have been made about update_fields, but it has not been included in an answer, so I am placing an important revision here. By default when django saves an instance, it saves every field in that instance. This is wasteful when only one field is being updated, and a more economical method is to only save the field being changed. This is done in django by using the update_fields parameter as in:
qt = QueuedTask.objects.create()
qt.status = PENDING
qt.save(update_fields=['status'])
Now your save method should take this into consideration, otherwise in the above save, the last_modified field is not also saved (Note that I am not sure about created, but as you could create a new instance, and use save method with update fields, I also include the condition in the save):
def save(self, *args, **kwargs):
if self.id is None:
self.created_on = timezone.now()
update_fields = kwargs.get('update_fields')
if update_fields is not None and 'created_on' not in update_fields:
update_fields += ['created_on']
else:
self.last_modified = timezone.now()
update_fields = kwargs.get('update_fields')
if update_fields is not None and 'last_modified' not in update_fields:
update_fields += ['last_modified']
return super(QueuedTask, self).save(*args, **kwargs)
Upvotes: 0
Reputation: 1
you can use this code technique for make a [creation date] (that automatically save date at creation of row and never update the date and time on every updation ) and a [updation date] (that change date and time every time when you update the row).
from django.db import models
from django.utils.timezone import now
class Artical(models.Model):
creation = models.DateTimeField(null=True, default=None, blank=True)
updation = models.DateTimeField(null=True,default=None,blank=True)
def save(self, *args, **kwargs):
if not self.creation:
self.creation = now()
self.updation = now()
super(Artical, self).save(*args, **kwargs)
Upvotes: 0
Reputation: 33724
Any field with the auto_now
attribute set will also inherit editable=False
and therefore will not show up in the admin panel. There has been talk in the past about making the auto_now
and auto_now_add
arguments go away, and although they still exist, I feel you're better off just using a custom save()
method.
So, to make this work properly, I would recommend not using auto_now
or auto_now_add
and instead define your own save()
method to make sure that created
is only updated if id
is not set (such as when the item is first created), and have it update modified
every time the item is saved.
I have done the exact same thing with other projects I have written using Django, and so your save()
would look like this:
from django.utils import timezone
class User(models.Model):
created = models.DateTimeField(editable=False)
modified = models.DateTimeField()
def save(self, *args, **kwargs):
''' On save, update timestamps '''
if not self.id:
self.created = timezone.now()
self.modified = timezone.now()
return super(User, self).save(*args, **kwargs)
Edit in response to comments:
The reason why I just stick with overloading save()
vs. relying on these field arguments is two-fold:
django.utils.timezone.now()
vs. datetime.datetime.now()
, because it will return a TZ-aware or naive datetime.datetime
object depending on settings.USE_TZ
.To address why the OP saw the error, I don't know exactly, but it looks like created
isn't even being populated at all, despite having auto_now_add=True
. To me it stands out as a bug, and underscores item #1 in my little list above: auto_now
and auto_now_add
are flaky at best.
Upvotes: 484
Reputation: 6193
class Feedback(models.Model):
feedback = models.CharField(max_length=100)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
Here, we have created and updated columns with a timestamp when created, and when someone modified feedback.
auto_now_add
will set the time when an instance is created whereas auto_now
will set the time when someone modified his feedback.
Upvotes: 31
Reputation: 3083
But I wanted to point out that the opinion expressed in the accepted answer is somewhat outdated. According to more recent discussions (django bugs #7634 and #12785), auto_now
and auto_now_add
are not going anywhere, and even if you go to the original discussion, you'll find strong arguments against the RY (as in DRY) in custom save methods.
A better solution has been offered (custom field types), but didn't gain enough momentum to make it into django. You can write your own in three lines (it's Jacob Kaplan-Moss' suggestion).
from django.db import models
from django.utils import timezone
class AutoDateTimeField(models.DateTimeField):
def pre_save(self, model_instance, add):
return timezone.now()
#usage
created_at = models.DateField(default=timezone.now)
updated_at = AutoDateTimeField(default=timezone.now)
Upvotes: 235
Reputation: 13526
I think the easiest (and maybe most elegant) solution here is to leverage the fact that you can set default
to a callable. So, to get around admin's special handling of auto_now, you can just declare the field like so:
from django.utils import timezone
date_field = models.DateField(default=timezone.now)
It's important that you don't use timezone.now()
as the default value wouldn't update (i.e., default gets set only when the code is loaded). If you find yourself doing this a lot, you could create a custom field. However, this is pretty DRY already I think.
Upvotes: 40
Reputation: 1146
As for your Admin display, see this answer.
Note: auto_now
and auto_now_add
are set to editable=False
by default, which is why this applies.
Upvotes: 4
Reputation: 178
I needed something similar today at work. Default value to be timezone.now()
, but editable both in admin and class views inheriting from FormMixin
, so for created in my models.py
the following code fulfilled those requirements:
from __future__ import unicode_literals
import datetime
from django.db import models
from django.utils.functional import lazy
from django.utils.timezone import localtime, now
def get_timezone_aware_now_date():
return localtime(now()).date()
class TestDate(models.Model):
created = models.DateField(default=lazy(
get_timezone_aware_now_date, datetime.date)()
)
For DateTimeField
, I guess remove the .date()
from the function and change datetime.date
to datetime.datetime
or better timezone.datetime
. I haven't tried it with DateTime
, only with Date
.
Upvotes: 3
Reputation: 1144
If you alter your model class like this:
class MyModel(models.Model):
time = models.DateTimeField(auto_now_add=True)
time.editable = True
Then this field will show up in my admin change page
Upvotes: 19
Reputation: 21740
You can use timezone.now()
for created and auto_now
for modified:
from django.utils import timezone
class User(models.Model):
created = models.DateTimeField(default=timezone.now())
modified = models.DateTimeField(auto_now=True)
If you are using a custom primary key instead of the default auto- increment int
, auto_now_add
will lead to a bug.
Here is the code of Django's default DateTimeField.pre_save withauto_now
and auto_now_add
:
def pre_save(self, model_instance, add):
if self.auto_now or (self.auto_now_add and add):
value = timezone.now()
setattr(model_instance, self.attname, value)
return value
else:
return super(DateTimeField, self).pre_save(model_instance, add)
I am not sure what the parameter add
is. I hope it will some thing like:
add = True if getattr(model_instance, 'id') else False
The new record will not have attr
id
, sogetattr(model_instance, 'id')
will return False will lead to not setting any value in the field.
Upvotes: 1
Reputation: 3722
auto_now=True
didn't work for me in Django 1.4.1, but the below code saved me. It's for timezone aware datetime.
from django.utils.timezone import get_current_timezone
from datetime import datetime
class EntryVote(models.Model):
voted_on = models.DateTimeField(auto_now=True)
def save(self, *args, **kwargs):
self.voted_on = datetime.now().replace(tzinfo=get_current_timezone())
super(EntryVote, self).save(*args, **kwargs)
Upvotes: 1
Reputation: 18549
Based on what I've read and my experience with Django so far, auto_now_add is buggy. I agree with jthanism --- override the normal save method it's clean and you know what's hapenning. Now, to make it dry, create an abstract model called TimeStamped:
from django.utils import timezone
class TimeStamped(models.Model):
creation_date = models.DateTimeField(editable=False)
last_modified = models.DateTimeField(editable=False)
def save(self, *args, **kwargs):
if not self.creation_date:
self.creation_date = timezone.now()
self.last_modified = timezone.now()
return super(TimeStamped, self).save(*args, **kwargs)
class Meta:
abstract = True
And then, when you want a model that has this time-stampy behavior, just subclass:
MyNewTimeStampyModel(TimeStamped):
field1 = ...
If you want the fields to show up in admin, then just remove the editable=False
option
Upvotes: 13
Reputation: 13899
Talking about a side question: if you want to see this fields in admin (though, you won't be able to edit it), you can add readonly_fields
to your admin class.
class SomeAdmin(ModelAdmin):
readonly_fields = ("created","modified",)
Well, this applies only to latest Django versions (I believe, 1.3 and above)
Upvotes: 49
Reputation: 87205
Is this cause for concern?
No, Django automatically adds it for you while saving the models, so, it is expected.
Side question: in my admin tool, those 2 fields aren't showing up. Is that expected?
Since these fields are auto added, they are not shown.
To add to the above, as synack said, there has been a debate on the django mailing list to remove this, because, it is "not designed well" and is "a hack"
Writing a custom save() on each of my models is much more pain than using the auto_now
Obviously you don't have to write it to every model. You can write it to one model and inherit others from it.
But, as auto_add
and auto_now_add
are there, I would use them rather than trying to write a method myself.
Upvotes: 6