Django auto_now und auto_now_add

272

Für Django 1.1.

Ich habe dies in meinen models.py:

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

Beim Aktualisieren einer Zeile erhalte ich:

[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)

Der relevante Teil meiner Datenbank ist:

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

Gibt dies Anlass zur Sorge?

Nebenfrage: In meinem Admin-Tool werden diese beiden Felder nicht angezeigt. Wird das erwartet?

Paul Tarjan
quelle
3
Haben Sie einen benutzerdefinierten Primärschlüssel anstelle des Standard-Auto-Inkrement-Int verwendet? Ich habe festgestellt, dass die Verwendung eines benutzerdefinierten Primärschlüssels dieses Problem verursacht. Wie auch immer, ich denke du hast es inzwischen gelöst. Aber der Fehler existiert immer noch. Nur meine 0.02 $
tapan
3
Nur noch eine Sache zur Erinnerung. update()Methode wird nicht aufgerufen, save()was bedeutet, dass das modifiedFeld nicht automatisch aktualisiert werden konnte
Chemical Programmer

Antworten:

383

Jedes Feld mit dem auto_nowAttributsatz erbt ebenfalls editable=Falseund wird daher nicht im Admin-Bereich angezeigt. Es hat in der Vergangenheit die Rede gewesen über die Herstellung auto_nowund auto_now_addArgumente gehen weg, und obwohl sie noch existieren, fühle ich Sie besser dran , nur unter Verwendung eines kundenspezifischen save()Verfahren .

Damit dies ordnungsgemäß funktioniert, würde ich empfehlen, keine auto_nowoder zu verwenden auto_now_addund stattdessen Ihre eigene save()Methode zu definieren , um sicherzustellen, dass diese creatednur aktualisiert wird, wenn sie idnicht festgelegt ist (z. B. wenn das Element zum ersten Mal erstellt wird), und sie modifiedjedes Mal aktualisieren zu lassen, wenn das Element erstellt wird ist gespeichert.

Ich habe genau das Gleiche mit anderen Projekten gemacht, die ich mit Django geschrieben habe, und so save()würde Ihr aussehen:

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)

Hoffe das hilft!

Als Antwort auf Kommentare bearbeiten:

Der Grund, warum ich mich nur an Überladung save()halte und mich nicht auf diese Feldargumente verlasse, ist zweierlei:

  1. Die oben genannten Höhen und Tiefen mit ihrer Zuverlässigkeit. Diese Argumente hängen stark von der Art und Weise ab, wie jeder Datenbanktyp, mit dem Django interagieren kann, ein Datums- / Zeitstempelfeld behandelt und zwischen den einzelnen Versionen zu brechen und / oder sich zu ändern scheint. (Was meiner Meinung nach der Anstoß für den Aufruf ist, sie insgesamt entfernen zu lassen).
  2. Die Tatsache, dass sie nur mit DateField, DateTimeField und TimeField funktionieren und mit dieser Technik können Sie jeden Feldtyp jedes Mal automatisch füllen, wenn ein Element gespeichert wird.
  3. Verwenden Sie django.utils.timezone.now()vs. datetime.datetime.now(), da datetime.datetimeabhängig davon ein TZ-fähiges oder naives Objekt zurückgegeben wird settings.USE_TZ.

Um zu klären, warum das OP den Fehler gesehen hat, weiß ich nicht genau, aber es sieht so aus, als wäre createdes überhaupt nicht besetzt auto_now_add=True. Für mich ist es ein Fehler und unterstreicht Punkt 1 in meiner kleinen Liste oben: auto_nowund auto_now_addsind bestenfalls schuppig.

Nathanismus
quelle
9
Aber woher kommt das Problem des Autors? Funktioniert auto_now_add manchmal nicht richtig?
Dmitry Risenberg
5
Ich bin bei dir, Dmitry. Ich bin neugierig, warum die beiden Felder Fehler ausgelöst haben. Und ich bin noch neugieriger, warum Sie denken, dass es besser ist, eine eigene benutzerdefinierte save () -Methode zu schreiben?
Hora
45
Das Schreiben eines Brauchs save()für jedes meiner Modelle ist viel schmerzhafter als das Verwenden von auto_now(da ich diese Felder gerne für alle meine Modelle habe). Warum funktionieren diese Parameter nicht?
Paul Tarjan
3
@ TM, aber das erfordert direktes Spielen mit Ihrer Datenbank, während Django nur auf models.py-Dateien abzielt, um das Schema zu definieren
akaihola
11
Ich bin vehement anderer Meinung. 1) editierbar = Falsch ist korrekt. Sie sollten das Feld nicht bearbeiten. Ihre Datenbank muss korrekt sein. 2) Es gibt alle möglichen Randfälle, in denen save () möglicherweise nicht aufgerufen wird, insbesondere wenn benutzerdefinierte SQL-Updates oder was auch immer verwendet werden. 3) Dies ist etwas, in dem Datenbanken wirklich gut sind, zusammen mit der referenziellen Integrität und so weiter. Das Vertrauen in die Datenbank, um es richtig zu machen, ist eine gute Standardeinstellung, da klügere Köpfe als Sie oder ich die Datenbank so konzipiert haben, dass sie auf diese Weise funktioniert.
Shayne
175

Ich wollte jedoch darauf hinweisen, dass die in der akzeptierten Antwort geäußerte Meinung etwas veraltet ist. Laut neueren Diskussionen (Django-Fehler # 7634 und # 12785 ) gehen auto_now und auto_now_add nirgendwo hin, und selbst wenn Sie zur ursprünglichen Diskussion gehen , finden Sie beim benutzerdefinierten Speichern starke Argumente gegen den RY (wie in DRY) Methoden.

Es wurde eine bessere Lösung angeboten (benutzerdefinierte Feldtypen), die jedoch nicht genügend Schwung erhielt, um es in Django zu schaffen. Sie können Ihre eigenen in drei Zeilen schreiben (es ist der Vorschlag von Jacob Kaplan-Moss ).

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
quelle
1
Das dreizeilige benutzerdefinierte Feld ist hier: link
hgcrpd
Ich denke nicht, dass ein benutzerdefiniertes Feld wirklich notwendig ist, da Sie die Standardeinstellung auf eine aufrufbare (dh timezone.now) setzen können. Siehe meine Antwort unten.
Josh
5
Dies ist das gleiche, was auto_add in Django macht und seit 2010: github.com/django/django/blob/1.8.4/django/db/models/fields/… . Sofern ich keine zusätzlichen Hooks in pre_save benötige, bleibe ich bei auto_add.
Jwhitlock
1
Hat bei mir mit Django 1.9 nicht funktioniert, daher funktioniert diese Lösung nicht überall, wie es bei auto_now * nie der Fall war. Die einzige Lösung, die in jedem Anwendungsfall funktioniert (auch mit dem Argumentproblem 'update_fields'), ist das Überschreiben von save
danius
4
Warum setzen Sie den Standardwert auf timezone.now, aber das pre_save-Signal verwendet datetime.datetime.now?
Bobort
32

Apropos Nebenfrage: Wenn Sie diese Felder in admin sehen möchten (Sie können sie jedoch nicht bearbeiten), können Sie readonly_fieldssie Ihrer admin-Klasse hinzufügen .

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

Nun, dies gilt nur für die neuesten Django-Versionen (ich glaube, 1.3 und höher)

DataGreed
quelle
3
Wichtig zu beachten: Dies sollte der XxAdminKlasse hinzugefügt werden . Ich habe es zu schnell gelesen und versucht, es meiner AdminFormoder meinen ModelFormKlassen hinzuzufügen, und hatte keine Ahnung, warum sie die "schreibgeschützten Felder" nicht renderten. Übrigens, gibt es eine Möglichkeit, echte "Nur-Lese-Felder" in einer Form zu haben?
Tomasz Gandor
28

Ich denke, die einfachste (und vielleicht eleganteste) Lösung besteht darin, die Tatsache zu nutzen, dass Sie defaulteinen Callable einstellen können . Um die spezielle Behandlung von auto_now durch den Administrator zu umgehen, können Sie das Feld einfach wie folgt deklarieren:

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

Es ist wichtig, dass Sie nicht verwenden, timezone.now()da der Standardwert nicht aktualisiert wird (dh der Standardwert wird nur festgelegt, wenn der Code geladen wird). Wenn Sie dies häufig tun, können Sie ein benutzerdefiniertes Feld erstellen. Dies ist jedoch schon ziemlich trocken, denke ich.

Josh
quelle
2
Ein Standardwert entspricht mehr oder weniger auto_now_add (festgelegter Wert beim ersten Speichern des Objekts), entspricht jedoch keinesfalls auto_now (festgelegter Wert bei jedem Speichern des Objekts).
Shai Berger
1
@ShaiBerger, ich denke, sie unterscheiden sich in einer wichtigen Hinsicht subtil. Das Dokument erklärte die Subtilität: "Setzen Sie das Feld automatisch ...; es ist nicht nur ein Standardwert, den Sie überschreiben können." - docs.djangoproject.com/de/dev/ref/models/fields/…
Thomas - BeeDesk
@ Thomas-BeeDesk: Einverstanden. Daher "mehr oder weniger äquivalent".
Shai Berger
1
Diese Lösung funktioniert schlecht, wenn Sie Migrationen verwenden. Jedes Mal, makemigrationswenn Sie es ausführen makemigrations, interpretiert es den Standard als den Zeitpunkt, zu dem Sie es ausführen , und denkt daher, dass sich der Standardwert geändert hat!
Nhinkle
8
@nhinkle, bist du sicher, dass du nicht spezifizierst, default=timezone.now()sondern was empfohlen wird: default=timezine.now(keine Klammern)?
Josh
18

Wenn Sie Ihre Modellklasse folgendermaßen ändern:

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

Dann wird dieses Feld auf meiner Admin-Änderungsseite angezeigt

Eric Zheng
quelle
1
Es funktioniert jedoch NUR beim Bearbeiten von Datensätzen. Wenn ich einen neuen Datensatz erstelle, wird der bis zum Datum übergebene Kachelwert ignoriert. Wenn ich diesen Datensatz ändere, wird ein neuer Wert festgelegt.
Anton Danilchenko
2
Funktioniert, aber es sollten Modelle sein.DateTimeField anstelle von Modellen.DatetimeField
matyas
2
fehlgeschlagen in python manage.py makemigrations: KeyError: u'editable '
laoyur
12

Basierend auf dem, was ich gelesen habe und meiner bisherigen Erfahrung mit Django ist auto_now_add fehlerhaft. Ich bin mit jthanism einverstanden - überschreibe die normale Speichermethode, sie ist sauber und du weißt, was passiert. Erstellen Sie zum Trocknen ein abstraktes Modell mit dem Namen 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

Und wenn Sie ein Modell mit diesem zeitstempeligen Verhalten wünschen, gehen Sie einfach in die Unterklasse:

MyNewTimeStampyModel(TimeStamped):
    field1 = ...

Wenn die Felder in admin angezeigt werden sollen, entfernen Sie einfach die editable=FalseOption

Edward Newell
quelle
1
Welches benutzt timezone.now()du hier? Ich nehme an django.utils.timezone.now(), aber ich bin nicht positiv. Warum auch timezone.now()lieber verwenden als datetime.datetime.now()?
coredumperror
1
Gute Argumente. Ich habe die Importanweisung hinzugefügt. Der Grund für die Verwendung timezone.now()ist, dass es zeitzonenbewusst ist, während datetime.datetime.now()es zeitzonennaiv ist. Sie können darüber hier lesen: docs.djangoproject.com/de/dev/topics/i18n/timezones
Edward Newell
@EdwardNewell Warum haben Sie das Festlegen des Erstellungsdatums beim Speichern anstelle des default=timezone.nowFeldkonstruktors gewählt?
Blackeagle52
Hmm .. vielleicht habe ich einfach nicht daran gedacht, das klingt besser.
Edward Newell
2
Nun, es gibt einen Fall, in dem last_modified nicht aktualisiert wird: Wenn update_fieldsarg angegeben ist und 'last_modified' nicht in der Liste enthalten ist, würde ich hinzufügen:if 'update_fields' in kwargs and 'last_modifed' not in kwargs['update_fields']: kwargs['update_fields'].append('last_modified')
danius
5

Gibt dies Anlass zur Sorge?

Nein, Django fügt es automatisch für Sie hinzu, während Sie die Modelle speichern. Es wird also erwartet.

Nebenfrage: In meinem Admin-Tool werden diese beiden Felder nicht angezeigt. Wird das erwartet?

Da diese Felder automatisch hinzugefügt werden, werden sie nicht angezeigt.

Wie Synack sagte, gab es eine Debatte über die Django-Mailingliste, um dies zu entfernen, da sie "nicht gut gestaltet" und "ein Hack" ist.

Das Schreiben eines benutzerdefinierten save () für jedes meiner Modelle ist viel schmerzhafter als die Verwendung von auto_now

Natürlich müssen Sie es nicht auf jedes Modell schreiben. Sie können es in ein Modell schreiben und andere davon erben.

Aber, wie auto_addund auto_now_addes gibt, würde ich sie verwendet anstatt zu versuchen , eine Methode selbst zu schreiben.

Lakshman Prasad
quelle
3

Ich brauchte heute bei der Arbeit etwas Ähnliches. Der Standardwert muss sein timezone.now(), kann jedoch sowohl in Administrator- als auch in Klassenansichten bearbeitet werden , die von erben. Daher FormMixinhat der in meinem models.pyCode erstellte folgende Code diese Anforderungen erfüllt:

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

Denn DateTimeFieldich denke, entfernen Sie die .date()aus der Funktion und wechseln Sie datetime.datezu datetime.datetimeoder besser timezone.datetime. Ich habe es nicht mit versucht DateTime, nur mit Date.

Tiphareth
quelle
2

Sie können timezone.now()zum Erstellen und auto_nowÄndern verwenden:

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

Wenn Sie anstelle des Standardschlüssels einen benutzerdefinierten Primärschlüssel verwenden auto- increment int, auto_now_addführt dies zu einem Fehler.

Hier ist der Code von Djangos Standard DateTimeField.pre_save mit auto_nowund 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)

Ich bin nicht sicher, was der Parameter addist. Ich hoffe es wird so etwas wie:

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

Der neue Datensatz hat kein attr. Wenn Sie idalso getattr(model_instance, 'id')False zurückgeben, wird kein Wert im Feld festgelegt.

suhailvs
quelle
7
Ich habe festgestellt, dass, wenn wir die Standardeinstellung als timezone.now () beibehalten, bei Migrationen das tatsächliche Datum und die Uhrzeit (dieses Augenblicks) an die Migrationsdatei übergeben werden. Ich denke, wir sollten dies vermeiden, da dieses Feld jedes Mal, wenn Sie Makemigrationen aufrufen, einen anderen Wert hat.
Karan Kumar
2

Informationen zu Ihrer Admin-Anzeige finden Sie in dieser Antwort .

Hinweis: auto_nowund auto_now_addsind editable=Falsestandardmäßig eingestellt, weshalb dies gilt.

jlovison
quelle
1

auto_now=Truehat in Django 1.4.1 nicht für mich funktioniert, aber der folgende Code hat mich gerettet. Es ist für zeitzonenbewusste datetime.

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
quelle
1
class Feedback(models.Model):
   feedback = models.CharField(max_length=100)
   created = models.DateTimeField(auto_now_add=True)
   updated = models.DateTimeField(auto_now=True)

Hier haben wir Spalten erstellt und aktualisiert, die beim Erstellen und beim Ändern des Feedbacks einen Zeitstempel haben.

auto_now_add legt die Zeit fest, zu der eine Instanz erstellt wird, während auto_now die Zeit festlegt , zu der jemand sein Feedback geändert hat.

Viraj Wadate
quelle
-1

Hier ist die Antwort, wenn Sie South verwenden und standardmäßig das Datum festlegen möchten, an dem Sie das Feld zur Datenbank hinzufügen:

Wählen Sie Option 2 und dann: datetime.datetime.now ()

Sieht aus wie das:

$ ./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
quelle
Dies wird aktualisiert: Die Module datetime und django.utils.timezone sind verfügbar, sodass Sie z. B. timezone.now ()
michel.iamit
Dies ist ein Fragebogen, wenn in Ihrem Modell Schlüsseldaten fehlen. Wenn Sie Ihr Modell richtig eingerichtet haben, sollte diese Eingabeaufforderung niemals angezeigt werden müssen.
Shayne