Come impostare il valore predefinito di un campo del modello Django su una chiamata / chiamata di funzione (ad esempio, una data relativa all'ora della creazione dell'oggetto del modello)


101

MODIFICATO:

Come posso impostare il valore predefinito di un campo Django su una funzione che viene valutata ogni volta che viene creato un nuovo oggetto modello?

Voglio fare qualcosa di simile a quanto segue, tranne che in questo codice, il codice viene valutato una volta e imposta il valore predefinito sulla stessa data per ogni oggetto del modello creato, piuttosto che valutare il codice ogni volta che viene creato un oggetto del modello:

from datetime import datetime, timedelta
class MyModel(models.Model):
  # default to 1 day from now
  my_date = models.DateTimeField(default=datetime.now() + timedelta(days=1))



ORIGINALE:

Voglio creare un valore predefinito per un parametro di funzione in modo che sia dinamico e venga chiamato e impostato ogni volta che viene chiamata la funzione. Come lo posso fare? per esempio,

from datetime import datetime
def mydate(date=datetime.now()):
  print date

mydate() 
mydate() # prints the same thing as the previous call; but I want it to be a newer value

Nello specifico, voglio farlo in Django, ad es.

from datetime import datetime, timedelta
class MyModel(models.Model):
  # default to 1 day from now
  my_date = models.DateTimeField(default=datetime.now() + timedelta(days=1))

La domanda è sbagliata: non ha nulla a che fare con i valori predefiniti dei parametri della funzione. Vedi la mia risposta.
Ned Batchelder,

Secondo il commento di @ NedBatchelder, ho modificato la mia domanda (indicata come "MODIFICATA:") e ho lasciato l'originale (indicato come "ORIGINALE:")
Rob Bednark

Risposte:


140

La domanda è fuorviante. Quando crei un campo modello in Django, non stai definendo una funzione, quindi i valori predefiniti della funzione sono irrilevanti:

from datetime import datetime, timedelta
class MyModel(models.Model):
  # default to 1 day from now
  my_date = models.DateTimeField(default=datetime.now() + timedelta(days=1))

Quest'ultima riga non definisce una funzione; sta invocando una funzione per creare un campo nella classe.

PRE Django 1.7

Django ti consente di passare un chiamabile come predefinito e lo invocherà ogni volta, proprio come vuoi:

from datetime import datetime, timedelta
class MyModel(models.Model):
  # default to 1 day from now
  my_date = models.DateTimeField(default=lambda: datetime.now() + timedelta(days=1))

Django 1.7+

Si noti che a partire da Django 1.7, l'utilizzo di lambda come valore predefinito non è raccomandato (cfr @stvnw comment). Il modo corretto per farlo è dichiarare una funzione prima del campo e usarla come richiamabile in default_value denominato arg:

from datetime import datetime, timedelta

# default to 1 day from now
def get_default_my_date():
  return datetime.now() + timedelta(days=1)

class MyModel(models.Model):
  my_date = models.DateTimeField(default=get_default_my_date)

Maggiori informazioni nella risposta @simanas di seguito


2
Grazie @ NedBatchelder. Questo e 'esattamente quello che stavo cercando. Non mi rendevo conto che "default" poteva richiedere un richiamo. E ora vedo come la mia domanda fosse davvero fuorviante riguardo all'invocazione della funzione rispetto alla definizione della funzione.
Rob Bednark,

25
Nota che per Django> = 1.7 l'uso di lambda non è raccomandato per le opzioni di campo perché sono incompatibili con le migrazioni. Rif: Documenti , biglietto
StvnW

4
Deseleziona questa risposta, poiché è sbagliato per Djange> = 1.7. La risposta di @Simanas è assolutamente corretta e va quindi accettata.
AplusKminus

Come funziona con le migrazioni? Tutti i vecchi oggetti ottengono una data basata sull'ora della migrazione? È possibile impostare un valore predefinito diverso da utilizzare con le migrazioni?
timthelion

3
E se volessi passare qualche parametro a get_default_my_date?
Sadan A.

59

Fare questo default=datetime.now()+timedelta(days=1)è assolutamente sbagliato!

Viene valutato quando avvii la tua istanza di django. Se sei sotto apache probabilmente funzionerà, perché su alcune configurazioni apache revoca la tua applicazione django ad ogni richiesta, ma puoi ancora ritrovarti un giorno a guardare il tuo codice e cercare di capire perché questo viene calcolato non come ti aspetti .

Il modo giusto per farlo è passare un oggetto richiamabile all'argomento predefinito. Può essere una funzione datetime.today o una funzione personalizzata. Quindi viene valutato ogni volta che richiedi un nuovo valore predefinito.

def get_deadline():
    return datetime.today() + timedelta(days=20)

class Bill(models.Model):
    name = models.CharField(max_length=50)
    customer = models.ForeignKey(User, related_name='bills')
    date = models.DateField(default=datetime.today)
    deadline = models.DateField(default=get_deadline)

21
Se il mio valore predefinito è basato sul valore di un altro campo nel modello, è possibile passare quel campo come parametro come in qualcosa di simile get_deadline(my_parameter)?
YPCrumble

@YPCrumble questa dovrebbe essere una nuova domanda!
jsmedmar

2
No. Non e possibile. Dovrai usare un approccio diverso per farlo.
Simanas

Come rimuovere deadlinenuovamente il campo eliminando anche la get_deadlinefunzione? Ho rimosso un campo con una funzione per un valore predefinito, ma ora Django si arresta in modo anomalo all'avvio dopo aver rimosso la funzione. Potrei modificare manualmente la migrazione, il che andrebbe bene in questo caso, ma cosa succederebbe se cambiassi semplicemente la funzione predefinita e volessi rimuovere la vecchia funzione?
beruic

8

C'è un'importante distinzione tra i seguenti due costruttori DateTimeField:

my_date = models.DateTimeField(auto_now=True)
my_date = models.DateTimeField(auto_now_add=True)

Se si utilizza auto_now_add=Truenel costruttore, il datetime a cui fa riferimento my_date è "immutabile" (impostato solo una volta quando la riga viene inserita nella tabella).

Con auto_now=True, tuttavia, il valore datetime verrà aggiornato ogni volta che l'oggetto viene salvato.

Questo è stato sicuramente un trucco per me a un certo punto. Per riferimento, i documenti sono qui:

https://docs.djangoproject.com/en/dev/ref/models/fields/#datetimefield


3
Hai confuso le tue definizioni di auto_now e auto_now_add, è il contrario.
Michael Bates

@MichaelBates va meglio adesso?
Hendy Irawan

4

A volte potrebbe essere necessario accedere ai dati del modello dopo aver creato un nuovo modello utente.

Ecco come generare un token per ogni nuovo profilo utente utilizzando i primi 4 caratteri del suo nome utente:

from django.dispatch import receiver
class Profile(models.Model):
    auth_token = models.CharField(max_length=13, default=None, null=True, blank=True)


@receiver(post_save, sender=User) # this is called after a User model is saved.
def create_user_profile(sender, instance, created, **kwargs):
    if created: # only run the following if the profile is new
        new_profile = Profile.objects.create(user=instance)
        new_profile.create_auth_token()
        new_profile.save()

def create_auth_token(self):
    import random, string
    auth = self.user.username[:4] # get first 4 characters in user name
    self.auth_token =  auth + ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits + string.ascii_lowercase) for _ in range(random.randint(3, 5)))

3

Non puoi farlo direttamente; il valore predefinito viene valutato quando viene valutata la definizione della funzione. Ma ci sono due modi per aggirarlo.

Innanzitutto, puoi creare (e quindi chiamare) una nuova funzione ogni volta.

Oppure, più semplicemente, usa un valore speciale per contrassegnare il valore predefinito. Per esempio:

from datetime import datetime
def mydate(date=None):
  if date is None:
    date = datetime.now()
  print date

Se Noneè un valore di parametro perfettamente ragionevole e non c'è nessun altro valore ragionevole che potresti usare al suo posto, puoi semplicemente creare un nuovo valore che è decisamente al di fuori del dominio della tua funzione:

from datetime import datetime
class _MyDateDummyDefault(object):
  pass
def mydate(date=_MyDateDummyDefault):
  if date is _MyDateDummyDefault:
    date = datetime.now()
  print date
del _MyDateDummyDefault

In alcuni rari casi, si sta scrivendo meta-codice che fa realmente bisogno di essere in grado di prendere qualsiasi cosa, anche, per esempio, mydate.func_defaults[0]. In tal caso, devi fare qualcosa del genere:

def mydate(*args, **kw):
  if 'date' in kw:
    date = kw['date']
  elif len(args):
    date = args[0]
  else:
    date = datetime.now()
  print date

1
Nota che non c'è motivo per istanziare effettivamente la classe del valore fittizio: usa semplicemente la classe stessa come valore fittizio.
Amber

Puoi farlo direttamente, basta passare la funzione invece del risultato della chiamata di funzione. Vedi la mia risposta pubblicata.

No non puoi. Il risultato della funzione non è lo stesso tipo di cose dei parametri normali, quindi non può essere ragionevolmente utilizzato come valore predefinito per quei parametri normali.
abarnert

1
Ebbene sì, se passi un oggetto invece di una funzione, solleverà un'eccezione. Lo stesso vale se si passa una funzione in un parametro in attesa di una stringa. Non vedo quanto sia rilevante.

È rilevante perché la sua myfuncfunzione è fatta per prendere (e stampare) a datetime; l'hai cambiato in modo che non possa essere utilizzato in questo modo.
abarnert

2

Passa la funzione come parametro invece di passare il risultato della chiamata alla funzione.

Cioè, invece di questo:

def myfunc(date=datetime.now()):
    print date

Prova questo:

def myfunc(date=datetime.now):
    print date()

Questo non funziona, perché ora l'utente non può effettivamente passare un parametro: proverai a chiamarlo come una funzione e genererai un'eccezione. In tal caso potresti anche non prendere parametri e print datetime.now()incondizionatamente.
abarnert

Provalo. Non stai passando una data, stai passando la funzione datetime.now.

Sì, l'impostazione predefinita è passare la datetime.nowfunzione. Ma qualsiasi utente che passa un valore non predefinito passerà un datetime, non una funzione. foo = datetime.now(); myfunc(foo)solleverà TypeError: 'datetime.datetime' object is not callable. Se davvero volessi usare la tua funzione, dovrei fare qualcosa di simile myfunc(lambda: foo), che non è un'interfaccia ragionevole.
abarnert

1
Le interfacce che accettano funzioni non sono insolite. Potrei iniziare a nominare esempi dallo stdlib di python, se vuoi.

2
Django accetta già un callable esattamente come suggerisce questa domanda.
Ned Batchelder,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.