proyecto - Cómo establecer el valor predeterminado de un campo de modelo Django en una llamada de función/invocable(por ejemplo, una fecha relativa al momento de creación del objeto modelo)
modelos en django (6)
EDITADO:
¿Cómo puedo establecer el campo predeterminado de Django en una función que se evalúa cada vez que se crea un nuevo objeto modelo?
Quiero hacer algo como lo siguiente, excepto que en este código, el código se evalúa una vez y establece el valor predeterminado en la misma fecha para cada objeto de modelo creado, en lugar de evaluar el código cada vez que se crea un objeto modelo:
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:
Quiero crear un valor predeterminado para un parámetro de función tal que sea dinámico y se llame y configure cada vez que se llame a la función. ¿Cómo puedo hacer eso? p.ej,
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
Específicamente, quiero hacerlo en Django, por ejemplo,
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))
En ocasiones, es posible que necesite acceder a los datos del modelo después de crear un nuevo modelo de usuario.
Así es como genero un token para cada nuevo perfil de usuario usando los primeros 4 caracteres de su nombre de usuario:
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)))
Hacer esto default=datetime.now()+timedelta(days=1)
es absolutamente incorrecto!
Se evalúa cuando comienza su instancia de django. Si estás bajo apache, probablemente funcione, porque en algunas configuraciones apache revoca tu aplicación django en cada solicitud, pero aún puedes encontrarte algún día revisando tu código e intentando averiguar por qué esto no se calcula como esperabas .
La forma correcta de hacerlo es pasar un objeto invocable al argumento predeterminado. Puede ser una función datetime.today o su función personalizada. Luego se evalúa cada vez que solicita un nuevo valor predeterminado.
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)
Hay una distinción importante entre los siguientes dos constructores DateTimeField:
my_date = models.DateTimeField(auto_now=True)
my_date = models.DateTimeField(auto_now_add=True)
Si usa auto_now_add=True
en el constructor, la fecha a la que hace referencia my_date es "inmutable" (solo se establece una vez cuando la fila se inserta en la tabla).
Con auto_now=True
, sin embargo, el valor de fecha y hora se actualizará cada vez que se guarde el objeto.
Esto fue definitivamente un problema para mí en un punto. Como referencia, los documentos están aquí:
https://docs.djangoproject.com/en/dev/ref/models/fields/#datetimefield
La pregunta es equivocada. Al crear un campo de modelo en Django, no está definiendo una función, por lo que los valores predeterminados de la función son irrelevantes:
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))
Esta última línea no define una función; está invocando una función para crear un campo en la clase.
PRE Django 1.7
Django le permite pasar un invocable como predeterminado y lo invocará cada vez, tal como lo desea:
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+
Tenga en cuenta que desde Django 1.7, no se recomienda el uso de lambda como valor predeterminado (cf @stvnw comment). La forma correcta de hacerlo es declarar una función antes del campo y usarla como un valor invocable en default_value llamado 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)
Más información en la respuesta de @simanas a continuación
No puedes hacer eso directamente; el valor predeterminado se evalúa cuando se evalúa la definición de la función. Pero hay dos formas de evitarlo.
Primero, puede crear (y luego llamar) una nueva función cada vez.
O, más simplemente, simplemente use un valor especial para marcar el valor predeterminado. Por ejemplo:
from datetime import datetime
def mydate(date=None):
if date is None:
date = datetime.now()
print date
Si None
es un valor de parámetro perfectamente razonable, y no hay otro valor razonable que pueda usar en su lugar, puede crear un valor nuevo que esté definitivamente fuera del dominio de su función:
from datetime import datetime
class _MyDateDummyDefault(object):
pass
def mydate(date=_MyDateDummyDefault):
if date is _MyDateDummyDefault:
date = datetime.now()
print date
del _MyDateDummyDefault
En algunos casos excepcionales, está escribiendo meta-código que realmente necesita poder tomar absolutamente cualquier cosa, incluso, por ejemplo, mydate.func_defaults[0]
. En ese caso, debes hacer algo como esto:
def mydate(*args, **kw):
if ''date'' in kw:
date = kw[''date'']
elif len(args):
date = args[0]
else:
date = datetime.now()
print date
Pase la función como un parámetro en lugar de pasar el resultado de la llamada a la función.
Es decir, en lugar de esto:
def myfunc(date=datetime.now()):
print date
Prueba esto:
def myfunc(date=datetime.now):
print date()