Django auto_now et auto_now_add

272

Pour Django 1.1.

J'ai ceci dans mes models.py:

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

Lors de la mise à jour d'une ligne, j'obtiens:

[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 partie pertinente de ma base de données est:

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

Est-ce une source de préoccupation?

Question secondaire: dans mon outil d'administration, ces deux champs n'apparaissent pas. Est-ce attendu?

Paul Tarjan
la source
3
utilisiez-vous une clé primaire personnalisée au lieu de l'incrémentation automatique par défaut int? J'ai découvert que l'utilisation d'une clé primaire personnalisée provoque ce problème. Quoi qu'il en soit, je suppose que vous l'avez résolu maintenant. Mais le bug existe toujours. Just my 0.02 $
tapan
3
Encore une chose à rappeler. update()méthode n'appellera pas, save()ce qui signifie qu'elle n'a pas pu mettre à jour le modifiedchamp automatiquement
Chemical Programmer

Réponses:

382

Tout champ avec l' auto_nowattribut défini héritera également editable=Falseet n'apparaîtra donc pas dans le panneau d'administration. Il a été question dans le passé de faire disparaître les arguments auto_nowet auto_now_add, et bien qu'ils existent toujours, je pense que vous feriez mieux d'utiliser une méthode personnaliséesave() .

Donc, pour que cela fonctionne correctement, je vous recommande de ne pas utiliser auto_nowou auto_now_addet de définir votre propre save()méthode pour vous assurer qu'elle createdn'est mise à jour que si elle idn'est pas définie (comme lorsque l'élément est créé pour la première fois), et la mettre à jour à modifiedchaque fois que l'élément est enregistré.

J'ai fait exactement la même chose avec d'autres projets que j'ai écrits en utilisant Django, et ainsi vous save()ressembleriez à ceci:

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)

J'espère que cela t'aides!

Modifier en réponse aux commentaires:

La raison pour laquelle je reste fidèle à la surcharge par save()rapport à l'utilisation de ces arguments de champ est double:

  1. Les hauts et les bas susmentionnés avec leur fiabilité. Ces arguments dépendent fortement de la façon dont chaque type de base de données avec lequel Django sait interagir traite un champ d'horodatage et semble rompre et / ou changer entre chaque version. (Ce qui, à mon avis, est l'impulsion derrière l'appel à les faire supprimer complètement).
  2. Le fait qu'ils ne fonctionnent que sur DateField, DateTimeField et TimeField et qu'en utilisant cette technique, vous pouvez remplir automatiquement n'importe quel type de champ chaque fois qu'un élément est enregistré.
  3. Utilisez django.utils.timezone.now()vs. datetime.datetime.now(), car cela retournera un datetime.datetimeobjet sensible à TZ ou naïf selon settings.USE_TZ.

Pour savoir pourquoi le PO a vu l'erreur, je ne sais pas exactement, mais il semble que ce createdne soit même pas du tout peuplé, malgré le fait auto_now_add=True. Pour moi, il s'agit d'un bug et souligne l'élément n ° 1 dans ma petite liste ci-dessus: auto_nowet auto_now_addest au mieux feuilleté.

jathanism
la source
9
Mais quelle est la source du problème de l'auteur? Auto_now_add fonctionne-t-il parfois de manière incorrecte?
Dmitry Risenberg
5
Je suis avec toi Dmitry. Je suis curieux de savoir pourquoi les deux champs ont généré des erreurs. Et je suis encore plus curieux de savoir pourquoi vous pensez qu'il est préférable d'écrire votre propre méthode save () personnalisée?
hora
46
Écrire une coutume save()sur chacun de mes modèles est beaucoup plus pénible que d'utiliser le auto_now(car j'aime avoir ces champs sur tous mes modèles). Pourquoi ces params ne fonctionnent-ils pas?
Paul Tarjan
3
@TM, mais cela nécessite de jouer directement avec votre base de données tandis que Django vise uniquement les fichiers models.py pour définir le schéma
akaihola
12
Je suis en désaccord avec véhémence. 1) modifiable = Faux est correct, vous ne devez pas modifier le champ, votre base de données doit être précise. 2) Il existe toutes sortes de cas limites où save () peut ne pas être appelé, en particulier lorsque des mises à jour SQL personnalisées ou quoi que ce soit sont utilisées. 3) C'est quelque chose dans lequel les bases de données sont réellement bonnes, ainsi que l'intégrité référentielle, etc. Faire confiance à la base de données pour bien faire les choses est une bonne valeur par défaut, car des esprits plus intelligents que vous ou moi avons conçu la base de données pour fonctionner de cette façon.
Shayne
175

Mais je voulais souligner que l'opinion exprimée dans la réponse acceptée est quelque peu dépassée. Selon des discussions plus récentes (bogues django # 7634 et # 12785 ), auto_now et auto_now_add ne vont nulle part, et même si vous allez à la discussion d'origine , vous trouverez des arguments solides contre le RY (comme dans DRY) dans la sauvegarde personnalisée méthodes.

Une meilleure solution a été proposée (types de champs personnalisés), mais n'a pas pris suffisamment d'élan pour en faire django. Vous pouvez écrire le vôtre en trois lignes (c'est la suggestion 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
la source
1
Le champ personnalisé à trois lignes est ici: lien
hgcrpd
Je ne pense pas qu'un champ personnalisé soit vraiment nécessaire étant donné que vous pouvez définir par défaut un appelable (c'est-à-dire timezone.now). Voir ma réponse ci-dessous.
Josh
6
C'est la même chose que fait auto_add dans Django, et depuis 2010: github.com/django/django/blob/1.8.4/django/db/models/fields/… . À moins d'avoir besoin de crochets supplémentaires dans pre_save, je m'en tiens à auto_add.
jwhitlock
1
Cela ne fonctionnait pas pour moi avec Django 1.9, donc cette solution ne fonctionne pas partout, comme elle ne l'a jamais été pour auto_now *. La seule solution qui fonctionne dans tous les cas d'utilisation (même avec le problème d'argument 'update_fields') est la sauvegarde prioritaire
danius
4
Pourquoi définissez-vous la valeur par défaut sur timezone.now, mais le signal pre_save utilise datetime.datetime.now?
Bobort
32

En parlant d'une question secondaire: si vous voulez voir ces champs dans admin (bien que vous ne puissiez pas le modifier), vous pouvez ajouter readonly_fieldsà votre classe admin.

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

Eh bien, cela ne s'applique qu'aux dernières versions de Django (je crois, 1.3 et plus)

DataGreed
la source
3
Important à noter: cela doit être ajouté à la XxAdminclasse. Je l'ai lu trop rapidement et j'ai essayé de l'ajouter à mes AdminFormou ModelFormclasses et je ne savais pas pourquoi ils ne rendaient pas les "champs en lecture seule". BTW, est-il possible d'avoir de vrais "champs en lecture seule dans un formulaire?"
Tomasz Gandor
28

Je pense que la solution la plus simple (et peut-être la plus élégante) ici est de tirer parti du fait que vous pouvez définir defaultun appelable. Donc, pour contourner la gestion spéciale par admin de auto_now, vous pouvez simplement déclarer le champ comme suit:

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

Il est important de ne pas l'utiliser timezone.now()car la valeur par défaut ne serait pas mise à jour (c'est-à-dire que la valeur par défaut n'est définie que lorsque le code est chargé). Si vous vous retrouvez souvent à faire cela, vous pouvez créer un champ personnalisé. Cependant, c'est déjà assez sec, je pense.

Josh
la source
2
Une valeur par défaut est plus ou moins équivalente à auto_now_add (définir la valeur lorsque l'objet est enregistré pour la première fois), mais ce n'est pas du tout comme auto_now (définir la valeur à chaque fois que l'objet est enregistré).
Shai Berger
1
@ShaiBerger, je pense qu'ils sont subtilement différents d'une manière importante. Le doc a déclaré la subtilité: "Définissez automatiquement le champ ...; ce n'est pas seulement une valeur par défaut que vous pouvez remplacer." - docs.djangoproject.com/en/dev/ref/models/fields/…
Thomas - BeeDesk
@ Thomas-BeeDesk: D'accord. Par conséquent, "plus ou moins équivalent".
Shai Berger
1
Cette solution fonctionne mal si vous utilisez des migrations. Chaque fois que vous l'exécutez, makemigrationsil interprète la valeur par défaut comme l'heure à laquelle vous exécutez makemigrationset pense donc que la valeur par défaut a changé!
nhinkle
8
@nhinkle, êtes-vous sûr de ne pas préciser default=timezone.now()plutôt que ce qui est recommandé: default=timezine.now(pas de parenthèses)?
Josh
18

Si vous modifiez votre classe de modèle comme ceci:

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

Ensuite, ce champ apparaîtra dans ma page de changement d'administrateur

Eric Zheng
la source
1
Mais cela fonctionne UNIQUEMENT sur le dossier d'édition. Lorsque je crée un nouvel enregistrement, la valeur de la vignette passée à la date est ignorée. Lorsque je modifie cet enregistrement, une nouvelle valeur est définie.
Anton Danilchenko
2
Fonctionne mais il doit s'agir de models.DateTimeField au lieu de models.DatetimeField
matyas
2
échoué dans python manage.py makemigrations: KeyError: u'editable '
laoyur
12

D'après ce que j'ai lu et mon expérience avec Django jusqu'à présent, auto_now_add est bogué. Je suis d'accord avec jthanism --- remplacer la méthode de sauvegarde normale, c'est propre et vous savez ce qui se passe. Maintenant, pour le rendre sec, créez un modèle abstrait appelé 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

Et puis, quand vous voulez un modèle qui a ce comportement horodaté, il suffit de sous-classe:

MyNewTimeStampyModel(TimeStamped):
    field1 = ...

Si vous souhaitez que les champs apparaissent dans l'administrateur, supprimez simplement l' editable=Falseoption

Edward Newell
la source
1
Lequel timezone.now()utilisez-vous ici? Je suppose django.utils.timezone.now(), mais je ne suis pas positif. Aussi, pourquoi utiliser timezone.now()plutôt que datetime.datetime.now()?
coredumperror
1
Bons points. J'ai ajouté la déclaration d'importation. La raison de l'utilisation timezone.now()est qu'il est sensible au fuseau horaire, alors qu'il datetime.datetime.now()est naïf. Vous pouvez en lire plus ici: docs.djangoproject.com/en/dev/topics/i18n/timezones
Edward Newell
@EdwardNewell Pourquoi avez-vous choisi de définir creation_date dans la sauvegarde, plutôt que default=timezone.nowdans le constructeur de champ?
Blackeagle52
Hmm .. peut-être que je n'y ai tout simplement pas pensé, ça sonne mieux.
Edward Newell
2
Eh bien, il y a un cas où last_modified ne sera pas mis à jour: lorsque update_fieldsarg est fourni et que 'last_modified' n'est pas dans la liste, j'ajouterais:if 'update_fields' in kwargs and 'last_modifed' not in kwargs['update_fields']: kwargs['update_fields'].append('last_modified')
danius
5

Est-ce une source de préoccupation?

Non, Django l'ajoute automatiquement pour vous lors de la sauvegarde des modèles, c'est donc normal.

Question secondaire: dans mon outil d'administration, ces 2 champs n'apparaissent pas. Est-ce attendu?

Étant donné que ces champs sont ajoutés automatiquement, ils ne sont pas affichés.

Pour ajouter à ce qui précède, comme l'a dit synack, il y a eu un débat sur la liste de diffusion django pour le supprimer, car il n'est "pas bien conçu" et est "un hack".

Écrire un save () personnalisé sur chacun de mes modèles est beaucoup plus difficile que d'utiliser l'auto_now

De toute évidence, vous n'avez pas à l'écrire sur tous les modèles. Vous pouvez l'écrire sur un modèle et en hériter d'autres.

Mais, comme auto_addet auto_now_addsont là, je les utiliserais plutôt que d'essayer d'écrire une méthode moi-même.

Lakshman Prasad
la source
3

J'avais besoin de quelque chose de similaire aujourd'hui au travail. La valeur par défaut est timezone.now(), mais modifiable à la fois dans les vues d'administration et de classe héritées de FormMixin, donc pour créé dans mon, models.pyle code suivant remplit ces conditions:

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

Pour DateTimeField, je suppose que supprimer le .date()de la fonction et changer datetime.datepour datetime.datetimeou mieux timezone.datetime. Je ne l'ai pas essayé avec DateTime, seulement avec Date.

Tiphareth
la source
2

Vous pouvez utiliser timezone.now()pour créé et auto_nowpour modifié:

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

Si vous utilisez une clé primaire personnalisée au lieu de la valeur par défaut auto- increment int, auto_now_addcela entraînera un bogue.

Voici le code de DateTimeField.pre_save par défaut de Django avec auto_nowet 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)

Je ne sais pas quel est le paramètre add. J'espère que ce sera quelque chose comme:

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

Le nouvel enregistrement n'aura pas d'attr id, donc getattr(model_instance, 'id')retournera False conduira à ne définir aucune valeur dans le champ.

suhailvs
la source
7
J'ai remarqué que si nous conservons la valeur par défaut timezone.now (), lorsque vous effectuez des migrations, la date et l'heure réelles (de ce moment) sont transmises au fichier de migrations. Je pense que nous devrions éviter cela car chaque fois que vous appelez makemigrations, ce champ aura une valeur différente.
Karan Kumar
2

Quant à votre écran Admin, voyez cette réponse .

Remarque: auto_nowet auto_now_addsont définis editable=Falsepar défaut, c'est pourquoi cela s'applique.

jlovison
la source
1

auto_now=Truen'a pas fonctionné pour moi dans Django 1.4.1, mais le code ci-dessous m'a sauvé. C'est pour le datetime sensible au fuseau horaire.

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

Ici, nous avons créé et mis à jour des colonnes qui auront un horodatage lors de la création et lorsque quelqu'un a modifié les commentaires.

auto_now_add fixera l'heure à laquelle une instance est créée tandis que auto_now fixera l'heure à laquelle quelqu'un modifiera ses commentaires.

Viraj Wadate
la source
-1

Voici la réponse si vous utilisez le sud et que vous souhaitez définir par défaut la date à laquelle vous ajoutez le champ à la base de données:

Choisissez l'option 2 puis: datetime.datetime.now ()

Ressemble à ça:

$ ./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
la source
mis à jour ce sera: Les modules datetime et django.utils.timezone sont disponibles, vous pouvez donc faire par exemple timezone.now ()
michel.iamit
Il s'agit d'un questionnaire pour savoir quand votre modèle manque de données clés. Si vous configurez votre modèle correctement, vous ne devriez jamais avoir besoin de voir cette invite.
Shayne