Як встановити значення за замовчуванням поля моделі 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))

Питання неправильне: це не має нічого спільного з типовими параметрами функції. Дивіться мою відповідь.
Нед Батчелдер

За коментарем Per @ NedBatchelder я відредагував своє запитання (вказане як "ЗДОРОВАНО:") і залишив оригінал (вказаний як "ОРИГІНАЛЬНИЙ:")
Роб Беднарк

Відповіді:


140

Питання помилкове. Створюючи модельне поле в 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))

Джанго 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

3
Що робити, якщо я хочу передати якийсь параметр get_default_my_date?
Садан А.

59

Робити це default=datetime.now()+timedelta(days=1)абсолютно неправильно!

Це оцінюється під час запуску екземпляра джанго. Якщо ви перебуваєте під апашем, це, ймовірно, спрацює, тому що в деяких конфігураціях апаш відкликає вашу програму 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 виходить з ладу при запуску після видалення функції. Я міг би вручну відредагувати міграцію, що було б нормально в цьому випадку, але що робити, якщо ви просто змінили функцію за замовчуванням і захотіли видалити стару функцію?
Беруїк

8

Існує важлива відмінність між двома наступними конструкторами DateTimeField:

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

Якщо ви використовуєте auto_now_add=Trueв конструкторі, час дати, на який посилається 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)))

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

1
Зауважте, що немає жодної причини насправді інсталювати цільовий клас манекенів - просто використовуйте сам клас як фіктивне значення.
Бурштинова

МОЖЕТЕ це зробити безпосередньо, просто передайте функцію замість результату виклику функції. Дивіться мою відповідь.

Ні, ти не можеш. Результат функції не є тим самим типом, що і звичайні параметри, тому він не може розумно використовуватись як значення за замовчуванням для цих нормальних параметрів.
abarnert

1
Ну так, якщо ви передаєте об'єкт замість функції, він створить виняток. Те саме відбувається, якщо ви передаєте функцію в параметр, який очікує рядок. Я не бачу, наскільки це актуально.

Це актуально, оскільки його myfuncфункція покликана брати (та друкувати) а 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
Джанго вже приймає дзвінок саме так, як це питання пропонує.
Нед Батчелдер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.