プライマリーキー以外で逆参照しているモデルにシリアライザーを紐づけではまった話

なんだかんだ情報が少なくて結構困ったので覚書。

以下のようなモデルがあるとする。

class User(models.Model):
  ...
  id # primary key
  some = models.CharField(..., unique=True) # primary keyとは別でMoneyへ紐づけているカラム

class Money(models.Model):
  some = models.ForeginKey("User", to_field="some", db_column="some", realted_name="money", null=False, on_delete=models.CASCADE)

今回やりたかったのはMoneyをPOST時に、MoneySerializerのsomeの値にはそのままsomeを文字列で渡し、GET時にはUserの情報も同時に取るというこであった。 ↓こんな感じ post -> {some: 'aaaaaaaa-abc',...} get -> {some: { id: 'user_name' }}

何も工夫をせずにPOSTされてきた情報をシリアライザーに投げるとsomeを文字列として保存は可能だが、それだとGET時にまでsomeの値が文字列で帰ってくることになる。

class UserSerializer(serializers.ModelSerializer):
  

  class Meta:
    model = User
    fields = "__all__"

class MoneySerializer(serializers.ModelSerializer):

  class Meta:
    model = Money
    fields= "__all__"

post -> {some: 'aaaaaaaa-abc',...} get -> {some: 'aaaaaaaa-abc',...}

では、取得時にはMoneySerializerの中でUserSerializerを通せばいいということで、someフィールドに対してUserSerializerを適用すると、今度は、POST時にまでUserモデルのインスタンスを求められてしまう。

class UserSerializer(serializers.ModelSerializer):
  

  class Meta:
    model = User
    fields = "__all__"

class MoneySerializer(serializers.ModelSerializer):
  some = UserSerializer(many=False)

  class Meta:
    model = Money
    fields= "__all__"

そこで、MoneySerializerの動作をPOST時とゲット時で分けるという方法だ。 確認はしていないが、MoneyモデルがUserモデルのプライマリキーに紐づいている場合には以下のようにすることで動作するはずだ。

class UserSerializer(serializers.ModelSerializer):
  

  class Meta:
    model = User
    fields = "__all__"

class MoneySerializer(serializers.ModelSerializer):
  some = UserSerializer(many=False, read_only=True) # readのみ
  some_id = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), source='some', write_only=True) #writeのみ

  class Meta:
    model = Money
    fields= "__all__"

ただ、上記のPrimaryKeyRelatedFieldはUserモデルのプライマリキーに紐づいている場合にのみ可能らしく、今回のようにプライマリーキー以外を利用している場合には使えない事が判明した。 ここで、利用できるのがSlugRelatedFieldである。これはフィールド(今回だとto_field="some")で指定している参照先へ関連をもたせてくれるものらしい。

class UserSerializer(serializers.ModelSerializer):

  class Meta:
    model = User
    fields = "__all__"

class MoneySerializer(serializers.ModelSerializer):
  some = UserSerializer(many=False, read_only=True) # readのみ
  some_id = serializers.SlugRelatedField(queryset=User.objects.all(), source='some', slug_field='uid' write_only=True) #writeのみ

  class Meta:
    model = Money
    fields= "__all__"

こうすることで、ばっちり動作した。 当然POSTで流れてきたsome_idがUserのsomeカラムに存在しない場合には保存できないというのが求められる挙動だが、しっかりと検証された上で動作することが確認できた。