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?
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.
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
.
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.
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
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.
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.
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()
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.
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)
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
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, ...)
où 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