Auto_now / auto_now_add'yi geçici olarak devre dışı bırakın


106

Bunun gibi bir modelim var:

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

Bazı model örnekleri için iki tarih alanının üzerine yazmak istiyorum (verileri taşırken kullanılır). Mevcut çözüm şuna benzer:

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

Daha iyi bir çözüm var mı?


new_entry.createtime.auto_now = Yanlış?
akonsu

3
+1 - Bu test için gerçekten güzel olurdu
Frank Henard 14'11

@akonsu Hayır: 'datetime.datetime' nesnesinin 'auto_now' özelliği yok
mlissner


new_entry._meta.get_field ('date_update') daha doğrudandır
Sérgio

Yanıtlar:


101

Son zamanlarda başvurumu test ederken bu durumla karşılaştım. Süresi dolmuş bir zaman damgasını "zorlamam" gerekiyordu. Benim durumumda bir sorgu seti güncellemesi kullanarak hile yaptım. Bunun gibi:

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

Bu cevap için teşekkür ederim. İşte güncelleme için dokümanlar (): docs.djangoproject.com/en/dev/ref/models/querysets/…
guettli

1
Aslında, veritabanına basmanızın bir sakıncası yoksa bu yöntem oldukça iyi çalışır. Bunu testler için de kullandım.
imjustmatthew

3
Django belgelerinden: docs.djangoproject.com/en/1.9/topics/db/queries/… update () yönteminin doğrudan bir SQL ifadesine dönüştürüldüğünü unutmayın. Doğrudan güncellemeler için toplu bir işlemdir. Modellerinizde herhangi bir kaydetme () yöntemi çalıştırmaz veya ön_save veya post_save sinyallerini yaymaz (bu, save () çağrısının bir sonucudur) veya auto_now alan seçeneğini
dikkate almaz

@NoamG Bence bu, bu update()davranışın tam olarak ihtiyacımız olan şey olduğu nadir bir durum .
David D.

54

Auto_now / auto_now_add'yi zaten yaptığınızdan başka bir şekilde gerçekten devre dışı bırakamazsınız. Bu değerleri değiştirme esnekliğine ihtiyacınız varsa, auto_now/ auto_now_adden iyi seçim değildir. Nesne kaydedilmeden hemen önce manipülasyon yapmak defaultiçin save()yöntemi kullanmak ve / veya geçersiz kılmak genellikle daha esnektir .

defaultVe geçersiz kılınmış bir save()yöntem kullanarak , probleminizi çözmenin bir yolu modelinizi şu şekilde tanımlamak olacaktır:

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)

Kodunuzda, otomatik son güncelleme süresi değişikliğini atlamak istediğiniz yerde,

new_entry.save(skip_lastupdatetime=True)

Nesneniz yönetici arayüzüne veya başka yerlere kaydedilmişse, save () skip_lastupdatetime argümanı olmadan çağrılacak ve daha önce olduğu gibi davranacaktır auto_now.


14
TL; DR Bunun yerine auto_now_addkullanmayın default.
Thane Brimhall

9
Bu örneğe bir uyarı, datetime.datetime.nownaif bir tarih saat döndürmesidir. Saat dilimine duyarlı bir tarih saat kullanmak için docs.djangoproject.com/en/1.9/topics/i18n/timezones/…from django.utils import timezonemodels.DateTimeField(default=timezone.now)
BenjaminGolder

Bu nedenle, açık olmak gerekirse: Yalnızca createtimealanı değiştirebilmek istiyorsanız, geçersiz kılmanıza gerek yoktur save(). auto_now_add=TrueEşdeğeri ile değiştirmek yeterlidir default=timezone.now, editable=False, blank=True( belgelere göre ). Son iki seçenek, yöneticide benzer davranışı sağlar.
djvg

29

Soruyu soranın önerisini kullandım ve bazı işlevler yarattım. İşte kullanım durumu:

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

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

İşte uygulama:

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)

Bunları tekrar açmak için benzer işlevler oluşturulabilir.


9
Çoğu durumda yineleme yerine muhtemelen yapabilirsiniz Clazz._meta.get_field_by_name(field_name)[0].
naktinis

Teşekkürler @naktinis. Değişti.
Frank Henard

4
nb (1.10) ModelClass._meta.get_field_by_name (field_name) [0] do_to_model'de benim için çalışmıyor gibi görünüyor - şu şekilde değiştirildi: ModelClass._meta.get_field (field_name)
Georgina S

27

Ayrıca, update_fieldsparametresini kullanabilir save()ve auto_nowalanlarınızı geçirebilirsiniz . İşte bir örnek:

# 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 belgelerinden açıklama: https://docs.djangoproject.com/en/stable/ref/models/instances/#specifying-which-fields-to-save


1
Keşke buna daha yüksek oy verebilseydim! Bu gerçekten istediğim şeydi ('otomatik' alanlara dokunmadan modelin parçalarını değiştirmek), ancak maalesef verilen soruya cevap vermiyor ( auto_nowve kullanarak alanlara açık değerleri kaydetmek auto_now_add).
Tim Tisdall

1
en iyi cevap, keşke çok basit ve zarif 10 oy
verebilseydim

18

Yeniden kullanılabilirlik için bağlam yöneticisi yoluna gittim.

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

Şöyle kullanın:

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

Boom.


8

Test yazarken buna bakanlar için, zamanı taklit etmenize izin veren frozenegun adlı bir python kitaplığı var - yani auto_now_addkod çalıştığında, gerçekten istediğiniz zamanı alır. Yani:

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

Aynı zamanda bir dekoratör olarak da kullanılabilir - temel belgeler için yukarıdaki bağlantıya bakın.


4

auto_now_addÖzel kod olmadan geçersiz kılabilirsiniz .

Belirli bir tarihe sahip bir nesne oluşturmaya çalıştığımda şu soruyla karşılaştım:

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

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

Yani yaptığım şey bu:

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

Bu başarıyla geçersiz kılındı auto_now_add.

Daha uzun vadeli bir çözüm olarak, geçersiz kılma saveyöntemi gitmenin yoludur: https://code.djangoproject.com/ticket/16583


2

Taşıma sırasında DateTime alanı için auto_now'u devre dışı bırakmam gerekiyordu ve bunu yapabildim.

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

Partiye geç kaldım, ancak diğer cevapların birçoğuna benzer şekilde, bu da veritabanı geçişi sırasında kullandığım bir çözüm. Diğer yanıtlardan farkı, böyle birden fazla alana sahip olmak için gerçekten bir neden olmadığı varsayımı altında, bunun model için tüm auto_now alanlarını devre dışı bırakmasıdır.

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

Daha sonra kullanmak için şunları yapabilirsiniz:

disable_auto_now_fields(Document, Event, ...)

Ve geçtiğiniz tüm model sınıfları için tüm alanlarınızı auto_nowve auto_now_addalanlarınızı geçecek ve bombalayacaktır .


1

Https://stackoverflow.com/a/35943149/1731460 adresinden bağlam yöneticisinin biraz daha temiz bir sürümü

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

Fabrikalarda bile kullanabilirsiniz (fabrika-çocuk)

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

0

Django kopyası - Models.DateTimeField - Dinamik olarak auto_now_add değerini değiştirme

Bu öğleden sonrayı öğrenmekle geçirdim ve ilk problem model nesnesinin nasıl getirildiği ve kodun neresi olduğu. Serializer.py'de yeniden çerçeve içindeyim, örneğin __init__serileştiricide henüz Modele sahip olamadı. Şimdi to_internal_value içinde, Alan'ı aldıktan sonra ve aşağıdaki örnekteki gibi alan özelliklerini değiştirdikten sonra model sınıfını alabilirsiniz:

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

Birlikte çalışacak çözüme ihtiyaç update_or_createnasıl yaptığınızı @andreaspelme koduna göre bu çözüm geldi.

Tek değişiklik, değiştirilen alanı olarak skipyalnızca kwarg'ı skip_modified_updatesave () yöntemine geçirerek atlamayı ayarlayabilmenizdir .

Sadece yourmodelobject.modified='skip've güncelleme atlanacak!

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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.