如何将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))

这个问题的措词是错误的:与功能参数默认值无关。看我的答案。
Ned Batchelder 2012年

根据@NedBatchelder的评论,我编辑了问题(指示为“已编辑:”),并保留了原始问题(指示为“原件:”)
Rob Bednark 2014年

Answers:


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

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

Django 1.7之前的版本

Django 允许您将callable作为默认值传递,并且每次都会调用它,如您所愿:

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答案


2
谢谢@NedBatchelder。这正是我想要的。我没有意识到“默认”可能会被调用。现在,我看到我的问题在函数调用与函数定义之间确实存在误导。
Rob Bednark 2012年

25
请注意,对于Django> = 1.7,建议不要对字段选项使用lambda,因为它们与迁移不兼容。参考:文件票证
StvnW 2014年

4
请取消选中此答案,因为Djange> = 1.7是错误的。@Simanas的回答是绝对正确的,因此应该接受。
AplusKminus 2015年

迁移如何运作?是否所有旧对象都会根据迁移时间获得日期?是否可以设置要用于迁移的其他默认值?
timthelion

3
如果我要将一些参数传递给该get_default_my_date怎么办?
Sadan A.

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 2015年

@YPCrumble这应该是一个新问题!
jsmedmar '16

2
不。那不可能 您将不得不使用一些不同的方法来做到这一点。
Simanas

deadline在删除get_deadline功能的同时如何再次删除该字段?我已经删除了带有默认值功能的字段,但是现在Django在删除该功能后会在启动时崩溃。我可以手动编辑迁移,在这种情况下可以的,但是如果您仅更改默认功能并想要删除旧功能怎么办?
2016年

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但是,使用时,每次保存对象时都会更新datetime值。

从某种意义上说,这绝对是我的陷阱。供参考,文档在这里:

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
请注意,没有理由实际实例化伪值类-只需将类本身用作伪值即可。
2012年

您可以直接执行此操作,只需传入函数而不是函数调用的结果即可。查看我发布的答案。

不,你不能。函数的结果与普通参数的类型不同,因此不能合理地将其用作这些普通参数的默认值。
abarnert's

1
好吧,是的,如果传入一个对象而不是一个函数,它将引发异常。如果将函数传递给需要字符串的参数,则同样如此。我不知道这有什么关系。

这很重要,因为他的myfunc功能是采用(并打印)a datetime;您已对其进行了更改,因此不能以这种方式使用。
abarnert's

2

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

也就是说,代替此:

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

试试这个:

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

这是行不通的,因为现在用户实际上无法传递参数-您将尝试将其作为函数调用并引发异常。在这种情况下,您也可以毫无print datetime.now()条件地接受任何参数。
abarnert 2012年

试试吧。您没有传递日期,而是传递了datetime.now函数。

是的,默认值是传递datetime.now函数。但是,任何传递非默认值的用户都将传递日期时间,而不是函数。foo = datetime.now(); myfunc(foo)会提高TypeError: 'datetime.datetime' object is not callable。如果我确实想使用您的函数,则必须做类似的事情myfunc(lambda: foo),这不是一个合理的接口。
abarnert's

1
接受功能的接口并不常见。如果愿意,我可以从python stdlib开始命名示例。

2
Django已完全按照此问题的建议接受了可调用对象。
Ned Batchelder 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.