ChatGPT解决这个技术问题 Extra ChatGPT

Django auto_now and auto_now_add

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?

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$
Just one more thing to remind. update() method will not call save() which means it could not update modified field automatically

u
user8193706

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)

Hope this helps!

Edit in response to comments:

The reason why I just stick with overloading save() vs. relying on these field arguments is two-fold:

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). 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. 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.


But what is the source of author's problem? Does auto_now_add sometimes work improperly?
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?
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?
@TM, but that requires fiddling directly with your db while Django aims for only models.py files to define the schema
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.
P
Paolo

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)

The three line custom field is here: link
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.
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.
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
Why do you set the default to timezone.now, but the pre_save signal is using datetime.datetime.now?
D
DataGreed

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)


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?
d
davnicwil

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.


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).
@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/…
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, are you sure you're not specifying default=timezone.now() rather than what's being recommended: default=timezine.now (no parentheses)?
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
O
Oyster773

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


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.
Works but it shoud be models.DateTimeField instead of models.DatetimeField
failed in python manage.py makemigrations: KeyError: u'editable'
E
Edward Newell

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


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()?
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/timezones
@EdwardNewell Why did you choose for setting creation_date in the save, instead of default=timezone.now within the field constructor?
Hmm.. maybe I just didn't think of it, that does sound better.
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')
V
Viraj Wadate
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 that will have a timestamp when created, and when someone modified feedback.

auto_now_add will set time when an instance is created whereas auto_now will set time when someone modified his feedback.


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.
l
lprsd

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.


D
Daniel Holmes

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.


It is an elegant solution!!!
D
Daniel Holmes

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.


P
Peter Mortensen

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)

P
Peter Mortensen

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.


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.
I think you should use default=timezone.now (no parentheses) which calls the function upon creation / modification, and not upon migration.
J
Jeff Hoye

Here's the answer if you're using south and you want to default to the date you add the field to the database:

Choose option 2 then: datetime.datetime.now()

Looks like this:

$ ./manage.py schemamigration myapp --auto
 ? The field 'User.created_date' does not have a default specified, yet is NOT NULL.
 ? Since you are adding this field, you MUST specify a default
 ? value to use for existing rows. Would you like to:
 ?  1. Quit now, and add a default to the field in models.py
 ?  2. Specify a one-off value to use for existing columns now
 ? Please select a choice: 2
 ? Please enter Python code for your one-off default value.
 ? The datetime module is available, so you can do e.g. datetime.date.today()
 >>> datetime.datetime.now()
 + Added field created_date on myapp.User

updated this will be: The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now()
This is a questionaire for when your model is missing key data. If you set your model up correctly, you should never need to see this prompt.