Cách đặt giá trị mặc định của trường mô hình Django thành một hàm có thể gọi / có thể gọi (ví dụ: ngày liên quan đến thời gian tạo đối tượng mô hình)


101

ĐÃ CHỈNH SỬA:

Làm cách nào để đặt mặc định của trường Django thành một hàm được đánh giá mỗi khi tạo một đối tượng mô hình mới?

Tôi muốn làm điều gì đó như sau, ngoại trừ việc trong mã này, mã được đánh giá một lần và đặt mặc định thành cùng một ngày cho mỗi đối tượng mô hình được tạo, thay vì đánh giá mã mỗi khi một đối tượng mô hình được tạo:

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



NGUYÊN:

Tôi muốn tạo giá trị mặc định cho một tham số hàm sao cho nó là động và được gọi và đặt mỗi khi hàm được gọi. Làm thế nào tôi có thể làm điều đó? ví dụ,

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

Cụ thể, tôi muốn làm điều đó trong Django, ví dụ:

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

Câu hỏi được đặt sai từ: nó không liên quan gì đến mặc định tham số hàm. Hãy xem câu trả lời của tôi.
Ned Batchelder

Theo nhận xét của @ NedBatchelder, tôi đã chỉnh sửa câu hỏi của mình (được chỉ định là "ĐÃ CHỈNH SỬA:") và để nguyên bản gốc (được chỉ định là "GỐC:")
Rob Bednark

Câu trả lời:


139

Câu hỏi là sai lầm. Khi tạo trường mô hình trong Django, bạn không xác định một hàm, vì vậy các giá trị mặc định của hàm không liên quan:

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

Dòng cuối cùng này không xác định một hàm; nó đang gọi một hàm để tạo một trường trong lớp.

PRE Django 1.7

Django cho phép bạn chuyển một lệnh có thể gọi làm mặc định và nó sẽ gọi nó mỗi lần như bạn muốn:

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+

Xin lưu ý rằng kể từ Django 1.7, việc sử dụng lambda làm giá trị mặc định không được khuyến khích (bình luận cf @stvnw). Cách thích hợp để làm điều này là khai báo một hàm trước trường và sử dụng nó như một hàm có thể gọi trong default_value có tên là 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)

Thông tin thêm trong câu trả lời @simanas bên dưới


2
Cảm ơn @NedBatchelder. Điều này thật đúng với gì mà tôi đã tìm kiếm. Tôi không nhận ra rằng 'mặc định' có thể nhận một cuộc gọi. Và bây giờ tôi thấy câu hỏi của mình đã thực sự bị hiểu sai về cách gọi hàm so với định nghĩa hàm.
Rob Bednark

25
Lưu ý rằng đối với Django> = 1.7, việc sử dụng lambdas không được khuyến khích cho các tùy chọn trường vì chúng không tương thích với việc di chuyển. Tham khảo: Tài liệu ,
StvnW

4
Vui lòng bỏ chọn câu trả lời này, vì nó sai đối với Djange> = 1.7. Câu trả lời của @Simanas là hoàn toàn chính xác và do đó nên được chấp nhận.
AplusKminus

Điều này hoạt động như thế nào với việc di chuyển? Tất cả các đối tượng cũ có nhận được ngày dựa trên thời gian di chuyển không? Có thể đặt một mặc định khác để sử dụng với di chuyển không?
timthelion

3
Điều gì sẽ xảy ra nếu tôi muốn chuyển một số tham số cho get_default_my_date?
Sadan A.

59

Việc làm này default=datetime.now()+timedelta(days=1)là hoàn toàn sai lầm!

Nó được đánh giá khi bạn bắt đầu phiên bản django của mình. Nếu bạn đang sử dụng apache, nó có thể sẽ hoạt động, vì trên một số cấu hình, apache thu hồi ứng dụng django của bạn theo mọi yêu cầu, nhưng bạn vẫn có thể thấy mình một ngày nào đó xem qua mã của bạn và cố gắng tìm ra lý do tại sao điều này được tính toán không như bạn mong đợi .

Cách làm đúng là chuyển một đối tượng có thể gọi đến đối số mặc định. Nó có thể là một hàm datetime.today hoặc một hàm tùy chỉnh của bạn. Sau đó, nó được đánh giá mỗi khi bạn yêu cầu một giá trị mặc định mới.

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
Nếu mặc định của tôi dựa trên giá trị của một trường khác trong mô hình, liệu có thể chuyển trường đó dưới dạng tham số như trong một cái gì đó như thế get_deadline(my_parameter)nào không?
YPCrumble

@YPCrumble đây phải là một câu hỏi mới!
jsmedmar

2
Không. Đó là không thể. Bạn sẽ phải sử dụng một số cách tiếp cận khác nhau để làm điều đó.
Simanas

Làm thế nào để bạn xóa deadlinetrường một lần nữa trong khi cũng xóa get_deadlinechức năng? Tôi đã xóa trường có hàm cho giá trị mặc định, nhưng bây giờ Django gặp sự cố khi khởi động sau khi xóa hàm. Tôi có thể chỉnh sửa quá trình di chuyển theo cách thủ công, điều này sẽ ổn trong trường hợp này, nhưng nếu bạn chỉ thay đổi chức năng mặc định và muốn xóa chức năng cũ thì sao?
beruic

8

Có một sự khác biệt quan trọng giữa hai hàm tạo DateTimeField sau:

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

Nếu bạn sử dụng auto_now_add=Truetrong hàm tạo, ngày giờ được tham chiếu bởi my_date là "không thay đổi" (chỉ được đặt một lần khi hàng được chèn vào bảng).

Với auto_now=TrueTuy nhiên, giá trị datetime sẽ được cập nhật mỗi khi đối tượng được lưu.

Điều này chắc chắn là một gotcha đối với tôi tại một thời điểm. Để tham khảo, các tài liệu ở đây:

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


3
Bạn đã có các định nghĩa về auto_now và auto_now_add lẫn lộn, đó là cách khác.
Michael Bates

@MichaelBates tốt hơn bây giờ?
Hendy Irawan

4

Đôi khi bạn có thể cần truy cập dữ liệu mô hình sau khi tạo mô hình người dùng mới.

Đây là cách tôi tạo mã thông báo cho mỗi hồ sơ người dùng mới bằng cách sử dụng 4 ký tự đầu tiên của tên người dùng của họ:

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

Bạn không thể làm điều đó trực tiếp; giá trị mặc định được đánh giá khi định nghĩa hàm được đánh giá. Nhưng có hai cách xung quanh nó.

Đầu tiên, bạn có thể tạo (và sau đó gọi) một hàm mới mỗi lần.

Hoặc, đơn giản hơn, chỉ cần sử dụng một giá trị đặc biệt để đánh dấu mặc định. Ví dụ:

from datetime import datetime
def mydate(date=None):
  if date is None:
    date = datetime.now()
  print date

Nếu Nonelà một giá trị tham số hoàn toàn hợp lý và không có giá trị hợp lý nào khác mà bạn có thể sử dụng ở vị trí của nó, bạn chỉ có thể tạo một giá trị mới chắc chắn nằm ngoài miền của hàm:

from datetime import datetime
class _MyDateDummyDefault(object):
  pass
def mydate(date=_MyDateDummyDefault):
  if date is _MyDateDummyDefault:
    date = datetime.now()
  print date
del _MyDateDummyDefault

Trong một số trường hợp hiếm hoi, bạn đang viết mã meta thực sự cần thiết để có thể thực hiện hoàn toàn mọi thứ, thậm chí, chẳng hạn như mydate.func_defaults[0]. Trong trường hợp đó, bạn phải làm như sau:

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

1
Lưu ý rằng không có lý do gì để thực sự khởi tạo lớp giá trị giả - chỉ cần sử dụng chính lớp đó làm giá trị giả.
Amber

Bạn CÓ THỂ làm điều này trực tiếp, chỉ cần truyền vào hàm thay vì kết quả của lệnh gọi hàm. Xem câu trả lời đã đăng của tôi.

Không, bạn không thể. Kết quả của hàm không cùng loại với các tham số bình thường, vì vậy nó không thể được sử dụng hợp lý làm giá trị mặc định cho các tham số bình thường đó.
abarnert

1
Vâng, vâng, nếu bạn truyền vào một đối tượng thay vì một hàm, nó sẽ tạo ra một ngoại lệ. Điều này cũng đúng nếu bạn truyền một hàm vào một tham số mong đợi một chuỗi. Tôi không thấy điều đó có liên quan như thế nào.

Nó có liên quan vì myfuncchức năng của anh ta được tạo ra để lấy (và in) a datetime; bạn đã thay đổi nó nên nó không thể được sử dụng theo cách đó.
abarnert

2

Truyền hàm vào dưới dạng tham số thay vì chuyển vào kết quả của lệnh gọi hàm.

Đó là, thay vì điều này:

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

Thử cái này:

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

Điều này không hoạt động, bởi vì bây giờ người dùng thực sự không thể chuyển một tham số — bạn sẽ cố gắng gọi nó như một hàm và ném một ngoại lệ. Trong trường hợp đó, bạn cũng có thể chỉ cần không có tham số và print datetime.now()vô điều kiện.
abarnert

Thử nó. Bạn không chuyển ngày, bạn đang chuyển hàm datetime.now.

Có, mặc định là chuyển datetime.nowhàm. Nhưng bất kỳ người dùng nào chuyển một giá trị không phải mặc định sẽ chuyển một ngày giờ chứ không phải một hàm. foo = datetime.now(); myfunc(foo)sẽ nâng cao TypeError: 'datetime.datetime' object is not callable. Nếu tôi thực sự muốn sử dụng chức năng của bạn, tôi sẽ phải làm một cái gì đó như myfunc(lambda: foo)thay thế, đó không phải là một giao diện hợp lý.
abarnert

1
Các giao diện chấp nhận các chức năng không phải là bất thường. Tôi có thể bắt đầu đặt tên cho các ví dụ từ python stdlib, nếu bạn thích.

2
Django đã chấp nhận một có thể gọi chính xác như câu hỏi này gợi ý.
Ned Batchelder 29/09/12
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.