web-dev-qa-db-fra.com

Désactiver temporairement auto_now/auto_now_add

J'ai un modèle comme celui-ci:

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

Je souhaite écraser les deux champs de date de certaines instances de modèle (utilisées lors de la migration de données). La solution actuelle ressemble à ceci:

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

Y a-t-il une meilleure solution?

80
jedie

J'ai récemment fait face à cette situation lors du test de mon application. Je devais "forcer" un horodatage expiré. Dans mon cas, j'ai fait le tour en utilisant une mise à jour de queryset. Comme ça:

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

Vous ne pouvez pas vraiment désactiver auto_now/auto_now_add d'une autre manière que vous ne le faites déjà. Si vous avez besoin de flexibilité pour changer ces valeurs, auto_now/auto_now_add n'est pas le meilleur choix. Il est souvent plus souple d'utiliser default et/ou de remplacer la méthode save() pour effectuer une manipulation juste avant que l'objet ne soit enregistré.

En utilisant default et une méthode save() surchargée, un moyen de résoudre votre problème serait de définir votre modèle comme suit:

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)

Dans votre code, lorsque vous souhaitez ignorer la modification automatique lastupdatetime, utilisez simplement

new_entry.save(skip_lastupdatetime=True)

Si votre objet est enregistré dans l'interface d'administration ou à d'autres endroits, save () sera appelé sans l'argument skip_lastupdatetime, et il se comportera exactement comme auparavant avec auto_now.

50
andreaspelme

J'ai utilisé la suggestion faite par le demandeur et créé quelques fonctions. Voici le cas d'utilisation:

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

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

Voici la mise en œuvre:

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)

Des fonctions similaires peuvent être créées pour les réactiver.

21
Frank Henard

Vous pouvez également utiliser le paramètre update_fields pour save() si vous savez à quels champs vous souhaitez le limiter et si vous excluez les champs auto_now/auto_now_add:

https://docs.djangoproject.com/fr/stable/ref/models/instances/#specifying-which-fields-to-save

13
Apollo Data

Je suis allé à la manière du gestionnaire de contexte pour la réutilisabilité.

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

Utilisez comme si:

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

Boom.

13
soulseekah

Pour ceux qui regardent cela lorsqu'ils écrivent des tests, il existe une bibliothèque python appelée freezegun qui vous permet de simuler le temps. Ainsi, lorsque le code auto_now_add est exécuté, il obtient le temps que vous souhaitez réellement. Alors:

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

Il peut également être utilisé comme décorateur - voir le lien ci-dessus pour les documents de base.

6
Hamish Downer

Je devais désactiver auto_now pour un champ DateTime pendant une migration et je pouvais le faire.

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

Je suis en retard pour la soirée, mais, à l'instar de plusieurs autres réponses, c'est une solution que j'ai utilisée lors d'une migration de base de données. La différence par rapport aux autres réponses est que cela désactive les champs all auto_now pour le modèle en supposant qu'il n'y a vraiment aucune raison d'avoir plus d'un tel champ.

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

Ensuite, pour l'utiliser, vous pouvez simplement faire:

disable_auto_now_fields(Document, Event, ...)

Et il passera en revue tous vos champs auto_now et auto_now_add pour toutes les classes de modèle que vous transmettez.

1
mlissner

J'avais besoin d'une solution qui fonctionnera avec update_or_create, je suis venu à cette solution basée sur le code @andreaspelme.

Le seul changement est que vous pouvez définir le saut en définissant le champ modifié sur skip et pas seulement en transmettant la méthode kwarg skip_modified_update à la méthode save ().

Juste yourmodelobject.modified='skip' et la mise à jour sera ignorée!

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

copie de Django - Models.DateTimeField - Changement dynamique de la valeur auto_now_add

Eh bien, j'ai passé cet après-midi à découvrir et le premier problème est de savoir comment récupérer l'objet de modèle et où dans le code. Je suis dans restframework dans serializer.py, par exemple dans __init__ du sérialiseur, il ne peut pas encore avoir le modèle. Maintenant, dans to_internal_value, vous pouvez obtenir la classe de modèle, après avoir obtenu le champ et après avoir modifié les propriétés du champ comme dans cet exemple: 

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

Vous pouvez remplacer auto_now_add sans code spécial.

Je suis tombé sur cette question lorsque j'ai essayé de créer un objet avec une date particulière:

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

publication_date = models.DateField(auto_now_add=True).

Alors voici ce que j'ai fait:

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

Cela a réussi à remplacer auto_now_add.

En tant que solution à plus long terme, il est préférable de remplacer la méthode save: https://code.djangoproject.com/ticket/16583

0
Pavel Vergeev