So setzen Sie den Standardwert eines Django-Modellfelds auf einen Funktionsaufruf / aufrufbar (z. B. ein Datum relativ zur Uhrzeit der Modellobjekterstellung)

101

EDITIERT:

Wie kann ich die Standardeinstellung eines Django-Felds auf eine Funktion festlegen, die jedes Mal ausgewertet wird, wenn ein neues Modellobjekt erstellt wird?

Ich möchte Folgendes tun, außer dass in diesem Code der Code einmal ausgewertet wird und der Standard für jedes erstellte Modellobjekt auf das gleiche Datum festgelegt wird, anstatt den Code jedes Mal auszuwerten, wenn ein Modellobjekt erstellt wird:

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



ORIGINAL:

Ich möchte einen Standardwert für einen Funktionsparameter erstellen, der dynamisch ist und bei jedem Aufruf der Funktion aufgerufen und festgelegt wird. Wie kann ich das machen? z.B,

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

Insbesondere möchte ich es in Django tun, z.

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))
Rob Bednark
quelle
Die Frage ist falsch formuliert: Sie hat nichts mit den Standardeinstellungen der Funktionsparameter zu tun. Siehe meine Antwort.
Ned Batchelder
Per @ NedBatchelder's Kommentar habe ich meine Frage bearbeitet (angezeigt als "EDITED:") und das Original (angezeigt als "ORIGINAL:")
Rob Bednark

Antworten:

139

Die Frage ist falsch. Wenn Sie ein Modellfeld in Django erstellen, definieren Sie keine Funktion, daher sind die Standardwerte für Funktionen irrelevant:

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

Diese letzte Zeile definiert keine Funktion. Es wird eine Funktion aufgerufen, um ein Feld in der Klasse zu erstellen.

PRE Django 1.7

Mit Django können Sie ein Callable als Standard übergeben und es wird jedes Mal aufgerufen, genau wie Sie möchten:

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+

Bitte beachten Sie, dass seit Django 1.7 die Verwendung von Lambda als Standardwert nicht empfohlen wird (siehe Kommentar @stvnw). Der richtige Weg, dies zu tun, besteht darin, eine Funktion vor dem Feld zu deklarieren und sie als aufrufbar in default_value mit dem Namen arg zu verwenden:

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)

Weitere Informationen finden Sie in der Antwort von @simanas unten

Ned Batchelder
quelle
2
Danke @NedBatchelder. Genau das habe ich gesucht. Ich wusste nicht, dass 'default' einen Callable annehmen kann. Und jetzt sehe ich, wie meine Frage in Bezug auf Funktionsaufruf und Funktionsdefinition tatsächlich falsch war.
Rob Bednark
25
Beachten Sie, dass für Django> = 1.7 die Verwendung von Lambdas für Feldoptionen nicht empfohlen wird, da diese nicht mit Migrationen kompatibel sind. Ref: Docs , Ticket
StvnW
4
Bitte deaktivieren Sie diese Antwort, da sie für Djange> = 1.7 falsch ist. Die Antwort von @Simanas ist absolut richtig und sollte daher akzeptiert werden.
AplusKminus
Wie funktioniert das bei Migrationen? Erhalten alle alten Objekte ein Datum basierend auf dem Zeitpunkt der Migration? Ist es möglich, einen anderen Standard für Migrationen festzulegen?
Timthelion
3
Was ist, wenn ich einen Parameter übergeben möchte get_default_my_date?
Sadan A.
59

Das zu tun default=datetime.now()+timedelta(days=1)ist absolut falsch!

Es wird ausgewertet, wenn Sie Ihre Instanz von Django starten. Wenn Sie unter Apache sind, wird es wahrscheinlich funktionieren, da Apache bei einigen Konfigurationen Ihre Django-Anwendung bei jeder Anfrage widerruft. Trotzdem können Sie eines Tages Ihren Code durchsehen und herausfinden, warum dies nicht wie erwartet berechnet wird .

Der richtige Weg, dies zu tun, besteht darin, ein aufrufbares Objekt an das Standardargument zu übergeben. Dies kann eine datetime.today-Funktion oder eine benutzerdefinierte Funktion sein. Dann wird es jedes Mal ausgewertet, wenn Sie einen neuen Standardwert anfordern.

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)
Simanas
quelle
21
Wenn meine Standardeinstellung auf dem Wert eines anderen Felds im Modell basiert, ist es möglich, dieses Feld als Parameter zu übergeben, wie in so etwas wie get_deadline(my_parameter)?
YPCrumble
@YPCrumble das sollte eine neue Frage sein!
jsmedmar
2
Nee. Das ist doch nicht möglich. Sie müssen einen anderen Ansatz verwenden, um dies zu tun.
Simanas
Wie entfernst du das deadlineFeld wieder und löschst gleichzeitig die get_deadlineFunktion? Ich habe ein Feld mit einer Funktion für einen Standardwert entfernt, aber jetzt stürzt Django beim Start ab, nachdem die Funktion entfernt wurde. Ich könnte die Migration manuell bearbeiten, was in diesem Fall in Ordnung wäre, aber was wäre, wenn Sie einfach die Standardfunktion ändern und die alte Funktion entfernen möchten?
Berühmte
8

Es gibt einen wichtigen Unterschied zwischen den folgenden beiden DateTimeField-Konstruktoren:

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

Wenn Sie auto_now_add=Trueim Konstruktor verwenden, ist die von my_date referenzierte Datums- und Uhrzeitangabe "unveränderlich" (nur einmal festgelegt, wenn die Zeile in die Tabelle eingefügt wird).

Mit auto_now=Truewird der Datum / Uhrzeit-Wert jedoch jedes Mal aktualisiert, wenn das Objekt gespeichert wird.

Dies war definitiv ein Problem für mich an einem Punkt. Als Referenz finden Sie die Dokumente hier:

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

Damzam
quelle
3
Sie haben Ihre Definitionen von auto_now und auto_now_add verwechselt, es ist umgekehrt.
Michael Bates
@ MichaelBates jetzt besser?
Hendy Irawan
4

Manchmal müssen Sie nach dem Erstellen eines neuen Benutzermodells möglicherweise auf Modelldaten zugreifen.

So generiere ich ein Token für jedes neue Benutzerprofil mit den ersten 4 Zeichen seines Benutzernamens:

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)))
CoreCreatives
quelle
3

Das kann man nicht direkt machen; Der Standardwert wird ausgewertet, wenn die Funktionsdefinition ausgewertet wird. Es gibt jedoch zwei Möglichkeiten.

Zuerst können Sie jedes Mal eine neue Funktion erstellen (und dann aufrufen).

Oder verwenden Sie einfach einen speziellen Wert, um die Standardeinstellung zu markieren. Beispielsweise:

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

Wenn Nonees sich um einen absolut vernünftigen Parameterwert handelt und es keinen anderen vernünftigen Wert gibt, den Sie an seiner Stelle verwenden könnten, können Sie einfach einen neuen Wert erstellen, der definitiv außerhalb des Bereichs Ihrer Funktion liegt:

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

In einigen seltenen Fällen schreiben Sie Metacode, der wirklich absolut alles aufnehmen muss, auch nicht mydate.func_defaults[0]. In diesem Fall müssen Sie Folgendes tun:

def mydate(*args, **kw):
  if 'date' in kw:
    date = kw['date']
  elif len(args):
    date = args[0]
  else:
    date = datetime.now()
  print date
abarnert
quelle
1
Beachten Sie, dass es keinen Grund gibt, die Dummy-Wertklasse tatsächlich zu instanziieren. Verwenden Sie einfach die Klasse selbst als Dummy-Wert.
Amber
Sie können dies direkt tun, indem Sie einfach die Funktion anstelle des Ergebnisses des Funktionsaufrufs übergeben. Siehe meine gepostete Antwort.
Nein, kannst du nicht. Das Ergebnis der Funktion ist nicht das gleiche wie die normalen Parameter, daher kann es vernünftigerweise nicht als Standardwert für diese normalen Parameter verwendet werden.
abarnert
1
Ja, wenn Sie ein Objekt anstelle einer Funktion übergeben, wird eine Ausnahme ausgelöst. Das Gleiche gilt, wenn Sie eine Funktion an einen Parameter übergeben, der eine Zeichenfolge erwartet. Ich sehe nicht, wie relevant das ist.
Es ist relevant, weil seine myfuncFunktion darin besteht, a zu nehmen (und zu drucken) datetime; Sie haben es geändert, damit es nicht so verwendet werden kann.
abarnert
2

Übergeben Sie die Funktion als Parameter, anstatt das Ergebnis des Funktionsaufrufs zu übergeben.

Das heißt, stattdessen:

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

Versuche dies:

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

quelle
Dies funktioniert nicht, da der Benutzer jetzt keinen Parameter mehr übergeben kann. Sie werden versuchen, ihn als Funktion aufzurufen und eine Ausnahme auszulösen. In diesem Fall können Sie genauso gut und print datetime.now()bedingungslos keine Parameter übernehmen .
Abarnert
Versuch es. Sie übergeben kein Datum, sondern die Funktion datetime.now.
Ja, die Standardeinstellung ist die Übergabe der datetime.nowFunktion. Jeder Benutzer, der einen nicht standardmäßigen Wert übergibt, übergibt jedoch eine Datums- und Uhrzeitangabe, keine Funktion. foo = datetime.now(); myfunc(foo)wird erhöhen TypeError: 'datetime.datetime' object is not callable. Wenn ich Ihre Funktion tatsächlich nutzen wollte, müsste ich myfunc(lambda: foo)stattdessen so etwas tun , was keine vernünftige Schnittstelle ist.
Abarnert
1
Schnittstellen, die Funktionen akzeptieren, sind keine Seltenheit. Ich könnte anfangen, Beispiele aus der Python-Stdlib zu benennen, wenn Sie möchten.
2
Django akzeptiert bereits ein Callable genau so, wie es diese Frage nahelegt.
Ned Batchelder