如何将 Django 模型字段的默认值设置为函数调用/可调用(例如,相对于模型对象创建时间的日期)

Posted

技术标签:

【中文标题】如何将 Django 模型字段的默认值设置为函数调用/可调用(例如,相对于模型对象创建时间的日期)【英文标题】:How to set a Django model field's default value to a function call / callable (e.g., a date relative to the time of model object creation) 【发布时间】:2012-09-20 21:47:49 【问题描述】:

已编辑:

如何将 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:”) 【参考方案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))

最后一行没有定义函数;它正在调用一个函数来在类中创建一个字段。

在这种情况下,datetime.now() + timedelta(days=1) 将被评估一次,并存储为默认值。

PRE Django 1.7

Django [让您将可调用对象作为默认值传递][1],它每次都会调用它,就像您想要的那样:

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 作为默认值(参见 @stvnw 评论)。正确的做法是在字段之前声明一个函数,并将其用作名为 arg 的 default_value 中的可调用对象:

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 答案中的更多信息 [1]:https://docs.djangoproject.com/en/dev/ref/models/fields/#default

【讨论】:

感谢@NedBatchelder。这正是我一直在寻找的。我没有意识到“默认”可以调用。现在我看到我的问题在函数调用与函数定义方面确实被误导了。 请注意,对于 Django>=1.7,不建议对字段选项使用 lambda,因为它们与迁移不兼容。参考:Docs,ticket 请取消选中此答案,因为 Djange>=1.7 是错误的。 @Simanas 的回答是绝对正确的,因此应该被接受。 这如何与迁移一起工作?是否所有旧对象都会根据迁移时间获得日期?是否可以设置不同的默认值以用于迁移? 如果我想将一些参数传递给get_default_my_date怎么办?【参考方案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)

【讨论】:

如果我的默认值基于模型中另一个字段的值,是否可以像 get_deadline(my_parameter) 那样将该字段作为参数传递? @YPCrumble 这应该是一个新问题! 不。那是不可能的。您将不得不使用一些不同的方法来做到这一点。 如何在删除get_deadline函数的同时再次删除deadline字段?我已经删除了一个带有默认值函数的字段,但是现在 Django 在删除该函数后在启动时崩溃。我可以手动编辑迁移,在这种情况下可以,但是如果您只是更改了默认功能,并想删除旧功能怎么办? default=datetime.today 不适用于 DJango 版本 3.2,我的数据库是 mysql,运行您的代码仍将默认值设置为 None。【参考方案3】:

以下两个 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

【讨论】:

你把 auto_now 和 auto_now_add 的定义搞混了,反之亦然。 @MichaelBates 现在好点了吗? default=datetime.today 不适用于 DJango 3.2 版,我的数据库是 MySQL,运行您的代码仍将默认值设置为 None。【参考方案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)))

【讨论】:

【参考方案5】:

你不能直接这样做;评估函数定义时评估默认值。但是有两种解决方法。

首先,您可以每次都创建(然后调用)一个新函数。

或者,更简单地说,只是使用一个特殊的值来标记默认值。例如:

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

【讨论】:

请注意,没有理由实际实例化虚拟值类 - 只需将类本身用作虚拟值即可。 你可以直接这样做,只是传入函数而不是函数调用的结果。请参阅我发布的答案。 不,你不能。函数的结果与普通参数不是同一类型的东西,因此不能合理地用作这些普通参数的默认值。 嗯,是的,如果你传入一个对象而不是函数,它会引发异常。如果将函数传递给期望字符串的参数,情况也是如此。我不明白这有什么关系。 这很重要,因为他的myfunc 函数用于获取(并打印)datetime;你已经改变了它,所以它不能这样使用。【参考方案6】:

将函数作为参数传入,而不是传入函数调用的结果。

也就是说,而不是这个:

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

试试这个:

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

【讨论】:

这不起作用,因为现在用户实际上无法传递参数——您将尝试将其作为函数调用并引发异常。在这种情况下,您不妨无条件地不带参数和print datetime.now() 试试看。您不是在传递日期,而是在传递 datetime.now 函数。 是的,默认是传递datetime.now函数。但是任何传递非默认值的用户都将传递日期时间,而不是函数。 foo = datetime.now(); myfunc(foo) 将提高 TypeError: 'datetime.datetime' object is not callable。如果我真的想使用你的功能,我不得不做类似myfunc(lambda: foo) 的事情,这不是一个合理的接口。 接受函数的接口并不罕见。如果你愿意,我可以从 python 标准库开始命名示例。 Django 已经完全按照这个问题的建议接受了一个可调用对象。

以上是关于如何将 Django 模型字段的默认值设置为函数调用/可调用(例如,相对于模型对象创建时间的日期)的主要内容,如果未能解决你的问题,请参考以下文章

将年份设置为默认值Django

如何使用数据为现有模型中的新模型字段设置默认值

Django 模型字段可调用默认值不起作用

使用 Django,如何在模板中动态设置 ModelForm 字段值?

Django 模型字段默认为 Null

如何获取 Django 模型中字段的默认值?