Django-プロパティを外部キーとして使用する


8

アプリのデータベースにデータが入力され、外部データソースとの同期が維持されます。私のDjango 2.2アプリのすべてのモデルが派生する抽象モデルがあり、次のように定義されています。

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")

object_idフィールドがユニークとして設定することはできません私は私のアプリで使用した各データソースが持つオブジェクトを持っているかもしれないのでobject_id = 1。したがって、フィールドによってオブジェクトの原点を追跡する必要がありますobject_origin

残念ながら、DjangoのORMは複数の列の外部キーをサポートしていません。

問題

自動生成された主キーをデータベース(id)に保持しながら、外部キーと多対多の関係を主キーではなくフィールドobject_idobject_originフィールドの両方で発生させたいと思いますid

私が試したこと

私はこのようなことをすることを考えました:

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)

しかしDjangoはそれについて不平を言っています:

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

そして、それは合法的に聞こえますが、Django to_fieldはデータベースフィールドとして指定されたフィールドを除きました。しかし、は2つのnull可能ではないフィールドを使用して構築されているCommonModelので、新しいフィールドを追加する必要はありませんcomposed_object_type_id...


2
興味深いアイデアですが、これは私の観点からはxy問題のように見えます... なぜこれが必要なのですか?
モニカを復活させる

回答:


6

他の回答のコメントで、object_idは一意ではないが、object_typeと組み合わせて一意であると述べたのでunique_together、メタクラスでaを使用できますか?すなわち

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

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

1

フィールドにunique属性を設定できますobject_idか?

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

これが機能しない場合は、フィールドタイプをフィールドに変更しuuidます。

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

object_id一意ではない場合があるため、残念ながら一意に設定できません。実際、アプリで使用するデータを提供する外部データソースでは、主キーは2つのフィールドで構成されています:object_typeobject_id
スペースブレイン

object_idが一意でない場合は、外部キーを作成しないでください。これはデータベースでエラーを引き起こす可能性があり、それは望ましくありません。代わりにpkを使用したくない場合は、組み込みmodels.Model関数で自分自身の関係を管理することもできます。
ビクターハグ

まあ、object_typeそしてobject_id一緒にユニークであることが保証されています。しかし、object_id一人ではありません。
スペースブレイン

ここでは、化合物に外部キー制約を作成するには、PIPパッケージ(2つのキーを)見つけることができます: django-composite-foreignkey.readthedocs.io/en/latest/...
ビクターハグ

残念ながらDjango 2.1以降のアプリはサポートされておらず、このライブラリは積極的にメンテナンスされていないようです。
スペースブレイン

1

あなたは「として、あなたの質問に記載されている残念ながら、DjangoのORMは、より-より-1列に外部キーをサポートしていません」。

はい、Djangoは私たちが思っているよりも信頼性が高いので、Djangoはそのタイプのサポートを提供していません:)

したがって、Djangoはこのタイプの問題を克服するための1つのメタオプションを提供し、そのオプションはunique_togetherです。

あなたの場合、一緒に取られて一意でなければならないフィールド名のセットを提供できます...

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

リストのリスト、セットのセットまたは単純なリスト、unique_togetherオプションの単純なセットを提供できますclass meta:

はい、しかしDjangoはそれを言った...

UniqueConstraintは、unique_togetherよりも多くの機能を提供します。

unique_togetherは将来廃止される可能性があります。

以下のように書くことができるあなたのケースUniqueConstraintunique_togetherは同じclass meta:ではなく追加することができます...

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

したがって、ベストプラクティスはのconstraints代わりにオプションを使用することunique_togetherですclass meta:


1

構成済みオブジェクトの起点IDを、としてcomposed_object_origin_id更新されsave、として使用されるフィールド()にすることができます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
    )
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.