ในวิธีการบันทึก () แบบกำหนดเอง django รุ่นคุณควรระบุวัตถุใหม่ได้อย่างไร


172

ฉันต้องการเรียกใช้การกระทำพิเศษในวิธีการบันทึก () ของวัตถุโมเดล Django เมื่อฉันบันทึกระเบียนใหม่ (ไม่อัปเดตระเบียนที่มีอยู่)

การตรวจสอบ (self.id! = None) เป็นสิ่งที่จำเป็นและเพียงพอที่จะรับประกันว่าการบันทึกข้อมูลตนเองเป็นเรื่องใหม่และไม่มีการปรับปรุงหรือไม่? มีกรณีพิเศษที่อาจมองข้ามไปหรือไม่


โปรดเลือกstackoverflow.com/a/35647389/8893667เป็นคำตอบที่ถูกต้อง คำตอบไม่ได้ผลในหลาย ๆ กรณีเช่นUUIDField pk
Kotlinboy

คำตอบ:


204

อัปเดต:ด้วยการชี้แจงที่self._stateไม่ใช่ตัวแปรอินสแตนซ์ส่วนตัว แต่ตั้งชื่อวิธีการหลีกเลี่ยงความขัดแย้งการตรวจสอบself._state.addingจึงเป็นวิธีที่ดีกว่าในการตรวจสอบ


self.pk is None:

ผลตอบแทนที่แท้จริงภายในรุ่นวัตถุใหม่เว้นแต่วัตถุที่มีฐานะUUIDFieldprimary_key

กรณีมุมที่คุณอาจต้องกังวลคือมีข้อ จำกัด ที่ไม่ซ้ำกันในฟิลด์อื่นที่ไม่ใช่ id (เช่นดัชนีเฉพาะรองในฟิลด์อื่น) ในกรณีนี้คุณยังสามารถมีระเบียนใหม่อยู่ในมือ แต่ไม่สามารถบันทึกได้


20
คุณควรใช้is notแทนการ!=ตรวจสอบตัวตนของNoneวัตถุ
Ben James

3
ไม่ได้ทุกรุ่นมีแอตทริบิวต์ ID models.OneToOneField(OtherModel, primary_key=True)คือรูปแบบการขยายอีกผ่าน ฉันคิดว่าคุณจำเป็นต้องใช้self.pk
AJP

4
อาจไม่ทำงานในบางกรณี โปรดตรวจสอบคำตอบนี้: stackoverflow.com/a/940928/145349
fjsj

5
นี่ไม่ใช่คำตอบที่ถูกต้อง หากใช้UUIDFieldเป็นคีย์หลักself.pkจะไม่มีวันเป็นNoneไปได้
Daniel van Flymen

1
หมายเหตุด้านข้าง: คำตอบนี้ไว้ล่วงหน้า UUIDField
เดฟดับบลิวสมิ ธ

190

ทางเลือกในการตรวจสอบself.pkเราสามารถตรวจสอบself._stateรูปแบบ

self._state.adding is True การสร้าง

self._state.adding is False ปรับปรุง

ฉันได้รับจากหน้านี้


12
นี่เป็นวิธีที่ถูกต้องเมื่อใช้ฟิลด์คีย์หลักที่กำหนดเอง
webtweakers

9
ไม่แน่ใจเกี่ยวกับรายละเอียดทั้งหมดของวิธีการself._state.addingทำงาน แต่เตือนอย่างเป็นธรรมว่ามันจะเสมอกันFalseถ้าคุณตรวจสอบหลังจากโทรsuper(TheModel, self).save(*args, **kwargs): github.com/django/django/blob/stable/1.10.x/django/db/models/ …
agilgur5

1
นี่เป็นวิธีที่ถูกต้องและควร upvoted / set เป็นคำตอบที่ถูกต้อง
flungo

7
@guival: _stateไม่เป็นส่วนตัว เช่น_metaนำหน้าด้วยเครื่องหมายขีดล่างเพื่อหลีกเลี่ยงความสับสนกับชื่อฟิลด์ (สังเกตว่ามีการใช้งานอย่างไรในเอกสารที่เชื่อมโยง)
Ry-

2
นี่เป็นวิธีที่ดีที่สุด ผมใช้is_new = self._state.addingแล้วsuper(MyModel, self).save(*args, **kwargs)และจากนั้นif is_new: my_custom_logic()
kotrfa

45

การตรวจสอบself.idสันนิษฐานว่าidเป็นคีย์หลักสำหรับโมเดล วิธีทั่วไปมากขึ้นจะใช้ทางลัด PK

is_new = self.pk is None


15
เคล็ดลับมือโปร: ใส่นี้ก่อน super(...).save()
sbdchd

39

สำหรับการตรวจสอบself.pk == Noneคือไม่เพียงพอที่จะตรวจสอบว่าวัตถุจะถูกแทรกหรือปรับปรุงในฐานข้อมูล

Django O / RM มีแฮ็คที่น่ารังเกียจโดยเฉพาะอย่างยิ่งซึ่งโดยทั่วไปจะตรวจสอบว่ามีบางสิ่งบางอย่างที่ตำแหน่ง PK และถ้าเป็นเช่นนั้นทำ UPDATE มิฉะนั้นจะแทรก INSERT (สิ่งนี้จะได้รับการปรับให้เหมาะสมกับ

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

ถ้าคุณอยากรู้ว่าคุณต้องทำในสิ่งที่ O / RM ทำได้และค้นหาในฐานข้อมูล

แน่นอนว่าคุณมีกรณีเฉพาะในรหัสของคุณและเป็นไปได้ค่อนข้างที่self.pk == Noneจะบอกคุณทั้งหมดที่คุณต้องรู้ แต่ไม่ใช่วิธีแก้ไขปัญหาทั่วไป


จุดดี! ฉันสามารถลบล้างสิ่งนี้ได้ในแอปพลิเคชันของฉัน (ตรวจสอบว่าไม่มีคีย์หลัก) เพราะฉันไม่เคยตั้งค่า pk สำหรับวัตถุใหม่ แต่นี่จะไม่เป็นการตรวจสอบที่ดีสำหรับปลั๊กอินที่นำมาใช้ซ้ำหรือส่วนหนึ่งของกรอบงานได้
MikeN

1
นี่เป็นจริงโดยเฉพาะเมื่อคุณกำหนดคีย์หลักด้วยตัวคุณเองและผ่านฐานข้อมูล ในกรณีนั้นสิ่งที่ต้องทำคือการเดินทางไปยังฐานข้อมูล
Constantine M

1
แม้ว่ารหัสแอปพลิเคชันของคุณไม่ได้ระบุ pks อย่างชัดเจนการแข่งขันสำหรับกรณีทดสอบของคุณอาจ แม้ว่ามันจะถูกโหลดโดยทั่วไปก่อนการทดสอบมันอาจจะไม่เป็นปัญหา
Risadinha

1
นี่คือความจริงโดยเฉพาะอย่างยิ่งในกรณีของการใช้ที่UUIDFieldเป็นคีย์หลัก: ที่สำคัญคือการไม่ได้บรรจุอยู่ในระดับฐานข้อมูลเพื่อให้อยู่เสมอself.pk True
Daniel van Flymen

10

คุณสามารถเชื่อมต่อกับสัญญาณ post_save ซึ่งจะส่ง kwargs "สร้าง" ถ้าเป็นจริงวัตถุของคุณถูกแทรก

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


8
ซึ่งอาจทำให้เกิดสภาพการแข่งขันหากมีภาระมาก นั่นเป็นเพราะสัญญาณ post_save ถูกส่งไปที่บันทึก แต่ก่อนที่จะมีการทำธุรกรรม นี่อาจเป็นปัญหาและทำให้การแก้ไขข้อบกพร่องยากมาก
Abel Mohler

ฉันไม่แน่ใจว่าสิ่งต่าง ๆ เปลี่ยนไป (จากเวอร์ชั่นเก่ากว่า) แต่ตัวจัดการสัญญาณของฉันถูกเรียกใช้ในการทำธุรกรรมเดียวกันดังนั้นจึงเกิดความล้มเหลวในการย้อนกลับของธุรกรรมทั้งหมด ฉันใช้ATOMIC_REQUESTSดังนั้นฉันไม่แน่ใจเกี่ยวกับค่าเริ่มต้น
Tim Tisdall

7

ตรวจสอบself.idและforce_insertตั้งค่าสถานะ

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

สิ่งนี้มีประโยชน์เพราะวัตถุที่คุณสร้างขึ้นใหม่มีความpkคุ้มค่า


5

ฉันมาช้ามากในการสนทนานี้ แต่ฉันพบปัญหากับ self.pk ที่มีการเติมเมื่อมีค่าเริ่มต้นที่เกี่ยวข้อง

วิธีที่ฉันได้รับสิ่งนี้คือการเพิ่มฟิลด์ date_created ให้กับโมเดล

date_created = models.DateTimeField(auto_now_add=True)

จากที่นี่คุณสามารถไป

created = self.date_created is None


4

สำหรับวิธีการแก้ปัญหาที่ใช้งานได้แม้ในขณะที่คุณมีUUIDFieldคีย์หลัก (ซึ่งคนอื่น ๆ ระบุไว้ไม่ใช่Noneว่าคุณเพิ่งจะแทนที่save) คุณสามารถเสียบสัญญาณpost_saveของ Django ได้ เพิ่มสิ่งนี้ไปยัง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

การเรียกกลับนี้จะบล็อกsaveวิธีการเพื่อให้คุณสามารถทำสิ่งต่าง ๆ เช่นการแจ้งเตือนทริกเกอร์หรืออัปเดตโมเดลเพิ่มเติมก่อนที่คำตอบของคุณจะถูกส่งกลับไปที่สายไม่ว่าคุณจะใช้แบบฟอร์มหรือกรอบ Django REST สำหรับการโทร AJAX แน่นอนใช้ความรับผิดชอบและลดภาระงานหนักให้กับคิวงานแทนที่จะทำให้ผู้ใช้ของคุณรอ :)



1

เป็นวิธีทั่วไปในการทำเช่นนั้น

id จะถูกกำหนดในขณะที่บันทึกเป็นครั้งแรกกับ db


0

ใช้ได้กับทุกสถานการณ์ข้างต้นหรือไม่

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

สิ่งนี้จะทำให้ฐานข้อมูลเพิ่มเติมถูกโจมตี
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)

ใช้ clean_data.get () ฉันสามารถตรวจสอบว่าฉันมีตัวอย่างฉันยังมี CharField ที่ว่างและว่างที่จริง สิ่งนี้จะอัปเดตในแต่ละการอัปเดตตามผู้ใช้ที่เข้าสู่ระบบ
Swelan Auguste

-3

หากต้องการทราบว่าคุณกำลังอัปเดตหรือแทรกวัตถุ (ข้อมูล) ให้ใช้self.instance.fieldnameในแบบฟอร์มของคุณ กำหนดฟังก์ชั่นใหม่ทั้งหมดในแบบฟอร์มของคุณและตรวจสอบว่ารายการค่าปัจจุบันเป็นเหมือนเดิมหรือไม่ถ้าไม่คุณกำลังอัพเดท

self.instanceและself.instance.fieldnameเปรียบเทียบกับค่าใหม่

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