ユーザ依存の評価値の取得
バックエンド
マッパー
backend/api/online/mappers.py
from typing import Literal # <- 追加
...(略)...
class MovieMapper:
def __init__(self, obj):
self.obj = obj
def as_dict(self, user_id): # <- user_idを追加
movie = self.obj
genres = [genre.name for genre in movie.genres.all()]
# ↓追加
rating = None
if user_id:
rating_model = movie.user_ratings[0] if movie.user_ratings else None
rating = RatingMapper(rating_model).as_dict(mode='simple') if rating_model else None
# ↑追加
return {
'id': movie.id,
'title': movie.title,
'year': movie.year,
'genres': genres,
'imdb_id': movie.imdb_id,
'tmdb_id': movie.tmdb_id,
'rating': rating, # <- 追加
}
class RatingMapper:
def __init__(self, obj):
self.obj = obj
def as_dict(self, mode: Literal['simple', 'full'] = 'full'): # <- modeを追加
rating = self.obj
# ↓追加
if mode == 'simple':
return {
'rating': rating.rating,
'rated_at': rating.rated_at,
}
# ↑追加
return {
'id': rating.id,
'user_id': rating.user_id,
'movie_id': rating.movie_id,
'rating': rating.rating,
'rated_at': rating.rated_at,
}
ビュー
backend/api/online/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import User, Movie, Rating
from .mappers import UserMapper, MovieMapper, RatingMapper
from .utils import hash
import uuid
from django.db.models import Prefetch # <- 追加
...(略)...
class MoviesView(APIView):
"""映画リストビュークラス
"""
def get(self, request, format=None):
"""映画リストを取得する。
Response
--------
movies : json
映画リスト
"""
# ↓追加
# ユーザ認証
user_id = request.GET.get('user_id') if 'user_id' in request.GET else None
# ↑追加
# ↓修正
# オブジェクトの取得
movies = []
if user_id:
movies = Movie.objects.order_by('?')[:20]\
.prefetch_related('genres')\
.prefetch_related(
Prefetch(
'movie_ratings',
queryset=Rating.objects.filter(user_id=user_id),
to_attr='user_ratings'
)
)
else:
movies = Movie.objects.order_by('?')[:20].prefetch_related('genres')
# ↑修正
# レスポンス
movies_dict = [MovieMapper(movie).as_dict(user_id) for movie in movies] # <- user_idを追加
data = {
'movies': movies_dict,
}
return Response(data, status.HTTP_200_OK)
class MovieView(APIView):
"""映画ビュークラス
"""
def get(self, request, id, format=None):
"""映画を取得する。
Attributes
----------
id : int
映画ID
Response
--------
movie : json
映画
"""
# ↓追加
# ユーザ認証
user_id = request.GET.get('user_id') if 'user_id' in request.GET else None
# ↑追加
# ↓修正
# オブジェクトの取得
movie = None
if user_id:
movie = Movie.objects.filter(id=id)\
.prefetch_related('genres')\
.prefetch_related(
Prefetch(
'movie_ratings',
queryset=Rating.objects.filter(user_id=user_id),
to_attr='user_ratings'
)
).first()
else:
movie = Movie.objects.get(pk=id)
# ↑修正
# レスポンス
movie_dict = MovieMapper(movie).as_dict(user_id) # <- user_idを追加
data = {
'movie': movie_dict,
}
return Response(data, status.HTTP_200_OK)
...(略)...
ブラウザで下記URLにアクセスしてください。
下記のように、ユーザによる評価値が付与された映画データが取得できます。
{
"movie": {
"id": 1,
"title": "Toy Story",
"year": 1995,
"genres": [
"Adventure",
"Animation",
"Children",
"Comedy",
"Fantasy"
],
"imdb_id": 114709,
"tmdb_id": 862,
"rating": {
"id": "【ユーザID】_000001",
"user_id": "【ユーザID】",
"movie_id": 1,
"rating": 4.5,
"rated_at": "2025-03-22T08:12:46.981694Z"
}
}
}
ブラウザで下記URLにアクセスしてください。
ユーザによる評価値が付与された映画リストが取得できます。確認しやすいように、映画リストでランダムに取得している箇所を一時的に下記のように修正しても良いです。
backend/api/online/views.py
...(略)...
class MoviesView(APIView):
"""映画リストビュークラス
"""
def get(self, request, format=None):
...(略)...
# オブジェクトの取得
if user_id:
# movies = Movie.objects.order_by('?')[:20]\
movies = Movie.objects.all()[:20]\ # <- 修正
.prefetch_related('genres')\
.prefetch_related(
Prefetch(
'movie_ratings',
queryset=Rating.objects.filter(user_id=user_id),
to_attr='user_ratings'
)
)
...(略)...
フロントエンド
API
frontend/src/services/movies/getMovies.ts
import { ApiContext, Movie, User } from '@/types/data'; // <- Userを追加
import { fetcher } from '@/utils';
const context: ApiContext = {
apiRootUrl: process.env.NEXT_PUBLIC_API_BASE_URL,
};
/**
* 映画リスト取得API
* @param user - ユーザ // <- 追加
* @returns 映画リスト
*/
const getMovies = async (user?: User): Promise<{ movies: Movie[] }> => { // <- userを追加
const userParam = user ? `?user_id=${user.id}` : ''; // <- 追加
return await fetcher(
`${context.apiRootUrl?.replace(/\/$/g, '')}/movies/${userParam}`, // <- userParamを追加
{
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
cache: 'no-store',
});
};
export default getMovies;
frontend/src/services/movies/getMovie.ts
import { ApiContext, Movie, User } from '@/types/data'; // <- Userを追加
import { fetcher } from '@/utils';
const context: ApiContext = {
apiRootUrl: process.env.NEXT_PUBLIC_API_BASE_URL,
};
/**
* 映画取得API
* @param user - ユーザ // <- 追加
* @param movieId - 映画ID
* @returns 映画
*/
const getMovie = async (movieId: number, user?: User): Promise<{ movie: Movie }> => { // <- userを追加
const userParam = user ? `?user_id=${user.id}` : ''; // <- 追加
return await fetcher(
`${context.apiRootUrl?.replace(/\/$/g, '')}/movies/${movieId}/${userParam}`, // <- userParamを追加
{
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
cache: 'no-store',
}
);
};
export default getMovie;
ページ
frontend/src/app/movies/[id]/page.tsx
...(略)...
const Movie = async ({ params }: { params: Promise<{ id: number }> }) => {
await connectUser();
const session = await auth();
const user = session ? await getUser(session?.user?.email!) : null;
const { id } = await params;
const movieId = id;
const { movie } = await getMovie(movieId, user!); // <- userを追加
...(略)...
};
export default Movie;
frontend/src/app/page.tsx
...(略)...
const Index = async () => {
await connectUser();
const session = await auth();
const user = session ? await getUser(session?.user?.email!) : null;
const { movies } = await getMovies(user!); // <- userを追加
...(略)...
};
export default Index;
ブラウザで下記それぞれのURLにアクセスしてください。
評価値を入力した後、ブラウザを更新してください。入力した評価値が維持されています。