Django auto_now và auto_now_add


272

Đối với Django 1.1.

Tôi có cái này trong mô hình của tôi:

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

Khi cập nhật một hàng tôi nhận được:

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

Phần có liên quan trong cơ sở dữ liệu của tôi là:

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

Đây có phải là nguyên nhân cho mối quan tâm?

Câu hỏi phụ: trong công cụ quản trị của tôi, hai trường đó không hiển thị. Điều đó có được mong đợi không?


3
bạn có đang sử dụng khóa chính tùy chỉnh thay vì int tăng tự động mặc định không? Tôi phát hiện ra rằng việc sử dụng khóa chính tùy chỉnh gây ra vấn đề này. Dù sao, tôi đoán bạn đã giải quyết nó bây giờ. Nhưng lỗi vẫn tồn tại. Chỉ cần 0,02 đô la của tôi
tapan

3
Chỉ một điều nữa để nhắc nhở. update()phương thức sẽ không gọi save()điều đó có nghĩa là nó không thể modifiedtự động cập nhật trường
Lập trình viên hóa học

Câu trả lời:


383

Bất kỳ trường nào có bộ auto_nowthuộc tính cũng sẽ kế thừa editable=Falsevà do đó sẽ không hiển thị trong bảng quản trị. Trước đây đã có những cuộc nói chuyện về việc làm cho các cuộc tranh luận auto_nowauto_now_addbiến mất, và mặc dù chúng vẫn tồn tại, tôi cảm thấy tốt hơn hết là chỉ sử dụng một phương thức tùy chỉnhsave() .

Vì vậy, để làm cho điều này hoạt động chính xác, tôi khuyên bạn không nên sử dụng auto_nowhoặc auto_now_addthay vào đó hãy xác định save()phương thức của riêng bạn để đảm bảo rằng creatednó chỉ được cập nhật nếu idkhông được đặt (chẳng hạn như khi mục này được tạo lần đầu tiên) và hãy cập nhật modifiedmỗi khi mục đó được lưu.

Tôi đã thực hiện chính xác điều tương tự với các dự án khác mà tôi đã viết bằng Django, và vì vậy bạn save()sẽ trông như thế này:

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)

Hi vọng điêu nay co ich!

Chỉnh sửa để phản hồi ý kiến:

Lý do tại sao tôi chỉ gắn bó với quá tải save()so với dựa vào các đối số trường này là hai lần:

  1. Những thăng trầm nói trên với độ tin cậy của chúng. Các đối số này phụ thuộc rất nhiều vào cách mỗi loại cơ sở dữ liệu mà Django biết cách tương tác với xử lý trường tem ngày / giờ và dường như phá vỡ và / hoặc thay đổi giữa mỗi lần phát hành. (Điều mà tôi tin là động lực đằng sau lời kêu gọi loại bỏ chúng hoàn toàn).
  2. Thực tế là chúng chỉ hoạt động trên DateField, DateTimeField và TimeField và bằng cách sử dụng kỹ thuật này, bạn có thể tự động điền bất kỳ loại trường nào mỗi khi một mục được lưu.
  3. Sử dụng django.utils.timezone.now()so với datetime.datetime.now(), bởi vì nó sẽ trả về một datetime.datetimeđối tượng nhận biết TZ hoặc ngây thơ tùy thuộc vào settings.USE_TZ.

Để giải quyết lý do tại sao OP thấy lỗi, tôi không biết chính xác, nhưng có vẻ như createdthậm chí không được phổ biến, mặc dù có auto_now_add=True. Đối với tôi, nó nổi bật như một lỗi và nhấn mạnh mục số 1 trong danh sách nhỏ của tôi ở trên: auto_nowauto_now_addkhông ổn định nhất.


9
Nhưng nguồn gốc của vấn đề của tác giả là gì? Có phải auto_now_add đôi khi hoạt động không đúng cách?
Dmitry Risenberg

5
Tôi với bạn Dmitry. Tôi tò mò về lý do tại sao hai trường ném lỗi .. Và tôi thậm chí còn tò mò hơn về lý do tại sao bạn nghĩ viết phương thức lưu () tùy chỉnh của riêng bạn là tốt hơn?
hora

45
Viết một tùy chỉnh save()trên mỗi mô hình của tôi đau đớn hơn nhiều so với việc sử dụng auto_now(vì tôi muốn có các trường này trên tất cả các mô hình của mình). Tại sao những thông số đó không hoạt động?
Paul Tarjan

3
@TM, nhưng điều đó đòi hỏi phải thay đổi trực tiếp với db của bạn trong khi Django chỉ nhắm mục tiêu các tệp mô hình để xác định lược đồ
akaihola

11
Tôi không đồng ý, kịch liệt. 1) có thể chỉnh sửa = Sai là chính xác, bạn không nên chỉnh sửa trường, cơ sở dữ liệu của bạn cần phải chính xác. 2) Có tất cả các loại trường hợp cạnh mà lưu () có thể không được gọi, đặc biệt khi các bản cập nhật SQL tùy chỉnh hoặc bất cứ thứ gì đang được sử dụng. 3) Đây là một cái gì đó cơ sở dữ liệu thực sự tốt, cùng với tính toàn vẹn tham chiếu và như vậy. Tin tưởng vào cơ sở dữ liệu để làm cho nó đúng là một mặc định tốt, bởi vì đầu óc thông minh hơn bạn hoặc tôi đã thiết kế cơ sở dữ liệu để làm việc theo cách này.
Shayne

175

Nhưng tôi muốn chỉ ra rằng ý kiến ​​thể hiện trong câu trả lời được chấp nhận có phần lỗi thời. Theo các cuộc thảo luận gần đây hơn (lỗi django # 7634# 12785 ), auto_now và auto_now_add sẽ không đi đến đâu và ngay cả khi bạn đi đến cuộc thảo luận ban đầu , bạn sẽ tìm thấy các đối số mạnh mẽ chống lại RY (như trong DRY) trong lưu tùy chỉnh phương pháp.

Một giải pháp tốt hơn đã được cung cấp (các loại trường tùy chỉnh), nhưng không có đủ động lực để biến nó thành django. Bạn có thể viết riêng của bạn trong ba dòng (đó là gợi ý của 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
Trường tùy chỉnh ba dòng ở đây: link
hgcrpd

Tôi không nghĩ rằng một trường tùy chỉnh là thực sự cần thiết khi bạn có thể đặt mặc định thành một cuộc gọi (ví dụ: timezone.now). Xem câu trả lời của tôi dưới đây.
Josh

5
Đây là điều tương tự auto_add làm trong Django và kể từ năm 2010: github.com/django/django/blob/1.8.4/django/db/models/fields/ đấm . Trừ khi tôi cần các móc bổ sung trong pre_save, tôi sẽ gắn bó với auto_add.
jwhitlock

1
Không hoạt động với tôi với Django 1.9, vì vậy giải pháp này không hoạt động ở mọi nơi, vì nó chưa bao giờ dành cho auto_now *. Giải pháp duy nhất hoạt động trong mọi trường hợp sử dụng (ngay cả với sự cố arg 'update_fields') là ghi đè lưu
danius

4
Tại sao bạn đặt mặc định thành timezone.now, nhưng tín hiệu pre_save đang sử dụng datetime.datetime.now?
Bobort

32

Nói về một câu hỏi phụ: nếu bạn muốn xem các trường này trong quản trị viên (mặc dù, bạn sẽ không thể chỉnh sửa nó), bạn có thể thêm readonly_fieldsvào lớp quản trị viên của mình.

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

Chà, điều này chỉ áp dụng cho các phiên bản Django mới nhất (tôi tin, từ 1.3 trở lên)


3
Điều quan trọng cần lưu ý: điều này nên được thêm vào XxAdminlớp. Tôi đọc nó quá nhanh và cố gắng thêm nó vào AdminFormhoặc ModelFormcác lớp của tôi và không biết tại sao họ không hiển thị "các trường chỉ đọc". BTW, có khả năng có "các trường chỉ đọc thực sự trong một hình thức không?
Tomasz Gandor

28

Tôi nghĩ rằng giải pháp dễ nhất (và có thể thanh lịch nhất) ở đây là tận dụng thực tế là bạn có thể đặt defaultthành một cuộc gọi. Vì vậy, để xử lý auto_now đặc biệt của quản trị viên, bạn chỉ cần khai báo trường như sau:

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

Điều quan trọng là bạn không sử dụng timezone.now()làm giá trị mặc định sẽ không cập nhật (nghĩa là mặc định chỉ được đặt khi mã được tải). Nếu bạn thấy mình làm điều này rất nhiều, bạn có thể tạo một trường tùy chỉnh. Tuy nhiên, điều này là khá DRY tôi đã nghĩ.


2
Mặc định ít nhiều tương đương với auto_now_add (đặt giá trị khi đối tượng được lưu lần đầu tiên), nhưng nó hoàn toàn không giống như auto_now (đặt giá trị mỗi khi đối tượng được lưu).
Shai Berger

1
@ShaiBerger, tôi nghĩ họ tinh tế khác nhau theo một cách quan trọng. Tài liệu nêu rõ sự tinh tế: "Tự động đặt trường ...; đó không chỉ là giá trị mặc định mà bạn có thể ghi đè." - docs.djangoproject.com/en/dev/ref/models/fields/...
Thomas - BeeDesk

@ Thomas-BeeDesk: Đồng ý. Do đó, "tương đương nhiều hơn hoặc ít hơn".
Shai Berger

1
Giải pháp này hoạt động kém nếu bạn đang sử dụng di chuyển. Mỗi khi bạn chạy, makemigrationsnó diễn giải mặc định là thời gian khi bạn chạy makemigrationsvà do đó nghĩ rằng giá trị mặc định đã thay đổi!
nheo

8
@nhinkle, bạn có chắc là bạn không chỉ định default=timezone.now()hơn là những gì được đề xuất: default=timezine.now(không có dấu ngoặc đơn)?
Josh

18

Nếu bạn thay đổi lớp mô hình của bạn như thế này:

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

Sau đó, trường này sẽ hiển thị trong trang thay đổi quản trị viên của tôi


1
Nhưng nó chỉ hoạt động trên bản ghi chỉnh sửa. Khi tôi tạo bản ghi mới - đã bỏ qua giá trị ô xếp ngày. Khi tôi thay đổi bản ghi này - giá trị mới được đặt.
Anton Danilchenko

2
Hoạt động nhưng nó sẽ là mô hình.DateTimeField thay vì mô hình.DatetimeField
matyas

2
thất bại trong python manage.py makemigrations: KeyError: u'editable '
laoyur

12

Dựa trên những gì tôi đã đọc và trải nghiệm của tôi với Django cho đến nay, auto_now_add là lỗi. Tôi đồng ý với jthanism --- ghi đè lên phương thức lưu thông thường, nó sạch sẽ và bạn biết điều gì đang xảy ra. Bây giờ, để làm cho nó khô, hãy tạo một mô hình trừu tượng có tên 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

Và sau đó, khi bạn muốn một mô hình có hành vi có dấu thời gian này, chỉ cần phân lớp:

MyNewTimeStampyModel(TimeStamped):
    field1 = ...

Nếu bạn muốn các trường hiển thị trong quản trị viên, thì chỉ cần xóa editable=Falsetùy chọn


1
Bạn timezone.now()đang sử dụng ở đây? Tôi giả sử django.utils.timezone.now(), nhưng tôi không tích cực. Ngoài ra, tại sao sử dụng timezone.now()chứ không phải datetime.datetime.now()?
coredumperror

1
Điểm tốt. Tôi đã thêm báo cáo nhập khẩu. Lý do để sử dụng timezone.now()là vì nó nhận thức được múi giờ, trong khi đó datetime.datetime.now()là múi giờ ngây thơ. Bạn có thể đọc về nó ở đây: docs.djangoproject.com/en/dev/topics/i18n/timezones
Edward Newell

@EdwardNewell Tại sao bạn chọn cài đặt Creation_date trong phần lưu, thay vì default=timezone.nowtrong hàm tạo của trường?
Blackeagle52

Hmm .. có lẽ tôi đã không nghĩ về nó, điều đó nghe có vẻ tốt hơn.
Edward Newell

2
Vâng, có một trường hợp mà Last_modified sẽ không được cập nhật: khi update_fieldsarg được cung cấp và 'last_modified' không có trong danh sách, tôi sẽ thêm:if 'update_fields' in kwargs and 'last_modifed' not in kwargs['update_fields']: kwargs['update_fields'].append('last_modified')
danius

5

Đây có phải là nguyên nhân cho mối quan tâm?

Không, Django tự động thêm nó cho bạn trong khi lưu các mô hình, vì vậy, nó được mong đợi.

Câu hỏi phụ: trong công cụ quản trị của tôi, 2 trường đó không hiển thị. Điều đó có được mong đợi không?

Vì các trường này được thêm tự động, chúng không được hiển thị.

Để thêm vào những điều trên, như synack đã nói, đã có một cuộc tranh luận về danh sách gửi thư django để loại bỏ điều này, bởi vì, nó "không được thiết kế tốt" và là "hack"

Viết một lưu tùy chỉnh () trên mỗi mô hình của tôi sẽ đau hơn nhiều so với sử dụng auto_now

Rõ ràng là bạn không phải viết nó cho mọi mô hình. Bạn có thể viết nó vào một mô hình và kế thừa những mô hình khác từ nó.

Nhưng, như auto_addauto_now_addở đó, tôi sẽ sử dụng chúng thay vì cố gắng tự viết một phương pháp.


3

Tôi cần một cái gì đó tương tự ngày hôm nay tại nơi làm việc. Giá trị mặc định là timezone.now(), nhưng có thể chỉnh sửa cả trong chế độ xem của quản trị viên và lớp kế thừa FormMixin, do đó, để tạo trong models.pymã sau đây của tôi đáp ứng các yêu cầu đó:

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

Đối với DateTimeField, tôi đoán loại bỏ .date()khỏi chức năng và thay đổi datetime.datethành datetime.datetimehoặc tốt hơn timezone.datetime. Tôi đã không thử nó với DateTime, chỉ với Date.


2

Bạn có thể sử dụng timezone.now()để tạo và auto_nowsửa đổi:

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

Nếu bạn đang sử dụng khóa chính tùy chỉnh thay vì mặc định auto- increment int, auto_now_addsẽ dẫn đến lỗi.

Đây là mã DateTimeField.pre_save mặc định của Django với auto_nowauto_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)

Tôi không chắc chắn các tham số addlà gì. Tôi hy vọng nó sẽ một số thứ như:

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

Bản ghi mới sẽ không có attr id, vì vậy getattr(model_instance, 'id')sẽ trả về Sai sẽ dẫn đến không đặt bất kỳ giá trị nào trong trường.


7
Tôi nhận thấy rằng nếu chúng tôi giữ mặc định là timezone.now (), khi bạn thực hiện, ngày và giờ thực tế (của thời điểm này) được chuyển sang tệp di chuyển. Tôi nghĩ chúng ta nên tránh điều này vì mỗi khi bạn gọi makemigations thì lĩnh vực này sẽ có một giá trị khác nhau.
Karan Kumar

2

Đối với màn hình Admin của bạn, hãy xem câu trả lời này .

Lưu ý: auto_nowauto_now_addđược đặt thành editable=Falsemặc định, đó là lý do tại sao điều này áp dụng.


1

auto_now=Truekhông hoạt động với tôi trong Django 1.4.1, nhưng đoạn mã dưới đây đã cứu tôi. Đó là thời gian nhận biết múi giờ.

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)

Ở đây, chúng tôi đã tạo và cập nhật các cột sẽ có dấu thời gian khi được tạo và khi ai đó sửa đổi phản hồi.

auto_now_add sẽ đặt thời gian khi một phiên bản được tạo trong khi auto_now sẽ đặt thời gian khi ai đó sửa đổi phản hồi của anh ấy.


-1

Đây là câu trả lời nếu bạn đang sử dụng phía nam và bạn muốn mặc định đến ngày bạn thêm trường vào cơ sở dữ liệu:

Chọn tùy chọn 2 sau đó: datetime.datetime.now ()

Trông như thế này:

$ ./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

đã cập nhật thông tin này sẽ là: Các mô-đun datetime và django.utils.timezone có sẵn, vì vậy bạn có thể làm, ví dụ: timezone.now ()
michel.iamit

Đây là một câu hỏi cho khi mô hình của bạn thiếu dữ liệu quan trọng. Nếu bạn thiết lập mô hình của mình một cách chính xác, bạn sẽ không bao giờ cần phải xem lời nhắc này.
Shayne
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.