Как установить значение по умолчанию для поля модели Django для вызова функции/вызываемого (например, даты относительно времени создания объекта модели)

Редакция:

Как установить поле Django по умолчанию для функции, которая получает оценку каждый раз, когда создается новый объект модели?

Я хочу сделать что-то вроде следующего, за исключением того, что в этом коде код получает оценку один раз и устанавливает по умолчанию одну и ту же дату для каждого созданного объекта модели, а не оценивает код каждый раз, когда создается объект модели:

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:

Я хочу создать значение по умолчанию для параметра функции, чтобы он был динамическим и вызывался и устанавливался каждый раз при вызове функции. Как я могу это сделать? например.

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

В частности, я хочу сделать это в Django, например,

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

Ответ 1

Вопрос ошибочен. При создании поля модели в Django вы не определяете функцию, поэтому значения по умолчанию для функции не имеют значения:

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

Эта последняя строка не определяет функцию; он вызывает функцию для создания поля в классе.

PRE Django 1.7

Django позволяет передавать вызываемый как по умолчанию, и он будет вызывать его каждый раз, как вы хотите:

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 +

Обратите внимание, что поскольку Django 1.7, использование lambda в качестве значения по умолчанию не рекомендуется (c.f. @stvnw comment). Правильный способ сделать это - объявить функцию до и использовать ее как вызываемую в default_value с именем 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)

Дополнительная информация в ответе @simanas ниже

Ответ 2

Выполнение этого default=datetime.now()+timedelta(days=1) абсолютно неверно!

Он оценивается при запуске вашего экземпляра django. Если вы находитесь под apache, это, вероятно, будет работать, потому что в некоторых конфигурациях apache аннулирует ваше приложение django по каждому запросу, но все же вы можете найти себя в какой-то день, просматривая свой код и пытаясь понять, почему это рассчитывается не так, как вы ожидаете,

Правильный способ сделать это - передать аргумент вызываемого объекта по умолчанию. Это может быть функция datetime.today или ваша пользовательская функция. Затем он получает оценку каждый раз, когда вы запрашиваете новое значение по умолчанию.

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)

Ответ 3

Существует важное различие между следующими двумя конструкторами DateTimeField:

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

Если вы используете auto_now_add=True в конструкторе, время datetime, на которое ссылается my_date, является "неизменным" (устанавливается только один раз, когда строка вставлена ​​в таблицу).

При auto_now=True значение datetime будет обновляться каждый раз, когда объект сохраняется.

Это определенно было для меня в какой-то момент. Для справки, документы находятся здесь:

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

Ответ 4

Вы не можете сделать это напрямую; значение по умолчанию оценивается при вычислении определения функции. Но есть два пути.

Во-первых, вы можете каждый раз создавать (а затем и вызывать) новую функцию.

Или, проще говоря, просто используйте специальное значение для отметки по умолчанию. Например:

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

Если None - вполне разумное значение параметра, и нет другого разумного значения, которое вы могли бы использовать на своем месте, вы можете просто создать новое значение, которое определенно вне домена вашей функции:

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

В некоторых редких случаях вы пишете метакод, который действительно должен иметь возможность принимать абсолютно все, даже, скажем, mydate.func_defaults[0]. В этом случае вам нужно сделать что-то вроде этого:

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

Ответ 5

Передайте функцию в качестве параметра вместо передачи в результате вызова функции.

То есть вместо этого:

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

Попробуйте следующее:

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

Ответ 6

Иногда вам может потребоваться доступ к данным модели после создания новой модели пользователя.

Вот как я генерирую токен для каждого нового пользовательского профиля, используя первые 4 символа их имени пользователя:

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