ปิดใช้งาน auto_now / auto_now_add ชั่วคราว


106

ฉันมีโมเดลเช่นนี้:

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

ฉันต้องการเขียนทับฟิลด์วันที่สองช่องสำหรับอินสแตนซ์บางรุ่น (ใช้เมื่อย้ายข้อมูล) โซลูชันปัจจุบันมีลักษณะดังนี้:

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

มีทางออกที่ดีกว่านี้หรือไม่?


new_entry.createtime.auto_now = เท็จ?
akonsu

3
+1 - นี่จะดีมากสำหรับการทดสอบ
Frank Henard

@akonsu Nope: วัตถุ 'datetime.datetime' ไม่มีแอตทริบิวต์ 'auto_now'
mlissner

2
เป็นสิ่งที่ควรค่าแก่การชี้ให้เห็นว่านักพัฒนาหลักมากกว่าสองสามรายสนับสนุนการเลิกใช้งานauto_now(_add)
mlissner

new_entry._meta.get_field ('date_update') ตรงกว่า
Sérgio

คำตอบ:


101

ฉันเพิ่งเผชิญกับสถานการณ์นี้ขณะทดสอบแอปพลิเคชันของฉัน ฉันจำเป็นต้อง "บังคับ" การประทับเวลาที่หมดอายุ ในกรณีของฉันฉันทำเคล็ดลับโดยใช้การอัปเดตชุดสืบค้น แบบนี้:

# 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.

ขอบคุณสำหรับคำตอบนี้ นี่คือเอกสารสำหรับการอัปเดต (): docs.djangoproject.com/en/dev/ref/models/querysets/…
guettli

1
จริงๆแล้ววิธีนี้ใช้ได้ดีถ้าคุณไม่สนใจที่จะกดฐานข้อมูล ฉันลงเอยด้วยการใช้สิ่งนี้สำหรับการทดสอบเช่นกัน
imjustmatthew

3
จากเอกสาร Django: docs.djangoproject.com/en/1.9/topics/db/queries/…โปรดทราบว่าเมธอด update () ถูกแปลงเป็นคำสั่ง SQL โดยตรง เป็นการดำเนินการจำนวนมากสำหรับการอัปเดตโดยตรง ไม่เรียกใช้เมธอด save () ใด ๆ ในโมเดลของคุณหรือปล่อยสัญญาณ pre_save หรือ post_save (ซึ่งเป็นผลมาจากการเรียก save ()) หรือให้เกียรติตัวเลือกฟิลด์
auto_now

@NoamG ฉันคิดว่านี่เป็นกรณีที่หายากซึ่งupdate()พฤติกรรมนี้เป็นสิ่งที่เราต้องการ
David D.

54

คุณไม่สามารถปิดใช้งาน auto_now / auto_now_add ด้วยวิธีอื่นนอกเหนือจากที่คุณทำอยู่แล้ว หากคุณต้องการความยืดหยุ่นในการเปลี่ยนแปลงค่าเหล่านั้นauto_now/ auto_now_addไม่ใช่ทางเลือกที่ดีที่สุด มักจะยืดหยุ่นกว่าในการใช้defaultและ / หรือแทนที่save()วิธีการจัดการก่อนที่วัตถุจะถูกบันทึก

การใช้defaultและsave()วิธีการลบล้างวิธีหนึ่งในการแก้ปัญหาของคุณคือการกำหนดโมเดลของคุณดังนี้:

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)

ในรหัสของคุณที่คุณต้องการข้ามการเปลี่ยนแปลง lastupdatetime อัตโนมัติเพียงใช้

new_entry.save(skip_lastupdatetime=True)

หากวัตถุของคุณจะถูกบันทึกไว้ในส่วนติดต่อผู้ดูแลระบบหรือสถานที่อื่น ๆ ประหยัด () จะถูกเรียกโดยไม่มีข้อโต้แย้ง skip_lastupdatetime auto_nowและมันจะทำงานเช่นเดียวกับที่มันทำมาก่อนด้วย


14
TL; DR อย่าใช้auto_now_addใช้defaultแทน
Thane Brimhall

9
ข้อแม้อย่างหนึ่งของตัวอย่างนี้คือdatetime.datetime.nowส่งคืนวันที่ที่ไร้เดียงสา หากต้องการใช้วันที่รับรู้เขตเวลาให้ใช้from django.utils import timezoneและmodels.DateTimeField(default=timezone.now)ดูdocs.djangoproject.com/th/1.9/topics/i18n/timezones/…
BenjaminGolder

ดังนั้นเพียงแค่ต้องมีความชัดเจน: ถ้าคุณต้องการที่จะสามารถปรับเปลี่ยนข้อมูลคุณไม่จำเป็นต้องแทนที่createtime save()มันเพียงพอที่จะแทนที่auto_now_add=Trueด้วยสิ่งที่เทียบเท่ากันdefault=timezone.now, editable=False, blank=True(ตามเอกสาร ) สองตัวเลือกหลังทำให้ผู้ดูแลระบบมีพฤติกรรมคล้ายกัน
djvg

29

ฉันใช้คำแนะนำของผู้ถามและสร้างฟังก์ชันบางอย่าง นี่คือกรณีการใช้งาน:

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

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

นี่คือการใช้งาน:

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)

ฟังก์ชันที่คล้ายกันสามารถสร้างขึ้นเพื่อเปิดใช้งานอีกครั้ง


9
Clazz._meta.get_field_by_name(field_name)[0]ในกรณีส่วนใหญ่แทนย้ำคุณสามารถอาจจะเพียงแค่ทำ
naktinis

ขอบคุณ @naktinis เปลี่ยนแล้ว.
Frank Henard

4
nb (1.10) ModelClass._meta.get_field_by_name (field_name) [0] ใน do_to_model ดูเหมือนจะไม่ทำงานสำหรับฉัน - เปลี่ยนเป็น: ModelClass._meta.get_field (field_name)
Georgina S

27

คุณยังสามารถใช้update_fieldsพารามิเตอร์ของsave()และส่งผ่านauto_nowฟิลด์ของคุณ นี่คือตัวอย่าง:

# 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'])

นี่คือคำอธิบายจากเอกสารของ Django: https://docs.djangoproject.com/en/stable/ref/models/instances/#specifying-which-fields-to-save


1
ฉันหวังว่าฉันจะโหวตให้สูงกว่านี้! นี่คือสิ่งที่ฉันต้องการจริงๆ (เพื่อแก้ไขบางส่วนของโมเดลโดยไม่ต้องแตะฟิลด์ "อัตโนมัติ") แต่น่าเสียดายที่ไม่ตอบคำถามที่ให้ไว้ (ซึ่งเป็นการบันทึกค่าที่ชัดเจนลงในฟิลด์โดยใช้auto_nowและauto_now_add)
Tim Tisdall

1
คำตอบที่ดีที่สุดฉันหวังว่าฉันจะให้ 10 คะแนนแบบเรียบง่ายและสง่างาม
Bedros

18

ฉันใช้วิธีจัดการบริบทเพื่อนำกลับมาใช้ใหม่

@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']

ใช้ดังนี้:

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

บูม


8

สำหรับผู้ที่มองหาสิ่งนี้เมื่อพวกเขากำลังเขียนการทดสอบมีไลบรารี python ที่เรียกว่าfreezegunซึ่งช่วยให้คุณปลอมเวลาได้ดังนั้นเมื่อauto_now_addโค้ดทำงานจะได้เวลาที่คุณต้องการจริงๆ ดังนั้น:

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

นอกจากนี้ยังสามารถใช้เป็นมัณฑนากร - ดูลิงก์ด้านบนสำหรับเอกสารพื้นฐาน


4

คุณสามารถลบล้างได้auto_now_addโดยไม่ต้องใช้รหัสพิเศษ

ฉันเจอคำถามนี้เมื่อฉันพยายามสร้างวัตถุที่มีวันที่เฉพาะ:

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

ที่ไหนpublication_date = models.DateField(auto_now_add=True).

นี่คือสิ่งที่ฉันทำ:

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

ลบล้างสิ่งนี้สำเร็จauto_now_addแล้ว

ในฐานะที่เป็นวิธีการแก้ปัญหาในระยะยาววิธีการแทนที่saveเป็นวิธีที่จะไป: https://code.djangoproject.com/ticket/16583


2

ฉันต้องการปิดใช้งาน auto_now สำหรับฟิลด์ DateTime ระหว่างการย้ายข้อมูลและสามารถทำได้

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

ฉันไปปาร์ตี้ช้า แต่ก็คล้ายกับคำตอบอื่น ๆ นี่เป็นวิธีแก้ปัญหาที่ฉันใช้ในระหว่างการย้ายฐานข้อมูล ความแตกต่างจากคำตอบอื่น ๆ คือการปิดใช้งานฟิลด์ auto_now ทั้งหมดสำหรับโมเดลภายใต้สมมติฐานว่าไม่มีเหตุผลที่จะมีฟิลด์ดังกล่าวมากกว่าหนึ่งฟิลด์

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

จากนั้นหากต้องการใช้งานคุณสามารถทำได้:

disable_auto_now_fields(Document, Event, ...)

และมันจะผ่านไปและเก็บข้อมูลทั้งหมดของคุณauto_nowและauto_now_addฟิลด์สำหรับคลาสโมเดลทั้งหมดที่คุณผ่านเข้าไป


1

ตัวจัดการบริบทเวอร์ชันที่สะอาดกว่าเล็กน้อยจาก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']

คุณสามารถใช้ได้แม้กับโรงงาน (เด็กโรงงาน)

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

0

สำเนาของDjango - Models.DateTimeField - การเปลี่ยนค่า auto_now_add แบบไดนามิก

ฉันใช้เวลาช่วงบ่ายนี้ในการค้นหาและปัญหาแรกคือวิธีดึงวัตถุโมเดลและตำแหน่งในโค้ด ฉันอยู่ใน restframework ใน serializer.py เช่นใน__init__serializer มันยังไม่มี Model ตอนนี้ใน to_internal_value คุณสามารถรับคลาสโมเดลหลังจากได้รับฟิลด์และหลังจากแก้ไขคุณสมบัติของฟิลด์ดังในตัวอย่างนี้:

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

ฉันต้องการโซลูชันที่จะใช้งานupdate_or_createได้ฉันมาถึงโซลูชันนี้โดยใช้รหัส @andreaspelme

การเปลี่ยนแปลงเพียงอย่างเดียวคือคุณสามารถตั้งค่าการข้ามได้โดยการตั้งค่าฟิลด์ที่แก้ไขskipไม่เพียง แต่ส่งผ่าน kwarg เท่านั้นskip_modified_updateไปยัง save () เท่านั้น

เพียงแค่yourmodelobject.modified='skip'อัปเดตจะถูกข้าม!

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)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.