Django auto_now e auto_now_add


272

Per Django 1.1.

Ho questo nei miei modelli.py:

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

Quando aggiorno una riga ottengo:

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

La parte rilevante del mio database è:

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

Questa è motivo di preoccupazione?

Domanda a margine: nel mio strumento di amministrazione, questi due campi non vengono visualizzati. È previsto?


3
stavi usando una chiave primaria personalizzata anziché l'int di incremento automatico predefinito? Ho scoperto che l'utilizzo di una chiave primaria personalizzata causa questo problema. Comunque, credo che tu l'abbia risolto ormai. Ma il bug esiste ancora. Solo i miei 0,02 $
tapan

3
Ancora un'altra cosa da ricordare. update()il metodo non chiamerà il save()che significa che non è possibile aggiornare modifiedautomaticamente il campo
Programmatore chimico

Risposte:


383

Anche qualsiasi campo con l' auto_nowattributo impostato erediterà editable=Falsee pertanto non verrà visualizzato nel pannello di amministrazione. In passato si è parlato di far sparire gli argomenti auto_nowe auto_now_add, e sebbene esistano ancora, sento che stai meglio usando solo un metodo personalizzatosave() .

Quindi, per far funzionare correttamente questo, consiglierei di non usare auto_nowo auto_now_adde invece definire il proprio save()metodo per assicurarsi che createdsia aggiornato solo se idnon è impostato (come quando l'elemento viene creato per la prima volta), e farlo aggiornare modifiedogni volta che l'articolo è salvato.

Ho fatto esattamente la stessa cosa con altri progetti che ho scritto usando Django, e quindi il tuo save()sarebbe simile a questo:

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)

Spero che questo ti aiuti!

Modifica in risposta ai commenti:

Il motivo per cui mi limito a sovraccaricare save()piuttosto che fare affidamento su questi argomenti di campo è duplice:

  1. Gli alti e bassi di cui sopra con la loro affidabilità. Questi argomenti dipendono fortemente dal modo in cui ogni tipo di database con cui Django sa come interagire tratta un campo data / ora e sembra rompersi e / o cambiare tra ogni versione. (Che credo sia l'impulso dietro la chiamata a rimuoverli del tutto).
  2. Il fatto che funzionano solo su DateField, DateTimeField e TimeField e utilizzando questa tecnica, è possibile popolare automaticamente qualsiasi tipo di campo ogni volta che viene salvato un elemento.
  3. Usa django.utils.timezone.now()vs. datetime.datetime.now(), perché restituirà un oggetto TZ-aware o ingenuo a datetime.datetimeseconda settings.USE_TZ.

Per capire perché l'OP abbia visto l'errore, non lo so esattamente, ma sembra che creatednon sia nemmeno stato popolato, nonostante ciò auto_now_add=True. Per me si distingue come un bug e sottolinea l'articolo 1 nella mia piccola lista sopra: auto_nowe auto_now_addnella migliore delle ipotesi è traballante.


9
Ma qual è la fonte del problema dell'autore? Auto_now_add a volte funziona in modo errato?
Dmitry Risenberg,

5
Sono con te Dmitry. Sono curioso di sapere perché i due campi hanno generato errori .. E sono ancora più curioso di sapere perché pensi che sia meglio scrivere il tuo metodo save () personalizzato?
hora,

45
Scrivere un'abitudine save()su ciascuno dei miei modelli è molto più doloroso che usare il auto_now(poiché mi piace avere questi campi su tutti i miei modelli). Perché quei parametri non funzionano?
Paul Tarjan,

3
@TM, ma questo richiede di armeggiare direttamente con il tuo db mentre Django punta solo ai file models.py per definire lo schema
Akaihola,

11
Non sono d'accordo, con veemenza. 1) editable = False è corretto, non è necessario modificare il campo, il database deve essere accurato. 2) Esistono tutti i tipi di casi limite in cui il save () potrebbe non essere chiamato, in particolare quando vengono utilizzati aggiornamenti SQL personalizzati o qualsiasi altra cosa. 3) Questo è qualcosa in cui i database sono effettivamente bravi, insieme all'integrità referenziale e così via. Affidarsi al database per farlo bene è un buon default, perché le menti più intelligenti di te o di me hanno progettato il database per funzionare in questo modo.
Shayne,

175

Ma volevo sottolineare che l'opinione espressa nella risposta accettata è in qualche modo obsoleta. Secondo discussioni più recenti (django bugs # 7634 e # 12785 ), auto_now e auto_now_add non stanno andando da nessuna parte, e anche se vai alla discussione originale , troverai argomenti forti contro il RY (come in DRY) nel salvataggio personalizzato metodi.

È stata offerta una soluzione migliore (tipi di campo personalizzati), ma non ha guadagnato abbastanza slancio per trasformarlo in django. Puoi scrivere il tuo in tre righe (è il suggerimento di 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)

1
Il campo personalizzato a tre righe è qui: link
hgcrpd

Non penso che un campo personalizzato sia davvero necessario dato che puoi impostare il default su un callable (cioè timezone.now). Vedi la mia risposta qui sotto.
Josh,

6
Questa è la stessa cosa che fa auto_add in Django e dal 2010: github.com/django/django/blob/1.8.4/django/db/models/fields/… . A meno che non ho bisogno di hook aggiuntivi in ​​pre_save, sto attaccando con auto_add.
jwhitlock,

1
Non ha funzionato per me con Django 1.9, quindi questa soluzione non funziona ovunque, come non è mai stata per auto_now *. L'unica soluzione che funziona in tutti i casi d'uso (anche con il problema arg 'update_fields') è il salvataggio
prioritario

4
Perché imposti il ​​predefinito su timezone.now, ma il segnale pre_save utilizza datetime.datetime.now?
Bobort,

32

Parlando di una domanda secondaria: se vuoi vedere questi campi in admin (però, non sarai in grado di modificarlo), puoi aggiungere readonly_fieldsalla tua classe admin.

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

Bene, questo vale solo per le ultime versioni di Django (credo, 1.3 e successive)


3
Importante da notare: questo dovrebbe essere aggiunto alla XxAdminclasse. L'ho letto troppo rapidamente e ho provato ad aggiungerlo al mio AdminFormo alle ModelFormclassi e non avevo idea del perché non stessero eseguendo il rendering dei "campi di sola lettura". A proposito, c'è la possibilità di avere veri "campi di sola lettura in una forma?
Tomasz Gandor

28

Penso che la soluzione più semplice (e forse più elegante) qui sia quella di sfruttare il fatto che è possibile impostare defaultun callable. Quindi, per aggirare la gestione speciale di auto_now da parte dell'amministratore, puoi semplicemente dichiarare il campo in questo modo:

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

È importante non utilizzarlo timezone.now()poiché il valore predefinito non viene aggiornato (ovvero, il valore predefinito viene impostato solo quando viene caricato il codice). Se ti ritrovi a fare molto, potresti creare un campo personalizzato. Tuttavia, questo è abbastanza ASCIUTTO già penso.


2
Un valore predefinito è più o meno equivalente a auto_now_add (imposta il valore quando l'oggetto viene salvato per la prima volta), ma non è affatto come auto_now (imposta il valore ogni volta che l'oggetto viene salvato).
Shai Berger,

1
@ShaiBerger, penso che siano sottilmente differenti in modo importante. Il documento affermava la sottigliezza: "Imposta automaticamente il campo ...; non è solo un valore predefinito che puoi ignorare." - docs.djangoproject.com/en/dev/ref/models/fields/…
Thomas - BeeDesk

@ Thomas-BeeDesk: concordato. Quindi, "più o meno equivalente".
Shai Berger,

1
Questa soluzione funziona male se si utilizzano le migrazioni. Ogni volta che lo esegui makemigrationsinterpreta il valore predefinito come il momento in cui esegui makemigrations, e quindi pensa che il valore predefinito sia cambiato!
Pensa

8
@nink, sei sicuro di non specificare default=timezone.now()invece di ciò che viene raccomandato: default=timezine.now(nessuna parentesi)?
Josh,

18

Se si modifica la classe del modello in questo modo:

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

Quindi questo campo verrà visualizzato nella mia pagina di modifica dell'amministratore


1
Funziona SOLO sul record di modifica. Quando creo un nuovo record, il valore della tessera passato alla data ignorato. Quando cambio questo record - viene impostato un nuovo valore.
Anton Danilchenko,

2
Funziona ma dovrebbe essere modelli.DateTimeField invece di modelli.DatetimeField
matyas

2
fallito in python manage.py makemigrations: KeyError: u'editable '
laoyur

12

Sulla base di ciò che ho letto e della mia esperienza con Django finora, auto_now_add è difettoso. Sono d'accordo con il jthanismo --- scavalcare il normale metodo di salvataggio è pulito e sai cosa sta succedendo. Ora, per renderlo asciutto, crea un modello astratto chiamato 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

E poi, quando si desidera un modello con questo comportamento scadente, basta eseguire una sottoclasse:

MyNewTimeStampyModel(TimeStamped):
    field1 = ...

Se desideri che i campi vengano visualizzati in admin, rimuovi semplicemente l' editable=Falseopzione


1
Quale timezone.now()stai usando qui? Sto assumendo django.utils.timezone.now(), ma non sono positivo. Inoltre, perché usare timezone.now()anziché datetime.datetime.now()?
coredumperror,

1
Punti buoni. Ho aggiunto la dichiarazione di importazione. Il motivo da utilizzare timezone.now()è perché è a conoscenza del fuso orario, mentre il datetime.datetime.now()fuso orario è ingenuo. Puoi leggerlo qui: docs.djangoproject.com/en/dev/topics/i18n/timezones
Edward Newell,

@EdwardNewell Perché hai scelto di impostare creation_date nel salvataggio, anziché default=timezone.nowall'interno del costruttore del campo?
Blackeagle52,

Hmm .. forse non ci ho pensato, suona meglio.
Edward Newell,

2
Bene, c'è un caso in cui last_modified non verrà aggiornato: quando update_fieldsarg viene fornito e 'last_modified' non è nell'elenco, aggiungerei:if 'update_fields' in kwargs and 'last_modifed' not in kwargs['update_fields']: kwargs['update_fields'].append('last_modified')
danius

5

Questa è motivo di preoccupazione?

No, Django lo aggiunge automaticamente per te durante il salvataggio dei modelli, quindi è previsto.

Domanda a margine: nel mio strumento di amministrazione, quei 2 campi non vengono visualizzati. È previsto?

Poiché questi campi vengono aggiunti automaticamente, non vengono visualizzati.

Per aggiungere a quanto sopra, come ha detto Synack, c'è stato un dibattito sulla mailing list di django per rimuovere questo, perché "non è ben progettato" ed è "un hack"

Scrivere un save () personalizzato su ciascuno dei miei modelli è molto più doloroso che usare auto_now

Ovviamente non devi scriverlo su ogni modello. Puoi scriverlo su un modello ed ereditarne altri.

Ma, come auto_adde auto_now_addsono lì, li userei piuttosto che provare a scrivere un metodo da solo.


3

Avevo bisogno di qualcosa di simile oggi al lavoro. Valore predefinito da essere timezone.now(), ma modificabile sia nelle viste di amministrazione che di classe ereditate da FormMixin, quindi per creato nel mio models.pyil seguente codice ha soddisfatto tali requisiti:

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

Perché DateTimeField, suppongo di rimuovere il .date()dalla funzione e passare datetime.datea datetime.datetimeo meglio timezone.datetime. Non l'ho provato con DateTime, solo con Date.


2

È possibile utilizzare timezone.now()per creato e auto_nowmodificato:

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

Se si utilizza una chiave primaria personalizzata anziché quella predefinita auto- increment int, si auto_now_addverificherà un bug.

Ecco il codice del DateTimeField.pre_save predefinito di Django con auto_nowe 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)

Non sono sicuro di quale sia il parametro add. Spero che qualcosa del genere:

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

Il nuovo record non avrà attr id, quindi getattr(model_instance, 'id')restituirà False porterà a non impostare alcun valore nel campo.


7
Ho notato che se manteniamo l'impostazione predefinita come timezone.now (), quando si esegue la migrazione, la data e l'ora effettive (di questo momento) vengono passate al file delle migrazioni. Penso che dovremmo evitarlo poiché ogni volta che chiamate makemigrations questo campo avrà un valore diverso.
Karan Kumar,

2

Per quanto riguarda il display dell'amministratore, vedi questa risposta .

Nota: auto_nowe auto_now_addsono impostati di editable=Falsedefault, motivo per cui questo vale.


1

auto_now=Truenon ha funzionato per me in Django 1.4.1, ma il codice seguente mi ha salvato. È per datetime consapevole del fuso orario.

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)

1
class Feedback(models.Model):
   feedback = models.CharField(max_length=100)
   created = models.DateTimeField(auto_now_add=True)
   updated = models.DateTimeField(auto_now=True)

Qui, abbiamo creato e aggiornato colonne che avranno un timestamp quando vengono create e quando qualcuno ha modificato il feedback.

auto_now_add imposterà l'ora in cui viene creata un'istanza mentre auto_now imposterà l'ora in cui qualcuno ha modificato il proprio feedback.


-1

Ecco la risposta se stai usando south e vuoi impostare come predefinito la data in cui aggiungi il campo al database:

Scegli quindi l'opzione 2 : datetime.datetime.now ()

Somiglia a questo:

$ ./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

aggiornato questo sarà: I moduli datetime e django.utils.timezone sono disponibili, quindi puoi fare ad esempio timezone.now ()
michel.iamit il

Questo è un questionario per quando nel tuo modello mancano i dati chiave. Se hai impostato correttamente il tuo modello, non dovresti mai vedere questo messaggio.
Shayne,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.