Nonaktifkan auto_now / auto_now_add untuk sementara

104

Saya punya model seperti ini:

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

Saya ingin menimpa dua bidang tanggal untuk beberapa contoh model (digunakan saat memigrasi data). Solusi saat ini terlihat seperti ini:

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

Apakah ada solusi yang lebih baik?

jedie
sumber
new_entry.createtime.auto_now = Salah?
akonsu
3
+1 - Ini akan sangat bagus untuk pengujian
Frank Henard
@akonsu Tidak: Objek 'datetime.datetime' tidak memiliki atribut 'auto_now'
mlissner
2
Perlu ditunjukkan bahwa lebih dari beberapa pengembang inti mendukung penghentianauto_now(_add)
mlissner
new_entry._meta.get_field ('date_update') lebih langsung
Sérgio

Jawaban:

99

Saya baru-baru ini menghadapi situasi ini saat menguji aplikasi saya. Saya perlu "memaksa" stempel waktu yang kedaluwarsa. Dalam kasus saya, saya melakukan trik dengan menggunakan pembaruan queryset. Seperti ini:

# 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.
dirleyrls.dll
sumber
Terima kasih atas jawaban ini. Berikut adalah dokumen untuk update (): docs.djangoproject.com/en/dev/ref/models/querysets/…
guettli
1
Sebenarnya, metode ini berfungsi dengan baik jika Anda tidak keberatan masuk ke database. Saya akhirnya menggunakan ini untuk tes juga.
imjustmatthew
3
dari dokumentasi Django: docs.djangoproject.com/en/1.9/topics/db/queries/… Ketahuilah bahwa metode update () diubah secara langsung ke pernyataan SQL. Ini adalah operasi massal untuk pembaruan langsung. Itu tidak menjalankan metode save () apa pun pada model Anda, atau memancarkan sinyal pre_save atau post_save (yang merupakan konsekuensi dari memanggil save ()), atau menerima opsi bidang
auto_now
@NoamG Saya pikir ini adalah kasus yang jarang terjadi di mana update()perilaku ini persis seperti yang kita butuhkan.
David D.
54

Anda tidak dapat benar-benar menonaktifkan auto_now / auto_now_add dengan cara lain daripada yang sudah Anda lakukan. Jika Anda membutuhkan fleksibilitas untuk mengubah nilai-nilai itu, auto_now/ auto_now_addbukanlah pilihan terbaik. Seringkali lebih fleksibel untuk menggunakan defaultdan / atau mengganti save()metode untuk melakukan manipulasi tepat sebelum objek disimpan.

Menggunakan defaultdan save()metode yang diganti , salah satu cara untuk menyelesaikan masalah Anda adalah dengan mendefinisikan model Anda seperti ini:

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)

Dalam kode Anda, di mana Anda ingin melewati perubahan waktu terakhir otomatis, cukup gunakan

new_entry.save(skip_lastupdatetime=True)

Jika objek Anda disimpan di antarmuka admin atau tempat lain, save () akan dipanggil tanpa argumen skip_lastupdatetime, dan itu akan berperilaku seperti sebelumnya dengan auto_now.

andreaspelme
sumber
14
TL; DR Jangan auto_now_addgunakan defaultsebagai gantinya.
Thane Brimhall
9
Satu peringatan untuk contoh ini adalah yang datetime.datetime.nowmengembalikan tanggal waktu naif. Untuk menggunakan tanggal waktu sadar zona, gunakan from django.utils import timezonedan models.DateTimeField(default=timezone.now)lihat docs.djangoproject.com/en/1.9/topics/i18n/timezones/…
BenjaminGolder
Jadi, untuk memperjelas: Jika Anda hanya ingin dapat mengubah createtimebidang, Anda tidak perlu menimpa save(). Cukup diganti auto_now_add=Truedengan yang setara default=timezone.now, editable=False, blank=True(menurut dokumen ). Dua opsi terakhir memastikan perilaku serupa di admin.
djvg
28

Saya menggunakan saran yang dibuat oleh penanya, dan membuat beberapa fungsi. Berikut kasus penggunaannya:

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

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

Berikut implementasinya:

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)

Fungsi serupa dapat dibuat untuk mengaktifkannya kembali.

Frank Henard
sumber
9
Dalam kebanyakan kasus, alih-alih iterasi, Anda mungkin bisa melakukannya Clazz._meta.get_field_by_name(field_name)[0].
naktinis
Terima kasih @naktinis. Berubah.
Frank Henard
4
nb (1.10) ModelClass._meta.get_field_by_name (field_name) [0] di do_to_model tampaknya tidak berfungsi untuk saya - diubah menjadi: ModelClass._meta.get_field (field_name)
Georgina S
27

Anda juga dapat menggunakan update_fieldsparameter save()dan meneruskan auto_nowbidang Anda . Berikut contohnya:

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

Berikut penjelasan dari dokumentasi Django: https://docs.djangoproject.com/en/stable/ref/models/instances/#specifying-which-fields-to-save

Data Apollo
sumber
1
Saya berharap saya dapat memilih ini lebih tinggi! Ini benar-benar yang saya inginkan (untuk mengubah bagian model tanpa menyentuh bidang 'otomatis'), tetapi sayangnya tidak menjawab pertanyaan yang diberikan (yaitu untuk menyimpan nilai eksplisit ke bidang menggunakan auto_nowdan auto_now_add).
Tim Tisdall
1
jawaban terbaik, saya harap saya bisa memberikannya 10 suara, sangat sederhana dan elegan
Bedros
18

Saya menggunakan cara manajer konteks untuk dapat digunakan kembali.

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

Gunakan seperti ini:

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

Ledakan.

Soulseekah
sumber
8

Bagi mereka yang melihat ini ketika mereka menulis tes, ada pustaka python yang disebut freezegun yang memungkinkan Anda untuk memalsukan waktu - jadi ketika auto_now_addkode berjalan, ia mendapatkan waktu yang Anda inginkan. Begitu:

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

Ini juga dapat digunakan sebagai dekorator - lihat tautan di atas untuk dokumen dasar.

Hamish Downer
sumber
4

Anda dapat mengganti auto_now_addtanpa kode khusus.

Saya menemukan pertanyaan ini ketika saya mencoba membuat objek dengan tanggal tertentu:

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

dimana publication_date = models.DateField(auto_now_add=True).

Jadi inilah yang telah saya lakukan:

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

Ini telah berhasil diganti auto_now_add.

Sebagai solusi jangka panjang, savemetode utama adalah cara yang tepat: https://code.djangoproject.com/ticket/16583

Pavel Vergeev
sumber
2

Saya perlu menonaktifkan auto_now untuk bidang DateTime selama migrasi dan dapat melakukan ini.

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

sumber
1

Saya terlambat ke pesta, tetapi mirip dengan beberapa jawaban lainnya, ini adalah solusi yang saya gunakan selama migrasi database. Perbedaan dari jawaban lain adalah bahwa ini menonaktifkan semua kolom auto_now untuk model dengan asumsi bahwa sebenarnya tidak ada alasan untuk memiliki lebih dari satu kolom seperti itu.

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

Kemudian untuk menggunakannya, Anda cukup melakukan:

disable_auto_now_fields(Document, Event, ...)

Dan itu akan melalui dan nuke semua Anda auto_nowdan auto_now_addbidang untuk semua kelas model yang Anda lewati.

mlissner
sumber
1

Versi pengelola konteks yang lebih bersih dari 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']

Anda dapat menggunakannya bahkan dengan Pabrik (pabrik-anak)

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

salinan Django - Models.DateTimeField - Mengubah nilai auto_now_add dinamis

Nah, saya habiskan sore ini untuk mencari tahu dan masalah pertama adalah bagaimana mengambil model objek dan di mana dalam kode. Saya dalam restframework di serializer.py, misalnya di __init__serializer itu belum bisa memiliki Model. Sekarang di to_internal_value Anda bisa mendapatkan kelas model, setelah mendapatkan Bidang dan setelah memodifikasi properti bidang seperti dalam contoh ini:

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
Sérgio
sumber
0

Saya membutuhkan solusi yang akan berfungsi update_or_create, saya datang ke solusi ini berdasarkan kode @andreaspelme.

Satu-satunya perubahan adalah Anda dapat menyetel skipping dengan menyetel bidang yang diubah menjadi skiptidak hanya dengan benar-benar meneruskan metode kwarg skip_modified_updateto save ().

Hanya yourmodelobject.modified='skip'dan pembaruan akan dilewati!

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)
lechup
sumber