352

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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Paul Tarjan
  • 48,968
  • 59
  • 172
  • 213
  • 4
    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$ – tapan Aug 08 '11 at 17:43
  • 4
    Just one more thing to remind. `update()` method will not call `save()` which means it could not update `modified` field automatically – Chemical Programmer Mar 23 '16 at 10:42

12 Answers12

471

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.

starball
  • 20,030
  • 7
  • 43
  • 238
jathanism
  • 33,067
  • 9
  • 68
  • 86
  • 12
    But what is the source of author's problem? Does auto_now_add sometimes work improperly? – Dmitry Risenberg Nov 15 '09 at 10:32
  • 7
    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? – hora Nov 15 '09 at 10:51
  • 63
    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? – Paul Tarjan Nov 15 '09 at 10:53
  • Here's some more on the matter: http://benspaulding.com/weblog/2008/aug/02/auto_now_add-evil/ – arxpoetica Jan 09 '11 at 00:07
  • 1
    Better than custom `save()` OR `auto_now` / `auto_now_add` is to allow your database to handle this, and let your frontend ignore it. – TM. Feb 13 '11 at 07:06
  • 4
    @TM, but that requires fiddling directly with your db while Django aims for only models.py files to define the schema – akaihola Feb 16 '11 at 12:31
  • @akaihola 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. – TM. Feb 16 '11 at 18:38
  • 2
    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() – Jon Tirsen Oct 15 '12 at 14:12
  • 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. – Matthew Purdon Nov 02 '12 at 03:42
  • Isn't it better to use `pre_save` signal instead? Overwriting `save()` method does not seem to be a good idea. https://docs.djangoproject.com/en/dev/ref/signals/#pre-save – Petr Peller Nov 28 '13 at 12:11
  • 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 answer – Edward Newell Jul 09 '14 at 20:19
  • 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) – Patrick Jan 26 '15 at 19:58
  • (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 set – Patrick Jan 26 '15 at 20:00
  • 1
    Shouldn't you replace `datetime.datetime.today()` with `timezone.datetime.today()`? https://docs.djangoproject.com/en/1.8/topics/i18n/timezones/#naive-and-aware-datetime-objects – nnyby Jul 01 '15 at 14:39
  • No answer back from **jathanism** – Almas Adilbek Sep 15 '15 at 06:34
  • 3
    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. – jathanism Sep 15 '15 at 14:50
  • 1
    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. – ckot Oct 03 '15 at 10:58
  • @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. – jathanism Oct 03 '15 at 19:15
  • The example import is missing the `s` from `utils`, 6 char limit prevents me from fixing =( – Rebs Nov 09 '15 at 23:59
  • @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. – Tiphareth Apr 26 '16 at 15:33
  • Note Shai Berger's answer below – Aur Saraf Apr 16 '18 at 13:28
  • 25
    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. – Shayne Sep 14 '18 at 21:35
  • Does overriding `save` work for migrations? It doesn't seem to be working for me: `null value in column` – dfrankow Sep 21 '21 at 21:36
  • 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. – David Dahan Sep 21 '22 at 11:34
  • If the model has fields with `unique` constraint, it may be a PITA to perform bulk operations with such approach. – Lev Pleshkov Apr 11 '23 at 20:03
224

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)
Paolo
  • 20,112
  • 21
  • 72
  • 113
Shai Berger
  • 2,963
  • 1
  • 20
  • 14
  • 1
    The three line custom field is here: [link](https://groups.google.com/d/msg/django-developers/TNYxwiXLTlI/L7srKCO8eEsJ) – hgcrpd Jan 25 '13 at 06:07
  • 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. – Josh Sep 11 '13 at 22:59
  • 15
    This is the same thing auto_add does in Django, and has since 2010: https://github.com/django/django/blob/1.8.4/django/db/models/fields/__init__.py#L1447-L1453 . Unless I need additional hooks in pre_save, I'm sticking with auto_add. – jwhitlock Sep 28 '15 at 16:09
  • 1
    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 save – danius Jan 08 '16 at 17:04
  • 4
    Why do you set the default to timezone.now, but the pre_save signal is using datetime.datetime.now? – Bobort Jun 02 '17 at 21:47
  • @Bobort 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. – Shai Berger Jun 04 '17 at 15:59
  • This is old now, but it was definitely working in 2.0.1: https://github.com/django/django/blob/2.0.1/django/db/models/fields/__init__.py#L1398 – djangomachine Nov 12 '19 at 00:56
  • @jwhitlock is right. Even in 2022, django's auto_add is doing the same thing. https://github.com/django/django/blob/5d13cc540e29eedafe695338d8ec9ec500185ccd/django/db/models/fields/__init__.py#L1517-L1520 – ch271828n Feb 14 '22 at 03:21
46

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)

DataGreed
  • 13,245
  • 8
  • 45
  • 64
  • 3
    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? – Tomasz Gandor Mar 04 '13 at 10:41
38

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.

davnicwil
  • 28,487
  • 16
  • 107
  • 123
Josh
  • 12,896
  • 4
  • 48
  • 49
  • 3
    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). – Shai Berger Nov 06 '13 at 14:48
  • 2
    @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." -- https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.DateField.auto_now_add – Thomas - BeeDesk Mar 16 '14 at 00:28
  • @Thomas-BeeDesk: Agreed. Hence, "more-or-less equivalent". – Shai Berger Mar 17 '14 at 07:13
  • 1
    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! – nhinkle May 25 '15 at 06:49
  • 9
    @nhinkle, are you sure you're not specifying `default=timezone.now()` rather than what's being recommended: `default=timezine.now` (no parentheses)? – Josh May 25 '15 at 19:42
  • 2
    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 right – ivbtar Oct 18 '18 at 08:38
28
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.

Alexander L. Hayes
  • 3,892
  • 4
  • 13
  • 34
Viraj Wadate
  • 5,447
  • 1
  • 31
  • 29
  • 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. – inyang kpongette Nov 08 '21 at 19:06
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

Oyster773
  • 375
  • 1
  • 10
Eric Zheng
  • 1,084
  • 1
  • 11
  • 23
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

Edward Newell
  • 17,203
  • 7
  • 34
  • 36
  • 1
    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()`? – coredumperror Sep 02 '14 at 18:43
  • 1
    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: https://docs.djangoproject.com/en/dev/topics/i18n/timezones/ – Edward Newell Sep 03 '14 at 22:20
  • @EdwardNewell Why did you choose for setting creation_date in the save, instead of `default=timezone.now` within the field constructor? – Blackeagle52 Jun 16 '15 at 13:44
  • Hmm.. maybe I just didn't think of it, that does sound better. – Edward Newell Jun 17 '15 at 02:24
  • 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... – danius Jan 08 '16 at 16:50
  • 3
    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')` – danius Jan 08 '16 at 16:58
  • 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. – Edward Newell Jan 09 '16 at 18:17
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.

lprsd
  • 84,407
  • 47
  • 135
  • 168
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.

Daniel Holmes
  • 1,952
  • 2
  • 17
  • 28
jlovison
  • 1,126
  • 1
  • 10
  • 12
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.

Daniel Holmes
  • 1,952
  • 2
  • 17
  • 28
Tiphareth
  • 178
  • 6
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)
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ryu_hayabusa
  • 3,666
  • 2
  • 29
  • 32
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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
suhailvs
  • 20,182
  • 14
  • 100
  • 98
  • 8
    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. – Karan Kumar Apr 21 '16 at 19:16
  • 2
    I _think_ you should use `default=timezone.now` (no parentheses) which calls the function upon creation / modification, and not upon migration. – Dominik Bucher Mar 09 '21 at 15:13