Problèmes de date et heure de Django (par défaut = datetime.now ())

284

J'ai le modèle db ci-dessous:

from datetime import datetime    

class TermPayment(models.Model):
    # I have excluded fields that are irrelevant to the question
    date = models.DateTimeField(default=datetime.now(), blank=True)

J'ajoute une nouvelle instance en utilisant ce qui suit:

tp = TermPayment.objects.create(**kwargs)

Mon problème: tous les enregistrements de la base de données ont la même valeur dans le champ date, qui est la date du premier paiement. Après le redémarrage du serveur, un enregistrement a la nouvelle date et les autres enregistrements ont le même que le premier. Il semble que certaines données soient mises en cache, mais je ne trouve pas où.

base de données: mysql 5.1.25

django v1.1.1

Shamanu4
la source
1
N'est pas possible par défaut à une fonction comme celle - ci ?: default=datetime.now- Note, sans appeler comme dans now()pas la norme pour DateTimeField, mais ... anycase à portée de main.
viridis

Réponses:

597

il semble qu'il datetime.now()soit en cours d'évaluation lorsque le modèle est défini, et non chaque fois que vous ajoutez un enregistrement.

Django a une fonctionnalité pour accomplir ce que vous essayez déjà de faire:

date = models.DateTimeField(auto_now_add=True, blank=True)

ou

date = models.DateTimeField(default=datetime.now, blank=True)

La différence entre le deuxième exemple et ce que vous avez actuellement est le manque de parenthèses. En passant datetime.nowsans les parenthèses, vous passez la fonction réelle, qui sera appelée chaque fois qu'un enregistrement est ajouté. Si vous la transmettez datetime.now(), vous évaluez simplement la fonction et lui transmettez la valeur de retour.

Plus d'informations sont disponibles sur la référence de champ de modèle de Django

Carson Myers
la source
206
note importante : l'utilisation de auto_now_add rend le champ non modifiable dans l'admin
Jiaaro
7
Excellente réponse, merci. Existe-t-il un moyen d'exprimer une expression plus complexe avec datetime.now par défaut? par exemple maintenant + 30 jours (ce qui suit ne fonctionne pas) expiry = models.DateTimeField(default=datetime.now + timedelta(days=30))
michela
26
@michela datetime.now est une fonction, et vous essayez d'ajouter un timedelta à une fonction. Vous devrez définir votre propre rappel pour définir la valeur par défaut, comme def now_plus_30(): return datetime.now() + timedelta(days = 30), puis utilisermodels.DateTimeField(default=now_plus_30)
Carson Myers
55
Ou vous pouvez bien sûr le fairedefault=lambda: datetime.now()+timedelta(days=30)
Michael Mior
34
CONNAÎTRE que l' utilisation auto_now_add est pas le même que l' aide d' un défaut , car le champ sera toujours égal à maintenant (non modifiable) .. Je sais que cela a été déjà dit, mais ce n'est pas seulement un metter de la page « admin »
VAShhh
115

Au lieu d'utiliser, datetime.nowvous devriez vraiment utiliserfrom django.utils.timezone import now

Référence:

alors optez pour quelque chose comme ça:

from django.utils.timezone import now


created_date = models.DateTimeField(default=now, editable=False)
andilabs
la source
13
Ceci est une note très importante! si vous utilisez datetime.now, vous pouvez un datetime local qui ne connaît pas le fuseau horaire. Si vous le comparez plus tard à un champ qui a été horodaté à l'aide auto_now_add(qui est conscient du fuseau horaire), vous pouvez obtenir des calculs erronés en raison de différences de fuseau horaire erronées.
Iftah
1
Ceci est certainement très important mais ne répond pas vraiment à la question par lui-même.
Czechnology
1
définitivement meilleur moyen
Carmine Tambascia
28

Dans la documentation sur le champ par défaut du modèle django:

La valeur par défaut du champ. Cela peut être une valeur ou un objet appelable. S'il est appelable, il sera appelé chaque fois qu'un nouvel objet est créé.

Par conséquent, ce qui suit devrait fonctionner:

date = models.DateTimeField(default=datetime.now,blank=True)
mykhal
la source
1
Cela n'est possible que dans django 1.8+
David Schumann
@DavidNathan pourquoi est-ce? Je pense que les deux options existent depuis 1.4 ou avant, y compris l'utilisation d'un appelable pour default( django-chinese-docs-14.readthedocs.io/en/latest/ref/models/… )
Mark
16

David avait la bonne réponse. La parenthèse () fait en sorte que le timezone.now () appelable soit appelé chaque fois que le modèle est évalué. Si vous supprimez le () de timezone.now () (ou datetime.now (), si vous utilisez l'objet datetime naïf) pour le faire juste ceci:

default=timezone.now

Ensuite, cela fonctionnera comme vous vous y attendez: les
nouveaux objets recevront la date actuelle lors de leur création, mais la date ne sera pas remplacée à chaque fois que vous faites manage.py makemigrations / migrate.

Je viens de rencontrer cela. Merci beaucoup à David.

MicahT
la source
10

Le datetime.now()est évalué lorsque la classe est créée, pas lorsqu'un nouvel enregistrement est ajouté à la base de données.

Pour réaliser ce que vous voulez, définissez ce champ comme:

date = models.DateTimeField(auto_now_add=True)

De cette façon, le datechamp sera défini à la date actuelle pour chaque nouvel enregistrement.

Bartosz
la source
6

La réponse à celle-ci est en fait fausse.

Remplissage automatique de la valeur (auto_now / auto_now_add n'est pas le même que par défaut). La valeur par défaut sera en fait ce que l'utilisateur voit s'il s'agit d'un tout nouvel objet. Ce que je fais généralement, c'est:

date = models.DateTimeField(default=datetime.now, editable=False,)

Assurez-vous, si vous essayez de représenter cela dans une page d'administration, que vous le répertoriez comme «read_only» et référencez le nom du champ

read_only = 'date'

Encore une fois, je le fais car ma valeur par défaut n'est généralement pas modifiable et les pages d'administration ignorent les éléments non modifiables, sauf indication contraire. Il y a certainement une différence cependant entre la définition d'une valeur par défaut et l'implémentation de l'auto_add qui est la clé ici. Testez-le!

Andrew Harris
la source
6

datetime.now()est évalué une fois, lorsque votre classe est instanciée. Essayez de supprimer les parenthèses pour que la fonction datetime.nowsoit renvoyée et ALORS évaluée. J'ai eu le même problème avec la définition de valeurs par défaut pour mes DateTimeFields et j'ai rédigé ma solution ici .

David
la source
5

Dans la référence du langage Python, sous Définitions des fonctions :

Les valeurs des paramètres par défaut sont évaluées lors de l'exécution de la définition de fonction. Cela signifie que l'expression est évaluée une fois, lorsque la fonction est définie, et que la même valeur «pré-calculée» est utilisée pour chaque appel.

Heureusement, Django a un moyen de faire ce que vous voulez, si vous utilisez l' auto_nowargument pour DateTimeField:

date = models.DateTimeField(auto_now=True)

Voir les documents Django pour DateTimeField .

ars
la source
0

Dans Django 3.0 auto_now_addsemble fonctionner avecauto_now

reg_date=models.DateField(auto_now=True,blank=True)

Bosco Oludhe
la source