Django auto_now i auto_now_add

272

Dla Django 1.1.

Mam to w moim models.py:

class User(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)

Podczas aktualizacji wiersza otrzymuję:

[Sun Nov 15 02:18:12 2009] [error] /home/ptarjan/projects/twitter-meme/django/db/backends/mysql/base.py:84: Warning: Column 'created' cannot be null
[Sun Nov 15 02:18:12 2009] [error]   return self.cursor.execute(query, args)

Odpowiednia część mojej bazy danych to:

  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,

Czy to jest powód do niepokoju?

Pytanie poboczne: w moim narzędziu administracyjnym te dwa pola nie są wyświetlane. Czy to jest oczekiwane?

Paul Tarjan
źródło
3
czy używałeś niestandardowego klucza podstawowego zamiast domyślnego automatycznego przyrostu int? Odkryłem, że użycie niestandardowego klucza podstawowego powoduje ten problem. W każdym razie, chyba już to rozwiązałeś. Ale błąd nadal istnieje. Tylko moje 0,02 $
tapan
3
Jeszcze jedna rzecz do przypomnienia. update()metoda nie zadzwoni, save()co oznacza, że ​​nie może modifiedautomatycznie zaktualizować pola
programista chemiczny

Odpowiedzi:

383

Każde pole z auto_nowustawionym atrybutem również odziedziczy editable=Falsei dlatego nie pojawi się w panelu administracyjnym. W przeszłości mówiono o tym, aby argumenty auto_nowi auto_now_addzniknęły, i chociaż nadal istnieją, uważam, że lepiej jest po prostu skorzystać z niestandardowej save()metody .

Tak więc, aby to działało poprawnie, odradzam używanie auto_nowlub auto_now_addzamiast tego zdefiniowanie własnej save()metody, aby upewnić się, że createdjest aktualizowana tylko wtedy, gdy idnie jest ustawiona (na przykład przy pierwszym tworzeniu elementu), i niech aktualizuje się za modifiedkażdym razem, gdy element jest zapisany.

Zrobiłem dokładnie to samo z innymi projektami, które napisałem przy użyciu Django, więc save()wyglądałoby to tak:

from django.utils import timezone

class User(models.Model):
    created     = models.DateTimeField(editable=False)
    modified    = models.DateTimeField()

    def save(self, *args, **kwargs):
        ''' On save, update timestamps '''
        if not self.id:
            self.created = timezone.now()
        self.modified = timezone.now()
        return super(User, self).save(*args, **kwargs)

Mam nadzieję że to pomoże!

Edytuj w odpowiedzi na komentarze:

Powód, dla którego po prostu trzymam się przeciążenia save()kontra poleganie na tych argumentach pola, jest dwojaki:

  1. Wspomniane wzloty i upadki z ich niezawodnością. Argumenty te są bardzo zależne od sposobu, w jaki każdy typ bazy danych, z którym Django wie, jak wchodzić w interakcje, traktuje pole znacznika daty / godziny i wydaje się, że ulega uszkodzeniu i / lub zmianie między poszczególnymi wersjami. (Uważam, że jest to impuls do wezwania do całkowitego ich usunięcia).
  2. Fakt, że działają one tylko na DateField, DateTimeField i TimeField, a dzięki tej technice możesz automatycznie wypełniać dowolny typ pola za każdym razem, gdy element jest zapisywany.
  3. Użyj django.utils.timezone.now()vs. datetime.datetime.now(), ponieważ w datetime.datetimezależności od tego zwróci obiekt rozpoznający TZ lub naiwny settings.USE_TZ.

Aby odpowiedzieć na pytanie, dlaczego OP widział błąd, nie wiem dokładnie, ale wygląda na to, że creatednawet nie jest w ogóle zaludniony, pomimo tego auto_now_add=True. Dla mnie wyróżnia się to jako błąd i podkreśla pozycję nr 1 na mojej małej liście powyżej: auto_nowi auto_now_addjest w najlepszym razie niestabilna.

jathanizm
źródło
9
Ale jakie jest źródło problemu autora? Czy auto_now_add czasami działa nieprawidłowo?
Dmitry Risenberg
5
Jestem z tobą Dmitry. Jestem ciekawy, dlaczego te dwa pola spowodowały błędy. A ja jestem nawet bardziej ciekawy, dlaczego uważasz, że napisanie własnej niestandardowej metody save () jest lepsze?
hora
45
Pisanie niestandardowych save()na każdym z moich modeli jest znacznie bardziej bolesne niż używanie auto_now(ponieważ lubię mieć te pola na wszystkich moich modelach). Dlaczego te parametry nie działają?
Paul Tarjan
3
@TM, ale wymaga to bezpośredniego manipulowania db, podczas gdy Django dąży tylko do plików models.py do zdefiniowania schematu
akaihola
11
Nie zgadzam się gwałtownie. 1) editable = False jest poprawny, nie powinieneś edytować pola, twoja baza danych musi być dokładna. 2) Istnieją różnego rodzaju przypadki brzegowe, w których save () może nie zostać wywołany, szczególnie gdy używane są niestandardowe aktualizacje SQL lub cokolwiek innego. 3) Jest to coś, w czym bazy danych są naprawdę dobre, wraz z integralnością referencyjną i tak dalej. Ufanie bazie danych, aby poprawnie ją wykonać, jest dobrym rozwiązaniem domyślnym, ponieważ mądrzejsi niż my lub ja zaprojektowaliśmy bazę danych w taki sposób.
Shayne
175

Chciałem jednak zaznaczyć, że opinia wyrażona w przyjętej odpowiedzi jest nieco przestarzała. Według najnowszych dyskusji (błędy django # 7634 i # 12785 ) auto_now i auto_now_add nigdzie się nie wybierają, a nawet jeśli przejdziesz do oryginalnej dyskusji , znajdziesz mocne argumenty przeciwko RY (jak w DRY) w niestandardowym zapisie metody

Zaproponowano lepsze rozwiązanie (niestandardowe typy pól), ale nie uzyskało ono wystarczającego tempa, aby przekształcić go w django. Możesz pisać własne w trzech wierszach (to sugestia Jacoba Kaplana-Mossa ).

from django.db import models
from django.utils import timezone


class AutoDateTimeField(models.DateTimeField):
    def pre_save(self, model_instance, add):
        return timezone.now()

#usage
created_at = models.DateField(default=timezone.now)
updated_at = models.AutoDateTimeField(default=timezone.now)
Shai Berger
źródło
1
Trzy liniowe pole niestandardowe znajduje się tutaj: link
hgcrpd
Nie sądzę, aby pole niestandardowe było naprawdę konieczne, biorąc pod uwagę, że można ustawić domyślnie wartość na żądanie (np. Timezone.now). Zobacz moją odpowiedź poniżej.
Josh
6
Jest to to samo, co auto_add robi w Django i ma to miejsce od 2010 roku: github.com/django/django/blob/1.8.4/django/db/models/fields/… . O ile nie potrzebuję dodatkowych haków w pre_save, trzymam się auto_add.
jwhitlock
1
Nie działało dla mnie z Django 1.9, więc to rozwiązanie nie działa wszędzie, ponieważ nigdy nie było dla auto_now *. Jedynym rozwiązaniem, które działa w każdym przypadku użycia (nawet w przypadku problemu z argumentem „update_fields”) jest nadpisywanie polecenia save
Danius
4
Dlaczego ustawiłeś wartość domyślną na timezone.now, ale sygnał pre_save używa datetime.datetime.now?
Bobort
32

Mówiąc o bocznym pytaniu: jeśli chcesz zobaczyć te pola w adminie (chociaż nie będziesz mógł ich edytować), możesz dodać readonly_fieldsdo swojej klasy administratora.

class SomeAdmin(ModelAdmin):
    readonly_fields = ("created","modified",)

Cóż, dotyczy to tylko najnowszych wersji Django (uważam, 1.3 i wyżej)

DataGreed
źródło
3
Ważne, aby pamiętać: należy to dodać do XxAdminklasy. Przeczytałem go zbyt szybko i próbowałem dodać do moich zajęć AdminFormlub ModelFormzajęć i nie miałem pojęcia, dlaczego nie renderują pól „tylko do odczytu”. BTW, czy istnieje możliwość posiadania prawdziwych pól „tylko do odczytu w formularzu?”
Tomasz Gandor
28

Myślę, że najłatwiejszym (i być może najbardziej eleganckim) rozwiązaniem jest wykorzystanie faktu, że można ustawić defaultna żądanie. Aby obejść specjalną obsługę auto_now przez administratora, możesz po prostu zadeklarować pole w następujący sposób:

from django.utils import timezone
date_filed = models.DateField(default=timezone.now)

Ważne jest, aby nie używać, timezone.now()ponieważ wartość domyślna nie byłaby aktualizowana (tj. Wartość domyślna jest ustawiana tylko po załadowaniu kodu). Jeśli często to robisz, możesz utworzyć niestandardowe pole. Jednak myślę, że to już dość SUCHE.

Josh
źródło
2
Wartość domyślna jest mniej więcej odpowiednikiem auto_now_add (ustaw wartość przy pierwszym zapisaniu obiektu), ale wcale nie przypomina auto_now (ustaw wartość za każdym razem, gdy obiekt jest zapisywany).
Shai Berger,
1
@ShaiBerger, myślę, że różnią się one subtelnością w ważny sposób. Doktor stwierdził subtelność: „Automatycznie ustaw pole ...; nie jest to tylko wartość domyślna, którą można zastąpić”. - docs.djangoproject.com/en/dev/ref/models/fields/…
Thomas - BeeDesk
@ Thomas-BeeDesk: Zgoda. Stąd „odpowiednik mniej lub bardziej”.
Shai Berger,
1
To rozwiązanie działa słabo, jeśli korzystasz z migracji. Przy każdym uruchomieniu makemigrationsinterpretuje wartość domyślną jako czas uruchomienia makemigrationsi dlatego uważa, że ​​wartość domyślna uległa zmianie!
nhinkle
8
@nhinkle, czy na pewno nie określasz, default=timezone.now()a nie co jest zalecane: default=timezine.now(bez nawiasów)?
Josh
18

Jeśli zmienisz klasę modelu w ten sposób:

class MyModel(models.Model):
    time = models.DateTimeField(auto_now_add=True)
    time.editable = True

To pole pojawi się na mojej stronie zmian administratora

Eric Zheng
źródło
1
Ale działa TYLKO na edycji rekordu. Kiedy tworzę nowy rekord - przekazana do dnia wartość kafelka jest ignorowana. Kiedy zmieniam ten rekord - ustawiana jest nowa wartość.
Anton Danilchenko
2
Działa, ale powinno być models.DateTimeField zamiast models.DatetimeField
matyas
2
nie powiodło się python manage.py makemigrations: KeyError: u'editable '
laoyur 10.1017
12

Na podstawie tego, co przeczytałem i moich dotychczasowych doświadczeń z Django, auto_now_add jest błędny. Zgadzam się z jthanizmem - zastępuj normalną metodę zapisu, jest czysta i wiesz, co się dzieje. Teraz, aby wyschło, utwórz abstrakcyjny model o nazwie TimeStamped:

from django.utils import timezone

class TimeStamped(models.Model):
    creation_date = models.DateTimeField(editable=False)
    last_modified = models.DateTimeField(editable=False)

    def save(self, *args, **kwargs):
        if not self.creation_date:
            self.creation_date = timezone.now()

        self.last_modified = timezone.now()
        return super(TimeStamped, self).save(*args, **kwargs)

    class Meta:
        abstract = True

A następnie, jeśli chcesz mieć model, który ma takie znaczniki czasu, po prostu podklasę:

MyNewTimeStampyModel(TimeStamped):
    field1 = ...

Jeśli chcesz, aby pola były wyświetlane w panelu administratora, po prostu usuń tę editable=Falseopcję

Edward Newell
źródło
1
Których timezone.now()używasz tutaj? Zakładam django.utils.timezone.now(), ale nie jestem pozytywny. Ponadto, dlaczego warto korzystać timezone.now()zamiast datetime.datetime.now()?
coredumperror
1
Słuszne uwagi. Dodałem instrukcję importu. Powodem użycia timezone.now()jest to, że datetime.datetime.now()jest świadomy strefy czasowej, podczas gdy strefa czasowa jest naiwna. Możesz przeczytać o tym tutaj: docs.djangoproject.com/en/dev/topics/i18n/timezones
Edward Newell
@EdwardNewell Dlaczego zdecydowałeś się ustawić datę utworzenia w zapisie zamiast default=timezone.noww polu konstruktora?
Blackeagle52
Hmm .. może po prostu o tym nie pomyślałem, to brzmi lepiej.
Edward Newell,
2
Cóż, jest przypadek, w którym last_modified nie zostanie zaktualizowany: kiedy update_fieldsarg zostanie podany, a „last_modified” nie będzie na liście, dodałbym:if 'update_fields' in kwargs and 'last_modifed' not in kwargs['update_fields']: kwargs['update_fields'].append('last_modified')
Danius
5

Czy to jest powód do niepokoju?

Nie, Django automatycznie dodaje go podczas zapisywania modeli, więc jest to oczekiwane.

Pytanie poboczne: w moim narzędziu administracyjnym te 2 pola nie są wyświetlane. Czy to jest oczekiwane?

Ponieważ pola te są dodawane automatycznie, nie są wyświetlane.

Aby dodać do powyższego, jak powiedział synack, debata na liście mailingowej django ma na celu usunięcie tego, ponieważ „nie jest dobrze zaprojektowany” i „hack”

Pisanie niestandardowego save () na każdym z moich modeli jest znacznie bardziej bolesne niż używanie auto_now

Oczywiście nie musisz pisać tego do każdego modelu. Możesz napisać do jednego modelu i odziedziczyć inne po nim.

Ale, auto_addi jak auto_now_addtam są, wolałbym ich użyć, niż samemu pisać metodę.

Lakshman Prasad
źródło
3

Potrzebowałem dziś czegoś podobnego w pracy. Domyślną wartością jest timezone.now(), ale można ją edytować zarówno w widokach administratora, jak i dziedziczących klas FormMixin, więc dla utworzonego w moim models.pykodzie poniższy kod spełniał te wymagania:

from __future__ import unicode_literals
import datetime

from django.db import models
from django.utils.functional import lazy
from django.utils.timezone import localtime, now

def get_timezone_aware_now_date():
    return localtime(now()).date()

class TestDate(models.Model):
    created = models.DateField(default=lazy(
        get_timezone_aware_now_date, datetime.date)()
    )

Bo DateTimeFieldchyba usuwam .date()z funkcji i zmieniam datetime.datena datetime.datetimelub lepiej timezone.datetime. Nie próbowałem tego DateTime, tylko z Date.

Tiphareth
źródło
2

Możesz użyć timezone.now()dla utworzonych i auto_nowzmodyfikowanych:

from django.utils import timezone
class User(models.Model):
    created = models.DateTimeField(default=timezone.now())
    modified = models.DateTimeField(auto_now=True)

Jeśli używasz niestandardowego klucza podstawowego zamiast domyślnego auto- increment int, auto_now_addspowoduje to błąd.

Oto kod domyślnego DateTimeField.pre_save Django za pomocą auto_nowi auto_now_add:

def pre_save(self, model_instance, add):
    if self.auto_now or (self.auto_now_add and add):
        value = timezone.now()
        setattr(model_instance, self.attname, value)
        return value
    else:
        return super(DateTimeField, self).pre_save(model_instance, add)

Nie jestem pewien, jaki addjest parametr . Mam nadzieję, że to coś takiego:

add = True if getattr(model_instance, 'id') else False

Nowy rekord nie będzie miał atrybutu id, więc getattr(model_instance, 'id')zwróci wartość False, co spowoduje brak ustawiania wartości w polu.

suhailvs
źródło
7
Zauważyłem, że jeśli zachowamy wartość domyślną jako timezone.now (), podczas migrowania rzeczywista data i godzina (tego momentu) są przekazywane do pliku migracji. Myślę, że powinniśmy tego unikać, ponieważ za każdym razem, gdy wywołasz makemigracje, to pole będzie miało inną wartość.
Karan Kumar
2

Jeśli chodzi o wyświetlanie administratora, zobacz tę odpowiedź .

Uwaga: auto_nowi auto_now_addsą ustawione editable=Falsedomyślnie, dlatego ma to zastosowanie.

jlovison
źródło
1

auto_now=Truenie działało dla mnie w Django 1.4.1, ale poniższy kod mnie uratował. Jest to data time świadoma strefy czasowej.

from django.utils.timezone import get_current_timezone
from datetime import datetime

class EntryVote(models.Model):
    voted_on = models.DateTimeField(auto_now=True)

    def save(self, *args, **kwargs):
        self.voted_on = datetime.now().replace(tzinfo=get_current_timezone())
        super(EntryVote, self).save(*args, **kwargs)
Ryu_hayabusa
źródło
1
class Feedback(models.Model):
   feedback = models.CharField(max_length=100)
   created = models.DateTimeField(auto_now_add=True)
   updated = models.DateTimeField(auto_now=True)

Tutaj utworzyliśmy i zaktualizowaliśmy kolumny, które po utworzeniu będą miały znacznik czasu i gdy ktoś zmodyfikuje opinię.

auto_now_add ustawi czas utworzenia instancji, natomiast auto_now ustawi czas, gdy ktoś zmodyfikuje jego opinię.

Viraj Wadate
źródło
-1

Oto odpowiedź, jeśli korzystasz z południa i chcesz ustawić domyślnie datę dodania pola do bazy danych:

Wybierz opcję 2, a następnie: datetime.datetime.now ()

Wygląda tak:

$ ./manage.py schemamigration myapp --auto
 ? The field 'User.created_date' does not have a default specified, yet is NOT NULL.
 ? Since you are adding this field, you MUST specify a default
 ? value to use for existing rows. Would you like to:
 ?  1. Quit now, and add a default to the field in models.py
 ?  2. Specify a one-off value to use for existing columns now
 ? Please select a choice: 2
 ? Please enter Python code for your one-off default value.
 ? The datetime module is available, so you can do e.g. datetime.date.today()
 >>> datetime.datetime.now()
 + Added field created_date on myapp.User
Jeff Hoye
źródło
zaktualizowane będą: Moduły datetime i django.utils.timezone są dostępne, więc możesz to zrobić np. timezone.now ()
michel.iamit
Jest to kwestionariusz, gdy w twoim modelu brakuje kluczowych danych. Jeśli poprawnie skonfigurujesz model, nigdy nie powinieneś widzieć tego monitu.
Shayne,