Auto_now e auto_now_add do Django


272

Para o Django 1.1.

Eu tenho isso no meu models.py:

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

Ao atualizar uma linha, recebo:

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

A parte relevante do meu banco de dados é:

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

Isso é motivo de preocupação?

Pergunta secundária: na minha ferramenta de administração, esses dois campos não estão aparecendo. Isso é esperado?


3
você estava usando uma chave primária personalizada em vez do int incremento automático padrão? Descobri que o uso de uma chave primária personalizada causa esse problema. Enfim, acho que você já resolveu. Mas o bug ainda existe. Apenas meu 0,02 $
tapan

3
Apenas mais uma coisa para lembrar. update()método não irá chamar save()o que significa que não foi possível atualizar o modifiedcampo automaticamente
Chemical programador

Respostas:


383

Qualquer campo com o auto_nowconjunto de atributos também será herdado editable=Falsee, portanto, não será exibido no painel de administração. No passado, houve conversas sobre auto_nowcomo auto_now_adddesaparecer os argumentos e , embora eles ainda existam, acho melhor você usar apenas um método personalizadosave() .

Portanto, para fazer isso funcionar corretamente, eu recomendaria não usar auto_nowou, em auto_now_addvez disso, definir seu próprio save()método para garantir que createdsomente seja atualizado se idnão estiver definido (como quando o item é criado pela primeira vez) e que ele seja atualizado modifiedsempre que o item é salvo.

Fiz exatamente a mesma coisa com outros projetos que escrevi usando o Django, e assim você save()ficaria assim:

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 isto ajude!

Edite em resposta aos comentários:

A razão pela qual eu continuo sobrecarregando save()versus confiando nesses argumentos de campo é dupla:

  1. Os mencionados altos e baixos com sua confiabilidade. Esses argumentos são fortemente dependentes da maneira como cada tipo de banco de dados com o qual o Django sabe como interagir trata um campo de data / hora, e parece quebrar e / ou mudar entre cada versão. (Que acredito ser o ímpeto por trás da chamada para removê-los completamente).
  2. O fato de eles funcionarem apenas no DateField, DateTimeField e TimeField e, usando essa técnica, você pode preencher automaticamente qualquer tipo de campo toda vez que um item é salvo.
  3. Use django.utils.timezone.now()vs. datetime.datetime.now(), pois retornará um datetime.datetimeobjeto sensível a TZ ou ingênuo, dependendo settings.USE_TZ.

Para resolver por que o OP viu o erro, não sei exatamente, mas parece que creatednem sequer estou sendo preenchido, apesar de ter auto_now_add=True. Para mim, isso se destaca como um bug e ressalta o item 1 da minha pequena lista acima: auto_nowe auto_now_addé, na melhor das hipóteses, esquisito.


9
Mas qual é a fonte do problema do autor? Às vezes, auto_now_add funciona incorretamente?
Dmitry Risenberg

5
Estou com você, Dmitry. Estou curioso para saber por que os dois campos geraram erros. E ainda mais curioso para saber por que você acha que escrever seu próprio método save () personalizado é melhor?
hora

45
Escrever um costume save()em cada um dos meus modelos é muito mais trabalhoso do que usar o auto_now(como eu gosto de ter esses campos em todos os meus modelos). Por que esses parâmetros não funcionam?
Paul Tarjan

3
@TM, mas que requer mexer diretamente com o seu db enquanto objetivos Django para apenas arquivos models.py para definir o esquema
akaihola

11
Eu discordo, veementemente. 1) editável = Falso está correto, você não deve editar o campo, seu banco de dados precisa ser preciso. 2) Existem todos os tipos de casos extremos em que o save () pode não ser chamado, principalmente quando atualizações SQL personalizadas ou o que estiver sendo usado. 3) Isso é algo em que os bancos de dados são realmente bons, juntamente com a integridade referencial e assim por diante. Confiar no banco de dados para acertar é um bom padrão, porque mentes mais inteligentes do que você ou eu projetamos o banco de dados para funcionar dessa maneira.
Shayne

175

Mas eu queria ressaltar que a opinião expressa na resposta aceita está um pouco desatualizada. De acordo com discussões mais recentes (erros do django # 7634 e # 12785 ), auto_now e auto_now_add não vão a lugar algum, e mesmo se você for para a discussão original , encontrará fortes argumentos contra o RY (como em DRY) no salvamento personalizado métodos.

Uma solução melhor foi oferecida (tipos de campos personalizados), mas não ganhou impulso suficiente para transformá-lo em django. Você pode escrever o seu em três linhas (é a sugestão 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)

1
O campo personalizado de três linhas está aqui: link
hgcrpd 25/01

Eu não acho que um campo personalizado seja realmente necessário, pois você pode definir o padrão como callable (ou seja, timezone.now). Veja minha resposta abaixo.
21713 Josh

6
É o mesmo que o auto_add faz no Django e tem desde 2010: github.com/django/django/blob/1.8.4/django/db/models/fields/… . A menos que eu precise de ganchos adicionais no pré-salvamento, estou usando o auto_add.
jwhitlock

1
Não funcionou para mim com o Django 1.9, portanto, esta solução não está funcionando em todos os lugares, como nunca foi para o auto_now *. A única solução que funciona em todos os casos de uso (mesmo com o problema do argumento 'update_fields') está substituindo save
danius

4
Por que você define o padrão como timezone.now, mas o sinal pre_save está usando datetime.datetime.now?
Bobort

32

Falando sobre uma pergunta paralela: se você quiser ver esses campos em admin (você não poderá editá-lo), poderá adicionar readonly_fieldsà sua classe de administrador.

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

Bem, isso se aplica apenas às versões mais recentes do Django (acredito, 1.3 e acima)


3
Importante notar: isso deve ser adicionado à XxAdminclasse. Eu o li muito rapidamente e tentei adicioná-lo às minhas AdminFormou ModelFormclasses e não tinha ideia de por que eles não estavam processando os "campos somente leitura". BTW, há uma possibilidade de ter verdadeiros "somente leitura campos em um formulário?
Tomasz Gandor

28

Acho que a solução mais fácil (e talvez a mais elegante) aqui é aproveitar o fato de que você pode definir defaultum valor que pode ser chamado. Portanto, para contornar o tratamento especial do auto_now pelo administrador, basta declarar o campo da seguinte forma:

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

É importante que você não use, timezone.now()pois o valor padrão não será atualizado (ou seja, o padrão será definido apenas quando o código for carregado). Se você se encontra fazendo muito isso, pode criar um campo personalizado. No entanto, isso já é bastante SECO, eu acho.


2
Um padrão é mais ou menos equivalente a auto_now_add (definir valor quando o objeto é salvo pela primeira vez), mas não é como o auto_now (definir valor toda vez que o objeto é salvo).
Shai Berger

1
@ShaiBerger, acho que são sutilezas diferentes de uma maneira importante. O documento afirmou a sutileza: "Defina automaticamente o campo ...; não é apenas um valor padrão que você pode substituir". - docs.djangoproject.com/en/dev/ref/models/fields/…
Thomas - BeeDesk

@ Thomas-BeeDesk: De acordo. Portanto, "mais ou menos equivalente".
precisa

1
Esta solução funciona mal se você estiver usando migrações. Toda vez que você executa, makemigrationsele interpreta o padrão como o horário em que você executa makemigrationse, portanto, pensa que o valor padrão foi alterado!
Nhinkle 25/05

8
@ nhinkle, você tem certeza de que não está especificando, default=timezone.now()e não o que está sendo recomendado: default=timezine.now(sem parênteses)?
Josh

18

Se você alterar sua classe de modelo assim:

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

Em seguida, este campo será exibido na minha página de alteração de administrador


1
Mas funciona apenas no registro de edição. Quando crio um novo valor do bloco passado a data ignorado. Quando altero esse registro - novo valor é definido.
Anton Danilchenko

2
No entanto, é possível que você não tenha conseguido acessar o
site e

2
falhou em python manage.py makemigrations: KeyError: u'editable '
laoyur 10/10

12

Baseado no que li e na minha experiência com o Django até agora, o auto_now_add é um buggy. Concordo com o jthanism - substitua o método normal de salvamento, ele está limpo e você sabe o que está acontecendo. Agora, para torná-lo seco, crie um modelo abstrato chamado 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 então, quando você quiser um modelo que tenha esse comportamento de carimbo de hora, basta subclasse:

MyNewTimeStampyModel(TimeStamped):
    field1 = ...

Se você deseja que os campos apareçam no administrador, remova a editable=Falseopção


1
Qual timezone.now()você está usando aqui? Estou assumindo django.utils.timezone.now(), mas não sou positivo. Além disso, por que usar em timezone.now()vez de datetime.datetime.now()?
Coredumperror # /

1
Bons pontos. Eu adicionei a declaração de importação. O motivo para usar timezone.now()é porque ele reconhece o fuso horário, enquanto datetime.datetime.now()é ingênuo. Você pode ler sobre isso aqui: docs.djangoproject.com/en/dev/topics/i18n/timezones
Edward Newell

@EdwardNewell Por que você escolheu definir a data de criação no salvamento, em vez de default=timezone.nowdentro do construtor de campos?
Blackeagle52

Hmm ... talvez eu não tenha pensado nisso, isso soa melhor.
Edward Newell

2
Bem, há um caso em que last_modified não será atualizado: quando update_fieldsarg é fornecido e 'last_modified' não está na lista, eu acrescentaria:if 'update_fields' in kwargs and 'last_modifed' not in kwargs['update_fields']: kwargs['update_fields'].append('last_modified')
danius

5

Isso é motivo de preocupação?

Não, o Django o adiciona automaticamente ao salvar os modelos, portanto, é esperado.

Pergunta secundária: na minha ferramenta de administração, esses 2 campos não estão aparecendo. Isso é esperado?

Como esses campos são adicionados automaticamente, eles não são mostrados.

Para acrescentar ao exposto, como o synack disse, houve um debate na lista de discussão do django para removê-lo, porque "ele não foi bem projetado" e é "um hack"

Escrever um save () personalizado em cada um dos meus modelos é muito mais trabalhoso do que usar o auto_now

Obviamente, você não precisa escrevê-lo em todos os modelos. Você pode gravá-lo em um modelo e herdar outros.

Mas, como auto_adde auto_now_addexistem, eu os usaria ao invés de tentar escrever um método.


3

Eu precisava de algo semelhante hoje no trabalho. Valor padrão a ser timezone.now(), mas editável nas visualizações de administrador e de classe herdadas de FormMixin, portanto, para criado no meu models.pycódigo a seguir atendeu a esses 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, eu acho que remover a .date()partir da função e mudança datetime.datepara datetime.datetimeou melhor timezone.datetime. Eu não tentei com DateTime, apenas com Date.


2

Você pode usar timezone.now()para criado e auto_nowpara modificado:

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

Se você estiver usando uma chave primária personalizada em vez da padrão auto- increment int, isso auto_now_addcausará um erro.

Aqui está o código do DateTimeField.pre_save padrão do Django com 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)

Não tenho certeza qual é o parâmetro add. Espero que seja algo como:

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

O novo registro não terá attr id, portanto getattr(model_instance, 'id'), retornar False levará a não definir nenhum valor no campo.


7
Percebi que, se mantivermos o padrão como timezone.now (), quando você faz as migrações, a data e a hora reais (deste momento) são passadas para o arquivo de migrações. Eu acho que devemos evitar isso, pois toda vez que você chama makemigrations esse campo terá um valor diferente.
21416 Karan Kumar

2

Quanto à exibição do administrador, consulte esta resposta .

Nota: auto_nowe auto_now_addsão definidos como editable=Falsepadrão, e é por isso que isso se aplica.


1

auto_now=Truenão funcionou para mim no Django 1.4.1, mas o código abaixo me salvou. É para data e hora com reconhecimento de fuso horário.

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)

Aqui, criamos e atualizamos colunas que terão um carimbo de data / hora quando criadas e quando alguém modificou o feedback.

auto_now_add definirá a hora em que uma instância será criada, enquanto auto_now definirá a hora em que alguém modificou seus comentários.


-1

Aqui está a resposta se você estiver usando o sul e desejar usar como padrão a data em que você adicionar o campo ao banco de dados:

Escolha a opção 2 e: datetime.datetime.now ()

Se parece com isso:

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

actualizado este será: Os módulos de data e hora e django.utils.timezone estão disponíveis, assim você pode fazer por exemplo timezone.now ()
michel.iamit

Este é um questionário para quando seu modelo está com dados faltantes. Se você configurar seu modelo corretamente, nunca precisará ver este prompt.
Shayne
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.