ERD 설계가 끝난 후 Django
를 활용하여 서버를 구성하는 작업을 진행하였다. 진행하면서 기록해놓으면 좋을 사항들만 추려내서 글로 작성하려고 한다.
from django.db import models
class Doctor(models.Model):
name = models.TextField()
def __str__(self):
return f'{self.pk}번 의사 {self.name}'
class Patient(models.Model):
name = models.TextField()
def __str__(self):
return f'{self.pk}번 환자 {self.name}'
# 직접 중개모델을 생성
class Reservation(models.Model):
doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE, related_name='reservations')
patient = models.ForeignKey(Patient, on_delete=models.CASCADE, related_name='reservations')
message = models.CharField(max_length=50)
def __str__(self):
return f'{self.doctor_id}번 의사의 {self.patient_id}번 환자'
Reservation.objects.create(doctor=doctor인스턴스, patient=patient인스턴스, message=문자열)
Doctor.reservations.get(patient=patient1)
bulk_create
를 통해 구성bulk_create
에서 create 작업을 통째로 하기 때문에 각각의 생성된 객체에 중개모델 데이터를 넣어주기 위한 add
작업 불가Director.objects.bulk_create(
[Director(
name = data.get('name'),
original_name = data.get('original_name'),
) for data in req.get('crew') if not Director.objects.filter(name=data.get('name')).exists() and data.get('job') == 'Director']
)
bulk_create
가 실행속도가 빠르나, 중개모델에 데이터를 넣어줘야 하는 우리의 상황에서는 적절하지 않은 방법임을 깨닫고 다음의 방법을 통해 해결for
문을 통해 각각의 정보를 순회하며 Model.objects.create
로 객체를 하나씩 생성변수.중개모델manager.add(모델)
로 중개모델 데이터 추가for data in req.get('crew'):
if not Director.objects.filter(name=data.get('name')).exists() and data.get('job') == 'Director':
director = Director.objects.create(name=data.get('name'), original_name=data.get('original_name'))
director.movies.add(movie)
구현 목표
방법
objects
매니저의 filter
와 exists()
를 활용해 현재 영화 정보가 DB에 존재하는지 확인title
을 사용해 기존 영화 정보 객체를 가져와 popularity
, tmdb_vote_sum
, tmdb_vote_cnt
필드값 업데이트create
를 사용해 새로운 정보 객체 생성 후 저장...
# 이미 DB에 있는 영화면 정보를 업데이트
if Movie.objects.filter(title=data.get('title')).exists():
movie = Movie.objects.get(title=data.get('title'))
movie.popularity = data.get('popularity')
movie.tmdb_vote_sum = data.get('vote_average') * data.get('vote_count')
movie.tmdb_vote_cnt = data.get('vote_count')
movie.save()
# DB에 없는 영화면 새로 생성
else:
movie = Movie.objects.create(...)
@authentication_classes([JSONWebTokenAuthentication]) # JWT가 유효한지
@permission_classes([IsAuthenticated]) # 인증 여부를 확인
추가로 accounts/views.py/profile 함수의 경우 데코레이터가 없을 시 미인증 에러 발생
포토티켓은 Vue에서의 infinite scroll 구현을 위해 django의 Paginator
를 사용하여 페이지를 구분해서 결과 반환
최대 페이지 수
를 넘겨도 계속 무한 스크롤 되는 이슈 발생최대 페이지 수
정보를 추가해서 넘겨서 → vue에서 해당 정보를 이용해서 현재 페이지 수
가 최대 페이지 수
이하일 때만 무한 스크롤 기능 작동하게 함추천 알고리즘 기능을 위해서 사용자가 영화에 평점을 주는 동시에 유저-장르-점수 중개테이블에 데이터 추가
rate 함수를 GET, POST, PUT에 따라 분기처리
POST는 단 한 번만 실행되어야 한다(사용자는 한 영화에 한 개의 평점만 생성할 수 있음)
Vue에서 if문 분기처리 필요(GET을 하고 인스턴스가 존재하면 PUT, 존재하지 않으면 POST)
rate 함수 실행과 동시에 유저-장르- 점수 중개테이블을 채우는 기능이 작동
# rate GET 방식일 때 내부에서 실행되는 함수
# 1. movie에 대한 장르 아이디들을 받아옴
genres = Genre.objects.filter(movies__pk=movie_pk)
if request.method == 'GET':
serializer = RateSerializer(rate)
return Response(serializer.data)
# rate POST 방식일 때 내부에서 실행되는 함수
# 2. for문을 통해 각 장르 아이디들에 대해서 유저장르점수 객체를 만듦
for genre in genres:
serializer_algo = RecommendAlgoScoreSerializer(data=request.data)
if serializer_algo.is_valid(raise_exception=True):
serializer_algo.save(genre=genre, user=request.user)
# rate POST 방식일 때 내부에서 실행되는 함수
# 3. for문을 통해 각 장르 아이디들에 대해 유저장르점수 객체 업데이트
for genre in genres:
recommend_algo_score = RecommendAlgoScore.objects.filter(user__pk=request.user.pk, genre__pk=genre.pk).first()
serializer_algo = RecommendAlgoScoreSerializer(recommend_algo_score, data=request.data)
if serializer_algo.is_valid(raise_exception=True):
serializer_algo.save()
random.choices
를 활용하여 각 장르에 가중치를 주고 랜덤한 장르를 6개 뽑아서 추천(중복 가능)
사용자가 각 장르에 준 평점의 누적합을 구해 각 장르의 가중치 설정
RecommendAlgoScore
테이블에 유저가 영화에 평점을 줄 때마다 해당 영화가 가진 장르에 대한 유저의 평점 값이 저장뽑은 장르들에 대해 for
문으로 순회하며 각 장르에 해당하는 영화를 인기있는 영화부터 차례로 25개씩 가져온 쿼리셋을 합집합
@api_view(['GET'])
@authentication_classes([JSONWebTokenAuthentication])
@permission_classes([IsAuthenticated])
def movie_list(request):
mode = request.GET.get('mode')
# 추천 0. mode - 알고리즘
if mode == 'algorithm':
weight = []
# 19가지 장르의 pk
for id in range(1, 20):
genre_weight = RecommendAlgoScore.objects.filter(user__pk=request.user.pk, genre__pk=id).aggregate(Sum('rate'))['rate__sum']
weight.append(genre_weight if genre_weight else 1)
recommend_genre_ids = random.choices([28, 12, 16, 35, 80, 99, 18, 10751, 14, 36, 27, 10402, 9648, 10749, 878, 10770, 53, 10752, 37], weight, k=6)
movies = Movie.objects.none()
for genre_id in recommend_genre_ids:
temp_movies = Movie.objects.filter(genres__tmdb_genre_id=genre_id)[:25]
movies = movies|temp_movies
filter
를 사용할 때 lookup
을 사용해서 M:N관계에서의 정보들을 원하는대로 뽑아올 수 있음# 조회 1. mode - 최신순, 평점순, 인기순
elif mode in ('release_date', 'vote_average', 'popularity'):
if mode != 'vote_average':
movies = Movie.objects.order_by(f'-{mode}')[:100]
else:
movies = Movie.objects.annotate(vote_average=(F('tmdb_vote_sum') + F('our_vote_sum')) / (F('tmdb_vote_cnt') + F('our_vote_cnt'))).order_by('-vote_average')[:100]
elif mode in ('director', 'actor', 'title'):
# 조회 2. mode - 감독별, 배우별, 영화명별
inputValue = request.GET.get('inputValue')
if mode == 'director':
# MtoM 관계에서 원하는 조건을 가지는 영화들을 가져오는 방법
# https://docs.djangoproject.com/en/3.2/topics/db/examples/many_to_many/
movies = Movie.objects.filter(Q(directors__name__icontains=inputValue)|Q(directors__original_name__icontains=inputValue)).distinct()[:100]
elif mode == 'actor':
movies = Movie.objects.filter(Q(actors__name__icontains=inputValue)|Q(actors__original_name__icontains=inputValue)).distinct()[:100]
else:
# 한글 제목이나 원본 제목이 사용자의 입력(inputValue)를 포함하는 영화들을 반환(대소문자 구분하지 않음)
movies = Movie.objects.filter(Q(title__icontains=inputValue)|Q(original_title__icontains=inputValue))[:100]
# 조회 3. mode - 장르별
elif mode == 'genre':
inputGenre = request.GET.get('inputGenre')
movies = Movie.objects.filter(genres__tmdb_genre_id=inputGenre)[:100]
serializer = MovieListSerializer(movies, many=True)
return Response(serializer.data)