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,

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

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

Ответ 1

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

Ответ 2

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

Ответ 3

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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

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

Ответ 6

На основе того, что я прочитал, и моего опыта работы с 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

Ответ 7

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

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

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

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

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

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

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

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

Ответ 8

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

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

Ответ 9

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

Ответ 10

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

Ответ 11

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)

Ответ 12

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

Выберите опцию 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