前置き
以下が、今回の例で使用するViewとModelです。
class PostViewSet(viewsets.ModelViewSet): authentication_classes = [FirebaseAuthentication] queryset = Post.objects.all() serializer_class = PostSerializer
class Post(models.Model): user = models.ForeignKey(User) comment = models.CharField(max_length=130, default='') class User(models.Model): uid = models.CharField(primary_key=True, max_length=64) name = models.CharField(max_length=30)
class PostSerializer(serializers.ModelSerializer): user = UserSerializer() class Meta: model = Post fields = ('id', 'user', 'comment')
このような実装でviewに対しGETでエンドポイントを叩くと、以下のようなレスポンスが帰ってきます。
[ { "id": 1, "user": { "uid": "uid-1", "name": "hoge1" }, "comment": "hoge", }, { "id": 3, "user": { "uid": "uid-2", "name": "hoge1", }, "comment": "hogehoge" } ]
完璧ですね。ネストしたフィールドであるuserが展開されています。
ただし、この状態だとPOSTするときは以下のようなjsonを投げないといけません。。
{ "user": { "uid": "hoge", "name": "hoge" }, "comment": "hoge" }
これは思っているのと違います。userはuidを指定するようにしたいですね。
そこで、PostSerializerを以下のように変更してみます。
class PostSerializer(serializers.ModelSerializer): user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all()) class Meta: model = Post fields = ('id', 'user', 'comment')
これで、以下のようにuidでpostできるようになりました!
{ "user": "hoge_uid", "comment": "", }
ただし、GETが・・・
[ { "id": 1, "user": "hoge_uid", "comment": "hoge" }, { "id": 2, "user": "hoge_uid2", "comment": "hoge" } ]
userがuidしか帰ってこなくなってしまいました。。
これは思ってるのと違いますね。。両立する方法を模索してみます。
本題
ここから本題です。
先程の2つの要件「POST時はネストしたオブジェクトをpkで指定」「GET時はネストしたオブジェクトを展開」を満たすように実装をしていきます。
ModelとViewに変更はありません。Serializerだけ変えていきます。
まず、以下のようにuserのserializerをread onlyに指定します
class PostSerializer(serializers.ModelSerializer): user = UserSerializer(read_only=True)
これの状態だと、GET時はネストしたオブジェクトが展開されて返さられます。 が、POSTの際にuserの指定ができなくなってしまいます。
そこで、更にuid用のフィールドを追加します。
class PostSerializer(serializers.ModelSerializer): user = UserSerializer(read_only=True) user_uid = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), write_only=True) class Meta: model = Post fields = ('id', 'user', 'user_uid', 'comment')
このとき、write_onlyを指定することによりこのフィールドをGET時には出さないようにしておきます。
これで、GET時は展開され、POST時はpk(user_uid)を指定することが可能になりました。
ただし、この状態ではPOSTしたときに「user_uid」カラムがModelにないためエラーが吐かれます。 そこで、最後にcreateメソッドをオーバーライドします。
class PostSerializer(serializers.ModelSerializer): user = UserSerializer(read_only=True) user_uid = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), write_only=True) def create(self, validated_date): validated_date['user'] = validated_date.get('user_uid', None) if validated_date['user'] is None: raise serializers.ValidationError("user not found.") del validated_date['user_uid'] return Post.objects.create(**validated_date)
user_uidを指定すると、validated_date['user_uid']には指定されたpkの「Userオブジェクト」が入っています。
Modelのフィールド名はuser_uidでなくuserなので、そのようにマッピングを変更した上でPostオブジェクトをcreateしてあげればOKです。
これで、POST時は以下のようなフォーマットで、
{ "user_uid": "hoge_uid", "comment": "hoge", }
GET時は以下のように展開されて帰ってきます。
{ "id": 1, "user": { "uid": "hoge_uid", "name": "hoge", }, "comment": "hoge" }
これで意図していた通りに実装ができました!
凄く簡単な要件ですが、少しだけ工夫が必要なんですねー。