"""
이 스크립트를 실행하기 전에 다음 단계를 따라주세요:
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
"""
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개 보여주는 코드"""classSignUpMovieListView(APIView):defget(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)
"""회원가입 시, 보여지는 영화 포스터 리스트 및 장르"""classSignupMovieListSerializer(serializers.ModelSerializer):classMeta:
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개 랜덤으로 보여줍니다!
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'])defSignUpMovieListView(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) # 에러 처리
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개 보여주는 코드"""classSignUpMovieListView(APIView):defget(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)
classCreateGenrePreferenceView(APIView):"""
장르 선호도 생성/조회
"""defget(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)
defpost(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 응답 반환
from django.db import models
from accounts.models import User
"""Genres model"""classGenre(models.Model):id = models.AutoField(primary_key=True) # 장르 ID (AutoField)
genre = models.CharField(max_length=100) # 장르 이름 (CharField)def__str__(self):return self.genre
"""장르 선호도 정리를 위한 model"""classGenrePreference(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와 같은 역할을 함"""classMeta:
unique_together = ('user_id_fk', 'genre_id_fk')
def__str__(self):returnf"{self.user_id_fk} liked/disliked {self.genre_id_fk}."
from rest_framework import serializers
from .models import Movie, Genre
"""GenreSerializer"""classGenreSerializer(serializers.ModelSerializer):classMeta:
model = Genre
fields = ['id', 'genre']
"""따로 수정할 게 아니라면 읽기 모드로 넣어서 데이터 보호"""
read_only_fields = ['id', 'genre']
"""회원가입 시, 보여지는 영화 포스터 리스트 및 장르"""classSignupMovieListSerializer(serializers.ModelSerializer):
genres = GenreSerializer(many=True, read_only=True)
classMeta:
model = Movie
fields = ['id', 'original_title', 'poster_path', 'genres']
read_only_fields = ['id']
장르를 추가해줘서, 기존에 만들어두었던 SignUpMovieListSerializer에다가 'genres'도 추가해줬어요.
classCreateGenrePreferenceView(APIView):"""
장르 선호도 생성/조회
"""defget(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)
defpost(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 처리를 안 했어요 🙄
내일은 이걸 마무리해야 할 것 같아요.
그리고
제가 짠 게 작동은 잘 되지만, 우리가 구현해내고자 한 방향대로 설계한 게 맞는지 모르겠어요..
from django.db import models
from accounts.models import User
from enum import Enum
"""Enum용 모델"""classPerformEumsType(Enum):
like = "like"
dislike = "dislike""""Movie model"""classMovie(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"""classMoviePreference(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를 직렬화"""classSignupMovieListSerializer(serializers.ModelSerializer):classMeta:
model = Movie
fields = ['id', 'original_title', 'poster_path']
read_only_fields = ['id']
Movie 모델에 있는 id, original_title, poster_path 필드만 쓸 거예요.
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'])defSignUpMovieListView(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를 함께 구현하게 될 때 다시 수정할 거예요