Django auto_now dan auto_now_add

272

Untuk Django 1.1.

Saya memilikinya di models.py saya:

class User(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)

Saat memperbarui baris saya mendapatkan:

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

Bagian yang relevan dari database saya adalah:

  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,

Apakah ini memprihatinkan?

Pertanyaan sampingan: di alat admin saya, kedua bidang itu tidak muncul. Apakah itu diharapkan?

Paul Tarjan
sumber
3
apakah Anda menggunakan kunci utama khusus alih-alih int kenaikan otomatis? Saya menemukan bahwa menggunakan kunci utama khusus menyebabkan masalah ini. Bagaimanapun, saya kira Anda telah menyelesaikannya sekarang. Tetapi bug itu masih ada. Hanya $ 0,02 saya
tapan
3
Satu hal lagi untuk diingatkan. update()metode tidak akan memanggil save()yang berarti tidak bisa memperbarui modifiedbidang secara otomatis
Chemical Programmer

Jawaban:

383

Setiap bidang dengan auto_nowset atribut juga akan mewarisi editable=Falsedan karenanya tidak akan muncul di panel admin. Telah ada pembicaraan di masa lalu tentang membuat auto_nowdan auto_now_addargumen hilang, dan meskipun masih ada, saya merasa Anda lebih baik hanya menggunakan metode kustomsave() .

Jadi, untuk membuat ini berfungsi dengan baik, saya akan merekomendasikan untuk tidak menggunakan auto_nowatau auto_now_adddan sebaliknya mendefinisikan save()metode Anda sendiri untuk memastikan bahwa createdhanya diperbarui jika idtidak disetel (seperti ketika item pertama kali dibuat), dan minta pembaruan modifiedsetiap kali item disimpan.

Saya telah melakukan hal yang sama persis dengan proyek lain yang saya tulis menggunakan Django, dan Anda save()akan terlihat seperti ini:

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)

Semoga ini membantu!

Edit dalam menanggapi komentar:

Alasan mengapa saya bertahan dengan kelebihan beban save()vs mengandalkan argumen lapangan ini adalah dua kali lipat:

  1. Pasang surut tersebut dengan keandalannya. Argumen ini sangat bergantung pada cara masing-masing jenis database yang Django tahu bagaimana berinteraksi dengan memperlakukan bidang cap tanggal / waktu, dan tampaknya pecah dan / atau berubah di antara setiap rilis. (Yang saya percaya adalah dorongan di balik panggilan untuk menghapusnya sama sekali).
  2. Fakta bahwa mereka hanya bekerja pada DateField, DateTimeField, dan TimeField, dan dengan menggunakan teknik ini Anda dapat secara otomatis mengisi setiap jenis bidang setiap kali item disimpan.
  3. Gunakan django.utils.timezone.now()vs. datetime.datetime.now(), karena akan mengembalikan objek TZ-aware atau naif datetime.datetimetergantung settings.USE_TZ.

Untuk mengatasi mengapa OP melihat kesalahan, saya tidak tahu persis, tetapi sepertinya createdbahkan tidak diisi sama sekali, meskipun sudah auto_now_add=True. Bagi saya itu menonjol sebagai bug, dan menggarisbawahi item # 1 dalam daftar kecil saya di atas: auto_nowdan paling auto_now_addtidak terkelupas.

jathanisme
sumber
9
Tapi apa sumber masalah penulis? Apakah auto_now_add terkadang berfungsi dengan tidak benar?
Dmitry Risenberg
5
Aku bersamamu Dmitry. Saya ingin tahu mengapa kedua bidang melempar kesalahan .. Dan saya bahkan lebih ingin tahu mengapa Anda berpikir menulis custom save () metode Anda lebih baik?
hora
45
Menulis kebiasaan save()pada masing-masing model saya jauh lebih menyakitkan daripada menggunakan auto_now(karena saya ingin memiliki bidang ini pada semua model saya). Mengapa param itu tidak berfungsi?
Paul Tarjan
3
@TM, tetapi itu membutuhkan mengutak-atik db Anda secara langsung sementara Django bertujuan hanya file
models.py
11
Saya tidak setuju, dengan keras. 1) dapat diedit = Salah benar, Anda tidak boleh mengedit bidang, database Anda harus akurat. 2) Ada segala macam tepi kasus di mana save () mungkin tidak dipanggil, terutama ketika pembaruan SQL kustom atau apa pun yang sedang digunakan. 3) Ini adalah sesuatu yang benar-benar bagus dalam database, bersama dengan integritas referensial dan sebagainya. Memercayai database untuk memperbaikinya adalah default yang baik, karena pikiran yang lebih cerdas daripada Anda atau saya telah merancang database untuk bekerja seperti ini.
Shayne
175

Tetapi saya ingin menunjukkan bahwa pendapat yang diungkapkan dalam jawaban yang diterima agak ketinggalan jaman. Menurut diskusi yang lebih baru (bug Django # 7634 dan # 12785 ), auto_now dan auto_now_add tidak ke mana-mana, dan bahkan jika Anda pergi ke diskusi asli , Anda akan menemukan argumen yang kuat terhadap RY (seperti pada KERING) di penyimpanan kustom metode.

Solusi yang lebih baik telah ditawarkan (jenis bidang khusus), tetapi tidak mendapatkan momentum yang cukup untuk membuatnya menjadi Django. Anda dapat menulis sendiri dalam tiga baris (ini saran Yakub Kaplan-Moss ).

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)
Shai Berger
sumber
1
Tiga bidang khusus ada di sini: tautan
hgcrpd
Saya tidak berpikir bidang khusus benar-benar diperlukan mengingat bahwa Anda dapat mengatur default ke callable (yaitu, timezone.now). Lihat jawaban saya di bawah ini.
Josh
6
Ini adalah hal yang sama yang dilakukan auto_add di Django, dan telah sejak 2010: github.com/django/django/blob/1.8.4/django/db/models/fields/… . Kecuali saya membutuhkan kait tambahan di pre_save, saya tetap menggunakan auto_add.
jwhitlock
1
Tidak bekerja untuk saya dengan Django 1.9, jadi solusi ini tidak berfungsi di mana-mana, karena tidak pernah untuk auto_now *. Satu-satunya solusi yang bekerja di setiap use case (bahkan dengan masalah arg 'update_fields') adalah
mengungguli
4
Mengapa Anda mengatur default ke timezone.now, tetapi sinyal pre_save menggunakan datetime.datetime.now?
Bobort
32

Berbicara tentang pertanyaan sampingan: jika Anda ingin melihat bidang ini di admin (meskipun, Anda tidak akan dapat mengeditnya), Anda dapat menambahkan readonly_fieldske kelas admin Anda.

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

Yah, ini hanya berlaku untuk versi Django terbaru (saya percaya, 1.3 dan di atas)

DataGreed
sumber
3
Penting untuk diperhatikan: ini harus ditambahkan ke XxAdminkelas. Saya membacanya terlalu cepat dan mencoba menambahkannya ke kelas saya AdminFormatau ModelFormdan tidak tahu mengapa mereka tidak menerjemahkan "bidang baca saja". BTW, apakah ada kemungkinan untuk memiliki "bidang baca-saja yang benar dalam formulir?
Tomasz Gandor
28

Saya pikir solusi termudah (dan mungkin paling elegan) di sini adalah untuk memanfaatkan fakta bahwa Anda dapat mengatur defaultpanggilan. Jadi, untuk menyiasati penanganan khusus admin auto_now, Anda bisa mendeklarasikan bidang seperti ini:

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

Penting bahwa Anda tidak menggunakan timezone.now()karena nilai default tidak akan diperbarui (yaitu, standar hanya akan ditetapkan ketika kode dimuat). Jika Anda sering melakukan hal ini, Anda bisa membuat bidang khusus. Namun, saya pikir ini sudah KERING.

Josh
sumber
2
Default lebih atau kurang setara dengan auto_now_add (setel nilai ketika objek disimpan pertama kali), tetapi sama sekali tidak seperti auto_now (setel nilai setiap kali objek disimpan).
Shai Berger
1
@ShaiBerger, saya pikir mereka sedikit berbeda dalam cara yang penting. Doc menyatakan kehalusan: "Secara otomatis mengatur bidang ...; itu bukan hanya nilai default yang bisa Anda timpa." - docs.djangoproject.com/en/dev/ref/models/fields/…
Thomas - BeeDesk
@ Thomas-BeeDesk: Setuju. Karenanya, "kurang lebih setara".
Shai Berger
1
Solusi ini berfungsi buruk jika Anda menggunakan migrasi. Setiap kali Anda menjalankannya makemigrations, menafsirkan standar sebagai waktu ketika Anda menjalankannya makemigrations, dan karenanya berpikir bahwa nilai default telah berubah!
nhinkle
8
@nhinkle, apakah Anda yakin tidak menentukan default=timezone.now()dan bukan apa yang disarankan: default=timezine.now(tidak ada tanda kurung)?
Josh
18

Jika Anda mengubah kelas model Anda seperti ini:

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

Maka bidang ini akan muncul di halaman perubahan admin saya

Eric Zheng
sumber
1
Tapi ini HANYA berfungsi pada edit record. Ketika saya membuat catatan baru - diteruskan ke nilai ubin tanggal diabaikan. Ketika saya mengubah catatan ini - nilai baru ditetapkan.
Anton Danilchenko
2
Bekerja tetapi harus menjadi model. TanggalTimeField alih-alih
model.DatetimeField
2
gagal di python manage.py makemigrations: KeyError: u'editable '
laoyur
12

Berdasarkan apa yang saya baca dan pengalaman saya dengan Django sejauh ini, auto_now_add buggy. Saya setuju dengan jthanism --- menimpa metode save normal itu bersih dan Anda tahu apa yang terjadi. Sekarang, untuk membuatnya kering, buat model abstrak yang disebut 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

Dan kemudian, ketika Anda menginginkan model yang memiliki perilaku time-stampy ini, cukup subkelas:

MyNewTimeStampyModel(TimeStamped):
    field1 = ...

Jika Anda ingin bidang ditampilkan di admin, maka hapus saja editable=Falseopsi

Edward Newell
sumber
1
Apa timezone.now()yang kamu gunakan di sini? Saya berasumsi django.utils.timezone.now(), tapi saya tidak positif. Juga, mengapa menggunakan timezone.now()bukan datetime.datetime.now()?
coredumperror
1
Poin bagus. Saya menambahkan pernyataan impor. Alasan penggunaannya timezone.now()adalah karena sadar akan zona waktu, sedangkan datetime.datetime.now()zona waktu itu naif. Anda dapat membacanya di sini: docs.djangoproject.com/en/dev/topics/i18n/timezones
Edward Newell
@ EdwardNewell Mengapa Anda memilih untuk mengatur creation_date di save, daripada default=timezone.nowdi dalam konstruktor bidang?
Blackeagle52
Hmm .. mungkin saya hanya tidak memikirkannya, itu terdengar lebih baik.
Edward Newell
2
Nah ada kasus di mana last_modified tidak akan diperbarui: ketika update_fieldsarg disediakan dan 'last_modified' tidak ada dalam daftar, saya akan menambahkan:if 'update_fields' in kwargs and 'last_modifed' not in kwargs['update_fields']: kwargs['update_fields'].append('last_modified')
danius
5

Apakah ini memprihatinkan?

Tidak, Django secara otomatis menambahkannya untuk Anda saat menyimpan model, jadi, itu diharapkan.

Pertanyaan sampingan: di alat admin saya, 2 bidang itu tidak muncul. Apakah itu diharapkan?

Karena bidang ini ditambahkan secara otomatis, bidang tersebut tidak ditampilkan.

Untuk menambahkan hal di atas, seperti yang dikatakan synack, telah ada perdebatan di milis Django untuk menghapus ini, karena, itu "tidak dirancang dengan baik" dan "hack"

Menulis custom save () pada masing-masing model saya jauh lebih menyakitkan daripada menggunakan auto_now

Jelas Anda tidak perlu menulisnya untuk setiap model. Anda dapat menulisnya ke satu model dan mewarisi yang lain dari itu.

Tetapi, sebagaimana ada auto_adddan di auto_now_addsana, saya akan menggunakannya daripada mencoba menulis metode sendiri.

Lakshman Prasad
sumber
3

Saya membutuhkan sesuatu yang serupa hari ini di tempat kerja. Nilai default menjadi timezone.now(), tetapi dapat diedit di tampilan admin dan kelas yang diwarisi FormMixin, jadi untuk dibuat dalam models.pykode saya berikut memenuhi persyaratan:

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

Untuk DateTimeField, saya kira hapus .date()dari fungsi dan ubah datetime.dateke datetime.datetimeatau lebih baik timezone.datetime. Saya belum mencobanya dengan DateTime, hanya dengan Date.

Tiphareth
sumber
2

Anda dapat menggunakan timezone.now()untuk dibuat dan auto_nowdimodifikasi:

from django.utils import timezone
class User(models.Model):
    created = models.DateTimeField(default=timezone.now())
    modified = models.DateTimeField(auto_now=True)

Jika Anda menggunakan kunci utama khusus alih-alih default auto- increment int, auto_now_addakan menyebabkan bug.

Berikut adalah kode DateTimeField.pre_save default Django dengan auto_nowdan 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)

Saya tidak yakin apa parameternya add. Saya harap ini akan seperti:

add = True if getattr(model_instance, 'id') else False

Catatan baru tidak akan memiliki attr id, jadi getattr(model_instance, 'id')akan mengembalikan False akan menyebabkan tidak menetapkan nilai apa pun di bidang.

suhailvs
sumber
7
Saya perhatikan bahwa jika kita mempertahankan default sebagai timezone.now (), saat Anda membuat migrasi, tanggal dan waktu aktual (saat ini) diteruskan ke file migrasi. Saya pikir kita harus menghindari ini karena setiap kali Anda menelepon macemigrations bidang ini akan memiliki nilai yang berbeda.
Karan Kumar
2

Sedangkan untuk tampilan Admin Anda, lihat jawaban ini .

Catatan: auto_nowdan auto_now_adddiatur editable=Falsesecara default, itulah sebabnya ini berlaku.

Jlovison
sumber
1

auto_now=Truetidak bekerja untuk saya di Django 1.4.1, tetapi kode di bawah ini menyelamatkan saya. Ini untuk datetime sadar zona waktu.

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)
Ryu_hayabusa
sumber
1
class Feedback(models.Model):
   feedback = models.CharField(max_length=100)
   created = models.DateTimeField(auto_now_add=True)
   updated = models.DateTimeField(auto_now=True)

Di sini, kami telah membuat dan memperbarui kolom yang akan memiliki cap waktu ketika dibuat, dan ketika seseorang mengubah umpan balik.

auto_now_add akan mengatur waktu ketika sebuah instance dibuat sedangkan auto_now akan mengatur waktu ketika seseorang mengubah umpan baliknya.

Viraj Wadate
sumber
-1

Inilah jawabannya jika Anda menggunakan selatan dan Anda ingin default ke tanggal Anda menambahkan bidang ke database:

Pilih opsi 2 lalu: datetime.datetime.now ()

Terlihat seperti ini:

$ ./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
Jeff Hoye
sumber
diperbarui ini akan menjadi: Modul datetime dan django.utils.timezone tersedia, sehingga Anda dapat melakukan misal timezone.now ()
michel.iamit
Ini adalah kuesioner untuk saat model Anda kehilangan data kunci. Jika Anda mengatur model dengan benar, Anda seharusnya tidak perlu melihat permintaan ini.
Shayne