Джанго auto_now и auto_now_add - PullRequest
       112

Джанго auto_now и auto_now_add

235 голосов
/ 15 ноября 2009

Для Джанго 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,

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

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

Ответы [ 12 ]

324 голосов
/ 15 ноября 2009

Любое поле с установленным атрибутом auto_now также будет наследовать editable=False и, следовательно, не будет отображаться в панели администратора. В прошлом говорили о том, что аргументы auto_now и auto_now_add исчезнут, и хотя они все еще существуют, я чувствую, что вам лучше использовать custom 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 в лучшем случае ненадежны.

153 голосов
/ 25 июля 2010

Но я хотел бы отметить, что мнение, выраженное в принятом ответе , несколько устарело. Согласно более поздним обсуждениям (ошибки 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)
29 голосов
/ 25 июля 2012

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

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

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

23 голосов
/ 12 сентября 2013

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

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

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

17 голосов
/ 25 февраля 2014

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

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

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

12 голосов
/ 10 июля 2014

Судя по тому, что я прочитал, и моему опыту работы с 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

5 голосов
/ 15 ноября 2009

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

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

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

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

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

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

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

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

2 голосов
/ 26 апреля 2016

Мне нужно что-то подобное сегодня на работе. Значение по умолчанию 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.

2 голосов
/ 18 июня 2014

Вы можете использовать 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, что приведет к тому, что в поле не будет установлено никакого значения.

2 голосов
/ 12 февраля 2013

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

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

...