วิธีตั้งค่าเริ่มต้นของฟิลด์โมเดล Django เป็นการเรียกใช้ฟังก์ชัน / เรียกได้ (เช่นวันที่ที่สัมพันธ์กับเวลาของการสร้างโมเดลอ็อบเจ็กต์)


102

แก้ไข:

ฉันจะตั้งค่าเริ่มต้นของฟิลด์ 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

ต่อความคิดเห็นของ @ NedBatchelder ฉันแก้ไขคำถามของฉัน (ระบุว่า "แก้ไข:") และทิ้งต้นฉบับไว้ (ระบุว่า "ORIGINAL:")
Rob Bednark

คำตอบ:


145

คำถามนี้เข้าใจผิด เมื่อสร้างฟิลด์โมเดลใน 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 ให้คุณส่งผ่าน 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 เป็นค่าเริ่มต้น (cf @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
ขอบคุณ @NedBatchelder นี่คือสิ่งที่ฉันกำลังมองหา ฉันไม่ทราบว่า 'ค่าเริ่มต้น' สามารถเรียกใช้ได้ และตอนนี้ฉันเห็นว่าคำถามของฉันเข้าใจผิดเกี่ยวกับการเรียกฟังก์ชันเทียบกับนิยามฟังก์ชันอย่างไร
Rob Bednark

26
โปรดทราบว่าสำหรับ Django> = 1.7 ไม่แนะนำให้ใช้ lambdas สำหรับอ็อพชันฟิลด์เนื่องจากไม่เข้ากันกับการโอนย้าย Ref: เอกสาร , ตั๋ว
StvnW

4
โปรดยกเลิกการเลือกคำตอบนี้เนื่องจาก Djange ไม่ถูกต้อง> = 1.7 คำตอบของ @Simanas นั้นถูกต้องและควรได้รับการยอมรับ
AplusKminus

วิธีนี้ทำงานกับการย้ายข้อมูลอย่างไร วัตถุเก่าทั้งหมดได้รับวันที่ตามเวลาที่ย้ายข้อมูลหรือไม่ เป็นไปได้ไหมที่จะตั้งค่าเริ่มต้นอื่นที่จะใช้กับการย้ายข้อมูล
timthelion

6
จะเกิดอะไรขึ้นถ้าฉันต้องการส่งผ่านพารามิเตอร์ไป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)

22
หากเริ่มต้นของฉันจะขึ้นอยู่กับค่าของฟิลด์อีกในรูปแบบที่มันเป็นไปได้ที่จะส่งผ่านข้อมูลที่เป็นพารามิเตอร์เป็นในสิ่งที่ชอบget_deadline(my_parameter)?
YPCrumble

@YPCrumble นี่น่าจะเป็นคำถามใหม่!
jsmedmar

2
ไม่ มันเป็นไปไม่ได้. คุณจะต้องใช้วิธีการที่แตกต่างกันในการทำ
Simanas

คุณจะลบ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ในตัวสร้างวันที่และเวลาที่อ้างถึงโดย my_date จะเป็น "ไม่เปลี่ยนรูป" (ตั้งค่าเพียงครั้งเดียวเมื่อแทรกแถวเข้ากับตาราง)

ด้วยauto_now=Trueแต่คุ้มค่าวันที่และเวลาจะมีการปรับปรุงทุกครั้งที่วัตถุที่ถูกบันทึกไว้

นี่เป็น gotcha สำหรับฉันในช่วงหนึ่ง สำหรับการอ้างอิงเอกสารอยู่ที่นี่:

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


3
คุณมีคำจำกัดความของ auto_now และ auto_now_add ผสมกันแล้วมันเป็นอีกทางหนึ่ง
Michael Bates

@MichaelBates ดีกว่าตอนนี้?
Hendy Irawan

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]meta-รหัสที่จริงๆไม่จำเป็นที่จะต้องสามารถที่จะเอาอะไรอย่างแม้กระทั่งการพูด ในกรณีนี้คุณต้องทำสิ่งนี้:

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

1
โปรดทราบว่าไม่มีเหตุผลที่จะสร้างอินสแตนซ์คลาสค่าดัมมี่จริง ๆ เพียงแค่ใช้คลาสตัวเองเป็นค่าดัมมี่
Amber

คุณสามารถทำได้โดยตรงเพียงแค่ส่งผ่านฟังก์ชันแทนผลลัพธ์ของการเรียกใช้ฟังก์ชัน ดูคำตอบที่โพสต์ของฉัน

ไม่คุณทำไม่ได้ ผลลัพธ์ของฟังก์ชันไม่ได้เป็นประเภทเดียวกันกับพารามิเตอร์ปกติดังนั้นจึงไม่สามารถใช้เป็นค่าเริ่มต้นสำหรับพารามิเตอร์ปกติเหล่านั้นได้อย่างสมเหตุสมผล
abarnert

1
ใช่ถ้าคุณส่งผ่านวัตถุแทนที่จะเป็นฟังก์ชันจะทำให้เกิดข้อยกเว้น เช่นเดียวกับที่คุณส่งผ่านฟังก์ชันไปยังพารามิเตอร์ที่คาดหวังสตริง ฉันไม่เห็นว่ามันเกี่ยวข้องอย่างไร

มันเกี่ยวข้องเพราะmyfuncหน้าที่ของเขาถูกสร้างขึ้นเพื่อใช้ (และพิมพ์) a datetime; คุณได้ทำการเปลี่ยนแปลงดังนั้นจึงไม่สามารถใช้ในลักษณะนั้นได้
abarnert

1

ส่งผ่านฟังก์ชันในเป็นพารามิเตอร์แทนที่จะส่งผ่านผลลัพธ์ของการเรียกฟังก์ชัน

นั่นคือแทนที่จะเป็นสิ่งนี้:

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)แทนซึ่งไม่ใช่อินเทอร์เฟซที่สมเหตุสมผล
ยกเลิก

1
อินเทอร์เฟซที่รับฟังก์ชั่นไม่ธรรมดา ฉันสามารถเริ่มตั้งชื่อตัวอย่างจาก python stdlib ได้หากต้องการ

2
Django รับสายตรงตามที่คำถามนี้แนะนำแล้ว
Ned Batchelder
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.