Джанго auto_now и auto_now_add

272

Для Джанго 1.1.

У меня есть это в моем models.py:

class User(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)

При обновлении строки я получаю:

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

Соответствующая часть моей базы данных:

  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,

Это повод для беспокойства?

Дополнительный вопрос: в моем админ-инструменте эти два поля не отображаются. Это ожидается?

Пол Тарьян
источник
3
Вы использовали собственный первичный ключ вместо автоматического увеличения по умолчанию int? Я обнаружил, что использование пользовательского первичного ключа вызывает эту проблему. Во всяком случае, я думаю, вы уже решили это. Но ошибка все еще существует. Просто мой 0,02 $
тапан
3
Еще одна вещь, чтобы напомнить. update()метод не будет вызываться, save()что означает, что он не может обновить modifiedполе автоматически
Chemical Programmer

Ответы:

382

Любое поле с установленным auto_nowатрибутом также будет наследоваться editable=Falseи, следовательно, не будет отображаться в панели администратора. В прошлом были разговоры о том, чтобы аргументы auto_nowи auto_now_addисчезли, и, хотя они все еще существуют, я чувствую, что вам лучше использовать собственный save()метод .

Поэтому для правильной работы я бы рекомендовал не использовать auto_nowили auto_now_addвместо этого определять собственный save()метод, чтобы убедиться, что createdон обновляется только в том случае, если idон не установлен (например, при первом создании элемента), и обновлять его modifiedкаждый раз, когда элемент сохраняется

Я проделал то же самое с другими проектами, написанными мной с использованием Django, и вы save()выглядите так:

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)

Надеюсь это поможет!

Редактировать в ответ на комментарии:

Причина, по которой я просто придерживаюсь перегрузки по save()сравнению с этими аргументами поля, имеет две причины :

  1. Вышеупомянутые взлеты и падения с их надежностью. Эти аргументы в значительной степени зависят от того, как каждый тип базы данных, с которой Django знает, как взаимодействовать, обрабатывает поле отметки даты / времени, и, кажется, ломается и / или изменяется между каждым выпуском. (Который я полагаю, является стимулом для вызова, чтобы удалить их полностью).
  2. Тот факт, что они работают только с DateField, DateTimeField и TimeField, и с помощью этого метода вы можете автоматически заполнять поля любого типа каждый раз, когда элемент сохраняется.
  3. Используйте django.utils.timezone.now()против datetime.datetime.now(), потому что он будет возвращать TZ-осведомленный или наивный datetime.datetimeобъект в зависимости от settings.USE_TZ.

Чтобы выяснить, почему ОП увидел ошибку, я точно не знаю, но похоже, что createdона даже не заполняется вообще, несмотря на то, что имеет auto_now_add=True. Для меня это выделяется как ошибка, и подчеркивает пункт # 1 в моем маленьком списке выше: auto_nowи auto_now_addв лучшем случае ненадежен.

jathanism
источник
9
Но каков источник авторской проблемы? Auto_now_add иногда работает неправильно?
Дмитрий Ризенберг
5
Я с тобой Дмитрий. Мне любопытно, почему в двух полях возникли ошибки ... И мне еще больше интересно, почему вы думаете, что лучше написать собственный метод save ()?
Hora
46
Написание кастома save()для каждой из моих моделей намного сложнее, чем использование auto_now(так как мне нравятся эти поля на всех моих моделях). Почему эти параметры не работают?
Пол Тарьян
3
@TM, но для этого нужно возиться непосредственно с вашей базой данных, в то время как Django стремится только к файлам models.py для определения схемы
akaihola
12
Я не согласен, яростно. 1) editable = False правильно, вы не должны редактировать поле, ваша база данных должна быть точной. 2) Существуют всевозможные крайние случаи, когда save () может не вызываться, особенно когда используются пользовательские обновления SQL или что-либо еще. 3) Это то, что базы данных действительно хороши, наряду со ссылочной целостностью и так далее. Доверие к базе данных для ее правильного использования является хорошим значением по умолчанию, потому что разумнее, чем вы или я, разработали базу данных для такой работы.
Шейн
175

Но я хотел бы отметить, что мнение, выраженное в принятом ответе , несколько устарело. Согласно последним обсуждениям (ошибки django # 7634 и # 12785 ), auto_now и auto_now_add никуда не денутся, и даже если вы перейдете к исходному обсуждению , вы найдете сильные аргументы против RY (как в DRY) в пользовательском сохранении методы.

Было предложено лучшее решение (пользовательские типы полей), но оно не набрало достаточного импульса, чтобы превратить его в django. Вы можете написать свой в три строки (это предложение Джейкоба Каплана-Мосса ).

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 = models.AutoDateTimeField(default=timezone.now)
Шай Бергер
источник
1
Здесь настраиваемое поле из трех строк: ссылка
hgcrpd
Я не думаю, что настраиваемое поле действительно необходимо, учитывая, что вы можете установить значение по умолчанию для вызываемого (то есть timezone.now). Смотрите мой ответ ниже.
Джош
6
Это то же самое, что auto_add делает в Django с 2010 года: github.com/django/django/blob/1.8.4/django/db/models/fields/… . Если мне не нужны дополнительные хуки в pre_save, я использую auto_add.
jwhitlock
1
У меня не работал с Django 1.9, так что это решение не работает везде, как это никогда не было для auto_now *. Единственное решение, которое работает в каждом случае использования (даже с проблемой arg 'update_fields') - это переопределение сохранения
danius
4
Почему вы устанавливаете по умолчанию значение timezone.now, но сигнал pre_save использует datetime.datetime.now?
Боборт
32

Говоря о дополнительном вопросе: если вы хотите видеть эти поля в админке (хотя вы не сможете их редактировать), вы можете добавить их readonly_fieldsв свой класс админки.

class SomeAdmin(ModelAdmin):
    readonly_fields = ("created","modified",)

Ну, это относится только к последним версиям Django (я думаю, 1.3 и выше)

DataGreed
источник
3
Важно отметить: это должно быть добавлено к XxAdminклассу. Я прочитал его слишком быстро и попытался добавить его в свои классы AdminFormили ModelFormклассы, и понятия не имел, почему они не рендерили «поля только для чтения». Кстати, есть ли возможность иметь настоящие «поля только для чтения» в форме?
Томаш Гандор
28

Я думаю, что самое простое (и, возможно, самое элегантное) решение здесь - это использовать тот факт, что вы можете установить defaultвызываемый. Итак, чтобы обойти специальную обработку администратора auto_now, вы можете просто объявить поле следующим образом:

from django.utils import timezone
date_filed = models.DateField(default=timezone.now)

Важно, чтобы вы не использовали его, timezone.now()поскольку значение по умолчанию не будет обновляться (то есть значение по умолчанию устанавливается только при загрузке кода). Если вы обнаружите, что делаете это много, вы можете создать собственное поле. Тем не менее, это уже довольно СУХОЙ, я думаю.

мистифицировать
источник
2
Значение по умолчанию более или менее эквивалентно auto_now_add (установить значение при первом сохранении объекта), но это совсем не то же самое, что auto_now (установить значение при каждом сохранении объекта).
Шай Бергер
1
@ShaiBerger, я думаю, что они отличаются друг от друга по важности. В документе говорилось о тонкости: «Автоматически устанавливать поле ...; это не просто значение по умолчанию, которое вы можете переопределить». - docs.djangoproject.com/en/dev/ref/models/fields/…
Томас - BeeDesk
@ Thomas-BeeDesk: Согласен. Следовательно, «более или менее эквивалентный».
Шай Бергер
1
Это решение плохо работает, если вы используете миграции. Каждый раз, когда вы запускаете, makemigrationsон интерпретирует значение по умолчанию как время, когда вы запускаете makemigrations, и поэтому считает, что значение по умолчанию изменилось!
nhinkle
8
@nhinkle, вы уверены, что не указываете, default=timezone.now()а не то, что рекомендуется: default=timezine.now(без скобок)?
Джош
18

Если вы измените свой класс модели следующим образом:

class MyModel(models.Model):
    time = models.DateTimeField(auto_now_add=True)
    time.editable = True

Тогда это поле появится на моей странице смены администратора.

Эрик Чжэн
источник
1
Но это работает ТОЛЬКО на редактировать запись. Когда я создаю новую запись - переданное на сегодняшний день значение плитки игнорируется. Когда я изменяю эту запись - устанавливается новое значение.
Антон Данильченко
2
Работает, но это должны быть модели. DateTimeField вместо моделей. DatetimeField
matyas
2
Ошибка python manage.py makemigrations: KeyError: u'editable '
laoyur
12

На основании того, что я прочитал, и моего опыта работы с Django, auto_now_add содержит ошибки. Я согласен с jthanism --- отвергни нормальный метод сохранения, он чистый, и ты знаешь, что происходит. Теперь, чтобы сделать его сухим, создайте абстрактную модель с именем 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

И затем, когда вы хотите модель, которая имеет такое поведение с отметкой времени, просто подкласс:

MyNewTimeStampyModel(TimeStamped):
    field1 = ...

Если вы хотите, чтобы поля отображались в админке, просто удалите editable=Falseопцию

Эдвард Ньюэлл
источник
1
Что timezone.now()вы используете здесь? Я предполагаю django.utils.timezone.now(), но я не уверен. Кроме того, зачем использовать, timezone.now()а не datetime.datetime.now()?
coredumperror
1
Хорошие моменты. Я добавил оператор импорта. Причина использования timezone.now()заключается в том, что он осведомлен о часовом поясе, тогда datetime.datetime.now()как часовой пояс наивен. Вы можете прочитать об этом здесь: docs.djangoproject.com/en/dev/topics/i18n/timezones
Эдвард Ньюэлл
@EdwardNewell Почему вы выбрали настройку creation_date при сохранении, а не default=timezone.nowв конструкторе полей?
Blackeagle52
Хм .. может быть, я просто не думал об этом, это звучит лучше.
Эдвард Ньюэлл
2
Что ж, есть случай, когда last_modified не будет обновлен: если update_fieldsуказан аргумент arg, а «last_modified» нет в списке, я бы добавил:if 'update_fields' in kwargs and 'last_modifed' not in kwargs['update_fields']: kwargs['update_fields'].append('last_modified')
danius
5

Это повод для беспокойства?

Нет, Django автоматически добавляет его для вас при сохранении моделей, так что это ожидаемо.

Дополнительный вопрос: в моем админ-инструменте эти 2 поля не отображаются. Это ожидается?

Поскольку эти поля добавляются автоматически, они не отображаются.

В дополнение к вышесказанному, как сказал synack, в списке рассылки django была дискуссия, чтобы убрать это, потому что он «не очень хорошо продуман» и является «хаком»

Написание пользовательских save () для каждой из моих моделей намного сложнее, чем использование auto_now.

Очевидно, вам не нужно писать это для каждой модели. Вы можете написать это для одной модели и наследовать от нее другие.

Но, как auto_addи auto_now_addтам, я бы использовал их, а не пытаться написать метод сам.

Лакшман Прасад
источник
3

Мне нужно что-то подобное сегодня на работе. Значение по умолчанию должно быть timezone.now(), но редактируемое как в представлениях администратора, так и в представлениях классов, унаследованных от FormMixin, поэтому для созданного в моем models.pyкоде ниже выполняются эти требования:

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

Для DateTimeField, я думаю , удалить .date()из функции и изменений datetime.dateк datetime.datetimeили лучше timezone.datetime. Я не пробовал с этим DateTime, только с Date.

Tiphareth
источник
2

Вы можете использовать timezone.now()для созданного и auto_nowдля измененного:

from django.utils import timezone
class User(models.Model):
    created = models.DateTimeField(default=timezone.now())
    modified = models.DateTimeField(auto_now=True)

Если вы используете пользовательский первичный ключ вместо по умолчанию auto- increment int,auto_now_add это приведет к ошибке.

Вот код Django по умолчанию DateTimeField.pre_save с auto_nowи 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)

Я не уверен, что это за параметр add. Я надеюсь, что это будет что-то вроде:

add = True if getattr(model_instance, 'id') else False

Новая запись не будет иметь атрибута id, поэтому getattr(model_instance, 'id')вернет значение False, что приведет к тому, что в поле не будет установлено никакого значения.

suhailvs
источник
7
Я заметил, что если мы сохраним значение по умолчанию как timezone.now (), при выполнении миграций фактическая дата и время (на данный момент) передаются в файл миграции. Я думаю, нам следует избегать этого, так как каждый раз, когда вы вызываете makemigrations, это поле будет иметь другое значение.
Каран Кумар
2

Что касается вашего дисплея администратора, см. Этот ответ .

Примечание: auto_nowи auto_now_addустановлены editable=Falseпо умолчанию, поэтому это применимо.

jlovison
источник
1

auto_now=Trueне работал для меня в Django 1.4.1, но приведенный ниже код спас меня. Это для часовых поясов с учетом даты и времени.

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)
Ryu_hayabusa
источник
1
class Feedback(models.Model):
   feedback = models.CharField(max_length=100)
   created = models.DateTimeField(auto_now_add=True)
   updated = models.DateTimeField(auto_now=True)

Здесь мы создали и обновили столбцы, которые будут иметь метку времени при создании и когда кто-то изменяет обратную связь.

auto_now_add будет устанавливать время при создании экземпляра, тогда как auto_now будет устанавливать время, когда кто-то изменил его отзыв.

Вирадж Вадате
источник
-1

Вот ответ, если вы используете юг и хотите по умолчанию указать дату добавления поля в базу данных:

Затем выберите вариант 2 : datetime.datetime.now ()

Выглядит так:

$ ./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
Джефф Хой
источник
обновлено это будет так: модули datetime и django.utils.timezone доступны, так что вы можете сделать, например, timezone.now ()
michel.iamit
Это вопросник, когда в вашей модели отсутствуют ключевые данные. Если вы правильно настроили свою модель, вам не нужно будет видеть это приглашение.
Шейн