web-dev-qa-db-fra.com

Comment définir un Django sur un appel de fonction / appelable (par exemple, une date relative à l'heure de création de l'objet de modèle))

édité:

Comment définir une valeur par défaut du champ Django) sur une fonction qui est évaluée chaque fois qu'un nouvel objet de modèle est créé?

Je veux faire quelque chose comme ce qui suit, sauf que dans ce code, le code est évalué une fois et définit la valeur par défaut à la même date pour chaque objet de modèle créé, plutôt que d'évaluer le code chaque fois qu'un objet de modèle est créé:

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:

Je veux créer une valeur par défaut pour un paramètre de fonction tel qu'il soit dynamique et qu'il soit appelé et défini à chaque appel de la fonction. Comment puis je faire ça? par exemple.,

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

Plus précisément, je veux le faire dans Django, par exemple,

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))
85
Rob Bednark

La question est erronée. Lorsque vous créez un champ de modèle dans Django, vous ne définissez pas de fonction. Par conséquent, les valeurs par défaut des fonctions ne sont pas pertinentes:

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

Cette dernière ligne ne définit pas une fonction; il appelle une fonction pour créer un champ dans la classe.

PRE Django 1.7

Django vous permet de passer un appelable par défaut , et l'invoquera à chaque fois, comme vous le souhaitez:

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 +

Veuillez noter que depuis Django 1.7, l’utilisation de lambda comme valeur par défaut n’est pas recommandée (cf commentaire @stvnw). La bonne façon de procéder consiste à déclarer une fonction avant le champ et l'utiliser comme appelable dans default_value nommée 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)

Plus d'informations dans la réponse @simanas ci-dessous

125
Ned Batchelder

Faire ceci default=datetime.now()+timedelta(days=1) est absolument faux!

Il est évalué lorsque vous démarrez votre instance de Django. Si vous êtes sous Apache, cela fonctionnera probablement, car sur certaines configurations, Apache révoque votre application Django à chaque requête, mais vous pouvez toujours vous retrouver vous-même un jour en parcourant votre code et en essayant de comprendre. pourquoi cela ne se calcule pas comme prévu.

La bonne façon de faire est de passer un objet appelable à l'argument par défaut. Ce peut être une fonction datetime.today ou votre fonction personnalisée. Ensuite, il est évalué chaque fois que vous demandez une nouvelle valeur par défaut.

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

Il existe une distinction importante entre les deux constructeurs DateTimeField suivants:

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

Si tu utilises auto_now_add=True dans le constructeur, la date/heure référencée par my_date est "immuable" (définie une seule fois lorsque la ligne est insérée dans la table).

Avec auto_now=True, cependant, la valeur datetime sera mise à jour chaque fois que l'objet est enregistré.

Ce fut certainement un getcha pour moi à un moment donné. Pour référence, les documents sont ici:

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

6
damzam

Transmettez la fonction en tant que paramètre au lieu de transmettre le résultat de l'appel de la fonction.

C'est à la place de ceci:

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

Essaye ça:

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

Vous ne pouvez pas faire cela directement; la valeur par défaut est évaluée lors de l'évaluation de la définition de la fonction. Mais il y a deux façons de le contourner.

Tout d'abord, vous pouvez créer (puis appeler) une nouvelle fonction à chaque fois.

Ou, plus simplement, utilisez simplement une valeur spéciale pour marquer la valeur par défaut. Par exemple:

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

Si None est une valeur de paramètre parfaitement raisonnable et qu'aucune autre valeur raisonnable ne peut être utilisée à sa place, vous pouvez simplement créer une nouvelle valeur qui est définitivement en dehors du domaine de votre fonction:

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

Dans de rares cas, vous écrivez du méta-code qui doit vraiment pouvoir prendre absolument n'importe quoi, même, disons, mydate.func_defaults[0]. Dans ce cas, vous devez faire quelque chose comme ceci:

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

Parfois, vous devrez peut-être accéder aux données du modèle après avoir créé un nouveau modèle utilisateur.

Voici comment je génère un jeton pour chaque nouveau profil utilisateur en utilisant les 4 premiers caractères de leur nom d'utilisateur:

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