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

101

ИЗМЕНЕНО:

Как я могу установить в поле 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))



ОРИГИНАЛ:

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

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))
Роб Беднарк
источник
Вопрос сформулирован неправильно: это не имеет ничего общего с значениями параметров функции по умолчанию. Смотрите мой ответ.
Нед Бэтчелдер,
Согласно комментарию @ NedBatchelder, я отредактировал свой вопрос (обозначен как «EDITED:») и оставил оригинал (обозначенный как «ORIGINAL:»)
Роб Беднарк

Ответы:

141

Вопрос неверный. При создании поля модели в 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 использование лямбда в качестве значения по умолчанию не рекомендуется (см. Комментарий @stvnw). Правильный способ сделать это - объявить функцию перед полем и использовать ее как вызываемый объект в 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
Спасибо @NedBatchelder. Это именно то, что я искал. Я не понимал, что «по умолчанию» можно вызвать. И теперь я понимаю, насколько ошибочным был мой вопрос относительно вызова функции и определения функции.
Роб Беднарк,
25
Обратите внимание, что для Django> = 1.7 использование лямбда-выражений для параметров поля не рекомендуется, поскольку они несовместимы с миграциями. Ссылка: Документы , билет
StvnW
4
Снимите отметку с этого ответа, так как он неверен для Djange> = 1.7. Ответ @Simanas абсолютно правильный, и поэтому его следует принять.
AplusKminus
Как это работает с миграциями? Все ли старые объекты получают дату, основанную на времени миграции? Можно ли установить другое значение по умолчанию для использования с миграциями?
Timthelion 07
3
Что, если я хочу передать какой-то параметр get_default_my_date?
Садан А.
59

Делая это 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)
Симанас
источник
21
Если мое значение по умолчанию основано на значении другого поля в модели, можно ли передать это поле в качестве параметра, например get_deadline(my_parameter)?
YPCrumble
@YPCrumble, это должен быть новый вопрос!
jsmedmar
2
Нет. Это невозможно. Для этого вам придется использовать другой подход.
Симанас
Как вы deadlineснова удалите поле, одновременно удалив get_deadlineфункцию? Я удалил поле с функцией для значения по умолчанию, но теперь Django вылетает при запуске после удаления функции. Я мог бы вручную отредактировать миграцию, что в данном случае было бы нормально, но что, если бы вы просто изменили функцию по умолчанию и захотели удалить старую функцию?
beruic
8

Между следующими двумя конструкторами 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, однако, значение даты и времени будет обновляться каждый раз , когда объект будет сохранен.

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

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

дамзам
источник
3
Вы перепутали свои определения auto_now и auto_now_add, все наоборот.
Майкл Бейтс
@MichaelBates теперь лучше?
Хенди Ираван
4

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

Вот как я генерирую токен для каждого нового профиля пользователя, используя первые 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)))
CoreCreatives
источник
3

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

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

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

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
Abarnert
источник
1
Обратите внимание, что нет никаких причин создавать экземпляр класса фиктивного значения - просто используйте сам класс как фиктивное значение.
Эмбер
Вы МОЖЕТЕ сделать это напрямую, просто передав функцию вместо результата ее вызова. См. Мой опубликованный ответ.
Нет, не можешь. Результат функции отличается от обычных параметров, поэтому его нельзя разумно использовать в качестве значения по умолчанию для этих обычных параметров.
abarnert
1
Ну да, если вы передадите объект вместо функции, это вызовет исключение. То же самое верно, если вы передаете функцию в параметр, ожидающий строку. Я не понимаю, насколько это актуально.
Это актуально, потому что его myfuncфункция состоит в том, чтобы брать (и печатать) a datetime; вы изменили его, поэтому его нельзя использовать таким образом.
abarnert
2

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

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

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

Попробуй это:

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

источник
Это не сработает, потому что теперь пользователь не может фактически передать параметр - вы попытаетесь вызвать его как функцию и выбросить исключение. В этом случае вы можете просто взять без параметров и print datetime.now()безоговорочно.
abarnert
Попытайся. Вы не передаете дату, вы передаете функцию datetime.now.
Да, по умолчанию передается datetime.nowфункция. Но любой пользователь, который передает значение, отличное от значения по умолчанию, будет передавать дату и время, а не функцию. foo = datetime.now(); myfunc(foo)поднимет TypeError: 'datetime.datetime' object is not callable. Если бы я действительно хотел использовать вашу функцию, мне пришлось бы сделать что-то вроде myfunc(lambda: foo)этого, что не является разумным интерфейсом.
abarnert
1
Интерфейсы, которые принимают функции, не являются чем-то необычным. Если хотите, я мог бы начать называть примеры из Python stdlib.
2
Django уже принимает вызываемый объект именно так, как предполагает этот вопрос.
Нед Батчелдер,