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.