Django auto_now และ auto_now_add


272

สำหรับ 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
คุณใช้คีย์หลักที่กำหนดเองแทนการเพิ่มค่าเริ่มต้นอัตโนมัติหรือไม่ ฉันค้นพบว่าการใช้คีย์หลักที่กำหนดเองทำให้เกิดปัญหานี้ อย่างไรก็ตามฉันคิดว่าคุณจะแก้ไขมันได้แล้ว แต่ข้อผิดพลาดยังคงมีอยู่ แค่ 0.02 $ ของฉัน
ปาน

3
อีกหนึ่งสิ่งที่ต้องเตือน update()วิธีการจะไม่เรียกsave()ซึ่งหมายความว่ามันไม่สามารถอัปเดตmodifiedฟิลด์โดยอัตโนมัติ
Chemical Programmer

คำตอบ:


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.datetimesettings.USE_TZ

เพื่อที่จะอธิบายว่าเหตุใด OP จึงเห็นข้อผิดพลาดฉันไม่ทราบแน่ชัด แต่ดูเหมือนcreatedว่าไม่ได้มีการเติมข้อมูลเลยแม้แต่auto_now_add=Trueน้อย สำหรับฉันมันโดดเด่นเป็นข้อบกพร่องและขีดรายการ # 1 ในรายการเล็ก ๆ ของฉันด้านบน: auto_nowและauto_now_addไม่สม่ำเสมอที่ดีที่สุด


9
แต่ที่มาของปัญหาของผู้เขียนคืออะไร? auto_now_add บางครั้งทำงานไม่ถูกต้องหรือไม่
Dmitry Risenberg

5
ฉันอยู่กับคุณมิทรี ฉันอยากรู้ว่าทำไมทั้งสองเขตข้อมูลโยนข้อผิดพลาด .. และฉันยิ่งอยากรู้ว่าทำไมคุณคิดว่าวิธีการบันทึกแบบกำหนดเอง () ของคุณเองดีกว่า
ฟลอรา

45
การเขียนแบบกำหนดเองsave()ในแบบจำลองแต่ละแบบของฉันนั้นเจ็บปวดกว่าการใช้auto_now(เพราะฉันต้องการให้มีฟิลด์เหล่านี้ในทุกรุ่นของฉัน) ทำไมพารามิเตอร์เหล่านี้ไม่ทำงาน
พอล Tarjan

3
@TM แต่ที่ต้องเล่นซอโดยตรงกับฐานข้อมูลของคุณในขณะที่ Django มุ่งหวังเฉพาะไฟล์ models.py เพื่อกำหนดสคีมา
akaihola

11
ฉันไม่เห็นด้วยอย่างร้อนแรง 1) editable = False ถูกต้องคุณไม่ควรแก้ไขฟิลด์ฐานข้อมูลของคุณต้องถูกต้อง 2) มีกรณีขอบทุกประเภทที่ไม่สามารถเรียกใช้บันทึก () โดยเฉพาะอย่างยิ่งเมื่อมีการปรับปรุง SQL แบบกำหนดเองหรือสิ่งที่กำลังใช้อยู่ 3) นี่คือบางสิ่งที่ฐานข้อมูลดีจริง ๆ พร้อมกับ Referential Integrity และอื่น ๆ การเชื่อถือฐานข้อมูลเพื่อทำให้ถูกต้องเป็นค่าเริ่มต้นที่ดีเพราะจิตใจที่ฉลาดกว่าคุณหรือฉันได้ออกแบบฐานข้อมูลให้ทำงานด้วยวิธีนี้
เชน

175

แต่ฉันต้องการชี้ให้เห็นว่าความคิดเห็นที่แสดงในคำตอบที่ยอมรับนั้นค่อนข้างล้าสมัย ตามการสนทนาล่าสุด (ข้อบกพร่อง django # 7634และ# 12785 ) auto_now และ auto_now_add จะไม่ไปที่ใดและแม้ว่าคุณจะไปที่การสนทนาดั้งเดิมคุณจะพบข้อโต้แย้งที่แข็งแกร่งกับ RY (เช่นเดียวกับ DRY) ในการบันทึกที่กำหนดเอง วิธีการ

มีการนำเสนอโซลูชันที่ดีกว่า (ประเภทฟิลด์ที่กำหนดเอง) แต่ไม่ได้รับแรงกระตุ้นมากพอที่จะทำให้เป็น django ได้ คุณสามารถเขียนของคุณเองในสามบรรทัด (เป็นคำแนะนำของJacob 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)

1
ฟิลด์กำหนดเองสามบรรทัดอยู่ที่นี่: ลิงก์
hgcrpd

ฉันไม่คิดว่าฟิลด์ที่กำหนดเองมีความจำเป็นจริง ๆ เนื่องจากคุณสามารถตั้งค่าเริ่มต้นเป็น callable (เช่น timezone.now) ดูคำตอบของฉันด้านล่าง
Josh

6
นี่เป็นสิ่งเดียวกันที่ auto_add ทำใน Django และมีตั้งแต่ปี 2010: github.com/django/django/blob/1.8.4/django/db/models/fields/ … นอกจากว่าฉันต้องการ hooks เพิ่มเติมใน pre_save ฉันจะติด auto_add
jwhitlock

1
ไม่ได้ผลสำหรับฉันกับ Django 1.9 ดังนั้นวิธีนี้จึงไม่สามารถทำงานได้ทุกที่เนื่องจากไม่เคยมีไว้สำหรับ auto_now * ทางออกเดียวที่ทำงานได้ในทุกกรณีการใช้งาน (แม้จะมีปัญหา ARG 'update_fields') จะเอาชนะบันทึก
danius

4
ทำไมคุณถึงตั้งค่าเริ่มต้นเป็น timezone.now แต่สัญญาณ pre_save กำลังใช้ datetime.datetime.now อยู่
Bobort

32

พูดคุยเกี่ยวกับคำถามด้านข้าง: หากคุณต้องการเห็นฟิลด์นี้ในผู้ดูแลระบบ (แม้ว่าคุณจะไม่สามารถแก้ไขได้) คุณสามารถเพิ่มreadonly_fieldsในชั้นผู้ดูแลระบบของคุณ

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

ทีนี้สิ่งนี้ใช้ได้กับเวอร์ชั่นล่าสุดของ Django เท่านั้น (ฉันเชื่อว่า 1.3 ขึ้นไป)


3
สิ่งสำคัญที่ควรทราบ: ควรเพิ่มในXxAdminชั้นเรียน ฉันอ่านมันเร็วเกินไปและพยายามที่จะเพิ่มเข้าไปในของฉันAdminFormหรือModelFormชั้นเรียนและมีความคิดว่าทำไมพวกเขาไม่ได้แสดงผล "อ่านทุ่งเท่านั้น" ไม่มี BTW มีความเป็นไปได้ที่จะมี "ฟิลด์อ่านอย่างเดียวในรูปแบบจริงหรือไม่?
Tomasz Gandor

28

ฉันคิดว่าทางออกที่ง่ายที่สุด (และอาจจะงดงามที่สุด) ที่นี่คือการใช้ประโยชน์จากความจริงที่ว่าคุณสามารถตั้งค่าdefaultให้เป็น callable ดังนั้นในการจัดการ auto_now แบบพิเศษของผู้ดูแลระบบคุณสามารถประกาศฟิลด์ดังนี้:

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

เป็นสิ่งสำคัญที่คุณจะไม่ใช้timezone.now()เนื่องจากค่าเริ่มต้นจะไม่อัปเดต (เช่นค่าเริ่มต้นจะได้รับการตั้งค่าเมื่อโหลดรหัสเท่านั้น) หากคุณพบว่าตัวเองทำสิ่งนี้มากคุณสามารถสร้างฟิลด์ที่กำหนดเองได้ อย่างไรก็ตามนี่เป็น DRY ที่น่ารักอยู่แล้ว


2
ค่าเริ่มต้นเทียบเท่ากับ auto_now_add มากขึ้นหรือน้อยลง (ค่าที่ตั้งไว้เมื่อวัตถุถูกบันทึกเป็นครั้งแรก) แต่ไม่เหมือน auto_now เลย (ตั้งค่าทุกครั้งที่มีการบันทึกวัตถุ)
Shai Berger

1
@ShaiBerger ฉันคิดว่าพวกเขามีความแตกต่างในวิธีที่สำคัญ เอกสารดังกล่าวระบุความละเอียดอ่อน: "ตั้งค่าฟิลด์โดยอัตโนมัติ ... ; ไม่ใช่เพียงค่าเริ่มต้นที่คุณสามารถแทนที่" - docs.djangoproject.com/en/dev/ref/models/fields/…
โทมัส - BeeDesk

@ Thomas-BeeDesk: เห็นด้วย ดังนั้น "เทียบเท่ามากกว่าหรือน้อยกว่า"
ไช

1
โซลูชันนี้ทำงานได้ไม่ดีหากคุณใช้การย้ายข้อมูล ทุกครั้งที่คุณเรียกใช้makemigrationsจะแปลค่าเริ่มต้นตามเวลาที่คุณเรียกใช้makemigrationsดังนั้นจึงคิดว่าค่าเริ่มต้นจะเปลี่ยนไป!
nhinkle

8
@nhinkle คุณแน่ใจหรือไม่ว่าคุณไม่ได้ระบุdefault=timezone.now()มากกว่าที่จะแนะนำ: default=timezine.now(ไม่มีวงเล็บ)
Josh

18

หากคุณเปลี่ยนคลาสโมเดลของคุณเช่นนี้:

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

จากนั้นฟิลด์นี้จะปรากฏในหน้าการเปลี่ยนแปลงผู้ดูแลระบบของฉัน


1
แต่มันทำงานได้เฉพาะในบันทึกการแก้ไข เมื่อฉันสร้างเร็กคอร์ดใหม่ - ส่งถึงค่าไทล์วันที่จะถูกละเว้น เมื่อฉันเปลี่ยนบันทึกนี้ - ตั้งค่าใหม่
Anton Danilchenko

2
ใช้งานได้ แต่มันเป็นแบบจำลองวันที่เวลาใช้งานแทนแบบจำลองตลอดเวลาฟิลด์
matyas

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
Edward Newell

@EdwardNewell ทำไมคุณถึงเลือกการตั้งค่า creation_date ในบันทึกแทนที่จะdefault=timezone.nowเป็นตัวสร้างฟิลด์
Blackeagle52

อืม .. บางทีฉันอาจไม่ได้คิดว่ามันจะฟังดูดีขึ้น
Edward Newell

2
มีกรณีที่ last_modified จะไม่ได้รับการปรับปรุง: เมื่อupdate_fieldsARG มีให้และ '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 ฟิลด์เหล่านั้นไม่ปรากฏขึ้น คาดหวังหรือไม่?

เนื่องจากฟิลด์เหล่านี้มีการเพิ่มอัตโนมัติจึงไม่ปรากฏ

หากต้องการเพิ่มด้านบนดังที่กล่าวไว้ใน synack ได้มีการถกเถียงกันในรายชื่อผู้รับจดหมาย 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ฉันไม่ได้พยายามกับมันเท่านั้นที่มีDateTimeDate


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')จะส่งกลับค่าเท็จจะนำไปสู่การไม่ตั้งค่าใด ๆ ในเขตข้อมูล


7
ฉันสังเกตเห็นว่าหากเราใช้ค่าเริ่มต้นเป็น timezone.now () เมื่อคุณทำการเปลี่ยนแปลงวันที่และเวลาจริง (ในขณะนี้) จะถูกส่งไปยังไฟล์การย้ายข้อมูล ฉันคิดว่าเราควรหลีกเลี่ยงสิ่งนี้เพราะทุกครั้งที่คุณเรียก makemigrations ฟิลด์นี้จะมีค่าแตกต่างกัน
Karan Kumar

2

สำหรับหน้าจอผู้ดูแลระบบของคุณให้ดูคำตอบนี้

หมายเหตุ: auto_nowและauto_now_addถูกตั้งค่าเป็นค่าeditable=Falseเริ่มต้นซึ่งเป็นสาเหตุที่สิ่งนี้มีผลบังคับใช้


1

auto_now=Trueไม่ทำงานสำหรับฉันใน Django 1.4.1 แต่รหัสด้านล่างช่วยฉันได้ มันมีไว้สำหรับ datetime ทราบเขตเวลา

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

นี่เป็นแบบสอบถามเมื่อแบบจำลองของคุณไม่มีข้อมูลสำคัญ หากคุณตั้งค่ารุ่นของคุณอย่างถูกต้องคุณไม่ควรเห็นพรอมต์นี้
เชน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.