Django auto_now y auto_now_add

272

Para Django 1.1.

Tengo esto en mis models.py:

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

Al actualizar una fila obtengo:

[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 relevante de mi base de datos es:

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

¿Es esto motivo de preocupación?

Pregunta secundaria: en mi herramienta de administración, esos dos campos no se muestran. ¿Es eso esperado?

Paul Tarjan
fuente
3
¿estaba utilizando una clave primaria personalizada en lugar del incremento automático predeterminado int? Descubrí que usar una clave primaria personalizada causa este problema. De todos modos, supongo que ya lo has resuelto. Pero el error aún existe. Solo mi 0.02 $
tapan
3
Solo una cosa más para recordar. update()método no llamará save()lo que significa que no ha podido actualizar modifiedel campo automáticamente
Química programador

Respuestas:

383

Cualquier campo con el auto_nowconjunto de atributos también heredará editable=Falsey, por lo tanto, no aparecerá en el panel de administración. Se ha hablado en el pasado sobre hacer que los argumentos auto_nowy auto_now_adddesaparezcan, y aunque todavía existen, creo que es mejor que solo use un método personalizadosave() .

Por lo tanto, para que esto funcione correctamente, recomendaría no usar auto_nowo auto_now_addy, en su lugar, definir su propio save()método para asegurarse de que createdsolo se actualice si idno está configurado (como cuando se creó el elemento por primera vez), y que se actualice modifiedcada vez que el elemento está guardado

He hecho exactamente lo mismo con otros proyectos que he escrito usando Django, y así save()se vería así:

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)

¡Espero que esto ayude!

Editar en respuesta a los comentarios:

La razón por la que me quedo con la sobrecarga en lugar de save()depender de estos argumentos de campo es doble:

  1. Los altibajos mencionados anteriormente con su fiabilidad. Estos argumentos dependen en gran medida de la forma en que cada tipo de base de datos con la que Django sabe interactuar trata un campo de marca de fecha / hora, y parece romperse y / o cambiar entre cada lanzamiento. (Lo que creo es el ímpetu detrás de la llamada para que se eliminen por completo).
  2. El hecho de que solo funcionan en DateField, DateTimeField y TimeField, y al usar esta técnica, puede completar automáticamente cualquier tipo de campo cada vez que se guarda un elemento.
  3. Use django.utils.timezone.now()vs. datetime.datetime.now(), porque devolverá un datetime.datetimeobjeto ingenuo o consciente de TZ dependiendo de settings.USE_TZ.

Para abordar por qué el OP vio el error, no sé exactamente, pero parece que createdni siquiera se está poblando, a pesar de haberlo hecho auto_now_add=True. Para mí, se destaca como un error y subraya el elemento n. ° 1 en mi pequeña lista anterior: auto_nowy auto_now_adden el mejor de los casos es escamoso.

jathanism
fuente
9
Pero, ¿cuál es la fuente del problema del autor? ¿Auto_now_add a veces funciona incorrectamente?
Dmitry Risenberg el
55
Estoy contigo Dmitry. Tengo curiosidad por saber por qué los dos campos arrojaron errores. ¿Y tengo más curiosidad por saber por qué crees que es mejor escribir tu propio método save () personalizado?
hora
45
Escribir una costumbre save()en cada uno de mis modelos es mucho más difícil que usar el auto_now(ya que me gusta tener estos campos en todos mis modelos). ¿Por qué no funcionan esos params?
Paul Tarjan
3
@ TM, pero eso requiere jugar directamente con su base de datos, mientras que Django solo busca archivos models.py para definir el esquema
akaihola
11
No estoy de acuerdo, con vehemencia. 1) editable = False es correcto, no debe editar el campo, su base de datos debe ser precisa. 2) Hay todo tipo de casos extremos en los que no se puede llamar a save (), especialmente cuando se utilizan actualizaciones SQL personalizadas o lo que sea que se esté utilizando. 3) Esto es algo en lo que las bases de datos son realmente buenas, junto con la integridad referencial, etc. Confiar en la base de datos para hacerlo bien es un buen valor predeterminado, porque las mentes más inteligentes que usted o yo hemos diseñado la base de datos para que funcione de esta manera.
Shayne
175

Pero quería señalar que la opinión expresada en la respuesta aceptada está algo desactualizada. De acuerdo con las discusiones más recientes (errores de django # 7634 y # 12785 ), auto_now y auto_now_add no irán a ninguna parte, e incluso si va a la discusión original , encontrará argumentos sólidos contra el RY (como en DRY) en el guardado personalizado métodos.

Se ha ofrecido una mejor solución (tipos de campo personalizados), pero no obtuvo el impulso suficiente para convertirse en django. Puedes escribir el tuyo en tres líneas (es la sugerencia de 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
fuente
1
El campo personalizado de tres líneas está aquí: enlace
hgcrpd
No creo que sea realmente necesario un campo personalizado dado que puede establecer el valor predeterminado en invocable (es decir, timezone.now). Vea mi respuesta a continuación.
Josh
66
Esto es lo mismo que auto_add hace en Django, y lo ha hecho desde 2010: github.com/django/django/blob/1.8.4/django/db/models/fields/… . A menos que necesite ganchos adicionales en pre_save, me quedo con auto_add.
jwhitlock
1
No funcionó para mí con Django 1.9, por lo que esta solución no funciona en todas partes, ya que nunca fue para auto_now *. La única solución que funciona en todos los casos de uso (incluso con el problema arg 'update_fields') es anular guardar
danius
44
¿Por qué establece el valor predeterminado en timezone.now, pero la señal pre_save está usando datetime.datetime.now?
Bobort
32

Hablando de una pregunta secundaria: si desea ver estos campos en admin (sin embargo, no podrá editarlo), puede agregar readonly_fieldsa su clase de administrador.

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

Bueno, esto se aplica solo a las últimas versiones de Django (creo, 1.3 y superior)

DataGreed
fuente
3
Importante tener en cuenta: esto debe agregarse a la XxAdminclase. Lo leí demasiado rápido e intenté agregarlo a mis clases AdminFormo ModelFormno tenía idea de por qué no mostraban los "campos de solo lectura". Por cierto, ¿existe la posibilidad de tener verdaderos "campos de solo lectura en un formulario?"
Tomasz Gandor
28

Creo que la solución más fácil (y tal vez la más elegante) aquí es aprovechar el hecho de que puede establecer defaultuna opción invocable. Entonces, para evitar el manejo especial del administrador de auto_now, puede declarar el campo de esta manera:

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

Es importante que no lo use timezone.now()ya que el valor predeterminado no se actualizaría (es decir, el valor predeterminado se establece solo cuando se carga el código). Si te encuentras haciendo esto mucho, puedes crear un campo personalizado. Sin embargo, esto es bastante SECO ya creo.

Josh
fuente
2
Un valor predeterminado es más o menos equivalente a auto_now_add (establecer el valor cuando el objeto se guarda por primera vez), pero no es en absoluto como auto_now (establecer el valor cada vez que se guarda el objeto).
Shai Berger
1
@ShaiBerger, creo que son muy diferentes de una manera importante. El documento declaró la sutileza: "Establecer automáticamente el campo ...; no es solo un valor predeterminado que puede anular". - docs.djangoproject.com/en/dev/ref/models/fields/…
Thomas - BeeDesk
@ Thomas-BeeDesk: De acuerdo. Por lo tanto, "más o menos equivalente".
Shai Berger
1
Esta solución funciona mal si está utilizando migraciones. Cada vez que lo ejecuta makemigrations, interpreta el valor predeterminado como el momento en que lo ejecuta makemigrationsy, por lo tanto, piensa que el valor predeterminado ha cambiado.
nhinkle
8
@nhinkle, ¿estás seguro de que no estás especificando en default=timezone.now()lugar de lo que se recomienda: default=timezine.now(sin paréntesis)?
Josh
18

Si modifica su clase de modelo así:

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

Entonces este campo aparecerá en mi página de cambio de administrador

Eric Zheng
fuente
1
Pero funciona SOLO en el registro de edición. Cuando creo un nuevo registro, el valor del mosaico pasado a la fecha se ignora. Cuando cambio este registro, se establece un nuevo valor.
Anton Danilchenko
2
Funciona, pero debe ser modelos. DateTimeField en lugar de modelos
DatetimeField
2
falló en python manage.py makemigrations: KeyError: u'editable '
laoyur
12

Según lo que he leído y mi experiencia con Django hasta ahora, auto_now_add tiene errores. Estoy de acuerdo con jthanism --- anule el método normal de guardar, está limpio y sabe lo que está sucediendo. Ahora, para hacerlo seco, cree un modelo abstracto llamado 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

Y luego, cuando desee un modelo que tenga este comportamiento de marca de tiempo, simplemente subclase:

MyNewTimeStampyModel(TimeStamped):
    field1 = ...

Si desea que los campos se muestren en admin, simplemente elimine la editable=Falseopción

Edward Newell
fuente
1
¿Qué timezone.now()estás usando aquí? Asumo django.utils.timezone.now(), pero no soy positivo. Además, ¿por qué usar en timezone.now()lugar de datetime.datetime.now()?
coredumperror
1
Buenos puntos. Agregué la declaración de importación. La razón para usar timezone.now()es porque es consciente de la zona horaria, mientras que datetime.datetime.now()es ingenua. Puede leer sobre esto aquí: docs.djangoproject.com/en/dev/topics/i18n/timezones
Edward Newell
@EdwardNewell ¿Por qué elegiste para configurar creation_date en guardar, en lugar de default=timezone.nowdentro del constructor de campo?
Blackeagle52
Hmm ... tal vez no lo pensé, eso suena mejor.
Edward Newell
2
Bueno, hay un caso en el que last_modified no se actualizará: cuando update_fieldsse proporciona arg y 'last_modified' no está en la lista, agregaría:if 'update_fields' in kwargs and 'last_modifed' not in kwargs['update_fields']: kwargs['update_fields'].append('last_modified')
danius
5

¿Es esto motivo de preocupación?

No, Django lo agrega automáticamente mientras guarda los modelos, por lo que se espera.

Pregunta secundaria: en mi herramienta de administración, esos 2 campos no se muestran. ¿Es eso esperado?

Como estos campos se agregan automáticamente, no se muestran.

Para agregar a lo anterior, como dijo synack, ha habido un debate sobre la lista de correo de django para eliminar esto, porque "no está bien diseñado" y es "un truco"

Escribir un save () personalizado en cada uno de mis modelos es mucho más doloroso que usar el auto_now

Obviamente no tienes que escribirlo en cada modelo. Puede escribirlo en un modelo y heredar otros de él.

Pero, como auto_addy auto_now_addestán allí, los usaría en lugar de tratar de escribir un método yo mismo.

Lakshman Prasad
fuente
3

Necesitaba algo similar hoy en el trabajo. El valor predeterminado es timezone.now(), pero editable tanto en vistas de administrador como de clase heredadas FormMixin, por lo que para mi creado, models.pyel siguiente código cumplía esos requisitos:

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

Para DateTimeField, supongo que eliminar el .date()de la función y cambiar datetime.datea datetime.datetimeo mejor timezone.datetime. No lo he probado con DateTime, solo con Date.

Tiphareth
fuente
2

Puede usar timezone.now()para creado y auto_nowpara modificado:

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

Si está utilizando una clave primaria personalizada en lugar de la predeterminada auto- increment int, auto_now_addprovocará un error.

Aquí está el código del DateTimeField.pre_save predeterminado de Django con auto_nowy 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)

No estoy seguro de cuál es el parámetro add. Espero que sea algo como:

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

El nuevo registro no tendrá atributo id, por getattr(model_instance, 'id')lo que devolverá False conducirá a no establecer ningún valor en el campo.

suhailvs
fuente
77
Noté que si mantenemos el valor predeterminado como timezone.now (), cuando realiza migraciones, la fecha y hora reales (de este momento) se pasan al archivo de migraciones. Creo que deberíamos evitar esto, ya que cada vez que llame a makemigrations este campo tendrá un valor diferente.
Karan Kumar
2

En cuanto a su pantalla de administrador, vea esta respuesta .

Nota: auto_nowy auto_now_addestán configurados editable=Falsede manera predeterminada, razón por la cual esto se aplica.

jlovison
fuente
1

auto_now=Trueno funcionó para mí en Django 1.4.1, pero el siguiente código me salvó. Es para datetime consciente de la zona horaria.

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

Aquí, hemos creado y actualizado columnas que tendrán una marca de tiempo cuando se creen y cuando alguien modifique los comentarios.

auto_now_add establecerá la hora cuando se crea una instancia, mientras que auto_now establecerá la hora en que alguien modificó sus comentarios.

Viraj Wadate
fuente
-1

Aquí está la respuesta si está usando South y desea establecer de manera predeterminada la fecha en que agrega el campo a la base de datos:

Elija la opción 2 y luego: datetime.datetime.now ()

Se ve como esto:

$ ./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
fuente
actualizado esto será: Los módulos datetime y django.utils.timezone están disponibles, por lo que puede hacer, por ejemplo, timezone.now ()
michel.iamit
Este es un cuestionario para cuando a su modelo le faltan datos clave. Si configura su modelo correctamente, nunca debería necesitar ver este mensaje.
Shayne