Django — Câu chuyện về các điều kiện cuộc đua với get_or_create và các ràng buộc duy nhất

Dec 01 2022
Đừng lặp lại chính mình. Bao giờ.

Đừng lặp lại chính mình. Bao giờ. Bao giờ. Oh, đợi đã, xấu của tôi. Xin lỗi.

Ở đây, tôi đã tiết lộ một trong 101 lọ thuốc bí mật mà chúng tôi yêu cầu mọi người uống trước khi gia nhập đội của chúng tôi! Nó chỉ đơn giản có nghĩa là nếu bạn đang viết một cái gì đó tương tự hai lần, thì phải có một cách tốt hơn để làm điều đó.

Gần đây, trong khi chúng tôi đang viết dịch vụ thu thập Tài liệu của mình, dưới tác dụng của loại thuốc trên. Mục đích là tập hợp một dịch vụ thu thập tài liệu tổng quát hơn để thu thập tất cả các loại tài liệu người dùng. Tôi sẽ nói về cách chúng tôi, mã của chúng tôi và sau đó là lính gác rơi vào tình thế khó xử kỳ lạ trong thời gian này và cách chúng tôi giải quyết vấn đề đó.

Một bảng quan trọng trong bất kỳ dịch vụ nào như vậy sẽ là một bảng Tài liệu với các trường chính, cụ thể là: user, document_type (AADHAAR_CARD/PAN_CARD/SALARY_SLIP), tài liệu. Lúc đầu, việc thêm một unique_togetherràng buộc cho các trường usersdocument_typecó vẻ hợp lý, hãy nhớ rằng một người dùng chỉ nên sở hữu một Thẻ Aadhaar hoặc PAN. Nhưng đó không phải là một trong những quyết định tốt nhất của chúng tôi vì chúng tôi sẽ lưu Phiếu lương, Tờ khai thuế trong cùng một bảng mà một người dùng có thể sở hữu với số lượng nhiều. Một cách tự tin, chúng tôi đã xác thực phương thức lưu của Mô hình Django của mình như thế này và tiếp tục đi ngủ một cách yên bình.

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

Việc thực hiện các chức năng nêu trên cho các yêu cầu đồng thời

Vì vậy, giả sử các ràng buộc duy nhất được yêu cầu ở vị trí của chúng, việc triển khai của bạn sẽ gây ra lỗi toàn vẹn, nhưng Django get_or_createsẽ nắm bắt được điều đó và cung cấp phản hồi gọn gàng cho bạn.

Nhưng vì chúng tôi không thể đặt bất kỳ unique_contraint nào ở trên, nên trong trường hợp của chúng tôi, nó sẽ vui vẻ tạo 2 Thẻ Addhaar cho một người dùng (UIDAI, mã màu đỏ ! get(user=user, document_type="AADHAAR_CARD")) get_or_createtrả lại cho tôi không bao giờ mong đợi Nhiều đối tượng. FML!

Django 2.2 để giải cứu; được giới thiệu UniqueConstraint, cho phép bạn áp dụng các ràng buộc duy nhất một cách có điều kiện cho các mô hình của mình. Trong mã, một cái gì đó tương tự như:

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

Sắp kết thúc, đạo đức của câu chuyện là -

Không bao giờ sử dụng get_or_createhoặc logic tương tự với một tra cứu không phải là "ràng buộc duy nhất" ở cấp độ cơ sở dữ liệu!