View on GitHub

ユーザ依存の評価値の取得

Home

ユーザ依存の評価値の取得

バックエンド

マッパー

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にアクセスしてください。

評価値を入力した後、ブラウザを更新してください。入力した評価値が維持されています。