BW-Movie.pdf
1.30MB

 

아직 팀원 소개 페이지와 트러블 슈팅 부분은 완성하지 못한 상태예요.


더보기
<h1>🎬 BW Movie 추천 🍿</h1>
<img src="https://github.com/user-attachments/assets/e97b6231-d311-499c-a6d2-d431dcca4418" width="1200" height="400"/>
<br><br>
<h3> 안성재 셰프, 백종원 사업가의 목소리로 추천받는 영화 AI 채팅 서비스 </h3>
<hr>
<h1>⚙️ Front를 위해서 아래의 항목들을 설치해주세요.</h1>
<code>$ cd react-frontend
 $ npm install axios react-router-dom react-toastify @emotion/react @emotion/styled @mui/icons-material</code><br>
 <h1>⚙️ Back을 위해서 아래의 항목들을 설치해주세요.</h1>
<code>$ pip install -r requirments.txt
 $ dataset/faiss/documents.pkl
 $ dataset/faiss/index.faiss
 $ dataset/faiss/index.pkl
</code>
<h1>⚙️ 서버 시작을 위해서 터미널에 입력해주세요.</h1>
<code>DRF-Backend/ : $ redis-server
DRF-Backend/ : $ daphne -b 0.0.0.0 -p 8000 main.asgi:application
react-frontend/ : $ npm start
</code>
 <hr>
<h1>⚙️ 사용된 핵심 기술</h1>
<blockquote><strong>LLM</strong></blockquote>
<il>1. GPT 4o</il><br>
<il>2. 11ElevenLabs</il><br><br>
<blockquote><strong>Backend</strong></blockquote>
<il>1. redis</il><br>
<il>2. django REST Framework</il><br>
<il>3. django Channels</il><br><br>
<blockquote><strong>Frontend</strong></blockquote>
<il>1. React-JS</il><br>
<hr>
<h2>📺 물음에 대한 답변 화면</h2>
<img src="https://github.com/user-attachments/assets/5596e61a-0d0b-4997-9a1c-6bd1c12f3850">

내일이면 서브 팀 프로젝트가 마무리되는 날이에요.
내일 최종 자료를 제출해야 하기 때문에, 발표 자료 준비 및 로직 마무리 단계를 가지는 날이에요.

부족한 점도 많았고, 모르는 개념도 많았지만 챙겨주셔서 너무 감사했던 팀 프로젝트였습니다. ☺️

대충 틀만 잡아놓았고요, 나중에 내용 수정과 함께 살을 덧붙여나갈 예정이에요.

pages.zip
0.01MB
랜덤으로 25개를 보여주기 때문에, 포스터는 계속 바뀌어요

10:00 ~ 12:10 아침 회의

14:10 ~ 16:00 실시간 화면 공유하며 해결해 나가는 작업

1. 회원 가입 시, 영화 선택 및 장르 끌고 오는 작업
2. jwt 토큰 코드 해결
3. 영화 최신화
4. 영화 벡터 검색 후, LLM으로 넘겨서 성격에 따라 추천 기능 구현
5. 회원 가입, 로그인&아웃 프런트 문제 해결

<변경 사항>
1. 프롬프트
2. 임베딩 시, title을 사용하고, 질문을 한글로 작성 시 영어로 번역되도록 설정

 

이렇게 오늘 해야 할 일을 공유했고, 주말 동안 구현해 왔던 걸 github를 통해서 merge도 했어요.

프런트 부분에서 회원 가입, 로그인, 로그아웃 기능 와중에 문제가 있어서 해결해야 합니다!

📘 DRF  


🔗 [👤기능구현] 이전에 만든 movie 수정 및 Genre model 생성

이 글에서 이어지는 내용이에요.

movie preference 관련해서 미완성했는데,

딱! zep에 들어갔을 때 팀원분이 계시더라고요.

그래서 코드 보여드리면서 보완 및 완성한 코드예요.

2025년 2월 23일 일요일


목차

  1. movies.models.py
  2. CSV 파일을 SQLite에 이식하기
  3. views.py

⚙️ movies/models.py

1️⃣ Movie Model

더보기
"""Movie model"""
class Movie(models.Model):
    id = models.CharField(max_length=100, primary_key=True)  # TMDB ID(기존 데이터셋의 ID)
    title = models.CharField(max_length=100)  # 제목
    revenue = models.IntegerField(default=0)  # 수익
    vote_average = models.FloatField(default=0.0)  # 평점
    imdb_id = models.CharField(max_length=100, default="")  # IMDB ID
    original_title = models.CharField(max_length=100)  # 원제(개봉국가제목)
    overview = models.TextField(default="")  # 줄거리
    popularity = models.FloatField(default=0.0)  # 인기도
    genres = models.CharField(max_length=100, default="")  # 장르
    poster_path = models.CharField(max_length=100, default="")  # 포스터 경로(url)
    keywords = models.CharField(max_length=100, default="")  # 키워드

    def __str__(self):
        return self.title

2️⃣ Movie Preference Model

더보기
"""영화 선호도 정리를 위한 model"""
class MoviePreference(models.Model):
    user_id_fk = models.ForeignKey(User, on_delete=models.CASCADE, related_name='genre_preference')# FK "유저 ID"
    movie_id_fk = models.ForeignKey(Movie, on_delete=models.CASCADE, related_name='movie_preference') # FK "영화 ID"
    preference_type = models.CharField(
        max_length=10,
        choices=[('like', 'Like'), ('dislike', 'Dislike')],
        default='dislike') # 선호도 유형 like, dislike

    def __str__(self):
        return f"{self.user_id_fk} liked/disliked {self.movie_id_fk}."

3️⃣ Genre Preference Model

더보기
"""장르 선호도 정리를 위한 model"""
class GenrePreference(models.Model):
    user_id_fk = models.ForeignKey(User, on_delete=models.CASCADE) # FK 유저 ID
    genre_id = models.CharField(max_length=100, default="", unique=True)
    preference_type = models.CharField(
        max_length=10,
        choices=[('like', 'Like'), ('dislike', 'Dislike')],
        default='dislike') # 선호도 유형 like, dislike
        
    def __str__(self):
        return f"{self.user_id_fk} liked/disliked {self.genre_id_fk}."

⚙️ SQLite에 CSV 내용 넣기

1️⃣ 시작 전에!

더보기
"""
이 스크립트를 실행하기 전에 다음 단계를 따라주세요:

1. Python 가상환경 생성 및 활성화:
   - Windows:
     python -m venv .venv
     .venv\Scripts\activate
   - Mac/Linux:
     python3 -m venv .venv
     source .venv/bin/activate

2. 필요한 패키지 설치:
   프로젝트 루트의 DRF-Backend 디렉토리에서:
   pip install -r requirements.txt

3. 데이터 파일 준비:
   - 프로젝트 루트에 'dataset' 폴더 생성
   - 'dataset' 폴더에 'revised_df.csv' 파일 위치

4. 데이터베이스 마이그레이션:
    python manage.py migrate

5. 스크립트 실행:
   python -m movies.import_movies
"""

2️⃣ 시~작!

더보기
import os
import sys

# 프로젝트 루트를 sys.path에 추가하여 import 문제를 방지합니다.
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))

# Django 설정 모듈을 지정합니다.
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "main.settings")
import django

django.setup()

from movies.models import Movie

import csv
from datetime import datetime


def import_movies(file_path):
    try:
        with open(file_path, "r", encoding="utf-8") as file:
            reader = csv.DictReader(file)
            count = 0
            for row in reader:
                try:
                    Movie.objects.create(
                        id=row["id"],
                        title=row["title"],
                        revenue=row["revenue"],
                        vote_average=row["vote_average"],
                        imdb_id=row["imdb_id"],
                        original_title=row["original_title"],
                        overview=row["overview"],
                        popularity=row["popularity"],
                        genres=row["genres"],
                        poster_path=row["poster_path"],
                        keywords=row["keywords"],
                    )
                    count += 1
                except Exception as e:
                    print(
                        f"Error importing movie {row.get('title', 'unknown')}: {str(e)}"
                    )
            print(f"Successfully imported {count} movies")
    except FileNotFoundError:
        print(f"Error: Could not find the CSV file at {file_path}")
        print("Please make sure the CSV file exists in the dataset directory")
        sys.exit(1)
    except Exception as e:
        print(f"Error reading the CSV file: {str(e)}")
        sys.exit(1)


if __name__ == "__main__":
    # 프로젝트 루트 디렉토리 기준으로 상대 경로 설정
    project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
    csv_file_path = os.path.join(project_root, "dataset", "revised_df.csv")

    print(f"Looking for CSV file at: {csv_file_path}")
    import_movies(csv_file_path)

from movies.models import Movie
  • 저희는 movies라는 app에 있는 Movie 모델을 쓸 거예요.
for row in reader:
    try:
        Movie.objects.create(
            id=row["id"],
            title=row["title"],
            revenue=row["revenue"],
            vote_average=row["vote_average"],
            imdb_id=row["imdb_id"],
            original_title=row["original_title"],
            overview=row["overview"],
            popularity=row["popularity"],
            genres=row["genres"],
            poster_path=row["poster_path"],
            keywords=row["keywords"],
            )
  • 이 부분은 사용하시는 모델에 맞게끔 변경해주세요.
  • 칼럼을 만들고 그 칼럼의 행에 값을 넣는 작용이에요.

 

그러면 CSV 파일에 있던 값들이 이렇게 SQLite에 들어가게 돼요!

얏호~!


⚙️ movies/urls.py

더보기
from django.urls import path
from . import views

urlpatterns = [
    path('movielist/', views.SignUpMovieListView.as_view()), # Signup movie list로 가기
]

⚙️ movies/views.py

더보기
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
import random

from .models import Movie
from .serializers import SignupMovieListSerializer

"""랜덤한 영화 리스트 25개 보여주는 코드"""
class SignUpMovieListView(APIView):

    def get(self, request):
        # 평점 내림차순으로 정렬하고 수익이 3억 달러 이상인 영화 100개 필터링
        top_movies = Movie.objects.filter(revenue__gte=300000000).order_by(
        "-vote_average")[:100]  # 수익이 3억 달러 이상인 영화 중 상위 100개 선택

        # 100개 중 랜덤으로 25개 선택
        random_movies = list(top_movies)  # 쿼리셋을 리스트로 변환
        random_selection = random.sample(
            random_movies, min(25, len(random_movies))
        )  # 랜덤으로 25개 영화 선택

        # 선택된 영화들을 직렬화
        serializer = SignupMovieListSerializer(random_selection, many=True)

        return Response(serializer.data, status=status.HTTP_200_OK)

SignupMovieListSerializer

더보기
"""회원가입 시, 보여지는 영화 포스터 리스트 및 장르"""
class SignupMovieListSerializer(serializers.ModelSerializer):
    
    class Meta:
        model = Movie
        fields = ['id', 'original_title', 'poster_path']
        read_only_fields = ['id']

Postman

프런트가 없어서 postman의 존재가 너무 감사해요 ㅠㅠㅠㅠ 으흐흑

 

일단 저희가 제작한 서비스는 로그인을 해야 접근이 가능해서

Headers에 Authorization - Bearer로 토큰을 집어넣어 줘요.

그러고 url을 movie/movielist/로 들어가서 Send를 해주면,

 

movie-id, original_title, poster_path로 총 25개 랜덤으로 보여줍니다!

 

 

이제는 선호하는 영화를 5개를 입력하면 저렇게 성공했다는 메시지가 떠요.

 

movie preference
genre preference

짜잔~!

DB에도 저장이 잘 되어 있음을 알 수 있어요!

 

 

5개 이상이면 error 메시지도 잘 뜨네요!

📘 DRF 

 

ERD를 기준으로 Genre 관련 Model들을 만들어 봅시다. 😎


목차

  1. movies/models.py 수정
  2. movies/views.py 수정
  3. Genre, Genre_preference 생성

⚙️ movies/models.py 수정

이전에는 SignUpMovieListModel만 완성하고 그쳤어요!

오늘 Genre 모델을 구현할 거예요

그래서

전에 만들었던 코드를 다시 확인했는데 🤔

 

<이전 models.py 코드>

더보기
더보기
from django.db import models
from accounts.models import User
from enum import Enum

"""Enum용 모델"""
class PerformEumsType(Enum):
    like = "like"
    dislike = "dislike"


"""Movie model"""
class Movie(models.Model):
    id = models.IntegerField(primary_key=True)  # TMDB ID(기존 데이터셋의 ID)
    title = models.CharField(max_length=100)  # 제목
    revenue = models.IntegerField(default=0)  # 수익
    vote_average = models.FloatField(default=0.0)  # 평점
    imdb_id = models.CharField(max_length=100, default="")  # IMDB ID
    original_title = models.CharField(max_length=100)  # 원제(개봉국가제목)
    overview = models.TextField(default="")  # 줄거리
    popularity = models.FloatField(default=0.0)  # 인기도
    genres = models.CharField(max_length=100, default="")  # 장르
    poster_path = models.CharField(max_length=100, default="")  # 포스터 경로(url)
    keywords = models.CharField(max_length=100, default="")  # 키워드

    def __str__(self):
        return self.title


"""영화 선호도 정리를 위한 model"""
class MoviePreference(models.Model):
    user_id_fk = models.ForeignKey(User, on_delete=models.CASCADE)# FK "유저 ID"
    movie_id_fk = models.ForeignKey(Movie, on_delete=models.CASCADE) # FK "영화 ID"
    preference_type = models.CharField(
        max_length=50,
        choices=[(tag.value, tag.name) for tag in PerformEumsType],
        default=PerformEumsType.dislike.value) #"선호도 유형 (ENUM: like, dislike)"
    # PRIMARY KEY "(user_id_fk, movie_id_fk)"

 

불편한 점 :

  1. 굳이 따로 Eums를 해줘야 하는지 싶어요.
  2. Movie(models.Model)
    genres = models.CharField(max_length=100, default="")  # 장르
    • CSV 파일을 보면 하나의 영화에 여러 개의 장르를 가지고 있어서 수정해야 할 거 같아요.

변경점 : 

  1. Eums 삭제하고 CharField에 "choices="로 깔끔하게 수정했어요.
  2. genres를 ManyToManyField로 수정했어요.

<전체 코드>

더보기
더보기
"""Movie model"""
class Movie(models.Model):
    ---    
    """genres : CharField에서 ManyToManyField로 변경"""
    genres = models.ManyToManyField(Genre, related_name="movies")  # 장르
    
"""영화 선호도 정리를 위한 model"""
class MoviePreference(models.Model):
    user_id_fk = models.ForeignKey(User, on_delete=models.CASCADE, related_name='genre_preference')# FK "유저 ID"
    movie_id_fk = models.ForeignKey(Movie, on_delete=models.CASCADE, related_name='movie_preference') # FK "영화 ID"
    preference_type = models.CharField(
        max_length=10,
        choices=[('like', 'Like'), ('dislike', 'Dislike')],
        default='dislike') # 선호도 유형 like, dislike

    """데이터베이스 내에서 고유하도록 강제하여, 복합 primary key rngus"""
    class Meta:
        unique_together = ('user_id_fk', 'movie_id_fk')
        
    def __str__(self):
        return f"{self.user_id_fk} liked/disliked {self.movie_id_fk}."

⚙️ movies/views.py 수정

<이전 views.py 코드>

더보기
더보기
from rest_framework.response import Response
from .serializers import SignupMovieListSerializer
from rest_framework.decorators import api_view
import pandas as pd

"""랜덤한 영화 리스트 25개 보여주는 코드"""
@api_view(['GET'])
def SignUpMovieListView(request):
    """이미지 들고올 코드"""
    TMDB_IMAGE_BASE_URL = "https://image.tmdb.org/t/p/w500"
    
    """csv 불러 오기"""
    df = pd.read_csv(
        '/Users/baeminkyung/Desktop/github/UNO_BWMovie_recsys/dataset/signup_movie_list.csv'
        )
    
    """100개의 original_title, poster_path 열 중에서 25개 랜덤으로 데리고 옴"""
    sampled_df = df.sample(n=25)[['original_title', 'poster_path']]
    
    """poster_path를 완전한 이미지 URL로 변환"""
    sampled_df['poster_url'] = TMDB_IMAGE_BASE_URL + sampled_df['poster_path']
    
    data = sampled_df[['original_title', 'poster_url']].to_dict(orient='records')

    """serializer를 사용하여 데이터 변환 및 유효성 검사"""
    serializer = SignupMovieListSerializer(data=data, many=True)
    if serializer.is_valid():
        return Response(serializer.data)
    return Response(serializer.errors, status=400) # 에러 처리

불편한 점 :

  1. FBV여서 너무 불편해요.
    • 메서드 하나하나 적어줘야 하는 게 불편해요.
  2. 프런트 때 필요한 poster_path은 백에선 불필요한 코드예요
  3. CSV 파일의 경로가 절대 경로예요.
    • 다른 팀원이 볼 때 오류가 발생할 수 있어요.

변경점 :

  1. CBV로 바꾸어주었어요.
  2. poster_path 관련 코드는 삭제해 주었어요.
  3. settings.py에 CSV_FILE_PATH 경로를 설정해 주었어요

<전체 코드>

더보기
더보기
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
import pandas as pd

from django.conf import settings
from .models import GenrePreference, Genre
from .serializers import SignupMovieListSerializer


"""랜덤한 영화 리스트 25개 보여주는 코드"""
class SignUpMovieListView(APIView):
    def get(self, request):
        """csv 불러 오기"""
        df = pd.read_csv(settings.CSV_FILE_PATH)

        """이미지 들고올 코드"""
        TMDB_IMAGE_BASE_URL = "https://image.tmdb.org/t/p/w500"

        """100개의 original_title, poster_path 열 중에서 25개 랜덤으로 데리고 옴"""
        sampled_df = df.sample(n=25)[['original_title', 'poster_path', 'genres']]

        """poster_path를 완전한 이미지 URL로 변환"""
        sampled_df['poster_url'] = TMDB_IMAGE_BASE_URL + sampled_df['poster_path']

        data = sampled_df[['original_title', 'genres']].to_dict(orient='records')

        """serializer를 사용하여 데이터 변환 및 유효성 검사"""
        serializer = SignupMovieListSerializer(data=data, many=True)
        if serializer.is_valid():
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class CreateGenrePreferenceView(APIView):
    """
    장르 선호도 생성/조회
    """

    def get(self, request):
        """
        특정 사용자의 장르 선호도 목록을 조회
        """
        user = request.user  # 현재 인증된 사용자
        genre_preferences = GenrePreference.objects.filter(user_id_fk=user)
        genre_data = [{'genre_id_fk': p.genre_id_fk.id,
                       'genre': p.genre_id_fk.genre,
                       'preference_type': p.preference_type} for p in genre_preferences]
        return Response(genre_data, status=status.HTTP_200_OK)


    def post(self, request):
        """
        사용자가 선택한 장르를 받아서 장르 선호도 데이터베이스에 저장
        """
        # 요청 데이터에서 선택한 장르 목록 가져오기. 없으면 빈 리스트 사용
        selected_genres = request.data.get('selected_genres', [])  
        user = request.user  # 현재 인증된 사용자 가져오기

        for genre_name in selected_genres:
            # 장르 이름으로 장르 객체 가져오기
            genre = Genre.objects.get(genre=genre_name)  # DB에서 찾
            # 장르 선호도 객체 가져오기
            GenrePreference.objects.get(
                user_id_fk=user, # 사용자 ID
                genre_id_fk=genre,  # 장르 ID
                defaults={'preference_type': 'like'}  # 기본 설정: '좋아요' (선호)
            )
        return Response(status=status.HTTP_201_CREATED)  # 성공적으로 생성되면 201 응답 반환

3. settings.py에 CSV_FILE_PATH 경로를 설정

"""csv file 경로 설정"""
BASE_DIR = Path(__file__).resolve().parent.parent.parent
CSV_FILE_PATH = os.path.join(BASE_DIR, 'dataset', 'signup_movie_list.csv')

 

폴더 형태가 저렇게 돼 있어서,

BASE_DIR = Path(__file__).resolve().parent.parent.parent

 

최상위 폴더로 갈 거면 parent를 세 번이나 해줘야 최상위로 가더라고요. ㄷㄷ

 

CSV_FILE_PATH = os.path.join(BASE_DIR, 'dataset', 'signup_movie_list.csv')

 

쓰고자 하는 CSV 파일명도 명확히 해주세요!

 

 

postman으로 제대로 작동하는지 확인하고!

Genre로 넘어갔어요. 😎


⚙️ Genre, Genre_preference 생성

1️⃣ movies/Models.py

  • Genre 모델 및 GenrePreference 모델 생성
더보기
더보기
from django.db import models
from accounts.models import User

"""Genres model"""
class Genre(models.Model):
    id = models.AutoField(primary_key=True) # 장르 ID (AutoField)
    genre = models.CharField(max_length=100) # 장르 이름 (CharField)
 
    def __str__(self):
        return self.genre
        
"""장르 선호도 정리를 위한 model"""
class GenrePreference(models.Model):
    user_id_fk = models.ForeignKey(User, on_delete=models.CASCADE) # FK 유저 ID
    genre_id_fk = models.ForeignKey(Genre, on_delete=models.CASCADE) # FK 장르 ID
    preference_type = models.CharField(
        max_length=10,
        choices=[('like', 'Like'), ('dislike', 'Dislike')],
        default='dislike') # 선호도 유형 like, dislike
    
    """데이터베이스 내에서 고유하도록 강제하여, 복합 primary key와 같은 역할을 함"""
    class Meta:
        unique_together = ('user_id_fk', 'genre_id_fk')
        
    def __str__(self):
        return f"{self.user_id_fk} liked/disliked {self.genre_id_fk}."

2️⃣ movies/urls.py

더보기
더보기
from django.urls import path
from . import views

urlpatterns = [
    path('genrepreference/', views.CreateGenrePreferenceView.as_view()), # 선호 장르 생성
]

3️⃣ movies/serializers.py

더보기
더보기
from rest_framework import serializers
from .models import Movie, Genre

"""GenreSerializer"""
class GenreSerializer(serializers.ModelSerializer):
    class Meta:
        model = Genre
        fields = ['id', 'genre']
        
        """따로 수정할 게 아니라면 읽기 모드로 넣어서 데이터 보호"""
        read_only_fields = ['id', 'genre']


"""회원가입 시, 보여지는 영화 포스터 리스트 및 장르"""
class SignupMovieListSerializer(serializers.ModelSerializer):
    genres = GenreSerializer(many=True, read_only=True)
    
    class Meta:
        model = Movie
        fields = ['id', 'original_title', 'poster_path', 'genres']
        read_only_fields = ['id']

 

  • 장르를 추가해줘서, 기존에 만들어두었던 SignUpMovieListSerializer에다가 'genres'도 추가해줬어요.

4️⃣ movies/views.py

더보기
더보기
class CreateGenrePreferenceView(APIView):
    """
    장르 선호도 생성/조회
    """

    def get(self, request):
        """
        특정 사용자의 장르 선호도 목록을 조회
        """
        user = request.user  # 현재 인증된 사용자
        genre_preferences = GenrePreference.objects.filter(user_id_fk=user)
        genre_data = [{'genre_id_fk': p.genre_id_fk.id,
                       'genre': p.genre_id_fk.genre,
                       'preference_type': p.preference_type} for p in genre_preferences]
        return Response(genre_data, status=status.HTTP_200_OK)


    def post(self, request):
        """
        사용자가 선택한 장르를 받아서 장르 선호도 데이터베이스에 저장
        """
        # 요청 데이터에서 선택한 장르 목록 가져오기. 없으면 빈 리스트 사용
        selected_genres = request.data.get('selected_genres', [])  
        user = request.user  # 현재 인증된 사용자 가져오기

        for genre_name in selected_genres:
            # 장르 이름으로 장르 객체 가져오기
            genre = Genre.objects.get(genre=genre_name)  # DB에서 찾
            # 장르 선호도 객체 가져오기
            GenrePreference.objects.get(
                user_id_fk=user, # 사용자 ID
                genre_id_fk=genre,  # 장르 ID
                defaults={'preference_type': 'like'}  # 기본 설정: '좋아요' (선호)
            )
        return Response(status=status.HTTP_201_CREATED)  # 성공적으로 생성되면 201 응답 반환

 

genre_id_fk는 movie_id_fk와 같은 거예요.

csv 파일에 있는 id가 영화도 지칭하고, 장르도 지칭하기 때문에 똑같은 애입니다.

 

genre_id_fk : 313369로 돼 있잖아요? 이게 "라라랜드"예요.

라라랜드가 가지고 있는 장르들을 좋아한다고 POST 보냈고,

 

 

GET 메서드로 해서 확인했을 때, 200 OK가 떴어요.

오늘 짠 views.py는 잘 작동하네요!


마무리

근데

이제 코드를 다 정리하고 깨달은 점이 movie preference에 대한 views.py 처리를 안 했어요 🙄

내일은 이걸 마무리해야 할 것 같아요.

 

그리고

제가 짠 게 작동은 잘 되지만, 우리가 구현해내고자 한 방향대로 설계한 게 맞는지 모르겠어요..

팀원분들께 코드 완성을 슬랙으로 알리고 해야겠어요

 

세미 프로젝트는 26일 수요일에 최종 발표이기 때문에 마음이 급해지네요,, 흑

 

 

📘 DRF 

이렇게 했지만 25개의 영화를 어떤 기준에서 보여줄지 논의를 끝마친 게 ↓

signup_movie_list.csv
0.06MB

 

겟츠 고 😎


SignUpMoviePreference.zip
0.03MB


DataFrame을 위해서 import 해줍시당

import pandas as pd
import numpy as np

 

csv 불러오기

# csv 불러 오기
df = pd.read_csv('/Users/baeminkyung/Desktop/github/UNO_BWMovie_recsys/dataset/signup_movie_list.csv')
df

 

총 행이 100개이고 13개의 칼럼을 가진 파일이에요!

여기서 랜덤으로 25개씩 불러와서 회원 가입 창에서 보여줄 거예요

포스터 사진과 함께요!

 

랜덤 25개 뽑기

# 랜덤으로 25개 뽑고, id, title, original_title, genres, poster_path 열만 추출, index 번호 새로 부여하기
sampled_df = df.sample(n=25)[['id', 'title', 'original_title', 'genres','poster_path']].reset_index(drop=True)

"""포스터 보기 : https://image.tmdb.org/t/p/w500/<이미지 파일명>"""

# display 함수를 사용하여 DataFrame을 표 형태로 출력
from IPython.display import display
display(sampled_df)

 

랜덤으로 25개가 계속 나오더라고요

sampled_df = df.sample(n=25)[['id', 'title', 'original_title', 'genres','poster_path']]

 

나중에 views.py에서 위에 코드를 활용할 예정이에요!


ERD를 토대로 모델 구성하기

 

작성해 두었던 ERD를 토대로 모델 field를 설정해 주었어요

 

Movie Preference model

더보기
from django.db import models
from accounts.models import User
from enum import Enum

"""Enum용 모델"""
class PerformEumsType(Enum):
    like = "like"
    dislike = "dislike"


"""Movie model"""
class Movie(models.Model):
    id = models.IntegerField(primary_key=True)  # TMDB ID(기존 데이터셋의 ID)
    title = models.CharField(max_length=100)  # 제목
    revenue = models.IntegerField(default=0)  # 수익
    vote_average = models.FloatField(default=0.0)  # 평점
    imdb_id = models.CharField(max_length=100, default="")  # IMDB ID
    original_title = models.CharField(max_length=100)  # 원제(개봉국가제목)
    overview = models.TextField(default="")  # 줄거리
    popularity = models.FloatField(default=0.0)  # 인기도
    genres = models.CharField(max_length=100, default="")  # 장르
    poster_path = models.CharField(max_length=100, default="")  # 포스터 경로(url)
    keywords = models.CharField(max_length=100, default="")  # 키워드

    def __str__(self):
        return self.title


"""영화 선호도 정리를 위한 model"""
class MoviePreference(models.Model):
    user_id_fk = models.ForeignKey(User, on_delete=models.CASCADE)# FK "유저 ID"
    movie_id_fk = models.ForeignKey(Movie, on_delete=models.CASCADE) # FK "영화 ID"
    preference_type = models.CharField(
        max_length=50,
        choices=[(tag.value, tag.name) for tag in PerformEumsType],
        default=PerformEumsType.dislike.value) #"선호도 유형 (ENUM: like, dislike)"
    # PRIMARY KEY "(user_id_fk, movie_id_fk)"

  • 다른 앱에 있는 User의 id 값과 Movie 모델에 있는 id를 ForeignKey로 설정해 주었어요.
  • preference_type은 like, dislike로 선택할 거기 때문에 enum에 정의해놓은 걸 데리고 왔어요
    • default로 dislike로 해두었습니다!
    • 어떻게 구현될지 나중에 front로 확인해봐야 해요 🤔

urls.py

main/urls.py

path("api/v1/movie/", include("movies.urls")) # movies의 url 불러 쓰기
movies/urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('movielist/', views.SignUpMovieListView), # Signup movie list로 가기
]

 

serializers.py

from rest_framework import serializers
from .models import Movie

"""df와 drf를 직렬화"""
class SignupMovieListSerializer(serializers.ModelSerializer):
    class Meta:
        model = Movie
        fields = ['id', 'original_title', 'poster_path']
        read_only_fields = ['id']

 

  • Movie 모델에 있는 id, original_title, poster_path 필드만 쓸 거예요.
  • id는 오직 읽기 모드로만 할 거예요.

 

views.py

더보기
from rest_framework.response import Response
from .serializers import SignupMovieListSerializer
from rest_framework.decorators import api_view
import pandas as pd

"""랜덤한 영화 리스트 25개 보여주는 코드"""
@api_view(['GET'])
def SignUpMovieListView(request):
    """이미지 들고올 코드"""
    TMDB_IMAGE_BASE_URL = "https://image.tmdb.org/t/p/w500"
    
    """csv 불러 오기"""
    df = pd.read_csv(
        '/Users/baeminkyung/Desktop/github/UNO_BWMovie_recsys/dataset/signup_movie_list.csv'
        )
    
    """100개의 original_title, poster_path 열 중에서 25개 랜덤으로 데리고 옴"""
    sampled_df = df.sample(n=25)[['original_title', 'poster_path']]
    
    """poster_path를 완전한 이미지 URL로 변환"""
    sampled_df['poster_url'] = TMDB_IMAGE_BASE_URL + sampled_df['poster_path']
    
    data = sampled_df[['original_title', 'poster_url']].to_dict(orient='records')

    """serializer를 사용하여 데이터 변환 및 유효성 검사"""
    serializer = SignupMovieListSerializer(data=data, many=True)
    if serializer.is_valid():
        return Response(serializer.data)
    return Response(serializer.errors, status=400) # 에러 처리
    """poster_path를 완전한 이미지 URL로 변환"""
    sampled_df['poster_url'] = TMDB_IMAGE_BASE_URL + sampled_df['poster_path']

 

  • 이 코드는 Front 구현할 때 쓰는 게 더 안정적인 코드가 될 거라고 하더라고요
    일단은 적어두었고, Front를 함께 구현하게 될 때 다시 수정할 거예요

 

postman에서 제대로 잘 작동되는지 확인했고, 200 OK 받아냈어요!

혹시나 original_title이 25개가 아닐까 봐 세봤는데 맞더라고요 😋

내일은 genres 모델을 구현할 거예요. 파이팅!

🐾Recent posts