django dolchu 프로젝트 마감.

김하진·2022년 6월 13일
0

오늘 django 의 첫번째 프로젝트인 dolchu 가 마감이 되었다!

한 2주 정도의 시간이였던 것 같은데, 이번 프로젝트는 우리가 목표로한 기능들을 전부다 완성을 해서 일단 기분이 좋다.

  • 로그인 / 회원가입 기능
  • 메인페이지에서 평점순으로 조회
  • 카테고리 클릭시 평점순으로 조회
  • 메인페이지/카테고리 무한스크롤 기능
  • 돌추리스트를 누르면 자신이 평점을 준 가게랑 비슷한 유저들이 준 평점들을 자동으로 비교해서 리스트를 서빙 (협업필터링)
  • 돌추리스트에서 예상 평점 출력하기
  • 디테일페이지 에서 자동으로 지도 API 구현
  • 디테일 페이지 코멘트/ 평점 기능
  • 검색 기능 (제목 / 주소/ 카테고리)
  • 내가 평점단 게시글 리스트 불러오기
  • 배포

정도의 기능이 있는 것 같은데, 전부 구현이 완료 되었다.

이번 프로젝트가 다른 프로젝트에 비해서 기간이 좀더 있어서 그런지 조금은 여유롭게 진행을 한 것 같다.

카테고리별 무한스크롤

views.py

@login_required
@csrf_exempt
def ajax_method(request, cate=None):
    user = request.user.is_authenticated
    if user:
        if request.method == 'POST':
            category = request.POST.get('category')
            return HttpResponse(category, content_type="text/json-comment-filtered")
        if request.method == 'GET':
            global count
            count += 1
            for i in str(count):
                if i == i:
                    category = Category.objects.get(id=cate)
                    food_data = Food.objects.filter(category=category).order_by('-staravg')[count * 10:count * 10 + 10]
                    category = serializers.serialize('json', food_data)
                    return HttpResponse(category, content_type="text/json-comment-filtered")

html

$('.cate_choice').click(function(){
        cate = $(this).val();
        sessionStorage.setItem("cate", cate)
        $.ajax({
                url : "{% url 'ajax_method' %}",
                type : "POST",
                data:{'cate':cate},
                datatype: 'json',
                success : function(cate) {
                }})
        });

    window.onscroll = function() {
        cate = sessionStorage.getItem('cate')
        if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
        $.ajax({
            url : "{% url 'ajax_method' %}" + cate ,
            type : "GET",
            datatype: 'json',
            success : function(category) {
                let test1 = document.getElementById('#scroll')
                category.forEach(function(element){
                    let store = element['fields']['store']
                    let address = element['fields']['address']
                    let id = element['pk']
                    let img = element['fields']['img']
                        $("#scroll").append(`
                            <a href="/detail/${id}"><div class="m-storelist">
                                <div><img class="storeimg" src="${img}"></div>
                                <div class="storedetail">
                                    <div class="storename">
                                        <h2>${store}</h2>
                                    </div>
                                    <div class="address">
                                        <h3>${address}</h3>
                                    </div>
                                </div>
                            </div></a>
                        `)
                    });
                }
            });
        };
    }

우선은 count 라는 변수는 전역변수로 선언을 해서 main 페이지나 category 페이지등 요청이 있을시마다 0으로 초기화를 한다.

그래서 html 에서 ajax 요청이 들어오면 count +1 씩 한다

초기는 0~10번째의 리스트 이니, 요청이 들어올때마다 10~20, 20~30 이렇게 반복적으로 ajax 요청을 받으면 무한스크롤을 구현 할 수 있다.

카테고리는 카테고리 아이콘을 버튼으로 만들어서 cate 라는 변수에 value 값을 줘서 cate 를 id =cate , 즉 1번은 한식 2번은 중식 이런식으로 카테고리값을 자동으로 받아와서 그에 맞는 리스트를 출력해준다.

또한 ajxax 에 데이터를 보낼때 json 형식으로 변환해줘서 보내주어야 한다!

검색 기능

@login_required
def search(request):
    global count 
    count = 0
    if request.method =='POST':
        post = request.POST.get('search','')
        all = Food.objects.all()
        result = all.filter(
            Q(category__category__contains= post)  |
            Q(store__icontains = post) |
            Q(address__icontains = post),
        )

        for results in result:
            if results.img == "":
                results.img = './static/img/noimage.png'

        return render(request,'search.html',{'post':post,'result':result})

검색기능은 Q 를 이용해서 보다 간편하게 구현할 수 있다.
POST 요청이 들어오면 search 를 formdate 로 받아와서
all 이라는 queryset 에 필터해서 검색결과를 받아온다.

협업 필터링 django 서빙

def recommand(request):
    userid = request.user.id
    comment = Comment.objects.filter(username=userid)

    categoies = Category.objects.all()
    category_count = 0
    categories1 = []
    categories2 = []
    for i in categoies:
        category_count += 1
        cate = {
            'id': i.id,
            'category' : i.category,
            'desc' : i.desc,
        }
        if category_count <= 4:
            categories1.append(cate)
        else:
            categories2.append(cate)

    best_store = []

    for x in categoies:
        store = Food.objects.filter(category=x.id)
        best_stores = store.all().order_by('-staravg')[:1]
        for s in best_stores:
            doc = {
                'id' : s.id,
                'store' : s.store,
                'img' : s.img,
            }
            best_store.append(doc)

    best_food = random.choice(best_store)

    if not comment:
        return render(request, 'food/recommand.html', {'message': '평점을 남겨주세요', 'best_food' : best_food, 'categories1' : categories1, 'categories2' : categories2})


    rating = pd.read_csv('user_rating.csv')
    store = pd.read_csv('store_info.csv')

	# 상점명(store)을 기준으로 rating 와 store 를 결합함
    store_ratings = pd.merge(rating, store, on='store')

	# user별로 상점에 부여한 rating 값을 볼 수 있도록 pivot table 사용
    title_user = store_ratings.pivot_table('rating', index='userid', columns='store')

	# 평점을 부여안한 영화는 그냥 0이라고 부여
    title_user = title_user.fillna(0)

	#  모든 유저간의 코사인 유사도를 구함
    user_based_collab = cosine_similarity(title_user, title_user)

	# 위는 그냥 numpy 행렬이니까, 이를 데이터프레임으로 변환
    user_based_collab = pd.DataFrame(user_based_collab, index=title_user.index, columns=title_user.index)

	# 현재 유저와 가장 비슷한 유저를 뽑고
    user = user_based_collab[userid].sort_values(ascending=False)[:10].index[1]
	# 위에 유저의 가장높게 부여한 가게를 내림차순으로 정렬
    recommand_store = title_user.query(f"userid == {user}").sort_values(ascending=False, by=user, axis=1)
    # 위에꺼 리스트로 10개만 가져옴
    recommand_list = recommand_store.columns.tolist()[:10]

	# pandas table to list
	# 만약 해당 유저가 아직 보지 않은 영화에 대해서, 평점을 예측하고자 한다면?
	# (어떤 유저와 비슷한 정도 * 그 유저가 영화에 대해 부여한 평점) 을 더해서 (유저와 비슷한 정도의 합)으로 나눠보면 됨!
	# index_list 는 비슷한 유저의 id 값 리스트 / weight_list 는 비슷한 유저와의 유사도 리스트
	
    user_index_list = user_based_collab[userid].sort_values(ascending=False)[:10].index.tolist()
    user_weight_list = user_based_collab[userid].sort_values(ascending=False)[:10].tolist()

    store_list = []

    for i in recommand_list:
        try:
            store = Food.objects.get(store=i)
        except:
            pass
        store_list.append(store)

    star_result = []
	#위에 10개의 상점을 돌면서 현재 유저가 부여될 평점을 예측	
    for i in store_list:
        try:
            store = i.store
        except:
            pass
        weighted_sum = []
        weighted_user = []
        for i in range(1, 10):
            # 해당 상점에 평점을 부여한 사람들의 유사도와 평점만 추가 (즉, 0이 아닌 경우에만 계산에 활용)
            if int(title_user[store][user_index_list[i]]) != 0:
                # 평점 * 유사도 추가
                weighted_sum.append(title_user[store][user_index_list[i]] * user_weight_list[i])
                # 유사도 추가
                weighted_user.append(user_weight_list[i])

        # 총 평점*유사도 / 총 유사도를 토대로 평점 예측
        result = sum(weighted_sum)/sum(weighted_user)

        star_result.append(result)

        dolchu = []

    for i,x in zip(store_list, star_result):
        dolchu_data = {
            'id': i.id,
            'store': i.store,
            'img': i.img,
            'address': i.address,
            'star': x,
        }
        dolchu.append(dolchu_data)
        dolchu.sort(key=lambda x: x['star'], reverse=True)

    return render(request, 'food/recommand.html',{'dolchu' : dolchu, 'store_list':store_list, 'star_result':star_result, 'categories1':categories1,'categories2':categories2,'best_food':best_food})

협업필터링은 중간중간 주석을 달아놔서 내가 작업하지 못한 부분도 단번에 이해가 잘 되었다.
머신러닝 모델에 비해서는 조금은 더 쉬웠던 느낌이였다.

지도는 이전글에서 포스팅을 해서 넘어가도록 하겠다.

이번 프로젝트에서 어려웠던 부분은 무한스코롤 이였는데, 템플릿 문법을 사용하지 못하고, 또한 ajax 를 통해서 데이터를 주고 받는데서 오류가 굉장히 많이 났다. 그래서 이부분에서 조금은 오래걸렸지만 다시 생각보면, 또 단순하게 생각하면 쉽게 처리가 가능한 부분 이였다.

이렇게 django 로는 첫번째 프로젝트가 마무리가 되었다. 내일은 프로젝트 정리를 하고, 발표, 피드백, 그리고 팀원들과 회고를 진행하고 남는시간에 그동안 못들었던 AWS 강의를 들을 예정이다.

또한 요즘 그외에 공부들을 좀 소흘히 하고 있는데,

중간중간 계속해서 공부들을 진행할 예정이다.

profile
진킴

0개의 댓글