[WEEK00] WIL 1. 프로젝트 회고 + SSR과 JWT (2)

장서영·2023년 4월 13일
0

정글_WIL

목록 보기
2/21

SSR과 JWT을 학습하면서, 이를 JungleGYM의 기능에 적용해 보고자 했다.

우선, 한 가지 의문이 들었다.

왜 JWT와 JinJa2를 사용하라고 하셨을까?

사용자 맞춤형 웹페이지 제작

  • 고정된 데이터를 (모든) 사용자가 일괄적으로 받는 게 아니라, 사용자별로 맞춤형 데이터가 담긴 웹 사이트를 보여주어야
  • 즉, 동적 웹 페이지 → 사용자에 따라 다른 정보를 보여주는 것!
  • 서버에서 HTML에 데이터를 직접 삽입하고, 완성된 HTML을 보여주는 방법

따라서, 의도에 맞는 기능을 구현한다면,

  1. 검색 필터에 → 사용자가 신청한 카드들이 보여질 수 있도록 : “신청한 내역”

  2. (클릭했을 때 뜨는) 상세 카드 페이지에

    → 이미 신청자는 “취소하기” 버튼을

    → 신청 안 한 사람은 “신청하기” 버튼을

을 구현해야겠다!고 당차게 시작했다.

하지만, 예상보다 쉽지 않았던 트러블 슈팅이 생겨 많이 헤매었다.

JinJa2 - main.html 렌더링 문제 발생

문제 상황

  • 베이스 기능을 하는 main.html을 상속 받는 listing.html과 finding.html이 있음 (두 소스 코드는 동일함)
  • main.html상의 {% content block %} {% endblock %} 사이에 listing.html은 렌더링 되는 반면, finding.html은 렌더링이 되지 않음
  • 즉, 조건에 맞는 데이터는 추출이 되어 변수(memos)에 담기고, 터미널에서 에러도 발생하지 않는데
    → 클라이언트에게 해당 finding.html이 보여지지 않는 문제.

조금 자세하게 풀어 쓰면, 아래와 같다.

DB에 저장된 모든 카드들을 보여주는 것

(조건을 가지고) DB에 저장된 카드들 중 조건을 만족하는 것만 보여주는 것

1.상단의 헤더/검색 필터까지 그리고 푸터를 main.html에서 구현하여 하나의 base.html로서의 기능을 하도록 함

  1. 화면 중간에
  • 모든 카드들 → listing.html
  • 조건에 맞는 카드들 → finding.html

을 뿌려주도록 하기 위해

메인에서 {% block content %} {% endblock %} 을 지정하여

이 부분에 listing.html과 finding.html을 렌더링 하도록 시도함

(물론, listing.html과 finding.html은 main.html을 상속 받음)

<!-- main.html -->
...

<div id="card-list" class="card-columns">
        {% block content %}
        {% endblock %}
</div>

...

listing.html과 finding.html은 양식이 동일하지만, 라우트를 복잡하게 하고 싶지 않아 별도로 만듦

{% extends 'main.html' %}

{% block content %}
    {% for memo in memos %}
        <div class="container row-cols-md-3" style="display: flexbox; margin-top: 50px;">
            <div class="wrap" id="card-id-{{ memo.card_id }}">
                <a onclick="modal_open('{{memo.card_id}}')">
                    <article class="message is-success" id="mainform-{{ memo.card_id }}">
                        <div class="message-header">
                            <p>{{ memo.creator }}</p>
                        </div>
                        <div class="message-body">
                            {{ memo.text }}
                        </div>
                    </article>
                </a>
            </div>

            <div class="modal" id="subform-{{ memo.card_id }}">
                <div class="modal-background"></div>
                <div class="modal-card">
                    <header class="modal-card-head">
                        <p class="modal-card-title">게시글 정보</p>

                    </header>
                    <section class="modal-card-body">
                        <p>레벨 : {{ memo.level }}</p>  
                        <p>종목 : {{ memo.sport }}</p>
                        <p>시간 : {{ memo.time }}</p>
                        <p>장소 : {{ memo.spot }}</p>
                        <p>성별 : {{ memo.gender }}</p>
                        
                        <p>총 {{ memo.cnt }}명 신청</p>
                        <p>{{ memo.m1 }} {{ memo.m2 }} {{ memo.m3 }}</p>
                    </section>
                    <footer class="modal-card-foot">
                        <button class="button is-success" onclick="join('{{ memo.card_id }}')">참가하기</button> 
                        <button class="button" onclick="modal_close('{{ memo.card_id }}')">닫기</button>
                    </footer>;
                </div>
            </div>
        {% endfor %}
{% endblock %}

3. listing.html은 렌더링 성공함

@app.route('/main', methods=['GET'])
def main(): 
    memos = list(db.memos.find({}, {'_id' : False}))
    return render_template('listing.html', memos=memos)

원리는 (진자2)

  • 페이지가 새로고침 될 때마다 → listing.html이 렌더링 되게 구현함
    • main.html(프론트)에서 app.py(백)이 DB에서 모든 데이터를 불러와, memos 변수로 담아, listing.html로 렌더링을 하면, listing.html은 기존 main.html 양식에서 content block 한 부분에서 모든 메모들을 반복해서 뿌려줌

4. finding.html은 렌더링 실패함

@app.route('/main/finding', methods=['POST'])
def finding(): 
    level_find = request.form['level']
    sport_find = request.form['sport']
    time_find = request.form['time']
    spot_find = request.form['spot']
    gender_find = request.form['gender']
    
    opt = {'level': level_find, 'sport': sport_find, 'time': time_find, 'spot': spot_find, 'gender': gender_find}
    memos = list(db.memos.find(opt, {'_id' : False}))
    
    return render_template('finding.html', memos=memos)

원리는 (진자2로 시도)

  • 사용자가 필터를 선택한 후 ‘검색하기’ 버튼을 누르면, 필터마다의 이벤트 값을 하나의 딕셔너리로 묶어, 백에 요청을 함
  • 백에서는 받은 데이터를 가지고 DB에서 조건에 부합하는 데이터를 추출해, 마찬가지로 finding.html을 렌더링 하게 함
  • content block에서 반복해서 출력하도록

시도한 방법

방법 1. POST 방식이 아닌 GET 방식으로 구현해 봄

**let queryString = $.param(result); // result 객체를 쿼리스트링으로 변환**

    $.ajax({
        **type: "GET", // GET 방식으로 변경
        url: "/main/finding?" + queryString, // 쿼리스트링을 URL에 추가**
        success: function(response) {
            if (response['result'] == 'success'){
                alert("찾았습니다!")
            } else {
                alert("찾기 실패!")
            }
        }
    })
@app.route('/main/finding', methods=['GET'])
def finding(): 
    # 1) 클라이언트로부터 데이터를 받는다.
    **level_find = request.args.get('level')**
    sport_find = request.args.get('sport')
    time_find = request.args.get('time')
    spot_find = request.args.get('spot')
    gender_find = request.args.get('gender')
    # DB에 있는 정보들 다 불러오기
    memos = list(db.memos.find({'level': level_find, 'sport': sport_find, 'time': time_find, 'spot': spot_find, 'gender': gender_find}, {'_id' : False}))

    # 화면을 띄울 때, 메모 카드들을 불러옴과 동시에 id_list를 셋팅한다.
    # 새로고침 하더라도 id_list에 card_id들이 중복되어 들어가지 않도록 한다.
    
    **return jsonify({'result': 'success', 'memos':memos})**

⇒ render_template()에 실패

  • 쿼리스트링에 조건 값을 담아 서버에 요청하면 백에서 이 GET 요청을 처리
  • request.args.get() 메소드 사용으로, 쿼리 스트링에서 조건 값을 추출해 해당 조건에 맞는 데이터 베이스 조회 후 처리 결과 클라이언트에게 반환하는 로직 구현

방법 2. redirect url_for로 변수를 다른 url에 전달한 후, 그 url에서 다시 render_template을 하는 방향

⇒ 어쨌든 안 됨

⇒ josn 형식의 response를 리턴해야 하는데, 즉, render_template() 사용이 불가능하다고 판단함

⇒ 이렇게 하든, 처음부터 render_template()을 하든 render_template() 함수 실행해서 계속 막힘

그래서 시도

검색하기 버튼 누를 때, 브라우저 개발자 도구에 들어가 네트워크를 확인하면 preview와 response는 데이터를 보여줌. 다만, 실제 화면으로 보여지지 않을 뿐.

그래서 결국

백에서 데이터를 프론트로 넘겨, 프론트에서 html을 구성해 일일히 append 하는 방식으로 구현했다.

즉, SSR이 아닌 CSR (클라이언트 사이드 렌더링) 방식으로 뿌릴 수밖에 없었다.


✍ 프로젝트 회고

즐거웠다.
체력적으로 많이 힘들었지만, 정말 즐거웠다.

개인적으로 좋았던 부분은, 노션 워크스페이스를 통한 개발 주기 관리를 한 측면이었다.
짧은 기간의 프로젝트여서 각각의 분업과 기록이 많이 남은 편은 아니었으나, 편리하고 빠르게 또 가시적으로 개발 타임라인을 관리할 수 있어서 유용했다.

또한 아쉬웠던 부분은, (최종 발표 이후에 받은 피드백이)
"오래 사용할 것 같지 않은 서비스"였던 것이다.
즉, UX UI가 부족한 (사용자를 고려하지 않은) 서비스였다.
돌이켜 보면, 기능을 여러 개 욕심 내다 보니 UX와 UI를 더 탄탄하게 구성하지 못 했던 것 같다.
따라서, 다음에는 한 가지 기능이더라도 탄탄하게 그리고 뻔쩍하게! 구현하는 데 집중해야겠다.


💡 (추가) 프로젝트 기획에 대한 조언

발표

  • 발표할 내용 흐름으로 대본을 만들기 (추천)
  • 시간 내에 함축된 메시지
  • 다른 사람들이 한 번에 이해할 수 있는 서비스 발표가 되어야
  • 발표 연습을 많이 하라 → 내가 설명하고자 하는 의도를 잘 전달하는 방법 숙달

발표 장표

  • 마지막 Thank you 쓰지 않기
  • 목차 페이지, 자간? 페이지 쓰지 않기 → 한 장에 요약되어 있어야 ⇒ 장표 쓸데 없이 많이 사용하지 않기. ⇒ 퓨처 플랜 필요 없음. 일단 구현할 거를 명확하게 보여주어야
  • 눈에 잘 들어와야!

기타

  • 미리 준비하기 (시간 절약 중요)
  • 기술적 챌린지는 “배우고 싶은 게” 아니다.
  • 대충하면 안 된다. 항상 최종이라고 생각하라.
profile
하루살이 개발자

0개의 댓글