Tạm thời tắt auto_now / auto_now_add


104

Tôi có một mô hình như thế này:

class FooBar(models.Model):
    createtime = models.DateTimeField(auto_now_add=True)
    lastupdatetime = models.DateTimeField(auto_now=True)

Tôi muốn ghi đè hai trường ngày cho một số trường hợp mô hình (được sử dụng khi di chuyển dữ liệu). Giải pháp hiện tại trông như thế này:

for field in new_entry._meta.local_fields:
    if field.name == "lastupdatetime":
        field.auto_now = False
    elif field.name == "createtime":
        field.auto_now_add = False

new_entry.createtime = date
new_entry.lastupdatetime = date
new_entry.save()

for field in new_entry._meta.local_fields:
    if field.name == "lastupdatetime":
        field.auto_now = True
    elif field.name == "createtime":
        field.auto_now_add = True

Có giải pháp nào tốt hơn không?


new_entry.createtime.auto_now = Sai?
akonsu

3
1 - Đây sẽ thực sự thoải mái để thử nghiệm
Frank Henard

@akonsu Nope: 'datetime.datetime' đối tượng không có thuộc tính 'auto_now'
mlissner

2
Đó là giá trị chỉ ra rằng hơn một vài nhà phát triển cốt lõi là ủng hộ tiauto_now(_add)
mlissner

new_entry._meta.get_field ('date_update') trực tiếp hơn
Sérgio

Câu trả lời:


99

Gần đây tôi đã gặp phải tình huống này trong khi thử nghiệm ứng dụng của mình. Tôi cần "buộc" một dấu thời gian hết hạn. Trong trường hợp của tôi, tôi đã thực hiện thủ thuật bằng cách sử dụng bản cập nhật bộ truy vấn. Như thế này:

# my model
class FooBar(models.Model):
    title = models.CharField(max_length=255)
    updated_at = models.DateTimeField(auto_now=True, auto_now_add=True)



# my tests
foo = FooBar.objects.get(pk=1)

# force a timestamp
lastweek = datetime.datetime.now() - datetime.timedelta(days=7)
FooBar.objects.filter(pk=foo.pk).update(updated_at=lastweek)

# do the testing.

Cảm ơn bạn vì câu trả lời này. Đây là tài liệu để cập nhật (): docs.djangoproject.com/en/dev/ref/models/querysets/…
guettli

1
Trên thực tế, phương pháp này hoạt động khá tốt nếu bạn không ngại đánh cơ sở dữ liệu. Tôi cũng đã sử dụng cái này cho các bài kiểm tra.
imjustmatthew

3
từ tài liệu Django: docs.djangoproject.com/en/1.9/topics/db/queries/… Lưu ý rằng phương thức update () được chuyển đổi trực tiếp thành câu lệnh SQL. Đây là một hoạt động hàng loạt để cập nhật trực tiếp. Nó không chạy bất kỳ tiết kiệm () phương pháp trên mô hình của bạn, hoặc phát ra các pre_save hoặc post_save tín hiệu (mà là một hệ quả của gọi tiết kiệm ()), hoặc tôn vinh những tùy chọn trường auto_now
NoamG

@NoamG Tôi nghĩ đây là trường hợp hiếm hoi mà update()hành vi này chính xác là những gì chúng ta cần.
David D.

54

Bạn thực sự không thể tắt auto_now / auto_now_add theo cách khác so với cách bạn đã làm. Nếu bạn cần sự linh hoạt để thay đổi các giá trị đó, auto_now/ auto_now_addkhông phải là lựa chọn tốt nhất. Thường linh hoạt hơn khi sử dụng defaultvà / hoặc ghi đè save()phương thức để thực hiện thao tác ngay trước khi đối tượng được lưu.

Sử dụng defaultvà một save()phương pháp bị ghi đè , một cách để giải quyết vấn đề của bạn là xác định mô hình của bạn như sau:

class FooBar(models.Model):
    createtime = models.DateTimeField(default=datetime.datetime.now)
    lastupdatetime = models.DateTimeField()

    def save(self, *args, **kwargs):
        if not kwargs.pop('skip_lastupdatetime', False):
            self.lastupdatetime = datetime.datetime.now()

        super(FooBar, self).save(*args, **kwargs)

Trong mã của bạn, nơi bạn muốn bỏ qua thay đổi thời gian cập nhật tự động, chỉ cần sử dụng

new_entry.save(skip_lastupdatetime=True)

Nếu đối tượng của bạn được lưu trong giao diện quản trị hoặc những nơi khác, save () sẽ được gọi mà không có đối số Bỏ qua_lastupdatetime và nó sẽ hoạt động giống như trước đây với auto_now.


14
TL; DR Không sử auto_now_adddụng defaultthay thế.
Thane Brimhall

9
Một lưu ý cho ví dụ này là nó datetime.datetime.nowtrả về một ngày giờ thực. Để sử dụng ngày giờ nhận biết múi giờ, hãy sử dụng from django.utils import timezonemodels.DateTimeField(default=timezone.now)xem docs.djangoproject.com/en/1.9/topics/i18n/timezones/…
BenjaminGolder

Vì vậy, chỉ cần nói rõ: Nếu bạn chỉ muốn có thể sửa đổi createtimetrường, bạn không cần phải ghi đè save(). Nó là đủ để thay thế auto_now_add=Truebằng tương đương default=timezone.now, editable=False, blank=True(theo tài liệu ). Hai tùy chọn sau đảm bảo hành vi tương tự trong quản trị viên.
djvg

28

Tôi đã sử dụng gợi ý do người hỏi đưa ra và tạo một số hàm. Đây là trường hợp sử dụng:

turn_off_auto_now(FooBar, "lastupdatetime")
turn_off_auto_now_add(FooBar, "createtime")

new_entry.createtime = date
new_entry.lastupdatetime = date
new_entry.save()

Đây là cách triển khai:

def turn_off_auto_now(ModelClass, field_name):
    def auto_now_off(field):
        field.auto_now = False
    do_to_model(ModelClass, field_name, auto_now_off)

def turn_off_auto_now_add(ModelClass, field_name):
    def auto_now_add_off(field):
        field.auto_now_add = False
    do_to_model(ModelClass, field_name, auto_now_add_off)

def do_to_model(ModelClass, field_name, func):
    field = ModelClass._meta.get_field_by_name(field_name)[0]
    func(field)

Các chức năng tương tự có thể được tạo để bật lại.


9
Trong hầu hết các trường hợp, thay vì lặp lại, bạn có thể chỉ cần làm Clazz._meta.get_field_by_name(field_name)[0].
naktinis

Cảm ơn @naktinis. Đã thay đổi.
Frank Henard

4
nb (1.10) ModelClass._meta.get_field_by_name (field_name) [0] trong do_to_model dường như không hoạt động với tôi - đã thay đổi thành: ModelClass._meta.get_field (field_name)
Georgina S

27

Bạn cũng có thể sử dụng update_fieldstham số save()và chuyển các auto_nowtrường của mình . Đây là một ví dụ:

# Date you want to force
new_created_date = date(year=2019, month=1, day=1)
# The `created` field is `auto_now` in your model
instance.created = new_created_date
instance.save(update_fields=['created'])

Đây là lời giải thích từ tài liệu của Django: https://docs.djangoproject.com/en/stable/ref/models/instances/#specify-which-fields-to-save


1
Tôi ước tôi có thể bỏ phiếu này cao hơn! Đây thực sự là những gì tôi muốn (để thay đổi các phần của mô hình mà không cần chạm vào các trường 'tự động'), nhưng tiếc là không trả lời câu hỏi được đưa ra (đó là lưu các giá trị rõ ràng vào các trường bằng cách sử dụng auto_nowauto_now_add).
Tim Tisdall

1
Câu trả lời tốt nhất, tôi ước gì tôi có thể cung cấp cho nó 10 phiếu, rất đơn giản và thanh lịch
Bedros

18

Tôi đã đi theo cách quản lý ngữ cảnh để có thể sử dụng lại.

@contextlib.contextmanager
def suppress_autotime(model, fields):
    _original_values = {}
    for field in model._meta.local_fields:
        if field.name in fields:
            _original_values[field.name] = {
                'auto_now': field.auto_now,
                'auto_now_add': field.auto_now_add,
            }
            field.auto_now = False
            field.auto_now_add = False
    try:
        yield
    finally:
        for field in model._meta.local_fields:
            if field.name in fields:
                field.auto_now = _original_values[field.name]['auto_now']
                field.auto_now_add = _original_values[field.name]['auto_now_add']

Sử dụng như vậy:

with suppress_autotime(my_object, ['updated']):
    my_object.some_field = some_value
    my_object.save()

Bùng nổ.


8

Đối với những người nhìn vào điều này khi họ đang viết các bài kiểm tra, có một thư viện python có tên là freezegun cho phép bạn giả mạo thời gian - vì vậy khi auto_now_addmã chạy, nó sẽ đạt được thời gian bạn thực sự muốn. Vì thế:

from datetime import datetime, timedelta
from freezegun import freeze_time

with freeze_time('2016-10-10'):
    new_entry = FooBar.objects.create(...)
with freeze_time('2016-10-17'):
    # use new_entry as you wish, as though it was created 7 days ago

Nó cũng có thể được sử dụng như một trình trang trí - xem liên kết ở trên để biết các tài liệu cơ bản.


4

Bạn có thể ghi đè auto_now_add mà không cần mã đặc biệt.

Tôi đã gặp câu hỏi này khi tôi cố gắng tạo một đối tượng với ngày cụ thể:

Post.objects.create(publication_date=date, ...)

Ở đâu publication_date = models.DateField(auto_now_add=True) .

Vì vậy, đây là những gì tôi đã làm:

post = Post.objects.create(...)
post.publication_date = date
post.save()

Điều này đã được ghi đè thành công auto_now_add .

Là một giải pháp lâu dài hơn, savephương pháp ghi đè là cách nên đi: https://code.djangoproject.com/ticket/16583


2

Tôi cần tắt auto_now cho trường DateTime trong quá trình di chuyển và có thể thực hiện việc này.

events = Events.objects.all()
for event in events:
    for field in event._meta.fields:
        if field.name == 'created_date':
            field.auto_now = False
    event.save()

1

Tôi đến muộn với bữa tiệc, nhưng tương tự như một số câu trả lời khác, đây là giải pháp tôi đã sử dụng trong quá trình di chuyển cơ sở dữ liệu. Sự khác biệt so với các câu trả lời khác là điều này vô hiệu hóa tất cả các trường auto_now cho mô hình với giả định rằng thực sự không có lý do gì để có nhiều hơn một trường như vậy.

def disable_auto_now_fields(*models):
    """Turns off the auto_now and auto_now_add attributes on a Model's fields,
    so that an instance of the Model can be saved with a custom value.
    """
    for model in models:
        for field in model._meta.local_fields:
            if hasattr(field, 'auto_now'):
                field.auto_now = False
            if hasattr(field, 'auto_now_add'):
                field.auto_now_add = False

Sau đó, để sử dụng nó, bạn chỉ cần làm:

disable_auto_now_fields(Document, Event, ...)

Và nó sẽ đi qua và xóa tất cả các trường auto_nowvà của bạn auto_now_addcho tất cả các lớp mô hình mà bạn truyền vào.


1

Một phiên bản rõ ràng hơn của trình quản lý ngữ cảnh từ https://stackoverflow.com/a/35943149/1731460

from contextlib import contextmanager

@contextmanager
def suppress_auto_now(model, field_names):
    """
    idea taken here https://stackoverflow.com/a/35943149/1731460
    """
    fields_state = {}
    for field_name in field_names:
        field = model._meta.get_field(field_name)
        fields_state[field] = {'auto_now': field.auto_now, 'auto_now_add': field.auto_now_add}

    for field in fields_state:
        field.auto_now = False
        field.auto_now_add = False
    try:
        yield
    finally:
        for field, state in fields_state.items():
            field.auto_now = state['auto_now']
            field.auto_now_add = state['auto_now_add']

Bạn có thể sử dụng nó ngay cả với Factories (factory-boy)

        with suppress_autotime(Click, ['created']):
            ClickFactory.bulk_create(post=obj.post, link=obj.link, created__iter=created)

0

bản sao của Django - Models.DateTimeField - Thay đổi giá trị auto_now_add động

Vâng, tôi đã dành cả buổi chiều nay để tìm hiểu và vấn đề đầu tiên là cách tìm nạp đối tượng mô hình và vị trí trong mã. Tôi đang xây dựng lại khung hình trong serializer.py, ví dụ như trong __init__serializer, nó không thể có Model. Bây giờ trong to_internal_value, bạn có thể nhận được lớp mô hình, sau khi lấy Trường và sau khi sửa đổi các thuộc tính của trường như trong ví dụ này:

class ProblemSerializer(serializers.ModelSerializer):

    def to_internal_value(self, data): 
        ModelClass = self.Meta.model
        dfil = ModelClass._meta.get_field('date_update')
        dfil.auto_now = False
        dfil.editable = True

0

Tôi cần giải pháp sẽ hoạt động với update_or_create , tôi đã tìm đến giải pháp này dựa trên mã @andreaspelme.

Chỉ có thay đổi là Bạn có thể đặt bỏ qua bằng cách đặt trường đã sửa đổi thành skipkhông chỉ bằng cách thực sự chuyển kwargskip_modified_update phương thức to save ().

Chỉ yourmodelobject.modified='skip'và cập nhật sẽ bị bỏ qua!

from django.db import models
from django.utils import timezone


class TimeTrackableAbstractModel(models.Model):
    created = models.DateTimeField(default=timezone.now, db_index=True)
    modified = models.DateTimeField(default=timezone.now, db_index=True)

    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
        skip_modified_update = kwargs.pop('skip_modified_update', False)
        if skip_modified_update or self.modified == 'skip':
            self.modified = models.F('modified')
        else:
            self.modified = timezone.now()
        super(TimeTrackableAbstractModel, self).save(*args, **kwargs)
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.