Django - Sử dụng tài sản làm khóa ngoại


8

Cơ sở dữ liệu của ứng dụng của tôi được điền và giữ syncd với các nguồn dữ liệu ngoài. Tôi có một mô hình trừu tượng mà tất cả các mô hình ứng dụng Django 2.2 của tôi xuất phát, được định nghĩa như sau:

class CommonModel(models.Model):
    # Auto-generated by Django, but included in this example for clarity.
  # id = models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')
    ORIGIN_SOURCEA = '1'
    ORIGIN_SOURCEB = '2'
    ORIGIN_CHOICES = [
        (ORIGIN_SOURCEA, 'Source A'),
        (ORIGIN_SOURCEB, 'Source B'),
    ]
    object_origin = models.IntegerField(choices=ORIGIN_CHOICES)
    object_id = models.IntegerField()

class A(CommonModel):
    some_stuff = models.CharField()

class B(CommonModel):
    other_stuff = models.IntegerField()
    to_a_fk = models.ForeignKey("myapp.A", on_delete=models.CASCADE)

class C(CommonModel):
    more_stuff = models.CharField()
    b_m2m = models.ManyToManyField("myapp.B")

Các object_idlĩnh vực không thể được thiết lập như là duy nhất vì mỗi nguồn dữ liệu tôi sử dụng trong ứng dụng của tôi có thể có một đối tượng với một object_id = 1. Do đó, cần phải theo dõi nguồn gốc của đối tượng, theo trường object_origin.

Thật không may, ORM của Django không hỗ trợ các khóa ngoại nhiều hơn một cột.

Vấn đề

Trong khi giữ khóa chính được tạo tự động trong cơ sở dữ liệu ( id), tôi muốn tạo khóa ngoại của mình và các mối quan hệ nhiều-nhiều xảy ra trên cả hai object_idobject_origincác trường thay vì khóa chính id.

Những gì tôi đã thử

Tôi nghĩ về việc làm một cái gì đó như thế này:

class CommonModel(models.Model):
    # Auto-generated by Django, but included in this example for clarity.
  # id = models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')
    ORIGIN_SOURCEA = '1'
    ORIGIN_SOURCEB = '2'
    ORIGIN_CHOICES = [
        (ORIGIN_SOURCEA, 'Source A'),
        (ORIGIN_SOURCEB, 'Source B'),
    ]
    object_origin = models.IntegerField(choices=ORIGIN_CHOICES)
    object_id = models.IntegerField()

    def _get_composed_object_origin_id(self):
        return f"{self.object_origin}:{self.object_id}"
    composed_object_origin_id = property(_get_composed_object_origin_id)

class A(CommonModel):
    some_stuff = models.CharField()

class B(CommonModel):
    other_stuff = models.IntegerField()
    to_a_fk = models.ForeignKey("myapp.A", to_field="composed_object_origin_id", on_delete=models.CASCADE)

Nhưng Django phàn nàn về điều đó:

myapp.B.to_a_fk: (fields.E312) The to_field 'composed_object_origin_id' doesn't exist on the related model 'myapp.A'.

Và nghe có vẻ hợp pháp, Django ngoại trừ hồ sơ được đưa ra to_fieldlà một trường cơ sở dữ liệu. Nhưng không cần thêm trường mới vào CommonModelvì tôi đã composed_object_type_idxây dựng hai trường không thể rỗng ...


2
Ý tưởng thú vị, nhưng điều này có vẻ như là một vấn đề xy từ quan điểm của tôi ... Tại sao bạn cần điều này?
Phục hồi lại

Câu trả lời:


6

Bạn đã đề cập trong nhận xét của mình trong câu trả lời khác rằng object_id không phải là duy nhất nhưng nó là duy nhất kết hợp với object_type, vì vậy bạn có thể sử dụng một unique_togethertrong siêu dữ liệu không? I E

class CommonModel(models.Model):
    object_type = models.IntegerField()
    object_id = models.IntegerField()

    class Meta:
        unique_together = (
            ("object_type", "object_id"),
        )

1

Có / Bạn có thể đặt uniquethuộc tính trên object_idtrường không?

class CommonModel(models.Model):
    object_type = models.IntegerField()
    object_id = models.IntegerField(unique=True)

Nếu điều này không hoạt động, tôi sẽ thay đổi loại trường thành một uuidtrường:

class CommonModel(models.Model):
    object_type = models.IntegerField()
    object_uuid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)

Thật không may, object_idkhông thể được đặt là duy nhất bởi vì có những trường hợp nó không phải là duy nhất. Trên thực tế, trong nguồn dữ liệu ngoài cung cấp cho tôi dữ liệu tôi sử dụng trong ứng dụng của mình, khóa chính được tạo thành với hai trường: object_typeobject_id.
Tàu vũ trụ

Nếu object_idkhông phải là duy nhất, bạn không nên tạo khóa ngoại. Điều này có thể gây ra lỗi trong cơ sở dữ liệu và bạn không muốn điều đó. Nếu bạn không muốn sử dụng pk thay vào đó, bạn cũng có thể tự mình quản lý sự hợp lý trong các models.Modelchức năng tích hợp.
Victor Hug

Vâng, object_typeobject_idcùng nhau được đảm bảo là duy nhất. Nhưng object_idmột mình thì không.
Tàu vũ trụ

Tại đây, bạn có thể tìm thấy một gói pip để tạo ràng buộc khóa ngoài đối với một hợp chất (hai khóa): django
Victor Hug

Thật không may, các ứng dụng Django 2.1+ không được hỗ trợ và có vẻ như thư viện này không được duy trì tích cực.
Tàu vũ trụ

1

Bạn được đề cập trong câu hỏi của mình là " Thật không may, ORM của Django không hỗ trợ nhiều hơn một cột khóa ngoài ".

Có, Django không cung cấp loại hỗ trợ đó vì Django có độ tin cậy cao hơn chúng ta nghĩ :)

Vì vậy, Django cung cấp một tùy chọn meta để khắc phục loại vấn đề này và tùy chọn đó là unique_together.

Bạn có thể cung cấp Bộ tên trường, được ghép lại với nhau, phải là duy nhất, trong trường hợp của bạn ...

class CommonModel(models.Model):
    # Auto-generated by Django, but included in this example for clarity.
    # id = models.AutoField(auto_created=True, primary_key=True, 
    serialize=False, verbose_name='ID')
    ORIGIN_SOURCEA = '1'
    ORIGIN_SOURCEB = '2'
    ORIGIN_CHOICES = [
        (ORIGIN_SOURCEA, 'Source A'),
        (ORIGIN_SOURCEB, 'Source B'),
    ]
    object_origin = models.IntegerField(choices=ORIGIN_CHOICES)
    object_id = models.IntegerField()

    class meta:
        unique_together = [['object_origin', 'object_id']]

Bạn có thể cung cấp danh sách danh sách, bộ tập hợp hoặc danh sách đơn giản, tập đơn giản để unique_togethertùy chọn class meta:.

Đúng nhưng Django nói rằng ...

UniqueConstraint cung cấp nhiều chức năng hơn unique_together.

unique_together có thể không được chấp nhận trong tương lai.

Bạn có thể thêm UniqueConstraintthay vì unique_togethertương tự class meta:trong trường hợp của bạn, bạn có thể viết như dưới đây ...

class CommonModel(models.Model):
    # Auto-generated by Django, but included in this example for clarity.
    # id = models.AutoField(auto_created=True, primary_key=True, 
    serialize=False, verbose_name='ID')
    ORIGIN_SOURCEA = '1'
    ORIGIN_SOURCEB = '2'
    ORIGIN_CHOICES = [
        (ORIGIN_SOURCEA, 'Source A'),
        (ORIGIN_SOURCEB, 'Source B'),
    ]
    object_origin = models.IntegerField(choices=ORIGIN_CHOICES)
    object_id = models.IntegerField()

    class meta:
        constraints = [ models.UniqueConstraint(fields=['object_origin', 'object_id'], name='unique_object')]

Vì vậy, việc thực hành tốt nhất là sử dụng constraintstùy chọn thay vì unique_togethercủa class meta:.


1

Bạn có thể đặt ID gốc đối tượng được tạo thành một trường ( composed_object_origin_id) được cập nhật savevà sử dụng làm to_field.

class CommonModel(models.Model):
    ORIGIN_SOURCEA = "1"
    ORIGIN_SOURCEB = "2"
    ORIGIN_CHOICES = [
        (ORIGIN_SOURCEA, "Source A"),
        (ORIGIN_SOURCEB, "Source B"),
    ]
    object_origin = models.IntegerField(choices=ORIGIN_CHOICES)
    object_id = models.IntegerField()
    composed_object_origin_id = models.CharField(max_length=100, unique=True)

    def save(self, **kwargs):
        self.composed_object_origin_id = f"{self.object_origin}:{self.object_id}"

        # Just in case you use `update_fields`, force inclusion of the composed object origin ID.
        # NOTE: There's definitely a less error-prone way to write this `if` statement but you get
        # the gist. e.g., this does not handle passing `update_fields=None`.
        if "update_fields" in kwargs:
            kwargs["update_fields"].append("composed_object_origin_id")

        super().save(**kwargs)


class A(CommonModel):
    some_stuff = models.CharField(max_length=1)


class B(CommonModel):
    other_stuff = models.IntegerField()
    to_a_fk = models.ForeignKey(
        "myapp.A", to_field="composed_object_origin_id", on_delete=models.CASCADE
    )
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.