Django — เรื่องราวของเงื่อนไขการแข่งขันพร้อม get_or_create และข้อจำกัดที่ไม่เหมือนใคร

Dec 01 2022
อย่าทำซ้ำตัวเอง เคย.

อย่าทำซ้ำตัวเอง เคย. เคย. เดี๋ยวก่อน ยัยตัวร้าย เสียใจ.

ที่นี่ ฉันทำยาลับ 101 ชนิดที่เราให้ทุกคนดื่มก่อนเข้าร่วมทีม! หมายความว่าหากคุณเขียนสิ่งที่คล้ายกันสองครั้ง จะต้องมีวิธีที่ดีกว่าในการเขียนสิ่งนั้น

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

ตารางที่สำคัญในบริการดังกล่าวจะเป็นตารางเอกสารที่มีฟิลด์สำคัญ ได้แก่user, document_type (AADHAAR_CARD/PAN_CARD/SALARY_SLIP), document ในตอนแรก การเพิ่มunique_togetherข้อจำกัดสำหรับฟิลด์usersและdocument_typeดูสมเหตุสมผล โปรดทราบว่าผู้ใช้หนึ่งคนควรมีการ์ด Aadhaar หรือ PAN เพียงใบเดียว แต่มันไม่ใช่หนึ่งในการตัดสินใจที่ดีที่สุดของเรา เนื่องจากเราจะบันทึกสลิปเงินเดือน ใบคืนภาษี ไว้ในตารางเดียวกัน ซึ่งผู้ใช้ 1 คนสามารถมีได้หลายปริมาณ เราตรวจสอบวิธีการบันทึกของ Django Model ของเราอย่างมั่นใจและเข้านอนอย่างสงบสุข

def save(self):
  if not self.document_type not in ['AADHAAR_CARD', 'PAN_CARD']:
    if Document.objects.filter(
          user=self.user, 
          document_type=self.document_type
       ).exclude(id=self.id).exists():
      raise ValidationError("Not allowed")
  super().save()

user_aadhaar_card = None
try:
  user_aadhaar_card = Document.objects.get(
        user=request.user, 
        document_type="AADHAAR_CARD"
)
except UserDocumentMapping.DoesNotExist:
  user_aadhaar_card = Document.objects.create(
        user=request.user, 
        document_type=AADHAAR_CARD
)

try:
  return some_model.get(**queryset), False
except some_model.DoesNotExist:
  try:
    instance = some_model.create(**queryset)
    return instance, True
  except IntegrityError:
    return some_model.get(**queryset), False

การดำเนินการของฟังก์ชันที่กล่าวถึงข้างต้นสำหรับคำขอที่เกิดขึ้นพร้อมกัน

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

แต่เนื่องจากเราไม่สามารถใส่ unique_contraint ด้านบนได้ ในกรณีของเรา มันจะสร้างการ์ด Addhaar 2 ใบสำหรับผู้ใช้คนเดียวอย่างมีความสุข (UIDAI, รหัสแดง!) และครั้งต่อไปเมื่อฉันมั่นใจที่จะเรียกใช้get(user=user, document_type="AADHAAR_CARD")ฟังก์ชันทำความสะอาดที่ไม่ดีของฉันและ Django get_or_createก็จะทำ คืนฉันไม่เคยคาดหวังหลายวัตถุ เอฟเอ็ม!

Django 2.2 เพื่อช่วยเหลือ; แนะนำUniqueConstraintซึ่งช่วยให้คุณสามารถใช้ข้อจำกัดเฉพาะอย่างมีเงื่อนไขกับโมเดลของคุณ ในโค้ด สิ่งที่คล้ายกับ:

class Document(models.Model)
  user = <>
  document_type = <>
  document = <>

  class Meta:
      constraints = [
        models.UniqueConstraint(
          fields=['user', 'document_type'], 
          condition=Q(
            document_type__in=["AADHAAR_CARD", "PAN_CARD"]
          ),
          name='unique_user_unique_document_type'
        )
      ]

ตอนจบ คติธรรมของเรื่องคือ...

ไม่เคยใช้get_or_createหรือตรรกะที่คล้ายกันกับการค้นหาที่ไม่ "จำกัดเฉพาะ" ในระดับฐานข้อมูล!