web-dev-qa-db-fra.com

Django auto_now et auto_now_add

Pour Django 1.1.

J'ai ceci dans mon models.py:

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

En mettant à jour une ligne, je reçois:

[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 prévu?

245
Paul Tarjan

Tout champ avec le jeu d'attributs auto_now héritera également de _editable=False_ et ne sera donc pas affiché dans le panneau d'administration. Il a été question dans le passé de faire disparaître les arguments _auto_now_ et auto_now_add , et même s'ils existent encore, je pense que vous valez mieux utiliser - méthode personnalisée save() .

Donc, pour que cela fonctionne correctement, je vous recommande de ne pas utiliser _auto_now_ ou _auto_now_add_ et de définir votre propre méthode save() pour vous assurer que created n'est mis à jour que si id n'est pas défini (par exemple, lorsque l'élément est créé pour la première fois) et doit le mettre à jour modified à chaque 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 votre save() ressemblerait à 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 juste avec la surcharge save() contre l'utilisation de ces arguments de champ est double:

  1. Les hauts et les bas mentionnés ci-dessus avec leur fiabilité. Ces arguments sont fortement dépendants de la manière dont chaque type de base de données que Django sait comment interagir avec traite un champ d'horodatage de date/heure, et semble se rompre et/ou changer entre chaque publication. (Ce qui, à mon avis, est l’élan derrière l’appel à leur suppression complète).
  2. Le fait qu'ils ne fonctionnent que sur DateField, DateTimeField et TimeField, et en utilisant cette technique, vous êtes en mesure de renseigner automatiquement tout type de champ à chaque fois qu'un élément est enregistré.
  3. Utilisez Django.utils.timezone.now() contre datetime.datetime.now(), car il renverra un objet _datetime.datetime_ naïf ou naïf, dépendant de _settings.USE_TZ_.

Pour expliquer pourquoi l’opérateur a vu l’erreur, je ne le sais pas exactement, mais il semble que created ne soit même pas rempli, bien que _auto_now_add=True_. Pour moi, il s’agit d’un bogue et souligne le point 1 de ma petite liste ci-dessus: _auto_now_ et _auto_now_add_ sont au mieux floconneux.

333
jathanism

Mais je voulais souligner que l’opinion exprimée dans le 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 passez à la discussion originale , vous trouverez des arguments solides contre le RY (comme dans DRY) dans les méthodes de sauvegarde personnalisées.

Une meilleure solution a été proposée (types de champs personnalisés), mais elle n’a pas gagné suffisamment d’élan pour être intégrée à Django. Vous pouvez écrire le vôtre en trois lignes (c'est 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)
156
Shai Berger

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

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

Cela ne concerne que les dernières versions de Django (je crois, versions 1.3 et supérieures).

30
DataGreed

Je pense que la solution la plus simple (et peut-être la plus élégante) consiste à tirer parti du fait que vous pouvez définir default sur un appelable. Donc, pour contourner le traitement spécial de auto_now par l'administrateur, vous pouvez simplement déclarer le champ de la manière suivante:

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

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

23
Josh

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 la page de changement de mon administrateur

17
Eric Zheng

D'après ce que j'ai lu et mon expérience avec Django jusqu'à présent, auto_now_add est un buggy. Je suis d'accord avec le jhanisme - écrasez 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 ensuite, lorsque vous voulez un modèle qui a ce comportement obsolète, il suffit de sous-classe:

MyNewTimeStampyModel(TimeStamped):
    field1 = ...

Si vous voulez que les champs apparaissent dans admin, supprimez simplement l'option editable=False

12
Edward Newell

Est-ce une source de préoccupation?

Non, Django l'ajoute automatiquement pour vous lors de l'enregistrement des modèles. C'est donc prévu.

Question secondaire: dans mon outil d'administration, ces 2 champs ne s'affichent pas. Est-ce prévu?

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

Pour ajouter à ce qui précède, comme le dit Synack, il y a eu un débat sur la liste de diffusion Django afin de la supprimer, car elle n'est "pas bien conçue" et est "un hack".

Écrire une sauvegarde personnalisée () sur chacun de mes modèles est beaucoup plus pénible que d’utiliser auto_now

De toute évidence, vous n'êtes pas obligé de l'écrire dans tous les modèles. Vous pouvez l'écrire dans un modèle et en hériter d'autres.

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

5
Lakshman Prasad

Vous pouvez utiliser timezone.now() pour créer et auto_now pour modifier:

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_add conduira à un bogue.

Voici le code par défaut de Django: DateTimeField.pre_save withauto_now et 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 suis pas sûr de ce que le paramètre add est. J'espère que ça va quelque chose comme:

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

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

2
suhailvs

En ce qui concerne votre affichage Admin, voir cette réponse .

Remarque: auto_now et auto_now_add sont réglés sur editable=False par défaut, raison pour laquelle cela s'applique.

2
jlovison

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'administrateur et de classe héritées de FormMixin, le code suivant a donc été créé dans mon models.py:

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 la .date() de la fonction et modifier datetime.date en datetime.datetime ou mieux timezone.datetime. Je n'ai pas essayé avec DateTime, seulement avec Date.

2
Tiphareth

auto_now=True ne fonctionnait pas pour moi dans Django 1.4.1, mais le code ci-dessous m'a sauvé. C'est pour le fuseau horaire prenant en compte la date et l'heure.

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
Ryu_hayabusa