Django auto_now та auto_now_add


272

Для Джанго 1.1.

Я маю це у своїх моделях.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
чи використовували ви користувацький первинний ключ замість стандартного автоматичного збільшення за промовчанням? Я виявив, що використання користувацького первинного ключа викликає цю проблему. У будь-якому випадку, я здогадуюсь, ти це вже вирішив. Але помилка все ще існує. Тільки мій 0,02 $
tapan

3
Ще одне, що потрібно нагадати. update()метод не зателефонує, save()що означає, що він не міг оновити modifiedполе автоматично
Хімічний програміст

Відповіді:


383

Будь-яке поле з 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в кращому випадку є лущими.


9
Але в чому джерело авторської проблеми? Чи іноді auto_now_add працює неправильно?
Дмитро Різенберг

5
Я з тобою, Дмитре. Мені цікаво, чому два поля кинули помилки. І мені ще цікавіше, чому ви вважаєте, що краще написати власний власний метод save ()?
hora

45
Написання користувачем save()на кожній з моїх моделей набагато більша, ніж використання auto_now(як мені подобається мати ці поля на всіх моїх моделях). Чому ці парами не працюють?
Пол Тарджан

3
@TM, але це вимагає прямого зіткнення з вашим db, тоді як Django має на меті лише файли models.py для визначення схеми
akaihola

11
Я не погоджуюся, жорстоко. 1) редаговане = Неправильно правильно, ви не повинні редагувати поле, ваша база даних повинна бути точною. 2) Є всілякі крайові випадки, коли Save () не може бути викликаний, особливо коли користувальницькі оновлення SQL або будь-що інше використовується. 3) Це щось, на що насправді добре базуються дані, поряд із референтною цілісністю тощо. Довіряти базі даних, щоб виправити це - хороший дефолт, тому що розумніші розуми, ніж ви чи я, створили базу даних для роботи таким чином.
Shayne

175

Але я хотів зазначити, що думка, висловлена ​​у прийнятій відповіді , дещо застаріла. Відповідно до останніх дискусій (django bugs # 7634 та # 12785 ), auto_now та auto_now_add нікуди не діються, і навіть якщо ви переходите до оригінальної дискусії , ви знайдете вагомі аргументи проти RY (як у DRY) у користувальницькому збереженні методи.

Запропоновано краще рішення (спеціальні типи полів), але воно не набрало достатньої сили, щоб перетворити його на джанго. Ви можете написати свій власний текст у три рядки (це пропозиція Якова Каплана-Мосса ).

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
Налаштування трьох
рядкових

Я не думаю, що користувальницьке поле насправді не потрібне, враховуючи те, що ви можете встановити за замовчуванням дзвінок (тобто 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 *. Єдине рішення, яке працює у кожному випадку використання (навіть із проблемою аргументації 'update_fields') - це переважне збереження
danius

4
Чому ви встановлюєте за замовчуванням timezone.now, але для сигналу pre_save використовується datetime.datetime.now?
Бобборт

32

Якщо говорити про побічне запитання: якщо ви хочете побачити ці поля в адміністраторі (хоча редагувати його не вдасться), ви можете додати його readonly_fieldsдо свого класу адміністратора.

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

Ну, це стосується лише останніх версій Django (я вважаю, 1.3 і вище)


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/…
Thomas - BeeDesk

@ Thomas-BeeDesk: Погоджено. Отже, "більш-менш еквівалент".
Шай Бергер

1
Це рішення погано працює, якщо ви використовуєте міграцію. Кожен раз, коли ви запускаєте, makemigrationsвін інтерпретує за замовчуванням як час запуску makemigrations, і тому вважає, що значення за замовчуванням змінилося!
нінгле

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
матч

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 Чому ви вибрали для налаштування create_date в збереженні, а не default=timezone.nowв конструкторі поля?
Blackeagle52

Хм .. можливо, я просто не думав про це, це звучить краще.
Едвард Ньюелл

2
Ну, є випадок, коли last_modified не буде оновлюватися: коли update_fieldsаргумент надано, а "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 поля не відображаються. Це очікується?

Оскільки ці поля додаються автоматично, вони не відображаються.

Щоб додати до сказаного, як говорив синак, в списку розсилки django відбулася дискусія, щоб усунути це, оскільки він "не розроблений добре" і є "хак"

Запис користувальницького збереження () на кожній моїй моделі набагато більший, ніж використання функції 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.


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

Новий запис не матиме attr id, тому getattr(model_instance, 'id')повернення False призведе до невстановлення жодного значення в полі.


7
Я помітив, що якщо ми зберігаємо за замовчуванням як timezone.now (), коли ви робите макеміграції, фактична дата та час (цього моменту) передаються у файл міграцій. Я думаю, що ми повинні уникати цього, оскільки кожного разу, коли ви називаєте макеміграції, це поле матиме інше значення.
Каран Кумар

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)

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

Це питання, коли у вашій моделі відсутні ключові дані. Якщо ви правильно налаштували свою модель, вам ніколи не потрібно буде бачити цю підказку.
Shayne
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.