Trong một phương thức django tùy chỉnh lưu (), bạn nên xác định một đối tượng mới như thế nào?


171

Tôi muốn kích hoạt một hành động đặc biệt trong phương thức save () của đối tượng mô hình Django khi tôi lưu bản ghi mới (không cập nhật bản ghi hiện có.)

Việc kiểm tra (self.id! = Không) có cần thiết và đủ để đảm bảo bản ghi tự là mới và không được cập nhật không? Bất kỳ trường hợp đặc biệt này có thể bỏ qua?


Vui lòng chọn stackoverflow.com/a/35647389/8893667 là câu trả lời đúng. Câu trả lời không hoạt động trong nhiều trường hợp nhưUUIDField pk
Kotlinboy

Câu trả lời:


203

Đã cập nhật: Với việc làm rõ self._statekhông phải là biến cá thể riêng tư, nhưng được đặt tên theo cách đó để tránh xung đột, kiểm tra self._state.addingbây giờ là cách tốt nhất để kiểm tra.


self.pk is None:

trả về True trong một đối tượng Model mới, trừ khi đối tượng có UUIDFieldas primary_key.

Trường hợp góc bạn có thể phải lo lắng là liệu có các ràng buộc duy nhất trên các trường không phải là id hay không (ví dụ: các chỉ mục duy nhất thứ cấp trên các trường khác). Trong trường hợp đó, bạn vẫn có thể có một bản ghi mới trong tay, nhưng không thể lưu nó.


20
Bạn nên sử dụng is notthay vì !=khi kiểm tra danh tính với Noneđối tượng
Ben James

3
Không phải tất cả các mô hình đều có thuộc tính id, tức là một mô hình mở rộng khác thông qua a models.OneToOneField(OtherModel, primary_key=True). Tôi nghĩ bạn cần sử dụngself.pk
AJP

4
Điều này có thể KHÔNG LÀM VIỆC trong một số trường hợp. Vui lòng kiểm tra câu trả lời này: stackoverflow.com/a/940928/145349
fjsj

5
Đây không phải là câu trả lời chính xác. Nếu sử dụng UUIDFieldnhư một khóa chính, self.pkthì không bao giờ None.
Daniel van Flymen

1
Lưu ý bên lề: Câu trả lời này trước ngày UUIDField.
Dave W. Smith

190

Cách khác để kiểm tra self.pkchúng ta có thể kiểm tra self._statemô hình

self._state.adding is True tạo ra

self._state.adding is False đang cập nhật

Tôi đã nhận nó từ trang này


12
Đây là cách duy nhất đúng khi sử dụng trường khóa chính tùy chỉnh.
webtweaker

9
Không chắc chắn về tất cả các chi tiết về cách thức self._state.addinghoạt động, nhưng cảnh báo công bằng rằng nó dường như luôn luôn bằng nhau Falsenếu bạn đang kiểm tra nó sau khi gọi super(TheModel, self).save(*args, **kwargs): github.com/django/django/blob/ sóng / 1.10.x / django / db / model / ...
agilgur5

1
Đây là cách chính xác và nên được nâng cấp / đặt làm câu trả lời đúng.
flungo

7
@guival: _statekhông riêng tư; giống như _meta, nó có tiền tố gạch dưới để tránh nhầm lẫn với tên trường. (Lưu ý cách sử dụng trong tài liệu được liên kết.)
Ry-

2
Đây la cach tôt nhât. Tôi đã sử dụng is_new = self._state.adding, sau đó super(MyModel, self).save(*args, **kwargs)và sau đóif is_new: my_custom_logic()
kotrfa

45

Kiểm tra self.idgiả định đó idlà khóa chính cho mô hình. Một cách chung hơn sẽ là sử dụng phím tắt pk .

is_new = self.pk is None


15
Pro Mẹo: đặt này TRƯỚC sự super(...).save().
sbdchd

39

Việc kiểm tra self.pk == Nonekhông đủ để xác định xem đối tượng sẽ được chèn hoặc cập nhật trong cơ sở dữ liệu.

Django O / RM có tính năng hack đặc biệt khó chịu, về cơ bản là để kiểm tra xem có gì ở vị trí PK hay không và nếu có thì CẬP NHẬT, nếu không thì hãy thực hiện INSERT (điều này được tối ưu hóa thành INSERT nếu PK không có).

Lý do tại sao nó phải làm điều này là vì bạn được phép đặt PK khi một đối tượng được tạo. Mặc dù không phổ biến khi bạn có một cột chuỗi cho khóa chính, nhưng điều này không giữ cho các loại trường khóa chính khác.

Nếu bạn thực sự muốn biết bạn phải làm những gì O / RM làm và tìm trong cơ sở dữ liệu.

Tất nhiên bạn có một trường hợp cụ thể trong mã của bạn và điều đó hoàn toàn có khả năng self.pk == Nonecho bạn biết tất cả những gì bạn cần biết, nhưng nó không phải là một giải pháp chung.


Điểm tốt! Tôi có thể thoát khỏi điều này trong ứng dụng của mình (kiểm tra Không có khóa chính nào) vì tôi không bao giờ đặt pk cho các đối tượng mới. Nhưng điều này chắc chắn sẽ không phải là một kiểm tra tốt cho một plugin có thể tái sử dụng hoặc một phần của khung.
MikeN

1
Điều này đặc biệt đúng khi bạn tự gán khóa chính và thông qua cơ sở dữ liệu. Trong trường hợp đó, điều chắc chắn nhất phải làm là thực hiện một chuyến đi đến db.
Constantine M

1
Ngay cả khi mã ứng dụng của bạn không chỉ định pks một cách rõ ràng các đồ đạc cho các trường hợp thử nghiệm của bạn có thể. Mặc dù, vì chúng thường được tải trước các bài kiểm tra nên nó có thể không phải là vấn đề.
Risadinha

1
Điều này đặc biệt đúng trong trường hợp sử dụng UUIDFieldlàm Khóa chính: khóa không được điền ở cấp DB, vì vậy self.pkluôn luôn như vậy True.
Daniel van Flymen

10

Bạn chỉ có thể kết nối với tín hiệu post_save sẽ gửi một kwarg "đã tạo", nếu đúng, đối tượng của bạn đã được chèn.

http://docs.djangoproject.com/en/urdy/ref/signals/#post-save


8
Điều đó có khả năng gây ra tình trạng chủng tộc nếu có nhiều tải. Đó là vì tín hiệu post_save được gửi khi lưu, nhưng trước khi giao dịch được thực hiện. Điều này có thể có vấn đề và có thể làm cho mọi thứ rất khó để gỡ lỗi.
Abel Mohler

Tôi không chắc liệu mọi thứ đã thay đổi (từ các phiên bản cũ hơn), nhưng trình xử lý tín hiệu của tôi được gọi trong cùng một giao dịch, do đó, bất kỳ nơi nào cũng quay trở lại toàn bộ giao dịch. Tôi đang sử dụngATOMIC_REQUESTS , vì vậy tôi không thực sự chắc chắn về mặc định.
Tim Tonomall

7

Kiểm tra self.idforce_insertcờ.

if not self.pk or kwargs.get('force_insert', False):
    self.created = True

# call save method.
super(self.__class__, self).save(*args, **kwargs)

#Do all your post save actions in the if block.
if getattr(self, 'created', False):
    # So something
    # Do something else

Điều này rất hữu ích vì đối tượng mới được tạo của bạn có pkgiá trị


5

Tôi đã rất muộn với cuộc trò chuyện này, nhưng tôi gặp phải một vấn đề với self.pk được đưa ra khi nó có giá trị mặc định liên quan đến nó.

Cách tôi giải quyết vấn đề này là thêm trường date_created vào mô hình

date_created = models.DateTimeField(auto_now_add=True)

Từ đây bạn có thể đi

created = self.date_created is None


4

Đối với một giải pháp cũng hoạt động ngay cả khi bạn có UUIDFieldkhóa chính (như những người khác đã lưu ý không phải là Nonenếu bạn chỉ ghi đè save), bạn có thể cắm vào tín hiệu post_save của Django . Thêm này để bạn models.py :

from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=MyModel)
def mymodel_saved(sender, instance, created, **kwargs):
    if created:
        # do extra work on your instance, e.g.
        # instance.generate_avatar()
        # instance.send_email_notification()
        pass

Cuộc gọi lại này sẽ chặn savephương thức, vì vậy bạn có thể thực hiện những việc như thông báo kích hoạt hoặc cập nhật mô hình thêm trước khi phản hồi của bạn được gửi lại qua dây, cho dù bạn đang sử dụng biểu mẫu hoặc khung Django REST cho các cuộc gọi AJAX. Tất nhiên, sử dụng có trách nhiệm và giảm tải các tác vụ nặng cho hàng đợi công việc thay vì khiến người dùng của bạn chờ đợi :)



1

Đó là cách phổ biến để làm như vậy.

id sẽ được cung cấp khi lưu lần đầu tiên vào db


0

Điều này sẽ làm việc cho tất cả các kịch bản trên?

if self.pk is not None and <ModelName>.objects.filter(pk=self.pk).exists():
...

điều này sẽ gây ra một cú đánh cơ sở dữ liệu bổ sung.
David Schumann

0
> def save_model(self, request, obj, form, change):
>         if form.instance._state.adding:
>             form.instance.author = request.user
>             super().save_model(request, obj, form, change)
>         else:
>             obj.updated_by = request.user.username
> 
>             super().save_model(request, obj, form, change)

Sử dụng clean_data.get (), tôi có thể xác định xem tôi có một cá thể hay không, tôi cũng có một CharField trong đó null và trống ở đâu là true. Điều này sẽ cập nhật tại mỗi bản cập nhật theo người dùng đã đăng nhập
Swelan Auguste

-3

Để biết bạn đang cập nhật hoặc chèn đối tượng (dữ liệu), hãy sử dụng self.instance.fieldnametrong biểu mẫu của bạn. Xác định hàm sạch trong biểu mẫu của bạn và kiểm tra xem mục nhập giá trị hiện tại có giống như trước không, nếu không thì bạn đang cập nhật nó.

self.instanceself.instance.fieldnameso sánh với giá trị mới

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.