【Next.js】DjangoのAPIで記事のタグを更新したい
DjangoのAPIを使って、Next.jsからデータを新規作成や更新する時に、同時に関連データ(ManyToMany)も変更したい。
今回は「記事(Post)」を新規作成/更新して、その記事が持つ「カテゴリ(Category)」と「タグ(Tag)」の関連データも変更する方法をまとめていきます。
目次
今回解決したいこと
最終的に、Next.jsからDjangoにデータを送信して、Post、PostCategoryRelation(中間テーブル)、PostTagRelation(中間テーブル)の3つが更新される状態にしたいです。
(今回の例:記事を更新して、その記事が持つカテゴリ・タグデータの更新がしたい)
そのために下記2つの疑問点を解消していこうと思います。
- Next.jsからどんなデータを送ればいい?
- Djangoでどんな処理を追加すればいい?
疑問1)Next.jsからどんなデータを送ればいい?
const post = {
title: "記事のタイトル"
categories: [
{id: 0},
{id: 1},
],
tags: [
{id: 0},
{id: 1},
{id: 2},
],
}
最終的にこのようなデータをDjangoへ送信します。
記事に追加したいカテゴリ/タグのidを、[{id:xxx}, {id:xxx}, ...]
という形で指定しています。
([xxx,xxx,...]
という配列でシンプルに指定できないかと思ったのですが、Djangoでの受け取り時にエラーでてしまいました。)
疑問2)Djangoでどんな処理を追加すればいい?
DjangoでManyToManyのデータを一緒に更新するには、Serializerで下記2つの処理を追加する必要がありました。
- CategoryとTagのSerializerで「id」を取得可能にする
- PostのSerializerのメソッドを追加する
2-1)CategoryとTagのSerializerで「id」を取得可能にする
class CategorySerializer(serializers.ModelSerializer):
id = serializers.IntegerField(label='id') # 追加
class Meta:
model = Category
fields = '__all__'
class TagSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(label='id') # 追加
class Meta:
model = Tag
fields = '__all__'
class PostSerializer(serializers.ModelSerializer):
categories = CategorySerializer(many=True)
tags = TagSerializer(many=True)
class Meta:
model = Post
fields = '__all__'
これでNext.jsから受け取るデータのcategoriesに指定された[{id: 0}, {id: 1}]
というデータが受け取れるようになります。
この記載がないとNext.jsからデータを受け取る時に下記のエラーがでてしまいます。
【エラー】
The .create() method does not support writable nested fields by default.
Write an explicit .create() method for serializer api.selializers.PostSerializer, or set read_only=True on nested serializer fields.
【Google翻訳】
.create() メソッドは、デフォルトでは書き込み可能なネストされたフィールドをサポートしていません。
シリアライザー api.selializers.PostSerializer の明示的な .create() メソッドを記述するか、ネストされたシリアライザー フィールドに read_only=True を設定します。
2-2)PostのSerializerのメソッドを追加する
class PostSerializer(serializers.ModelSerializer):
categories = CategorySerializer(many=True)
tags = TagSerializer(many=True)
class Meta:
model = Post
fields = '__all__'
# ------------------------------------------------
# 1)「新規作成」の処理
# ------------------------------------------------
def create(self, validated_data):
# 1.1)Next.jsからのデータからCategoriesとTagsを取得する
category_OrderedDicts = validated_data.pop('categories')
tag_OrderedDicts = validated_data.pop('tags')
# 1.2)Postデータを新規作成する
post = Post.objects.create(**validated_data)
# 1.3)Post Category Relationを新規作成する
for category_OrderedDict in category_OrderedDicts:
for key, category_id in category_OrderedDict.items():
category = Category.objects.get(id=category_id)
post.categories.add(category)
# 1.4)Post Tag Relationを新規作成する
for category_OrderedDict in tag_OrderedDicts:
for key, tag_id in category_OrderedDict.items():
tag = Tag.objects.get(id=tag_id)
post.tags.add(tag)
return post
Postの新規作成を行う時に実行されるcreate()
メソッドを上書きして、PostCategoryRelationとPostTagRelationも一緒に作成する処理を追加しています。
またcreate()
はPostを新規作成する場合のみに実行されるので、Postのデータを更新をしたい場合にはupdate()
の処理を変更する必要があります。
update()
の処理については後述の「今回使用しているコード」でまとめています。
今回使用したコード
- Django「models.py」
from django.db import models
# カテゴリ
class Category(models.Model):
name = models.CharField(max_length=50)
# タグ
class Tag(models.Model):
name = models.CharField(max_length=50)
# 記事
class Post(models.Model):
title = models.CharField(max_length=255)
categories = models.ManyToManyField(Category, through='PostCategoryRelation')
tags = models.ManyToManyField(Tag, through='PostTagRelation')
# 記事 カテゴリ 中間テーブル
class PostCategoryRelation(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
# 記事 タグ 中間テーブル
class PostTagRelation(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
- Django「urls.py」
from django.urls import path
from .views import PostsNewView
urlpatterns = [
path('posts/new', PostsNewView.as_view()), # 記事作成用のAPIエンドポイント
]
- Django「views.py」
from rest_framework import generics
from .models import Post
from .serializers import PostSerializer
class PostsNewView(generics.CreateAPIView)
queryset = Post.objects.all()
serializer_class = PostSerializer
- Django「serializers.py」
from rest_framework import serializers
from .models import Category, Tag, Post
class CategorySerializer(serializers.ModelSerializer):
id = serializers.IntegerField(label='id')
class Meta:
model = Category
fields = '__all__'
class TagSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(label='id')
class Meta:
model = Tag
fields = '__all__'
class PostSerializer(serializers.ModelSerializer):
categories = CategorySerializer(many=True)
tags = TagSerializer(many=True)
class Meta:
model = Post
fields = '__all__'
# ------------------------------------------------
# 1)「新規作成」の処理
# ------------------------------------------------
def create(self, validated_data):
# 1.1)Next.jsからのデータからCategoriesとTagsを取得する
category_OrderedDicts = validated_data.pop('categories')
tag_OrderedDicts = validated_data.pop('tags')
# 1.2)Postデータを新規作成する
post = Post.objects.create(**validated_data)
# 1.3)Post Category Relationを新規作成する
for category_OrderedDict in category_OrderedDicts:
for key, category_id in category_OrderedDict.items():
category = Category.objects.get(id=category_id)
post.categories.add(category)
# 1.4)Post Tag Relationを新規作成する
for category_OrderedDict in tag_OrderedDicts:
for key, tag_id in category_OrderedDict.items():
tag = Tag.objects.get(id=tag_id)
post.tags.add(tag)
return post
# ------------------------------------------------
# 2)「更新」の処理
# ------------------------------------------------
def update(self, instance, validated_data):
# Next.jsからのデータからCategoriesとTagsを取得する
category_OrderedDicts = validated_data.pop('categories')
tag_OrderedDicts = validated_data.pop('tags')
# Postデータを更新する
instance.title = validated_data["title"]
instance.save()
# Postを取得する
post = Post.objects.get(id=instance.id)
# PostCategoryRelationとPostTagRelationを削除する
post.categories.clear()
post.tags.clear()
# PostCategoryRelationを作成する
for category_OrderedDict in category_OrderedDicts:
for key, category_id in category_OrderedDict.items():
category = Category.objects.get(id=category_id)
post.categories.add(category)
# PostTagRelationを作成する
for category_OrderedDict in tag_OrderedDicts:
for key, tag_id in category_OrderedDict.items():
tag = Tag.objects.get(id=tag_id)
post.tags.add(tag)
return post
- Next.js「APIのリクエスト」
// Djangoに送信するデータ
const post = {
title: "記事のタイトル"
categories: [
{id: 0},
{id: 1},
],
tags: [
{id: 0},
{id: 1},
{id: 2},
],
}
// Postを新規作成するAPIリクエスト
fetch('http://127.0.0.1:8000/posts/new', {
method: "POST",
headers: { 'Content-Type': 'application/json', },
body: JSON.stringify(post),
credentials: 'include',
})
まとめ
今回はひとまず動くことを目標にまとめてみたので、もう少し他の方法やスマートな方法があるかもしれません。今後見つかり次第、追記していきたいと思います!
最近の記事





