Problemy z Django datetime (default = datetime.now ())

283

Mam poniższy model db:

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)

Dodaję nową instancję, korzystając z poniższych:

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

Mój problem: wszystkie rekordy w bazie danych mają tę samą wartość w polu daty, która jest datą pierwszej płatności. Po ponownym uruchomieniu serwera jeden rekord ma nową datę, a pozostałe rekordy są takie same jak pierwszy. Wygląda na to, że niektóre dane są buforowane, ale nie mogę znaleźć gdzie.

baza danych: mysql 5.1.25

django v1.1.1

Shamanu4
źródło
1
Czy nie jest możliwe ustawienie domyślnej funkcji takiej jak ta ?: default=datetime.now- uwaga, bez wywoływania jak w now()Nie jest standardem dla DateTimeField, ale ... przydatna anycase.
viridis

Odpowiedzi:

597

wygląda na to, że datetime.now()jest oceniane podczas definiowania modelu, a nie za każdym razem, gdy dodajesz rekord.

Django ma funkcję umożliwiającą osiągnięcie tego, co próbujesz już zrobić:

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

lub

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

Różnica między drugim przykładem a tym, co aktualnie masz, to brak nawiasów. Przechodząc datetime.nowbez nawiasów, przekazujesz aktualną funkcję, która będzie wywoływana przy każdym dodawaniu rekordu. Jeśli go zdasz datetime.now(), to właśnie oceniasz funkcję i przekazujesz jej wartość zwracaną.

Więcej informacji jest dostępnych pod numerem referencyjnym modelu Django

Carson Myers
źródło
206
ważna uwaga : użycie auto_now_add powoduje, że pole nie może być edytowane przez administratora
Jiaaro
7
Świetna odpowiedź, dzięki. Czy istnieje sposób na wyrażenie bardziej złożonego wyrażenia za pomocą datetime.now jako domyślnego? np. teraz + 30 dni (poniższe nie działa) expiry = models.DateTimeField(default=datetime.now + timedelta(days=30))
michela
26
@michela datetime.now jest funkcją i próbujesz dodać timedelta do funkcji. Musisz zdefiniować własne wywołanie zwrotne, aby ustawić wartość domyślną, na przykład def now_plus_30(): return datetime.now() + timedelta(days = 30), a następnie użyćmodels.DateTimeField(default=now_plus_30)
Carson Myers
55
Albo możesz oczywiście zrobićdefault=lambda: datetime.now()+timedelta(days=30)
Michael Mior,
34
PAMIĘTAJ , że stosując auto_now_add to nie takie same, jak przy użyciu domyślnego ponieważ pole będzie zawsze równa się (nieedytowalny) .. wiem, że to było już powiedziane, ale to nie jest tylko Metter strony „admin”
VAShhh
115

Zamiast używać datetime.now, powinieneś naprawdę używaćfrom django.utils.timezone import now

Odniesienie:

więc wybierz coś takiego:

from django.utils.timezone import now


created_date = models.DateTimeField(default=now, editable=False)
andilabs
źródło
13
To bardzo ważna uwaga! jeśli korzystasz z datetime.now, możesz uzyskać lokalną datę, która nie jest świadoma strefy czasowej. Jeśli później auto_now_addporównasz je z polem oznaczonym znacznikiem czasu (który jest świadomy strefy czasowej), możesz uzyskać błędne obliczenia z powodu różnic w erżowych strefach czasowych.
Iftah
1
Jest to zdecydowanie bardzo ważne, ale tak naprawdę samo nie odpowiada na pytanie.
Czechnology
1
zdecydowanie lepszy sposób
Carmine Tambascia
28

Z dokumentacji domyślnego pola modelu django:

Wartość domyślna dla pola. Może to być wartość lub obiekt na żądanie. Jeśli można wywołać, będzie wywoływany za każdym razem, gdy tworzony jest nowy obiekt.

Dlatego następujące powinny działać:

date = models.DateTimeField(default=datetime.now,blank=True)
mykhal
źródło
1
Jest to możliwe tylko w wersji django 1.8+
David Schumann
@DavidNathan dlaczego tak jest? Wydaje mi się, że obie opcje są dostępne od 1.4 lub wcześniej, w tym użycie opcji na żądanie default( django-chinese-docs-14.readthedocs.io/en/latest/ref/models/… )
Mark
16

David miał właściwą odpowiedź. Nawias () powoduje, że wywoływana funkcja timezone.now () jest wywoływana przy każdej ocenie modelu. Jeśli usuniesz () z timezone.now () (lub datetime.now (), jeśli używasz naiwnego obiektu datetime), aby to zrobić:

default=timezone.now

Wtedy będzie działał zgodnie z oczekiwaniami:
Nowe obiekty otrzymają aktualną datę, kiedy zostaną utworzone, ale data nie zostanie zastąpiona za każdym razem, gdy zrobisz manage.py makemigrations / migrate.

Właśnie to spotkałem. Ogromne podziękowania dla Davida.

Mika
źródło
10

datetime.now()Jest oceniana gdy klasa jest tworzona, gdy nowy rekord nie jest dodany do bazy danych.

Aby osiągnąć to, co chcesz, zdefiniuj to pole jako:

date = models.DateTimeField(auto_now_add=True)

W ten sposób datepole zostanie ustawione na bieżącą datę dla każdego nowego rekordu.

Bartosz
źródło
6

Odpowiedź na to pytanie jest w rzeczywistości błędna.

Automatyczne wypełnianie wartości (auto_now / auto_now_add nie jest takie samo jak domyślne). Domyślną wartością będzie faktycznie to, co widzi użytkownik, jeśli jest to zupełnie nowy obiekt. Zazwyczaj robię to:

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

Upewnij się, że jeśli próbujesz reprezentować to na stronie administratora, umieść go na liście „tylko do odczytu” i odwołaj się do nazwy pola

read_only = 'date'

Ponownie robię to, ponieważ mojej wartości domyślnej zwykle nie można edytować, a strony administracyjne ignorują elementy nieedytowalne, chyba że określono inaczej. Z pewnością jednak istnieje różnica między ustawieniem wartości domyślnej a wdrożeniem auto_add, który jest tutaj kluczowy. Przetestuj to!

Andrew Harris
źródło
6

datetime.now()jest oceniany raz, gdy twoja instancja jest tworzona. Spróbuj usunąć nawias, aby funkcja datetime.nowzostała zwrócona, a następnie obliczona. Miałem ten sam problem z ustawieniem wartości domyślnych dla moich DateTimeFieldsi zapisałem tutaj swoje rozwiązanie .

David
źródło
5

Z odwołania do języka Python w części Definicje funkcji :

Domyślne wartości parametrów są oceniane podczas wykonywania definicji funkcji. Oznacza to, że wyrażenie jest oceniane raz, gdy funkcja jest zdefiniowana, i że dla każdego wywołania używana jest ta sama „wstępnie obliczona” wartość.

Na szczęście Django ma sposób na robienie tego, co chcesz, jeśli użyjesz auto_nowargumentu dla DateTimeField:

date = models.DateTimeField(auto_now=True)

Zobacz dokumentację Django dla DateTimeField .

ars
źródło
0

W Django 3.0 auto_now_addwydaje się działaćauto_now

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

Bosco Oludhe
źródło