Джанго auto_now и auto_now_add

218

Для Django 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 $
  • 2
    Еще одна вещь, чтобы напомнить. Метод update() не будет вызывать save() что означает, что он не может автоматически обновить modified поле
Теги:
datetime
django-models
django-admin

12 ответов

296
Лучший ответ

Любое поле с auto_now атрибутов 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() vs. datetime.datetime.now(), потому что он вернет объект с поддержкой TZ или наивный datetime.datetime зависимости от settings.USE_TZ.

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

  • 9
    Но каков источник авторской проблемы? Auto_now_add иногда работает неправильно?
  • 4
    Я с тобой Дмитрий. Мне любопытно, почему в этих двух полях возникли ошибки ... И мне еще интереснее, почему вы думаете, что лучше написать собственный метод save ()?
Показать ещё 22 комментария
143

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

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

class AutoDateTimeField(models.DateTimeField):
    def pre_save(self, model_instance, add):
        return datetime.datetime.now()

#usage
created_at = models.DateField(default=timezone.now)
updated_at = models.AutoDateTimeField(default=timezone.now)
  • 1
    Три строки пользовательского поля здесь: ссылка
  • 0
    Я не думаю, что настраиваемое поле действительно необходимо, учитывая, что вы можете установить значение по умолчанию для вызываемого (то есть timezone.now). Смотрите мой ответ ниже.
Показать ещё 5 комментариев
26

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

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

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

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

Я думаю, что самое легкое (и, может быть, самое элегантное) решение состоит в том, чтобы использовать тот факт, что вы можете установить 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/…
Показать ещё 4 комментария
14

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

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

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

  • 2
    Да, это работает для меня. Джанго 1.6.х
  • 1
    Но это работает ТОЛЬКО на редактировать запись. Когда я создаю новую запись - переданная на дату плитка значение игнорируется. Когда я изменяю эту запись - устанавливается новое значение.
Показать ещё 2 комментария
12

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

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

  • 1
    Какой timezone.now() вы используете здесь? Я предполагаю, django.utils.timezone.now() , но я не уверен. Кроме того, зачем использовать timezone.now() а не datetime.datetime.now() ?
  • 1
    Хорошие моменты. Я добавил оператор импорта. Причина использования timezone.now() заключается в том, что он поддерживает часовой пояс, а datetime.datetime.now() - наивный. Вы можете прочитать об этом здесь: docs.djangoproject.com/en/dev/topics/i18n/timezones
Показать ещё 5 комментариев
4

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

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

Является ли это поводом для беспокойства?

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

Боковой вопрос: в моем админ-инструменте эти 2 поля не отображаются. Ожидается ли это?

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

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

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

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

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

2

Мне понадобилось что-то подобное сегодня на работе. Значение по умолчанию - timezone.now(), но редактируемое как в представлении администратора, так и в классе, наследующем FormMixin, поэтому для созданных в моих моделях .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

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

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

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

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

Выберите опцию 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
  • 0
    будет обновлено следующее: доступны модули datetime и django.utils.timezone, так что вы можете сделать, например, timezone.now ()
  • 0
    Это вопросник, когда в вашей модели отсутствуют ключевые данные. Если вы правильно настроили свою модель, вам никогда не нужно будет видеть это приглашение.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню