Django auto_now and auto_now_add
Asked Answered
P

15

359

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?

Petry answered 15/11, 2009 at 8:47 Comment(2)
were you using a custom primary key instead of the default auto-increment int? I discovered that using a custom primary key causes this problem. Anyway, i guess you have solved it by now. But the bug still exists. Just my 0.02$Pettit
Just one more thing to remind. update() method will not call save() which means it could not update modified field automaticallyKristelkristen
M
477

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:

  1. The aforementioned ups and downs with their reliability. These arguments are heavily reliant on the way each type of database that Django knows how to interact with treats a date/time stamp field, and seems to break and/or change between every release. (Which I believe is the impetus behind the call to have them removed altogether).
  2. The fact that they only work on DateField, DateTimeField, and TimeField, and by using this technique you are able to automatically populate any field type every time an item is saved.
  3. Use 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.

Monson answered 15/11, 2009 at 9:26 Comment(25)
But what is the source of author's problem? Does auto_now_add sometimes work improperly?Appressed
I'm with you Dmitry. I'm curious as to why the two fields threw errors.. And I'm even more curious as to why you think writing your own custom save() method is better?Vander
Writing a custom save() on each of my models is much more pain than using the auto_now (as I like to have these fields on all my models). Why don't those params work?Petry
Here's some more on the matter: benspaulding.com/weblog/2008/aug/02/auto_now_add-evilLodged
Better than custom save() OR auto_now / auto_now_add is to allow your database to handle this, and let your frontend ignore it.Freesia
@TM, but that requires fiddling directly with your db while Django aims for only models.py files to define the schemaBoron
@Boron true you will have to deal with your database directly (create a trigger or something similar, depending on which type you use), but in most non trivial projects you are going to have to tweak the db settings yourself anyway. Another big advantage here is if you have any other jobs that access your database, it's kept consistent across the board. It also simplifies your life if you ever have to write insert/update statements using djangos raw SQL features.Freesia
For those that do not let Django automatically generate the id you can handle created like this: if not self.created: self.created = datetime.datetime.now()Flabellate
Another reason why overriding the save handler is "better" than the auto_now and auto_now_add properties is that you have no ability to set the field value even if you wanted to. I'd rather see the default property used as it is for all other fields (ie: default=auto_now). In a custom save handler, when you do want to specify a creation date, you can skip setting it if it's already been set. I run into this a lot when trying to write unit tests where I have logic based on the date of the items to be created to test. This is impossible to test with auto_now and auto_now_add.Wailful
Isn't it better to use pre_save signal instead? Overwriting save() method does not seem to be a good idea. docs.djangoproject.com/en/dev/ref/signals/#pre-saveRoadability
You can use jthanism's approach, but define it on an abstract class. Any model you want to have the timestamp behavior (which is often useful) can just inheret from that instead of models.Model. See my answerSindysine
Using jathanism's approach, I found that at the second pass in save() method (self.created has been created with timezone.now() at the first pass), self.created is now a unicode string and the Super.save() method raises a warning (because unicode date is converted to a naive datetime in to_python() function). This behavior is repeated many times in my program and eventually it raises an illegal date (an impossible date because of DST). To avoid such an error, I had to add an else clause (next comment)Barnes
(next) if isinstance(self.created,unicode): v = models.DateTimeField().to_python(self.created); self.created = timezone.make_aware(v, self.modified.tzinfo) #self.modified = timezone.now() is executed before the if to have tzinfo value setBarnes
Shouldn't you replace datetime.datetime.today() with timezone.datetime.today()? docs.djangoproject.com/en/1.8/topics/i18n/timezones/…Macedonia
No answer back from jathanismFatuitous
The short answer is yes, at this point it would be better to use timezone.now(). (Keeping in mind when I originally answered this, it was in 2009!) I have updated the answer to reflect this.Monson
Is there any particular advantage for the self.modified being included in the overriden save()? The created timestamp definitely needs to be in there because you only want to set the value once, but wouldn't modified = DateTimeField(default=timezone.now) be sufficient for that field? I suppose I might be answering myself, but keeping it in save() would mean that you can't override the default value.Krenek
@ckot: Yes, by overriding it in save, it means you can never explicitly set the value of modified. That's the point of this implementation. Also, specifying the value of default= is used only at create time as far as I know.Monson
The example import is missing the s from utils, 6 char limit prevents me from fixing =(Penicillium
@Pol: editable=True is not working when you create an object. Use lazy() from django.utils.functional as the field's default if you are only interested in created else override the model's save() as the solution above recommends. Using lazy() is less code and reusable compared to overriding save(). The save() can be reusable if you make it abstract=True in the model's class Meta, but still has more code/operations.Aras
Note Shai Berger's answer belowSkylar
I disagree, vehemently. 1) editable=False is correct, you shouldn't edit the field, your database needs to be accurate. 2) There are all sorts of edge cases where the save() might not be called , particularly when custom SQL updates or whatever are being used. 3) This is something databases are actually good at, along with referential integrity and so on. Trusting the database to get it right is a good default, because smarter minds than you or I have designed the database to work this way.Top
Does overriding save work for migrations? It doesn't seem to be working for me: null value in columnBeore
timezone.now should be calculated once, for example with now = timezone.now(). This way, after the initial object creation, created and modified have the exact same values. This allows to know if the item has already been modified or not.Pyo
If the model has fields with unique constraint, it may be a PITA to perform bulk operations with such approach.Correspondent
I
233

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)
Isallobar answered 25/7, 2010 at 17:1 Comment(8)
The three line custom field is here: linkLuciferous
I don't think a custom field is really necessary given that you can set default to a callable (i.e., timezone.now). See my answer below.Tillery
This is the same thing auto_add does in Django, and has since 2010: github.com/django/django/blob/1.8.4/django/db/models/fields/… . Unless I need additional hooks in pre_save, I'm sticking with auto_add.Usher
Did not work for me with Django 1.9, so this solution it's not working everywhere, as it never was for auto_now*. The only solution that works in every use case (even with the 'update_fields' arg problem) is overriding saveDefeatism
Why do you set the default to timezone.now, but the pre_save signal is using datetime.datetime.now?Lm
@Lm the code snippet was added by @pdenya; I suspect he copied the field code from JKM's post (linked above), which was written before django.utils.timezone existed, and then added more modern code as usage.Isallobar
This is old now, but it was definitely working in 2.0.1: github.com/django/django/blob/2.0.1/django/db/models/fields/…Stacked
@Usher is right. Even in 2022, django's auto_add is doing the same thing. github.com/django/django/blob/…Cabinet
S
48

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)

Snare answered 25/7, 2012 at 9:25 Comment(1)
Important to note: this should be added to the XxAdmin class. I read it too quickly and tried to add it to my AdminForm or ModelForm classes and had no idea why they weren't rendering the "read only fields". BTW, is there a possibility to have true "read-only fields in a form?Tungusic
T
40

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.

Tillery answered 11/9, 2013 at 22:58 Comment(6)
A default is more-or-less equivalent to auto_now_add (set value when object is first saved), but it is not at all like auto_now (set value every time the object is saved).Isallobar
@ShaiBerger, I think they are subtlety different in an important way. The doc stated the subtlety: "Automatically set the field ...; it’s not just a default value that you can override." -- docs.djangoproject.com/en/dev/ref/models/fields/…Hales
@Thomas-BeeDesk: Agreed. Hence, "more-or-less equivalent".Isallobar
This solution works poorly if you're using migrations. Every time you run makemigrations it interprets the default as the time when you run makemigrations, and therefore thinks the default value has changed!Alkaline
@nhinkle, are you sure you're not specifying default=timezone.now() rather than what's being recommended: default=timezine.now (no parentheses)?Tillery
Not working. it sets the default time once. And always it uses same time although date change. You have to restart django service everyday to keep following dates rightMorisco
C
30
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.

Cody answered 23/4, 2020 at 8:27 Comment(1)
This is accurate, I am using Django version 3.2.6 and this works perfect for me, most concise and elegant. Please everyone should stick to this answer and be done with it. auto_now_add will set time when an instance is created whereas auto_now will set time when someone modified his feedback.Hogback
M
19

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

Monney answered 25/2, 2014 at 13:18 Comment(3)
But it works ONLY on edit record. When I create new record - passed to date tile value ignored. When I change this record - new value is set.Uella
Works but it shoud be models.DateTimeField instead of models.DatetimeFieldFining
failed in python manage.py makemigrations: KeyError: u'editable'Goatskin
S
13

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

Sindysine answered 9/7, 2014 at 20:18 Comment(7)
Which timezone.now() are you using here? I'm assuming django.utils.timezone.now(), but I'm not positive. Also, why use timezone.now() rather than datetime.datetime.now()?Libradalibrarian
Good points. I added the import statement. The reason to use timezone.now() is because it is timezone aware, whereas datetime.datetime.now() is timezone naive. You can read about it here: docs.djangoproject.com/en/dev/topics/i18n/timezonesSindysine
@EdwardNewell Why did you choose for setting creation_date in the save, instead of default=timezone.now within the field constructor?Melodics
Hmm.. maybe I just didn't think of it, that does sound better.Sindysine
This solution is safer than the marked one, as it's not always present 'id' parameter in an object (it can be for instance a ForeignKey as PrimaryKey, with another name), so this one works for every case I can imagine...Defeatism
Well there is a case where last_modified won't be updated: when update_fields arg is provided and 'last_modified' is not in list, I would add: if 'update_fields' in kwargs and 'last_modifed' not in kwargs['update_fields']: kwargs['update_fields'].append('last_modified')Defeatism
You're right. Feature or bug? Perhaps feature: the caller specifically asked to update only certain fields. Then again, maybe integrity trumps the caller's wishes, if so, your solution is the way to go.Sindysine
C
6

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.

Caloric answered 15/11, 2009 at 13:9 Comment(0)
J
4

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.

Jillion answered 12/2, 2013 at 12:32 Comment(1)
It is an elegant solution!!!Palaearctic
A
3

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.

Aras answered 26/4, 2016 at 15:15 Comment(0)
L
1

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)
Lowney answered 28/2, 2014 at 15:31 Comment(0)
F
1

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, so getattr(model_instance, 'id') will return False will lead to not setting any value in the field.

Facilitate answered 18/6, 2014 at 3:14 Comment(2)
I noticed that if we keep the default as timezone.now(), when you makemigrations, the actual date and time(of this moment) is passed to migrations file. I think we should avoid this as every time you call makemigrations this field will have a different value.Clementius
I think you should use default=timezone.now (no parentheses) which calls the function upon creation / modification, and not upon migration.Dorpat
E
0

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)
Eatage answered 8/11, 2023 at 10:24 Comment(0)
E
0

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)
Estebanesteem answered 4/12, 2023 at 1:59 Comment(0)
M
0

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()
Mychael answered 27/3 at 14:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.