diadia

興味があることをやってみる。自分のメモを残しておきます。

DRFでGeneric ForeignKey を扱う

GenericForeignKeyの扱い方はDRFドキュメントにある

Serializer relations - Django REST framework

このドキュメントの解釈を行う。自分が作った例は時間があれば、Githubにあげておく。

class TaggedItem(models.Model):
    """
    Tags arbitrary model instances using a generic relation.

    See: https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/
    """
    tag_name = models.SlugField()
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    tagged_object = GenericForeignKey('content_type', 'object_id')

    def __str__(self):
        return self.tag_name
class Bookmark(models.Model):
    """
    A bookmark consists of a URL, and 0 or more descriptive tags.
    """
    url = models.URLField()
    tags = GenericRelation(TaggedItem)


class Note(models.Model):
    """
    A note consists of some text, and 0 or more descriptive tags.
    """
    text = models.CharField(max_length=1000)
    tags = GenericRelation(TaggedItem)
class TaggedObjectRelatedField(serializers.RelatedField):
    """
    A custom field to use for the `tagged_object` generic relationship.
    """

    def to_representation(self, value):
        """
        Serialize tagged objects to a simple textual representation.
        """
        if isinstance(value, Bookmark):
            return 'Bookmark: ' + value.url
        elif isinstance(value, Note):
            return 'Note: ' + value.text
        raise Exception('Unexpected type of tagged object')

1つ目のpart

class TaggedItem(models.Model):
    """
    Tags arbitrary model instances using a generic relation.

    See: https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/
    """
    tag_name = models.SlugField()
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    tagged_object = GenericForeignKey('content_type', 'object_id')

    def __str__(self):
        return self.tag_name

このモデルは単にDjangoのプロジェクトにGeneric ForeignKeyを設けたモデルを定義しているだけだ。Generic ForeignKeyをどう設けるのかは別の資料をチェックしてもらいたい。このGeneric ForeignKeyが存在することでDRFの出力が困るという人に以下を続けて読んでもらいたい。

2つ目のpart

class Bookmark(models.Model):
    """
    A bookmark consists of a URL, and 0 or more descriptive tags.
    """
    url = models.URLField()
    tags = GenericRelation(TaggedItem)


class Note(models.Model):
    """
    A note consists of some text, and 0 or more descriptive tags.
    """
    text = models.CharField(max_length=1000)
    tags = GenericRelation(TaggedItem)

2つ目のパートでもDRFでとくにGeneric ForeignKeyを表示するために何か特別なことをしているわけではない。
ちなみにGenericRelationはGenericForeignKeyで定めたモデルデータの属性を表示する役割である。
参考:The contenttypes framework | Django documentation | Django

3つ目のpart

class TaggedObjectRelatedField(serializers.RelatedField):
    """
    A custom field to use for the `tagged_object` generic relationship.
    """

    def to_representation(self, value):
        """
        Serialize tagged objects to a simple textual representation.
        """
        if isinstance(value, Bookmark):
            return 'Bookmark: ' + value.url
        elif isinstance(value, Note):
            return 'Note: ' + value.text
        raise Exception('Unexpected type of tagged object')

ここでの要点は2点ある。
DRFでForeignKeyのモデルを表示するためにserializers.RelatedFieldを継承したクラスを作成している。 通常のForeignKeyを参照する方法をアレンジしto_representationメソッドで返すデータをクラスによって場合分けを行っている。

あとはこの継承したサブクラスをGeneric ForeignKeyを設定したモデルのSerializerの属性値として設定してやれば、DRFの出力としてGeneric ForeignKeyも出力できるようになる。 属性値としては、read_only=Trueを引数にすることも忘れずに。無いとエラーが出てしまうから。

関連記事:DRFでForeign Keyの値を参照するやり方 - diadia

参考

python - How to Serialize generic foreign key In DRF - Stack Overflow