it-roy-ru.com

Django auto_now и auto_now_add

Для 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,

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

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

235
Paul Tarjan

Любое поле с установленным атрибутом 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 в лучшем случае ненадежны.

323
jathanism

Но я хотел бы отметить, что мнение, выраженное в принятый ответ , несколько устарело. Согласно последним обсуждениям (ошибки 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)
153
Shai Berger

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

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

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

29
DataGreed

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

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

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

23
Josh

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

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

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

17
Eric Zheng

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

12
Edward Newell

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

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

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

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

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

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

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

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

5
Lakshman Prasad

Вы можете использовать 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
suhailvs

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

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

2
jlovison

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

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)
1
Ryu_hayabusa