<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Oreo</title>
        <link>https://velog.io/</link>
        <description>2023-2 오소플</description>
        <lastBuildDate>Thu, 14 Dec 2023 06:44:25 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Oreo</title>
            <url>https://velog.velcdn.com/images/didi_delos/profile/087ea9a4-3309-4039-9cc0-e1d6b05e4784/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Oreo. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/didi_delos" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[팁/디버깅 3] 랭킹시스템 구현]]></title>
            <link>https://velog.io/@didi_delos/%ED%8C%81%EB%94%94%EB%B2%84%EA%B9%85-3-%EB%9E%AD%ED%82%B9%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@didi_delos/%ED%8C%81%EB%94%94%EB%B2%84%EA%B9%85-3-%EB%9E%AD%ED%82%B9%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Thu, 14 Dec 2023 06:44:25 GMT</pubDate>
            <description><![CDATA[<p>db에서 대학별로 내림차순 정렬된 랭킹을 받아올 수 있는 랭킹시스템 html, css, js 코드를 구현하고자 한다. 
초록색 체크박스 다음의 html, css, js는 코드의 위치를 나타냈다.</p>
<h1 id="1-대학-정렬-버튼">1. 대학 정렬 버튼</h1>
<h2 id="✅-html-file">✅ HTML file</h2>
<p>대학을 선택하면 대학별로 정렬된 랭킹이 보일 수 있도록 onclick = &quot;location.href = &#39;{{url_for(&#39;ranking&#39;, page=i, college=&#39;<!--대학명-->&#39;)}}&#39;&quot; 코드를 포함하고 있는 대학 선택 button을 만들어준다.</p>
<pre><code>&lt;!--btn--&gt;
        &lt;br&gt;
        &lt;div class=&quot;padding&quot;&gt;
            &lt;button class=&quot;btn10&quot; onclick=&quot;location.href=&#39;{{url_for(&#39;ranking&#39;, page=i, college=&#39;all&#39;)}}&#39;&quot;&gt;전체&lt;/button&gt;
            &lt;button class=&quot;btn10&quot; onclick=&quot;location.href=&#39;{{url_for(&#39;ranking&#39;, page=i, college=&#39;인문과학대학&#39;)}}&#39;&quot;&gt;인문과학대학&lt;/button&gt;
            &lt;button class=&quot;btn10&quot; onclick=&quot;location.href=&#39;{{url_for(&#39;ranking&#39;, page=i, college=&#39;사회과학대학&#39;)}}&#39;&quot;&gt;사회과학대학&lt;/button&gt;
            &lt;button class=&quot;btn10&quot; onclick=&quot;location.href=&#39;{{url_for(&#39;ranking&#39;, page=i, college=&#39;자연과학대학&#39;)}}&#39;&quot;&gt;자연과학대학&lt;/button&gt;
            &lt;button class=&quot;btn10&quot; onclick=&quot;location.href=&#39;{{url_for(&#39;ranking&#39;, page=i, college=&#39;엘텍공과대학&#39;)}}&#39;&quot;&gt;엘텍공과대학&lt;/button&gt;
            &lt;button class=&quot;btn10&quot; onclick=&quot;location.href=&#39;{{url_for(&#39;ranking&#39;, page=i, college=&#39;음악대학&#39;)}}&#39;&quot;&gt;음악대학&lt;/button&gt;
            &lt;button class=&quot;btn10&quot; onclick=&quot;location.href=&#39;{{url_for(&#39;ranking&#39;, page=i, college=&#39;조형예술대학&#39;)}}&#39;&quot;&gt;조형예술대학&lt;/button&gt;
            &lt;button class=&quot;btn10&quot; onclick=&quot;location.href=&#39;{{url_for(&#39;ranking&#39;, page=i, college=&#39;사범대학&#39;)}}&#39;&quot;&gt;사범대학&lt;/button&gt;
            &lt;button class=&quot;btn10&quot; onclick=&quot;location.href=&#39;{{url_for(&#39;ranking&#39;, page=i, college=&#39;경영대학&#39;)}}&#39;&quot;&gt;경영대학&lt;/button&gt;
            &lt;button class=&quot;btn10&quot; onclick=&quot;location.href=&#39;{{url_for(&#39;ranking&#39;, page=i, college=&#39;신산업융합대학&#39;)}}&#39;&quot;&gt;신산업융합대학&lt;/button&gt;
            &lt;button class=&quot;btn10&quot; onclick=&quot;location.href=&#39;{{url_for(&#39;ranking&#39;, page=i, college=&#39;의과대학&#39;)}}&#39;&quot;&gt;의과대학&lt;/button&gt;
            &lt;button class=&quot;btn10&quot; onclick=&quot;location.href=&#39;{{url_for(&#39;ranking&#39;, page=i, college=&#39;간호대학&#39;)}}&#39;&quot;&gt;간호대학&lt;/button&gt;
            &lt;button class=&quot;btn10&quot; onclick=&quot;location.href=&#39;{{url_for(&#39;ranking&#39;, page=i, college=&#39;약학대학&#39;)}}&#39;&quot;&gt;약학대학&lt;/button&gt;
            &lt;button class=&quot;btn10&quot; onclick=&quot;location.href=&#39;{{url_for(&#39;ranking&#39;, page=i, college=&#39;스크랜튼대학&#39;)}}&#39;&quot;&gt;스크랜튼대학&lt;/button&gt;
            &lt;button class=&quot;btn10&quot; onclick=&quot;location.href=&#39;{{url_for(&#39;ranking&#39;, page=i, college=&#39;호크마교양대학&#39;)}}&#39;&quot;&gt;호크마교양대학&lt;/button&gt; 
        &lt;/div&gt;
        &lt;br&gt;</code></pre><h2 id="✅-css">✅ CSS</h2>
<p>버튼이 hover되고 active 되었을 때 색상이 변경될 수 있도록 css를 추가해준다.</p>
<pre><code class="language-css">.btn10 {
    width: 110px;
    border: none;
    /*border-color: var(--default-blue);*/
    border-radius: 10px;
    transition: all 0.3s ease; /* 변경 효과를 주기 위한 transition 속성 추가 */
}

.btn10:hover {
    width: 110px;
    color: white;
    background-color: var(--default-blue);
    border:none;
    border-radius: 10px;
}

.btn10-active {
    width: 110px;
    color: white;
    background-color: var(--default-blue);
    border:none;
    border-radius: 10px;
}</code></pre>
<h2 id="✅-js">✅ js</h2>
<p>추가한 css가 js의 이벤트리스너 함수로 적용되었을 때, 페이지가 리로드 되어도 css가 유지될 수 있도록 상태를 local storage에 저장해주는 함수를 추가한다. </p>
<pre><code class="language-javascript">&lt;!--버튼 hover, active 속성 변경 js 추가--&gt;
&lt;!--js 유지하기 위해 브라우저 local storage에 저장하는 코드 포함--&gt;
&lt;script&gt;
    document.addEventListener(&quot;DOMContentLoaded&quot;, function () {
        var buttons = document.querySelectorAll(&#39;.btn10&#39;);

        // 눌러진 버튼 local storage set
        var activeButtonIndex = localStorage.getItem(&#39;activeButtonIndex&#39;);
        if (activeButtonIndex !== null) {
            buttons[activeButtonIndex].classList.add(&#39;btn10-active&#39;);
        }

        // click
        buttons.forEach(function (button, index) {
            button.addEventListener(&#39;click&#39;, function () {
                buttons.forEach(function (btn) {
                    btn.classList.remove(&#39;btn10-active&#39;);
                });
                button.classList.add(&#39;btn10-active&#39;);

                // 눌러진 버튼의 인덱스 local storage에 저장
                localStorage.setItem(&#39;activeButtonIndex&#39;, index);
            });


            // hover
            button.addEventListener(&#39;mouseover&#39;, function () {
                button.classList.add(&#39;btn10-hover&#39;);
            });

            button.addEventListener(&#39;mouseout&#39;, function () {
                button.classList.remove(&#39;btn10-hover&#39;);
            });
        });
    });
&lt;/script&gt;</code></pre>
<h1 id="2-바차트-만들기">2. 바차트 만들기</h1>
<h2 id="✅-html-file-1">✅ HTML file</h2>
<p>디비와 연동할 수 있는 변수를 테이블에 채워 넣어 한 블록의 랭킹을 만들고 데이터에 유저가 존재하는 동안 반복할 수 있도록 한다.</p>
<pre><code class="language-html">&lt;div class=&quot;ranking-inner&quot;&gt;
            &lt;p class=&quot;warning-msg&quot;&gt;*총 거래 포인트(판매포인트+구매포인트)를 기준으로 한 순위입니다.&lt;/p&gt;
            {% if session[&#39;id&#39;] %}           
            &lt;p&gt;{{session[&#39;id&#39;]}}님의 랭킹포인트는 {{ user_rankingpoint }}p입니다.&lt;/p&gt;  
            {% else%}
            &lt;p&gt;랭킹포인트 확인을 위해 로그인을 해주세요.&lt;/p&gt;
            {% endif %}

            &lt;table style=&quot;width:100%; margin: 0 auto;&quot;&gt;
                {% for rank, user_data in datas %}
                    &lt;tr&gt;
                        &lt;td&gt; {{ loop.index }} &lt;/td&gt;
                        &lt;td&gt;&lt;img src=&quot;/static/img/profile.jpg&quot;&gt;&lt;/td&gt;
                        &lt;td&gt; {{ user_data[&#39;nickname&#39;] }} &lt;/td&gt;
                        &lt;td&gt;
                            &lt;div class=&quot;bar-container&quot;&gt;
                                &lt;div class=&quot;bar&quot; id=&quot;bar{{ rank }}&quot;&gt;&lt;/div&gt;
                            &lt;/div&gt;
                        &lt;/td&gt;
                        &lt;td&gt;{{ user_data[&#39;rankingpoint&#39;] }}&lt;/td&gt;
                    &lt;/tr&gt;
                {% endfor %}
            &lt;/table&gt;
        &lt;/div&gt;</code></pre>
<h2 id="✅--css">✅  CSS</h2>
<p>페이지가 로드되면 바가 차오를 수 있도록 transition: width 1s; 코드를 포함하는 style.css를 추가해준다. </p>
<pre><code class="language-css">.bar-container {
    width: 300px;
    height: 40px;
    background-color: #f0f0f0;
    position: relative;
    margin-bottom: 10px;
}

.bar {
    height: 100%;
    background-color: var(--default-blue);
    width: 0;
    position: absolute;
    transition: width 1s;
}</code></pre>
<h2 id="✅-js-1">✅ js</h2>
<p>마지막으로 버튼으로 선택된 대학에 따라 bar를 채워주는 script를 추가한다.</p>
<pre><code class="language-html">&lt;script&gt;
    $(document).ready(function () {
    // alert(&quot;{{college}}&quot;);
    $(&#39;#college option:contains(&quot;{{college}}&quot;)&#39;).prop(&quot;selected&quot;, true);
    });
    &lt;/script&gt;

&lt;!-- 선택된 college에 따라 상위 10위 랭킹 동적으로 구현하기 --&gt;
{% for rank, user_data in datas %}
&lt;script&gt;
    document.addEventListener(&quot;DOMContentLoaded&quot;, function() {
        setBarWidth(&#39;bar{{ rank }}&#39;, {{ user_data[&#39;rankingpoint&#39;] }});
    });

    function setBarWidth(barId, value) {
        const bar = document.getElementById(barId);
        {% if max is defined %}
            const width = (value / {{ max }}) * 100;
            bar.style.width = width + &#39;%&#39;;
        {% endif %}
    }
&lt;/script&gt;
{% endfor %}</code></pre>
<h2 id="💡-tip">💡 TIP</h2>
<ul>
<li>js를 많이 포함하고 있는 코드들임이 html과 css를 구현하면서 예상되므로 이 부분들을 고려하며 화면설계를 하는 것이 좋다. js 함수들과 화면 구성 요소들간의 다이어그램을 생각하면서 구현하면 동적으로 값을 할당하는 부분을 만들기 수월할 것이다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[해설 3] 프로젝트 해설3: 전체 코드 기능 설명 3]]></title>
            <link>https://velog.io/@didi_delos/%ED%95%B4%EC%84%A4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%95%B4%EC%84%A43</link>
            <guid>https://velog.io/@didi_delos/%ED%95%B4%EC%84%A4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%95%B4%EC%84%A43</guid>
            <pubDate>Wed, 06 Dec 2023 12:19:53 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>[해설 2]에 이어지는 글입니다.
<a href="https://velog.io/@didi_delos/%ED%95%B4%EC%84%A4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%95%B4%EC%84%A4-2">✨해설 2 보러가기</a></p>
</blockquote>
<blockquote>
<p>💡 <strong>9 ~ 12</strong> [해설3] : 백_윤소민
회원가입, 아이디 중복확인, 닉네임 중복확인, 로그인 및 로그아웃, 상품 구매시 포인트, 랭킹포인트 증감 기능, 사용자 포인트별 랭킹 정렬 기능</p>
</blockquote>
<h2 id="9-회원가입--session을-이용한-새로고침-시-값-소실-방지">9. 회원가입 – Session을 이용한 새로고침 시 값 소실 방지</h2>
<p><strong>[app.py]</strong></p>
<ul>
<li><strong><em>Tip</em></strong> : 닉네임, 비밀번호확인시 session 사용하면 새로고침을 하여도 값이 날아가지 않는다.</li>
<li>세션(Session): 쿠키와 같은 고객의 데이터 값, 클라이언트의 브라우저에 저장해놓는다.</li>
</ul>
<pre><code class="language-python">@application.route(&quot;/signup_post&quot;,methods=[&#39;POST&#39;])
def register_user():
    data=request.form
      id=request.form[&#39;id&#39;]
      pw=request.form[&#39;pw&#39;]
      pw2=request.form[&#39;PWconfirm&#39;]
      nname=request.form[&#39;nickname&#39;]
      pw_hash=hashlib.sha256(pw.encode(&#39;utf-8&#39;)).hexdigest()
      pw_hash2=hashlib.sha3_256(pw.encode(&#39;utf-8&#39;)).hexdigest()
      session[&#39;id_&#39;] = id
      session[&#39;pw&#39;]=pw
      session[&#39;pw2&#39;]=pw2
    session[&#39;nickname&#39;]=nname
    session[&#39;email&#39;]=request.form[&#39;email&#39;]
    session[&#39;HP&#39;]=request.form[&#39;HP&#39;]
    session[&#39;college&#39;]=request.form[&#39;dropdown1&#39;]
    session[&#39;major&#39;]=request.form[&#39;dropdown2&#39;]</code></pre>
<ul>
<li>이처럼 우선 form에 입력한 클라이언트의 정보를 변수에 저장한다.</li>
<li>session[‘’]안에 session이름으로 사용할 변수를 적고 session에 넣고 싶은 값을 이전에 저장한 변수를 이용해 대입한다.</li>
</ul>
<pre><code class="language-python">return render_template(&quot;8~10/signup.html&quot;,
    id=session.get(&#39;id_&#39;, &#39;&#39;),pw=session.get(&#39;pw&#39;,&#39;&#39;),nickname=session.get(&#39;nickname&#39;,&#39;&#39;),hp=session.get(&#39;HP&#39;,&#39;&#39;),pw2=session.get(&#39;pw2&#39;,&#39;&#39;),
    email=session.get(&#39;email&#39;,&#39;&#39;),college=session.get(&#39;college&#39;,&#39;&#39;),major=session.get(&#39;major&#39;,&#39;&#39;))</code></pre>
<ul>
<li>이후에 화면을 넘길 때 값이 유지되도록 render_template()안에 session 값들을 모두 넣는다.</li>
</ul>
<h2 id="10-회원가입--중복확인-및-비밀번호-확인-기능">10. 회원가입 – 중복확인 및 비밀번호 확인 기능</h2>
<p><strong>[app.py]</strong></p>
<pre><code class="language-python">#아이디중복확인
    if &#39;check_duplicate_id&#39; in request.form:
        if DB.id_duplicate_check(id):
            flash(&#39;사용할 수 있는 아이디입니다.&#39;)
            return render_template(&quot;8~10/signup.html&quot;,
            id=session.get(&#39;id_&#39;, &#39;&#39;),pw=session.get(&#39;pw&#39;,&#39;&#39;),nickname=session.get(&#39;nickname&#39;,&#39;&#39;),hp=session.get(&#39;HP&#39;,&#39;&#39;),pw2=session.get(&#39;pw2&#39;,&#39;&#39;),
            email=session.get(&#39;email&#39;,&#39;&#39;),college=session.get(&#39;college&#39;,&#39;&#39;),major=session.get(&#39;major&#39;,&#39;&#39;))
        else:
            flash(&#39;이미 존재하는 아이디입니다.&#39;)
            return render_template(&quot;8~10/signup.html&quot;,
            id=session.get(&#39;id_&#39;, &#39;&#39;),pw=session.get(&#39;pw&#39;,&#39;&#39;),nickname=session.get(&#39;nickname&#39;,&#39;&#39;),hp=session.get(&#39;HP&#39;,&#39;&#39;),pw2=session.get(&#39;pw2&#39;,&#39;&#39;),
            email=session.get(&#39;email&#39;,&#39;&#39;),college=session.get(&#39;college&#39;,&#39;&#39;),major=session.get(&#39;major&#39;,&#39;&#39;))</code></pre>
<ul>
<li><strong><em>Tip</em></strong> : flash값을 사용해서 백에서 전달하는 메시지를 프론트에 넘긴다.</li>
<li>py에서 검증을 거친 후 나온 결과를 flash(‘’)안에 메시지 값을 넣어주고 프론트에서 이를 화면에 표시한다. 예시로는</li>
</ul>
<p><strong>[signup.html]</strong></p>
<pre><code class="language-html">{% extends &quot;index.html&quot; %}
    {% block section %}
    {% with mesg = get_flashed_messages() %}
    {% if mesg !=[]%}
    &lt;script&gt;alert(&quot;{{ mesg[0] }}&quot;)&lt;/script&gt;
    {% endif %}
    {% endwith %}</code></pre>
<ul>
<li>Html에 이렇게 flash값이 있을 때 mesg안에 값을 넣고 alert를 통해 알림창에 메시지가 뜨는 방식으로 구현할 수 있다.</li>
</ul>
<p><strong>[database.py]</strong></p>
<pre><code class="language-python"># 회원가입 시 아이디 중복확인
    def id_duplicate_check(self, id_string):
        users = self.db.child(&quot;user&quot;).get()

        print(&quot;users###&quot;,users.val())
        if str(users.val()) == &quot;None&quot;: # first registration
            return True
        else:
            for res in users.each():
                value = res.val()
                if value[&#39;id&#39;] == id_string:
                    return False
            return True</code></pre>
<ul>
<li>중복을 검증하는 방법은 db에서 아이디에 해당하는 정보들을 모두 찾은 후, 이것이 사용자가 입력한 아이디와 같을 시 false값을 리턴하고 존재하지 않을 경우 true값을 넘겨준다. 넘겨받은 이 값을 py에서 flash값으로 프론트에 메시지를 넘겨준다.</li>
</ul>
<h2 id="11-유저-포인트-증감-구현">11. 유저 포인트 증감 구현</h2>
<p><strong>[app.py]</strong></p>
<pre><code class="language-python">#구매하기 버튼 누르면
@application.route(&quot;/1~4/order_item/&lt;item_name&gt;/&quot;)
def view_order_confirmation(item_name):

    point=DB.get_price(str(item_name))
    seller=DB.get_seller(str(item_name))

                        … 추가 코드 …

        DB.update_point(session[&#39;id&#39;], point) #구매자 포인트 감소
        DB.update_ranking_point(session[&#39;id&#39;], point) #구매자 랭킹 포인트 증가
        DB.update_point_2(seller,point) #판매자 포인트 증가
        DB.update_ranking_point(seller,point) #판매자 랭킹 포인트 증가

        flash(&#39;포인트가 차감되었습니다&#39;)

        data=DB.get_item_byname(str(item_name))
        session[&#39;user_point&#39;] = DB.get_user_point(session[&#39;id&#39;])

    return render_template(&quot;1~4/order_item.html&quot;, data=data, item_name=item_name, seller_email=seller_email, download_count=download_count)</code></pre>
<ul>
<li>구매하기 버튼을 눌렀을 때 db에서 구매자와 판매자의 포인트가 변하도록 구현한다.</li>
</ul>
<p><strong>[database.py]</strong></p>
<pre><code class="language-python">#구매하기
    #가격 가져오기
    def get_price(self, item_name):

        data = self.db.child(&quot;item&quot;).child(item_name).get().val()
        point=int(self.db.child(&quot;item&quot;).child(item_name).get().val()[&#39;price&#39;])
        return point
    #판매자 가져오기
    def get_seller(self, name):
        seller=self.db.child(&quot;item&quot;).child(name).get().val()[&#39;writer&#39;]</code></pre>
<ul>
<li>우선 아이템의 가격과 판매자를 가져온다.</li>
</ul>
<pre><code class="language-python">#구매자 포인트 감소
    def update_point(self, user_id, point):
        user_data = self.db.child(&quot;user&quot;).child(user_id).get().val()
        if user_data is not None and &#39;point&#39; in user_data:
            b_point = int(user_data[&#39;point&#39;])
            a_point = b_point - point
            point_info = {
                &quot;point&quot;: a_point
            }
            self.db.child(&quot;user&quot;).child(user_id).update(point_info)
        return True

    #판매자 포인트 증가
    def update_point_2(self, user_id, point):
        user_data = self.db.child(&quot;user&quot;).child(user_id).get().val()
        if user_data is not None and &#39;point&#39; in user_data:
            b_point = int(user_data[&#39;point&#39;])
            a_point = b_point + point
            point_info = {
                &quot;point&quot;: a_point
            }
            self.db.child(&quot;user&quot;).child(user_id).update(point_info)
        return True</code></pre>
<ul>
<li>이후 구매자는 session에 있는 현재 로그인된 유저의 정보를 이용해 DB에 있는 유저의 포인트를 감소시켜준다. 판매자는 get_seller를 통해 가져온 판매자 정보를 이용해 DB에 있는 판매자의 포인트를 증가시켜준다.</li>
</ul>
<pre><code class="language-python">#사용자 포인트 가져오기
    def get_user_point(self, name):
        point=int(self.db.child(&quot;user&quot;).child(name).get().val()[&#39;point&#39;])
        return point</code></pre>
<ul>
<li>이를 이용하여 헤더나 다른 곳에 유저의 포인트를 표시해주고 싶다면, 위 함수를 사용하여 사용자의 포인트를 가져와서 표시해준다.</li>
</ul>
<h2 id="12-유저-포인트-랭킹-구현">12. 유저 포인트 랭킹 구현</h2>
<p><strong>:</strong> 사용자의 랭킹 포인트를 가져와서 랭킹 포인트가 큰 순서대로 정렬한다.</p>
<p><strong>[app.py]</strong></p>
<pre><code class="language-python">#랭킹
@application.route(&quot;/ranking&quot;)
def ranking():
    page = request.args.get(&quot;page&quot;, 0, type=int)
    per_page=int(10) 
    per_row=int (1) 
    college = request.args.get(&quot;category&quot;, &quot;all&quot;)
    row_count=int(per_page/per_row)
    start_idx=per_page*page
    end_idx=per_page*(page+1)

    if college==&quot;all&quot;:
        data = DB.get_users() #전체상품조회 그대로
    else:
        data = DB.get_items_bycollege(college)
    data = dict(sorted(data.items(), key=lambda x: x[1][&#39;rankingpoint&#39;], reverse=True))
    item_counts = len(data)
    if item_counts&lt;=per_page:
        data = dict(list(data.items())[:item_counts])
    else:
        data = dict(list(data.items())[start_idx:end_idx])
    tot_count = len(data)
    for i in range(row_count): 
        if (i == row_count-1) and (tot_count%per_row != 0):
            locals()[&#39;data_{}&#39;.format(i)] = dict(list(data.items())[i*per_row:])
        else: 
            locals()[&#39;data_{}&#39;.format(i)] = dict(list(data.items())[i*per_row:(i+1)*per_row])

    if &#39;id&#39; in session:
        user_id = session[&#39;id&#39;]  
        user_ranking_point=DB.get_user_ranking_point(user_id)
    else: 
        user_ranking_point=0</code></pre>
<ul>
<li>한페이지에 표시하고 싶은 인원수를 perpage안에 넣어주고 랭킹포인트대로 배열하는 것을 data = dict(sorted(data.items(), key=lambda x: x[1][&#39;rankingpoint&#39;], reverse=True)) 로 구현한다.</li>
<li>여기서 x[1][&#39;rankingpoint&#39;]는 랭킹 포인트 순서대로 표시한다는 것이고 reverse=True를 통해 내림차순으로 만들어준다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/didi_delos/post/a956a4a9-8a90-43ce-8fc8-57960910352d/image.png" alt=""></p>
<ul>
<li>위 화면과 같이 화면에 보유 포인트 순으로 정렬됨을 확인할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[해설 2] 프로젝트 해설 2: 전체 코드 기능 설명 2]]></title>
            <link>https://velog.io/@didi_delos/%ED%95%B4%EC%84%A4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%95%B4%EC%84%A4-2</link>
            <guid>https://velog.io/@didi_delos/%ED%95%B4%EC%84%A4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%95%B4%EC%84%A4-2</guid>
            <pubDate>Wed, 06 Dec 2023 12:17:35 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>[해설 1]에 이어지는 글입니다.
<a href="https://velog.io/@didi_delos/%ED%95%B4%EC%84%A4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%95%B4%EC%84%A4-1">✨해설 1 보러가기</a></p>
</blockquote>
<blockquote>
<p>💡 <strong>7 ~ 8</strong> [해설2] : 백_황혜진
리뷰 작성, 등록한 리뷰 조회, 상품별 리뷰 조회 화면, 리뷰 상세보기 화면, 회원별 마이페이지 구현</p>
</blockquote>
<h2 id="7-사용자의-전체-구매-목록-화면의-페이지네이션-구현">7. 사용자의 전체 구매 목록 화면의 페이지네이션 구현</h2>
<p><strong>[app.py]</strong></p>
<pre><code class="language-python"># 5~7
# 리뷰작성 -&gt; 구매내역페이지
@application.route(&quot;/5~7/reg_reviews&quot;)
def view_reg_review():
    if &#39;id&#39; not in session or not session[&#39;id&#39;]:
        flash(&#39;리뷰를 작성하려면 로그인을 해주세요.&#39;)
        return redirect(url_for(&#39;login&#39;))
    else:
        #return render_template(&quot;5~7/reg_reviews.html&quot;)
        page = request.args.get(&quot;page&quot;, 0, type=int)
        per_page=6
        per_row=1
        row_count=int(per_page)
        start_idx=per_page*page
        end_idx=per_page*(page+1)

        user_id = session.get(&#39;id&#39;)
        purchase = DB.get_purchase(user_id)        #구매내역 불러오기
        if purchase == None:                       #구매내역 
            none = &quot;Y&quot;
            return render_template(&quot;/5~7/mypage.html&quot;,none=none)
        else:
            none = &quot;N&quot;
        item_counts = len(purchase)
        purchase = dict(list(purchase.items())[start_idx:end_idx])
        tot_count = len(purchase)
        for i in range(row_count):
            if (i == row_count-1):
                locals()[&#39;data_{}&#39;.format(i)] = dict(list(purchase.items())[i*per_row:])
            else:
                locals()[&#39;data_{}&#39;.format(i)] = dict(list(purchase.items())[i*per_row:(i+1)*per_row])
        return render_template(&quot;/5~7/mypage.html&quot;,none=none, purchase=purchase.items(), row1=locals()[&#39;data_0&#39;].items(), row2=locals()[&#39;data_1&#39;].items(),
                           row3=locals()[&#39;data_2&#39;].items(), row4=locals()[&#39;data_3&#39;].items(),row5=locals()[&#39;data_4&#39;].items(), row6=locals()[&#39;data_5&#39;].items(),
                           limit=per_page, page=page, page_count=int((item_counts/per_page)+1), total=item_counts)</code></pre>
<ul>
<li>현재 로그인하고 있는 아이디의 구매내역을 불러와서 한 페이지에 6개씩 보여주도록 페이지네이션 구현</li>
</ul>
<pre><code class="language-python">if &#39;id&#39; not in session or not session[&#39;id&#39;]:
        flash(&#39;리뷰를 작성하려면 로그인을 해주세요.&#39;)
        return redirect(url_for(&#39;login&#39;))
    else:</code></pre>
<ul>
<li>로그인이 되어 있지 않을 경우, 로그인 화면으로 연결한다.</li>
</ul>
<pre><code class="language-python">user_id = session.get(&#39;id&#39;)
        purchase = DB.get_purchase(user_id)        #구매내역 불러오기
        if purchase == None:                       #구매내역 
            none = &quot;Y&quot;
            return render_template(&quot;/5~7/mypage.html&quot;,none=none)</code></pre>
<pre><code class="language-python">else:
            none = &quot;N&quot;
            item_counts = len(purchase)
            purchase = dict(list(purchase.items())[start_idx:end_idx])
            tot_count = len(purchase)
            for i in range(row_count):
                if (i == row_count-1):
                    locals()[&#39;data_{}&#39;.format(i)] = dict(list(purchase.items())[i*per_row:])
                else:
                    locals()[&#39;data_{}&#39;.format(i)] = dict(list(purchase.items())[i*per_row:(i+1)*per_row])
            return render_template(&quot;/5~7/mypage.html&quot;,none=none, purchase=purchase.items(), row1=locals()[&#39;data_0&#39;].items(), row2=locals()[&#39;data_1&#39;].items(),
                           row3=locals()[&#39;data_2&#39;].items(), row4=locals()[&#39;data_3&#39;].items(),row5=locals()[&#39;data_4&#39;].items(), row6=locals()[&#39;data_5&#39;].items(),
                           limit=per_page, page=page, page_count=int((item_counts/per_page)+1), total=item_counts)</code></pre>
<ul>
<li>get_purchase() 함수를 사용해서 아이디에 해당하는 구매내역을 불러온다.</li>
<li>구매내역 없을 경우 none값만 구매목록화면으로 보낸다.</li>
<li><strong><em>Tip</em></strong> : ‘NoneType’ 객체에는 ‘items()’ 메서드가 없음</li>
</ul>
<p>따라서, 구매내역이 없을 때(purchase = none) purchase.items()에서 오류가 발생하기 때문에 구매내역이 none일때와 아닐 때 두 경우로 나눠서 접근함</p>
<ul>
<li>구매내역이 있을 때, 해당페이지에 표시할 구매내역들만 추출하여 row_i에 담아서 html로 전송</li>
</ul>
<p><strong>[database.py]</strong></p>
<pre><code class="language-python">def get_purchase(self, user_id):
        purchase = self.db.child(&quot;user_purchase_history&quot;).get().val() 

        for id, purchase_items in purchase.items(): #각 내역에 대해 반복
            if id == user_id:                       #key값이 사용자 id와 같으면 그 구매내역 반환
                return purchase_items
        return None</code></pre>
<ul>
<li>firebase에 &quot;user_purchase_history&quot;라는 특정 위치에 저장되어 있는 각 사용자의 구매내역 전체를 불러온다.</li>
<li>반복문을 사용하여 사용자id에 해당하는 구매내역을 찾아서 반환하고, 찾지 못할 경우 none을 반환한다.</li>
</ul>
<h2 id="8-상품-세부-화면에서-리뷰-작성-버튼-클릭-후-해당-상품에-대한-리뷰-작성-화면으로-연결">8. 상품 세부 화면에서 리뷰 작성 버튼 클릭 후 해당 상품에 대한 리뷰 작성 화면으로 연결</h2>
<p><strong>[app.py]</strong></p>
<p><strong><em>Tip</em></strong> : 동적라우팅 사용하여 상품이름으로 라우팅</p>
<p>동적라우팅(Dynamic Routing): 라우팅이 사용자의 요청이나 다른 동적인 조건에 따라 동적으로 결정되는 프로세스. URL 경로의 일부가 고정되어 있지 않고 가변적인 경우에 적용.</p>
<pre><code class="language-python">@application.route(&quot;/reg_review_init/&lt;name&gt;/&quot;)
def reg_review_init(name):
    if &#39;id&#39; not in session or not session[&#39;id&#39;]:
        flash(&#39;리뷰를 작성하려면 로그인을 해주세요.&#39;)
        return redirect(url_for(&#39;login&#39;))
    else:
        info = DB.get_item_byname(name)
        item_name = info.get(&quot;item_name&quot;,None)
        professor = info.get(&quot;professor&quot;,None)
        subject = info.get(&quot;course_number&quot;,None)
        writer = info.get(&quot;writer&quot;,None)
        reviewer = session[&#39;id&#39;]
        return render_template(&quot;5~7/reg_reviews.html&quot;, writer=writer, item_name = item_name, reviewer=reviewer, professor=professor,subject=subject)</code></pre>
<ul>
<li>동적라우팅으로 받은 매개변수<name>을 이용해서 get_item_byname() 함수를 호출하여 그 상품에 대한 정보를 리뷰작성화면으로 넘겨준다.</li>
</ul>
<p><strong>[database.py]</strong></p>
<pre><code class="language-python">def get_item_byname(self, name):
        items = self.db.child(&quot;item&quot;).get()
        target_value=&quot;&quot;
        print(&quot;#############&quot;, name)
        for res in items.each():
            key_value = res.key()
            if key_value == name:
                target_value=res.val()
        return target_value</code></pre>
<ul>
<li>firebase에 &quot;item&quot;라는 특정 위치에 저장되어 있는 각 상품내역 전체를 불러온다.</li>
<li>반복문을 사용하여 상품이름에 해당하는 상품정보를 찾아서 반환한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[팁/디버깅 2] flask run 오류]]></title>
            <link>https://velog.io/@didi_delos/%ED%8C%81%EB%94%94%EB%B2%84%EA%B9%85-2-flask-run-%EC%98%A4%EB%A5%98</link>
            <guid>https://velog.io/@didi_delos/%ED%8C%81%EB%94%94%EB%B2%84%EA%B9%85-2-flask-run-%EC%98%A4%EB%A5%98</guid>
            <pubDate>Wed, 06 Dec 2023 12:03:53 GMT</pubDate>
            <description><![CDATA[<h1 id="🚩1-termimal에서-base가-뜨지-않는-경우">🚩1. termimal에서 (base)가 뜨지 않는 경우</h1>
<p>이는 conda 실행이 안 되어 있는 경우로, 수동으로 실행시켜준 후에 가상환경을 실행시켜야 한다.</p>
<h2 id="✅-해결-방법-1">✅ 해결 방법 1</h2>
<ul>
<li><p>cd 명령어를 사용하여 app.py가 있는 폴더로 들어간다.
<img src="https://velog.velcdn.com/images/didi_delos/post/e7384b7e-52e7-487d-a9bf-aaddda82f667/image.png" alt=""></p>
</li>
<li><p>conda activate 해준다.
<img src="https://velog.velcdn.com/images/didi_delos/post/4402e8ae-8a7a-4c84-a197-fb2b72642bf7/image.png" alt=""></p>
</li>
</ul>
<h2 id="✅-해결-방법-2">✅ 해결 방법 2</h2>
<p>anaconda가 설치되어 있는 폴더 아래의 Scripts 폴더 아래 activate를 수동으로 실행시켜준다.
<code>anaconda 위치/Scripts/activate</code>를 터미널에서 실행하면 된다.</p>
<h2 id="✅-해결-방법-3">✅ 해결 방법 3</h2>
<ul>
<li><p>new terminal을 실행시킨다. 새로운 터미널을 여는 단축키는 <code>Ctrl+Shift+`</code>이다.</p>
</li>
<li><p>Command Prompt로 들어간다.
<img src="https://velog.velcdn.com/images/didi_delos/post/8af76797-266d-4a4e-9a3c-aa7659057c61/image.png" alt=""></p>
</li>
<li><p>Show all commands: 단축키 <code>Ctrl+Shift+P</code> 또는 <code>F1</code>을 눌러 들어갈 수 있다.</p>
</li>
<li><p><code>Python: Select Interpreter</code>를 검색하여 들어간다.
<img src="https://velog.velcdn.com/images/didi_delos/post/1da03231-67af-43f3-aa5d-9972b4bc6cf1/image.png" alt=""></p>
</li>
<li><p>인터프리터를 선택한다. (내가 선택 할 인터프리터는 Python 3.10.13 (‘OSWP_F’))
<img src="https://velog.velcdn.com/images/didi_delos/post/3eb469e6-6a7e-44c7-bbd4-1f289bf53a94/image.png" alt=""></p>
</li>
<li><p>conda activate 완료
<img src="https://velog.velcdn.com/images/didi_delos/post/efbf58df-c930-4f58-9e20-df51bd600851/image.png" alt=""></p>
</li>
</ul>
<h1 id="🚩2-가상환경-실행된-상태인데도-flask-run-시-오류가-나는-경우">🚩2. 가상환경 실행된 상태인데도 flask run 시 오류가 나는 경우</h1>
<p><code>Error: Could not locate a Flask application.</code></p>
<p><img src="https://velog.velcdn.com/images/didi_delos/post/d2e2a5cb-d789-45d8-ab15-54e49bcbea99/image.png" alt=""></p>
<h2 id="✅-해결">✅ 해결</h2>
<p>현재 작업폴더, 즉 flask run을 실행하고 있는 폴더 (pwd)가 app.py가 속해있는 폴더가 아니어서 발생하는 오류이다. app.py가 속해있는 폴더로 이동하여 flask run 실행하면 해결할 수 있다.</p>
<h2 id="💡-tip">💡 TIP</h2>
<ul>
<li>anaconda의 위치, working directory 위치 등 폴더들의 저장위치를 꼬이지 않게 잘 저장하는 것이 중요하다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[팁/디버깅 1] 아이디중복확인, 비밀번호확인, 닉네임 중복확인 구현]]></title>
            <link>https://velog.io/@didi_delos/%ED%8C%81%EB%94%94%EB%B2%84%EA%B9%85-1-%EC%95%84%EC%9D%B4%EB%94%94%EC%A4%91%EB%B3%B5%ED%99%95%EC%9D%B8-%EB%B9%84%EB%B0%80%EB%B2%88%ED%98%B8%ED%99%95%EC%9D%B8-%EB%8B%89%EB%84%A4%EC%9E%84-%EC%A4%91%EB%B3%B5%ED%99%95%EC%9D%B8-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@didi_delos/%ED%8C%81%EB%94%94%EB%B2%84%EA%B9%85-1-%EC%95%84%EC%9D%B4%EB%94%94%EC%A4%91%EB%B3%B5%ED%99%95%EC%9D%B8-%EB%B9%84%EB%B0%80%EB%B2%88%ED%98%B8%ED%99%95%EC%9D%B8-%EB%8B%89%EB%84%A4%EC%9E%84-%EC%A4%91%EB%B3%B5%ED%99%95%EC%9D%B8-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Wed, 06 Dec 2023 11:54:10 GMT</pubDate>
            <description><![CDATA[<h1 id="🚩-alert창이-띄워지면서-값이-날아가는-문제">🚩 alert창이 띄워지면서 값이 날아가는 문제</h1>
<h3 id="✅-해결-시도-1-flash-값을-alert로-출력하는-대신-innerhtml로-출력">✅ 해결 시도 1: flash 값을 alert로 출력하는 대신 innerHTML로 출력</h3>
<h4 id="1-warning-msg를-추가">1. warning-msg를 추가</h4>
<ul>
<li><p>아이디 중복확인
<img src="https://velog.velcdn.com/images/didi_delos/post/24bf8ff3-64e4-438d-ac44-445b3258762f/image.png" alt=""></p>
</li>
<li><p>비밀번호 확인
<img src="https://velog.velcdn.com/images/didi_delos/post/6bd555b2-5938-4424-9095-c76f925672e0/image.png" alt=""></p>
</li>
<li><p>닉네임
<img src="https://velog.velcdn.com/images/didi_delos/post/9508f832-632f-4f2b-b0e8-f8f7980e8470/image.png" alt=""></p>
</li>
</ul>
<h4 id="2-innerhtml-js를-추가">2. innerHTML js를 추가</h4>
<p> <img src="https://velog.velcdn.com/images/didi_delos/post/2b5ed9fa-6ab5-4c46-8dd6-928c1f3c04de/image.png" alt=""></p>
<h4 id="3-flask-run">3. flask run</h4>
<ul>
<li>Input값 모두 입력
<img src="https://velog.velcdn.com/images/didi_delos/post/f921df40-32e3-4040-b632-6bf27a7a0f09/image.png" alt=""></li>
<li>중복확인 버튼을 누르면 warning문구가 이상하게 변경됨, 입력한 input값이 모두 날아간다.
<img src="https://velog.velcdn.com/images/didi_delos/post/d264a347-8141-4862-a599-a08da9e8c981/image.png" alt=""></li>
</ul>
<h3 id="분석">분석</h3>
<p>if-else문으로 구현된 flash값의 return값은 하나로 고정되어 있어서 배열을 사용하여 변경이 필요한 워닝문구를 부분적으로 변경시키는 것에서 문제가 발생한 것으로 보인다.</p>
<p>아이디 중복확인 return flash, 비밀번호 확인 return flash, 닉네임 중복확인 return flash 값을 서로 다른 배열에 저장하여 각각의 버튼이 onclick 되었을 때 해당 flash값으로 문구를 바꾸면 해결이 될 것 같지만, 이미 만들어진 register_user() 함수의 주 기능을 수정해야 하는 위험이 있다고 판단했다. 따라서 구현된 함수를 최대한 활용하여 페이지에 출력할 수 있도록 해결하고자 한다.</p>
<h3 id="✅-해결-시도-2----apppy의-register_user-함수가-입력받은-input값을-return-하도록-수정">✅ 해결 시도 2:    app.py의 register_user() 함수가 입력받은 input값을 return 하도록 수정</h3>
<pre><code class="language-python">return render_template(&quot;8~10/signup.html&quot;)</code></pre>
<p>만 있던 기존 코드에</p>
<pre><code class="language-python">id=session.get(&#39;id_&#39;,&#39;&#39;), pw=session.get(&#39;pw&#39;,&#39;&#39;), nickname=session.get(&#39;nickname&#39;,&#39;&#39;), hp=session.get(&#39;HP&#39;,&#39;&#39;), pw2=session.get(&#39;pw2&#39;,&#39;&#39;), email=session.get(&#39;email&#39;,&#39;&#39;), college=session.get(&#39;college&#39;,&#39;&#39;), major=session.get(&#39;major&#39;,&#39;&#39;)</code></pre>
<p>을 return값으로 추가했다.
 <img src="https://velog.velcdn.com/images/didi_delos/post/8c9684bf-8a95-44fb-8b63-27d3321bf1e7/image.png" alt=""></p>
<p> <img src="https://velog.velcdn.com/images/didi_delos/post/f2969a7e-7e7d-44fe-910c-0f632c12241d/image.png" alt=""></p>
<ul>
<li><p>alert창 뜨면 값 날라가는 문제를 해결하였으므로 html에 flash값을 alert로 출력하는 코드를 추가했다.
<img src="https://velog.velcdn.com/images/didi_delos/post/17d9ebbf-d92e-49b1-821c-447238c7bdd7/image.png" alt=""></p>
</li>
<li><p>flask run
아까와 같은 input값을 입력하고 중복확인버튼을 누르면 flash값이 alert창에 정상적으로 출력된다.
<img src="https://velog.velcdn.com/images/didi_delos/post/016f9653-b5a4-4091-89e0-a869b21f345a/image.png" alt=""></p>
</li>
<li><p>alert창을 닫으면 입력했던 input값들이 return되어 날라가지 않고 뜬다.
<img src="https://velog.velcdn.com/images/didi_delos/post/373dfc52-1cd1-408a-9b4e-7fb81d8c9942/image.png" alt=""></p>
</li>
</ul>
<h3 id="✅-해결-확인-다양한-경우에서의-적용-확인">✅ 해결 확인: 다양한 경우에서의 적용 확인</h3>
<p>-확인한 아이디 중복확인과 동일한 결과가 비밀번호 확인, 닉네임 중복확인에서도 잘 작동한다.</p>
<h2 id="💡-tip">💡 TIP</h2>
<ul>
<li>경우에 따라 HTML 페이지에 출력을 하는 방법을 적절히 선택한다. window.alert(), innerHTML 등</li>
<li>문제 없이 구현된 부분을 최대한 활용할 것</li>
<li>오류가 있는 부분을 확인하기 위해 출력문을 작은 단위로 쪼개서 실행시켜보는 것도 좋은 방법이다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[해설 1] 프로젝트 해설: 전체 코드 기능 설명 1]]></title>
            <link>https://velog.io/@didi_delos/%ED%95%B4%EC%84%A4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%95%B4%EC%84%A4-1</link>
            <guid>https://velog.io/@didi_delos/%ED%95%B4%EC%84%A4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%95%B4%EC%84%A4-1</guid>
            <pubDate>Wed, 06 Dec 2023 11:36:29 GMT</pubDate>
            <description><![CDATA[<h1 id="1-개발환경-소개">1. 개발환경 소개</h1>
<ul>
<li>프론트엔드 : HTML, CSS, JavaScript</li>
<li>백엔드 : flask</li>
<li>데이터베이스 : firebase</li>
<li>버전관리 : Git, GitHub</li>
</ul>
<h1 id="2-백엔드-담당-파트">2. 백엔드 담당 파트</h1>
<blockquote>
<p>💡 <strong>1 ~ 6</strong> [해설1] : 백_양지원
상품 등록, 등록한 상품 조회, 전체 상품 리스트 조회, 상품 상세보기 화면, 좋아요 및 다운로드 횟수 카운팅 기능, 구매 완료 화면 연결</p>
</blockquote>
<blockquote>
<p>💡 <strong>7 ~ 8</strong> [해설2] : 백_황혜진
리뷰 작성, 등록한 리뷰 조회, 상품별 리뷰 조회 화면, 리뷰 상세보기 화면, 회원별 마이페이지 구현</p>
</blockquote>
<blockquote>
<p>💡 <strong>9 ~ 12</strong> [해설3] : 백_윤소민
회원가입, 아이디 중복확인, 닉네임 중복확인, 로그인 및 로그아웃, 상품 구매시 포인트, 랭킹포인트 증감 기능, 사용자 포인트별 랭킹 정렬 기능</p>
</blockquote>
<h1 id="3-기능과-코드">3. 기능과 코드</h1>
<h2 id="1-상품-등록-화면-접근-시">1. 상품 등록 화면 접근 시</h2>
<p><strong>[app.py]</strong></p>
<pre><code class="language-python">@application.route(&quot;/1~4/item_reg&quot;)
def view_reg_items():
    if &#39;id&#39; not in session or not session[&#39;id&#39;]:
        flash(&#39;상품을 등록하려면 로그인을 해주세요.&#39;)
        return redirect(url_for(&#39;login&#39;))
    else:
        return render_template(&quot;1~4/item_reg.html&quot;)</code></pre>
<ul>
<li>비로그인 상태의 경우, ‘상품을 등록하려면 로그인을 해주세요’ flash 창 팝업 후 로그인 화면으로 이동</li>
<li>로그인 상태의 경우, 정상적으로 상품 등록 화면으로 연결</li>
</ul>
<h2 id="2-상품-등록-화면에서-작성한-값-db에-저장--상품-등록-후-상품-세부-보기-화면으로-연결">2. 상품 등록 화면에서 작성한 값 DB에 저장 &amp; 상품 등록 후 상품 세부 보기 화면으로 연결</h2>
<p><strong>[item_reg.html]</strong></p>
<pre><code class="language-python">&lt;div id=&quot;regitem-container&quot;&gt; 
&lt;form id=&quot;regitem&quot; action=&quot;/submit_item_post&quot; method=&quot;post&quot; enctype=&quot;multipart/form-data&quot;&gt;
     … 입력 내용 …  &lt;/form&gt;</code></pre>
<ul>
<li>html에서 사용자가 입력한 내용들이 모두 ‘/submit_item_post’라는 링크로 넘어감. 해당 링크로 넘어갔을 때의 동작은 아래 app.py의 함수에서 담당함.</li>
</ul>
<p><strong>[app.py]</strong></p>
<pre><code class="language-python">@application.route(&quot;/submit_item_post&quot;, methods=[&#39;POST&#39;])

def reg_item_submit_post():
    if &#39;id&#39; not in session or not session[&#39;id&#39;]:
        flash(&#39;상품을 등록하려면 로그인을 해주세요.&#39;)
        return redirect(url_for(&#39;login&#39;))
    else:
        item_file=request.files[&#39;item_upload&#39;]
        item_file.save(&quot;static/items/{}&quot;.format(item_file.filename))
        photo_file=request.files.getlist(&quot;photo_upload[]&quot;)
        data=request.form
        writer = session[&#39;id&#39;]
        for f in photo_file:
            f.save(&#39;static/photos/&#39; + f.filename)
        DB.insert_item(data[&#39;item_name&#39;], data, item_file.filename, [f.filename for f in photo_file], session[&#39;id&#39;])
    return render_template(&quot;1~4/item_detail.html&quot;, data=data, item_path=&quot;static/items/{}&quot;.format(item_file.filename), photo_paths=[&quot;static/photos/{}&quot;.format(f.filename) for f in photo_file])</code></pre>
<ul>
<li>사용자가 등록한 상품(item) 저장: 사용자가 입력한 정보들을 넘겨받은 지점인 ‘request’로부터 ‘item_upload’라는 name을 찾아 item_file 변수에 저장한다. 그 item_file을 static/items/ 경로에 저장한다.</li>
<li>사용자가 등록한 사진(photo) 저장: 사용자가 입력한 정보들을 넘겨받은 지점인 ‘request’로부터 ‘photo_upload’라는 name의 배열을 찾아 pthoto_file 변수에 저장한다. 그 photo_file 배열을 차례로 순회하며 static/photos/ 경로에 하나씩 저장한다.</li>
<li>사용자가 입력한 모든 정보 저장: 사용자가 입력한 정보들을 넘겨받은 지점인 ‘request’로부터 저장한다.</li>
<li>insert_item()함수를 이용하여, 사용자가 등록한 상품의 이름, 등록한 모든 정보, 등록한 상품, 등록한 사진, 그리고 현재 접속 중인 id (상품을 등록한 유저)를 firebase에 저장한다.</li>
<li>데이터베이스에 상품의 값을 모두 저장한 후, 정상 등록된 상품의 세부 정보를 보여주는 상품 세부화면으로 render_template함. 이때 html 파일에서 사용할 정보인 data, item_path, photo_path를 함께 전달한다.</li>
</ul>
<p><strong>[database.py]</strong></p>
<pre><code class="language-python">#상품 정보 등록하기
    def insert_item(self, item_name, data, item_path, photo_path, user_id):
        item_info = {
            &quot;writer&quot;: user_id,
            &quot;item_name&quot;: data[&#39;item_name&#39;],
            &quot;item_type&quot;: data[&#39;item_type&#39;],
            &quot;price&quot;: data[&#39;price&#39;],
            &quot;course_type&quot;: data.get(&#39;course_type&#39;),
            &quot;faculty&quot;: data.get(&#39;faculty&#39;),
            &quot;major&quot;: data[&#39;major&#39;],
            &quot;course_number&quot;: data[&#39;course_number&#39;],
            &quot;professor&quot;: data[&#39;professor&#39;],
            &quot;description&quot;: data[&#39;description&#39;],
            &quot;tag&quot;: data[&#39;tag&#39;],
            &quot;item_path&quot;: item_path,
            &quot;photo_path&quot;: photo_path,
            &quot;download_count&quot;: 0
        }
        user_and_item = user_id + &#39;_&#39; + data[&#39;item_name&#39;]
        self.db.child(&quot;item&quot;).child(user_and_item).set(item_info)
        print(data, item_path)
        for path in photo_path:
            print(&quot;사진 경로:&quot;, path)
        return True</code></pre>
<ul>
<li>사용자로부터 등록받은 정보를 다음과 같이 저장한다.</li>
<li>등록한 정보들은 ‘item’ child 아래에 저장되며, 각 상품을에 대한 저장 항목의 이름은 ‘등록한 유저의 아이디_상품의 이름’으로 설정하여 서로 다른 유저가 같은 이름으로 상품을 등록하였을 때 database에 값이 덮어씌워지는 문제를 방지하였다.</li>
<li>각 상품에 대한 다운로드 횟수를 측정하기 위해 사용자가 상품 등록 시 입력한 값 외에 ‘download_count’라는 key를 추가하였다.</li>
<li>위 함수의 결과로 데이터베이스에 저장되는 상품의 정보는 다음과 같다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/didi_delos/post/663db009-9b69-41dc-b1c1-cd586b568024/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/didi_delos/post/cafdd566-5f69-41fb-ae96-dfc0258e7f0e/image.png" alt=""></p>
<h2 id="3-상품-전체보기-화면의-페이지네이션-구현">3. 상품 전체보기 화면의 페이지네이션 구현</h2>
<p><strong>[app.py]</strong></p>
<pre><code class="language-python">#### 맨 처음 화면이 이 view_items()함수로 옴.
@application.route(&quot;/1~4/view_item&quot;)
def view_items():
    page = request.args.get(&quot;page&quot;, 0, type=int)
    per_page=5 # item count to display per page
    per_row=1 # item count to display per row
    row_count=int(per_page/per_row)
    start_idx=per_page*page
    end_idx=per_page*(page+1)

    data = DB.get_items()
    item_counts = len(data)
    data = dict(list(data.items())[start_idx:end_idx])
    tot_count = len(data)

    for i in range(row_count): #last row
        if (i == row_count-1) and (tot_count%per_row != 0):
            locals()[&#39;data_{}&#39;.format(i)] = dict(list(data.items())[i*per_row:])
        else: 
            locals()[&#39;data_{}&#39;.format(i)] = dict(list(data.items())[i*per_row:(i+1)*per_row])
    return render_template(&quot;1~4/view_item.html&quot;, datas=data.items(), row1=locals()[&#39;data_0&#39;].items(), row2=locals()[&#39;data_1&#39;].items(), limit=per_page, page=page, page_count=int((item_counts/per_page) +1), total = item_counts)</code></pre>
<ul>
<li>View_items() 함수는 페이지 매개변수 ‘page’를 받아와 해당 페이지에 해당하는 아이템 목록을 가져와 템플릿으로 전달한다.</li>
<li>페이지당 5개의 아이템을 표시하며, 각 행에 1개의 아이템이 표시된다. 페이지 수와 총 아이템 개수도 함께 전달된다.</li>
</ul>
<h2 id="4-상품-전체보기-리스트에서-특정-상품-클릭-시-상품-세부보기-화면으로-연결">4. 상품 전체보기 리스트에서 특정 상품 클릭 시 상품 세부보기 화면으로 연결</h2>
<p><strong>[view_item.html]</strong></p>
<pre><code class="language-html">&lt;div class=&quot;item-card&quot; onclick=&quot;location.href=&#39;/1~4/view_item_detail/{{key}}/&#39;;&quot; style=&quot;cursor:pointer;&quot;&gt;
                            … 추가 내용 …                     &lt;/div&gt;</code></pre>
<ul>
<li>등록된 상품 전체를 리스트로 보여주는 html에서 특정 상품에 대한 item card를 클릭 시, ‘/1~4/view_item_detail/{{key}}/’ 로 연결된다.</li>
<li>해당 연결 지점에 대한 py의 함수를 아래에 구현하였다.</li>
</ul>
<p><strong>[app.py]</strong></p>
<pre><code class="language-python">#전체 리스트에서 상품 클릭 시 세부정보 볼 수 있음
@application.route(&quot;/1~4/view_item_detail/&lt;item_name&gt;/&quot;)
def view_item_detail(item_name):
    data=DB.get_item_byname(str(item_name))
    return render_template(&quot;1~4/detail.html&quot;, item_name=item_name, data=data)</code></pre>
<ul>
<li>위 html 파일에서의 key 값은 사용자가 클릭한 item card에 대한 특정 상품이므로, 그 클릭된 상품 이름에 따라서 동적으로 라우팅된다.</li>
<li>get_item_byname 함수를 이용해, 사용자가 클릭한 상품의 이름을 key로 하여 DB에서 해당 아이템의 정보를 찾아 가지고 온다.</li>
<li>찾은 정보 data와 item_name을 갖고, 상품의 세부 정보를 확인할 수 있는 상품 세부보기 화면 (1~4/detail.html)으로 render_template 한다.</li>
</ul>
<p><strong>[database.py]</strong></p>
<pre><code class="language-python">#상품 이름으로 상품 정보 가져오기
    def get_item_byname(self, name):
        items = self.db.child(&quot;item&quot;).get()
        target_value=&quot;&quot;
        for res in items.each():
            key_value = res.key()
            if key_value == name:
                target_value=res.val()
        return target_value</code></pre>
<ul>
<li>Firebase의 child(&quot;item&quot;).get() 메서드를 사용하여 &quot;item&quot; 경로의 데이터를 가져온 후, 반복문을 통해 각 상품을 확인하여 입력받은 이름과 일치하는 상품을 찾는다. 일치하는 상품이 발견되면 해당 상품의 정보를 변수에 저장하고 반환한다. 일치하는 상품이 없으면 빈 문자열을 반환한다.</li>
</ul>
<h2 id="5-상품-세부">5. 상품 세부</h2>
<p>화면에서 좋아요 기능 구현</p>
<p><strong>[Detail.html]</strong></p>
<pre><code class="language-html">&lt;script&gt;
        function showHeart() {
        $.ajax({
            type: &#39;GET&#39;,
            url: &#39;/show_heart/{{item_name}}/&#39;,
            data: {},
            success: function (response){
                let my_heart = response[&#39;my_heart&#39;];
                if (my_heart[&#39;interested&#39;] == &#39;Y&#39;)
                {
                    $(&quot;#heart&quot;).css(&quot;color&quot;,&quot;red&quot;);
                    $(&quot;#heart&quot;).attr(&quot;onclick&quot;,&quot;unlike()&quot;);
                }
                else
                {
                    $(&quot;#heart&quot;).css(&quot;color&quot;,&quot;grey&quot;);
                    $(&quot;#heart&quot;).attr(&quot;onclick&quot;,&quot;like()&quot;);
                }
                //alert(&quot;showheart!&quot;)
                }
            }); 
        } 

        function like() {
            $.ajax({
                type: &#39;POST&#39;,
                url: &#39;/like/{{item_name}}/&#39;,
                data: {
                    interested : &quot;Y&quot;
                },
                success: function (response) {
                    alert(response[&#39;msg&#39;]);
                    window.location.reload()
                }
            });
        }

        function unlike() {
            $.ajax({
                type: &#39;POST&#39;,
                url: &#39;/unlike/{{item_name}}/&#39;,
                data: {
                    interested : &quot;N&quot;
                },
                success: function (response) {
                    alert(response[&#39;msg&#39;]);
                    window.location.reload()
                }
                });
            }

        $(document).ready(function () {
            showHeart();
        });

    &lt;/script&gt;</code></pre>
<ul>
<li>상품의 세부 정보를 확인할 수 있는 상세페이지를 구현한 html에서, 하트 아이콘을 클릭할 시 동작하는 javascript 코드를 확인할 수 있다.</li>
<li>ShowHeart(): 해당 상품에 대해 현 사용자가 관심을 눌렀는지, 누르지 않았었는지 확인하기 위해 서버에 ajax get 요청을 보내는 함수이다. 이때 사용자가 이전에 누른 기록이 있다는 응답을 받으면 현재 클릭에 대한 결과를 unlike() 함수로 연결하고, 누른 기록이 없다는 응답을 받으면 현재 클릭에 대한 결과를 like() 함수로 연결한다.</li>
<li>Like(): 해당 상품에 대해 현 사용자가 하트를 클릭하는 경우 서버에 ajax post 요청을 보내고 응답을 받으면 alert 메시지를 띄운 후 페이지를 다시 로드한다.</li>
<li>Unlike(): 해당 상품에 대해 이미 하트를 눌렀던 사용자가 하트를 재클릭하여 관심을 취소하는 경우, 서버에 ajax post 요청을 보내고 응답을 받으면 alert 메시지를 띄운 후 페이지를 다시 로드한다.</li>
</ul>
<p><strong>[app.py]</strong></p>
<pre><code class="language-python">#좋아요 관련 기능
@application.route(&#39;/show_heart/&lt;item_name&gt;/&#39;, methods=[&#39;GET&#39;])
def show_heart(item_name):
    if &#39;id&#39; not in session or not session[&#39;id&#39;]:
        flash(&#39;상품을 찜하려면 로그인을 해주세요.&#39;)
        return redirect(url_for(&#39;login&#39;))
    else:
        my_heart = DB.get_heart_byname(session[&#39;id&#39;],item_name)
        return jsonify({&#39;my_heart&#39;: my_heart})</code></pre>
<ul>
<li>비로그인 상태에서 하트를 누르는 경우, ‘상품을 찜하려면 로그인을 해주세요’라는 flash 알림을 띄운 후 login 화면으로 바로 redirect한다.</li>
<li>로그인 상태에서 하트를 누르는 경우, DB.get_heart_byname 함수를 통해 사용자의 id와 상품 이름을 활용하여 해당 사용자가 이 상품에 대해 좋아요를 눌렀었는지 여부를 조회한다.</li>
</ul>
<pre><code class="language-python">@application.route(&#39;/like/&lt;item_name&gt;/&#39;, methods=[&#39;POST&#39;])
def like(item_name):
    if &#39;id&#39; not in session or not session[&#39;id&#39;]:
        flash(&#39;상품을 찜하려면 로그인을 해주세요.&#39;)
        return redirect(url_for(&#39;login&#39;))
    else:
        my_heart = DB.update_heart(session[&#39;id&#39;],&#39;Y&#39;,item_name)
        return jsonify({&#39;msg&#39;: &#39;좋아요 완료!&#39;})</code></pre>
<pre><code class="language-python">@application.route(&#39;/unlike/&lt;item_name&gt;/&#39;, methods=[&#39;POST&#39;])
def unlike(item_name):
    if &#39;id&#39; not in session or not session[&#39;id&#39;]:
        flash(&#39;상품을 찜하려면 로그인을 해주세요.&#39;)
        return redirect(url_for(&#39;login&#39;))
    else:
        my_heart = DB.update_heart(session[&#39;id&#39;],&#39;N&#39;,item_name)
        return jsonify({&#39;msg&#39;: &#39;안좋아요 완료!&#39;})</code></pre>
<ul>
<li>update.heart() 함수를 이용하여 데이터베이스에 해당 상품에 대한 좋아요 결과를 저장한다. 정상적으로 등록된 DB의 모습은 다음과 같다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/didi_delos/post/56ca396e-ec48-4391-bbf5-36bd6d8a1bfe/image.png" alt=""></p>
<p><strong>[database.py]</strong></p>
<pre><code class="language-python">#heart 정보 가져오기
    def get_heart_byname(self, uid, name):
        hearts = self.db.child(&quot;heart&quot;).child(uid).get()
        target_value=&quot;&quot;
        if hearts.val() == None:
            return target_value

        for res in hearts.each():
            key_value = res.key()

            if key_value == name:
                target_value=res.val()
        return target_value</code></pre>
<ul>
<li>특정 사용자(‘uid’)의 하트 정보를 특정 상품(‘name’)에 대해 조회한다. 데이터베이스에서 해당 사용자의 하트 정보를 가져와서 반복문을 통해 특정 상품과 일치하는 정보를 찾는다.</li>
</ul>
<pre><code class="language-python">#heart 값 변경하기    
    def update_heart(self, user_id, isHeart, item):
        heart_info ={
            &quot;interested&quot;: isHeart
        }
        self.db.child(&quot;heart&quot;).child(user_id).child(item).set(heart_info)</code></pre>
<ul>
<li>사용자 ID, 상품에 대한 하트 여부 (‘isHeart’), 상품 이름(item)을 인자로 받아서 업데이트할 정보를 딕셔너리 형태로 만든다. 해당 정보를 Firebase의 “heart” child에 업데이트한다.</li>
</ul>
<h2 id="6-상품-세부-화면에서-구매-버튼-클릭-후-구매-완료-화면으로-연결">6. 상품 세부 화면에서 구매 버튼 클릭 후 구매 완료 화면으로 연결</h2>
<p><strong>[Detail.html]</strong></p>
<pre><code class="language-html">&lt;div&gt;
&lt;button onclick=&quot;location.href=&#39;/1~4/order_item/{{item_name}}/&#39;;&quot; title=&quot;to-payment&quot; id=&quot;order-btn&quot; class=&quot;button btnFade btnDefaultblue&quot;&gt;바로 구매하기&lt;/button&gt;</code></pre>
<ul>
<li>상품 세부 화면에서 ‘바로 구매하기’ 버튼 클릭 시, ‘/1~4/order_item/{{item_name}}/’이라는 경로로 라우팅됨.</li>
</ul>
<p><strong>[app.py]</strong></p>
<p>#구매하기 버튼 누르면</p>
<pre><code class="language-python">#구매하기 버튼 누르면
@application.route(&quot;/1~4/order_item/&lt;item_name&gt;/&quot;)
def view_order_confirmation(item_name):

    point=DB.get_price(str(item_name))
    seller=DB.get_seller(str(item_name))

    if &#39;id&#39; not in session or not session[&#39;id&#39;]:
        flash(&#39;구매하시려면 로그인을 해주세요.&#39;)
        return redirect(url_for(&#39;login&#39;))
    else:

        download_count = DB.increase_download_count(item_name) #다운로드 횟수 증가

        DB.update_point(session[&#39;id&#39;], point) #구매자 포인트 감소
        DB.update_ranking_point(session[&#39;id&#39;], point) #구매자 랭킹 포인트 증가
        DB.update_point_2(seller,point) #판매자 포인트 증가
        DB.update_ranking_point(seller,point) #판매자 랭킹 포인트 증가

        DB.insert_purchase_history(item_name, session[&#39;id&#39;])
        seller_email = DB.get_seller_email(item_name)

        flash(&#39;포인트가 차감되었습니다&#39;)

        data=DB.get_item_byname(str(item_name))
        session[&#39;user_point&#39;] = DB.get_user_point(session[&#39;id&#39;])

    return render_template(&quot;1~4/order_item.html&quot;, data=data, item_name=item_name, seller_email=seller_email, download_count=download_count)</code></pre>
<ul>
<li>insert_purchase_history() 함수를 이용하여, 각 사용자별 구매한 상품의 리스트가 업데이트될 수 있도록 한다.</li>
<li>increase_download_count() 함수를 이용하여, 구매 버튼 클릭 시마다 해당 구매된 상품의 다운로드 횟수가 하나씩 증가될 수 있도록 한다.</li>
<li>get_seller_email() 함수를 이용하여, 구매한 상품의 이름을 key로 하여 구매된 상품의 판매자의 이메일 정보를 DB로부터 찾아올 수 있도록 한다.</li>
<li>get_item_byname() 함수를 이용하여, 구매한 상품의 이름을 key로 하여 구매된 상품의 모든 정보를 data 라는 변수에 담아온다.</li>
<li>위 DB 함수들로 얻은 정보를 포함하여 ‘1~4/order_item.html’로 render_template한다.</li>
</ul>
<p><strong>[database.py]</strong></p>
<pre><code class="language-python">def insert_item(self, item_name, data, item_path, photo_path, user_id):
        item_info = {
           … 추가 내용들 …
            &quot;download_count&quot;: 0
        }</code></pre>
<ul>
<li>각 상품에 대한 다운로드 횟수를 카운트하기 위해, 상품 최초 등록 시 각 상품에 대한 database에 “download_count”라는 key를 추가하였다. 최초 등록 시이므로 초기값은 0으로 설정하였다.</li>
</ul>
<pre><code class="language-python">#사용자별 구매내역 저장하기
def insert_purchase_history(self, item_name, user_id):

      timestamp = int(time.time())

      purchase_info = {
          &quot;item_name&quot;: item_name,
          &quot;timestamp&quot;: timestamp
      }
      self.db.child(&quot;user_purchase_history&quot;).child(user_id).set(purchase_info)
        return True</code></pre>
<ul>
<li>Database에 “user_purchase_history”라는 child를 만들어, 그 아래 각 user_id별로 purchase_info를 key로 하여 저장한다. Purchase_info에는 구매한 상품의 이름과, 구매한 시간이 저장된다.</li>
</ul>
<pre><code class="language-python">#구매 버튼 누를 때마다 download 횟수 하나씩 늘려 저장
    def increase_download_count(self, item_name):
        current_count = self.db.child(&quot;item&quot;).child(item_name).child(&quot;download_count&quot;).get().val()

        new_count = current_count + 1
        self.db.child(&quot;item&quot;).child(item_name).update({&quot;download_count&quot;: new_count})
      return new_count</code></pre>
<ul>
<li>현재 상품의 download_count 값을 db로부터 검색하여 가져오고, 그 값을 1 증가하여 유에 저장된 값을 업데이트한다.</li>
</ul>
<pre><code class="language-python">#판매자 이메일 가져오기
    def get_seller_email(self, item_name):
        writer = self.db.child(&quot;item&quot;).child(item_name).get().val().get(&#39;writer&#39;)
        if writer:
            user_data = self.db.child(&quot;user&quot;).child(writer).get().val()
            if user_data:
                email = user_data.get(&#39;email&#39;)
                return email        return None</code></pre>
<ul>
<li>Firebase의 ‘item’ child에서 ‘item_name’에 해당하는 데이터를 찾아, 그 안의 writer 값을 가져온다.</li>
<li>만약 writer 값이 존재한다면, firebase의 ‘user’ child에서 그 writer 값과 일치하는 데이터를 찾는다. 일치하는 사용자가 존재하는 경우, 그 사용자의 email 값을 반환한다. 찾지 못한 경우 None을 반환한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[개념] 웹 개발의 필수 - HTML css javascript]]></title>
            <link>https://velog.io/@didi_delos/%EA%B0%9C%EB%85%90-%EC%9B%B9-%EA%B0%9C%EB%B0%9C%EC%9D%98-%ED%95%84%EC%88%98-HTML-css-javascript</link>
            <guid>https://velog.io/@didi_delos/%EA%B0%9C%EB%85%90-%EC%9B%B9-%EA%B0%9C%EB%B0%9C%EC%9D%98-%ED%95%84%EC%88%98-HTML-css-javascript</guid>
            <pubDate>Wed, 06 Dec 2023 11:22:23 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/didi_delos/post/1683eeb5-1ae1-4bad-bbc8-a3c6f982dffe/image.png" alt=""></p>
<h1 id="html">HTML</h1>
<p>HTML이란 웹사이트의 모습을 기술하기 위한 마크업 언어. 웹 브라우저에서 문서 및 웹 페이지가 표시되는 방법을 규정하는 언어이다. 이를 사용하여 웹사이트의 구조를 만들 수 있다.</p>
<blockquote>
<p><strong>마크업 언어(Markup Language)란?</strong>
문서가 화면에 표시되는 형식을 나타내거나 데이터의 논리적인 구조를 명시하기 위한 규칙들을 정의한 언어의 일종이다. </p>
</blockquote>
<h1 id="csscascading-style-sheets">CSS(Cascading Style Sheets)</h1>
<p>HTML등의 마크업 언어로 만들어진 문서의 스타일을 지정하는 방식을 규정하는  스타일 시트 언어이다. 색상, 테두리, 폰트 등 간단한 스타일 지정부터 반응형 특징을 정의하는 등 문서를 꾸며주는 역할을 한다.</p>
<p>여기서 CSS의 C는 Cascading의 약자다. 한국말로 직역하면 ‘폭포수처럼 흐르는&#39;을 뜻하는데 이는 상위 요소의 스타일 속성을 자손 요소들에게 상속시켜주는 모습이 DOM 트리구조에서 마치 폭포수처럼 내려가는 모습을 닮았기 때문에 쓰였다.</p>
<p>CSS는 총 3가지 방법으로 사용할 수 있다. </p>
<ol>
<li>태그 내의 style 속성<pre><code>&lt;h1 style=&quot;display: hidden;&quot;&gt;&lt;/h1&gt;</code></pre></li>
<li>내부 css 파일<pre><code>&lt;style&gt;
 h1 {
 display: hidden;
 }
&lt;/style&gt;</code></pre></li>
<li>외부 css 파일: html 파일에는 다음 코드만 입력. <pre><code>&lt;link href=&quot;css 파일의 경로&quot; type=&quot;text/css&quot; rel=&quot;stylesheet&quot;&gt;</code></pre></li>
</ol>
<h1 id="javascript">JavaScript</h1>
<p>자바스크립트는 객체 기반의 스크립트 언어이다. </p>
<blockquote>
<p>*<em>스크립트 언어란? *</em>
프로그래밍 언어의 한 종류로, 기존에 이미 존재하는 소프트웨어(애플리케이션)를 제어하기 위한 용도로 쓰이는 언어이다.</p>
</blockquote>
<p>자바스크립트가 가지고 있는 언어적 특징은 다음과 같다.</p>
<ol>
<li>자바스크립트는 <strong>객체 기반의 스크립트 언어</strong>이다.</li>
<li>자바스크립트는 <strong>동적</strong>이며, 타입을 명시할 필요가 없는 <strong>인터프리터 언어</strong>이다.</li>
<li>자바스크립트는 <strong>객체 지향형 프로그래밍과 함수형 프로그래밍을 모두 표현</strong>할 수 있다.</li>
</ol>
<h1 id="html-css-js가-웹을-이루는-방식">HTML, CSS, JS가 웹을 이루는 방식</h1>
<p>세 가지는 웹을 이루는 기본적인 요소이다. 이들이 웹에서 하는 역할을 정리해보자면 다음과 같이 말할 수 있다.</p>
<ul>
<li><p><strong>HTML</strong>: 웹 페이지의 기본 구조와 뼈대 담당</p>
</li>
<li><p><strong>CSS</strong>: 웹을 디자인</p>
</li>
<li><p><strong>JavaScript</strong>: 클라이언트 단에서 웹 페이지가 동작하는 것을 담당</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[가이드]Flask 설치+ python 가상 환경 설정 가이드 (Windows OS)]]></title>
            <link>https://velog.io/@didi_delos/Flask-%EC%84%A4%EC%B9%98-%EA%B0%80%EC%9D%B4%EB%93%9C-Windows</link>
            <guid>https://velog.io/@didi_delos/Flask-%EC%84%A4%EC%B9%98-%EA%B0%80%EC%9D%B4%EB%93%9C-Windows</guid>
            <pubDate>Wed, 06 Dec 2023 10:55:32 GMT</pubDate>
            <description><![CDATA[<p>*이 글은 2023년 12월 6일 기준으로 작성되었다.</p>
<p>일단 프로젝트에 쓸 가상환경을 설정하자.
vscode에서 플라스크 프로젝트를 작업한다는 것을 가정하여 쓰인 가이드이다.
vscode는 이미 설치되어 있다고 가정한다. vscode가 설치되어 있지 않다면 먼저 설치하고 올 것.</p>
<h1 id="사양-체크">사양 체크</h1>
<ol>
<li>운영체제 확인<ul>
<li>Mac OS인가? ➡ windows powershell에서 실행하는 것 외에는 모두 유사하다.</li>
<li>Windows OS인가?<ul>
<li>64bit인가? ➡ 아래 그대로 실행</li>
<li>32bit인가? ➡ 32bit Windows를 위한 아나콘다는 더이상 지원하지 않는 것으로 보인다. Installer를 찾지 못했다.</li>
</ul>
</li>
</ul>
</li>
</ol>
<h1 id="아나콘다-설치-가상환경-설정">아나콘다 설치, 가상환경 설정</h1>
<p>다음 링크는 아나콘다 인슽톨러 다운로드 페이지로 이동하는 링크이다.
<a href="https://www.anaconda.com/download">https://www.anaconda.com/download</a>
위 링크에 접속하면 다음과 같은 화면이 나온다.
<img src="https://velog.velcdn.com/images/didi_delos/post/a89d4e3c-1dc4-45cb-bc2c-6c3c7dc99312/image.png" alt="아나콘다 다운로드 화면 캡쳐">
아래 캡쳐는 위 화면의 확대샷이다. 
<img src="https://velog.velcdn.com/images/didi_delos/post/c62f5740-d494-4a6b-9ec7-551aa1cad2e8/image.png" alt="">
여기에서 1번, &#39;Download&#39; 버튼을 누르면 아래 화면과 같이 자동으로 자신의 운영체제에 맞는 인스톨러가 다운로드된다.
<img src="https://velog.velcdn.com/images/didi_delos/post/420787bf-8dd5-404f-86cc-991b06c2f22b/image.png" alt="Download 버튼 누른 후 anaconda installer가 설치되고  있는 화면 캡쳐">
또는 &#39;Download&#39; 아래에 있는 2번, &#39;Get Additional Installers&#39;를 눌러 직접 어떤 인스톨러를 다운받을지 선택할 수 있다.
<img src="https://velog.velcdn.com/images/didi_delos/post/babc75d5-68db-4631-a570-1232ac3dd9a2/image.png" alt=""></p>
<h2 id="인스톨러-실행-설치">인스톨러 실행, 설치</h2>
<p>인스톨러 파일을 모두 다운받았으면 다운받은 파일을 실행한다. 
<img src="https://velog.velcdn.com/images/didi_delos/post/0333c7ab-2217-4642-a76d-a6285983a355/image.png" alt="">
그러면 아래와 같이 셋업을 시작한다.
<img src="https://velog.velcdn.com/images/didi_delos/post/2eedaa18-f735-46bc-bbe6-fb77521f8e93/image.png" alt="">
아래 캡쳐와 같이 계속 진행하면 된다.
<img src="https://velog.velcdn.com/images/didi_delos/post/b44dfca8-e27f-4058-80eb-06f2f398edae/image.PNG" alt="">
<img src="https://velog.velcdn.com/images/didi_delos/post/fc580f93-e8f3-4951-82b3-8c3a3bf78df6/image.PNG" alt="">
<img src="https://velog.velcdn.com/images/didi_delos/post/7ef0170c-ba20-4aa5-806d-e9089c064399/image.PNG" alt="">
<img src="https://velog.velcdn.com/images/didi_delos/post/12974b8c-c697-402b-884f-4bf6749e70ee/image.PNG" alt="">
<img src="https://velog.velcdn.com/images/didi_delos/post/47bdb65a-e22e-4c71-aca3-9ed1e310eff6/image.PNG" alt="">
<img src="https://velog.velcdn.com/images/didi_delos/post/50e5d1bc-69a2-4767-acb9-385145a49d2d/image.PNG" alt="">
<img src="https://velog.velcdn.com/images/didi_delos/post/07b2fbf5-ea79-4688-b7f6-dbe3fa1ec08c/image.PNG" alt="">
<img src="https://velog.velcdn.com/images/didi_delos/post/df5b308a-4564-4c41-95fc-427bec11f78d/image.PNG" alt="">
완료</p>
<h2 id="아나콘다-설치-확인">아나콘다 설치 확인</h2>
<p><img src="https://velog.velcdn.com/images/didi_delos/post/3ba8a56d-5d0a-418e-9645-96b9fea3f3d7/image.png" alt="">
시작 버튼을 누르면 Anaconda3 (64-bit) 폴더가 보일 것이다. 이 안에서 Anaconda Prompt를 클릭하여 실행한다. Anaconda Prompt를 찾기 힘들면 시작버튼 오른쪽의 &#39;찾기&#39; 탭에서 검색하여 찾을 수도 있다.
<img src="https://velog.velcdn.com/images/didi_delos/post/ae36cbb9-7aab-407a-8332-495eede220db/image.png" alt="">
Anaconda Prompt에 <code>python</code>을 쳐서 위와 같은 결과가 나오면 설치를 성공한 것이다.</p>
<h2 id="vscode">vscode</h2>
<p><img src="https://velog.velcdn.com/images/didi_delos/post/1dcc2ba1-eb7a-41f1-b87c-ae0cd2571491/image.png" alt="">
vscode 왼쪽 탭의 네 개의 블럭 아이콘은 vscode extension(확장프로그램)을 설치하는 곳이다. vscode extension에서 python을 찾아 설치해주어야 한다.</p>
<h2 id="가상환경-생성">가상환경 생성</h2>
<p>그럼 기본 설정이 완료되었다! 이제 가상환경을 생성해보자.
이번엔 Anaconda PowershellPrompt를 열자.
<img src="https://velog.velcdn.com/images/didi_delos/post/cac343f0-9543-4fd5-9636-8304430d4fdd/image.png" alt="">
위에서 아나콘다 설치 확인 시에 확인했던 폴더와 같은 폴더 아래에 있다. 실행하면 다음과 같은 창이 뜰 것이다.
<img src="https://velog.velcdn.com/images/didi_delos/post/1c256507-7d73-4fd5-bfd1-87be197d33dd/image.png" alt="">
<code>conda create -n VENV_NAME python=3.10</code>
VENV_NAME 부분에 자신이 만들고자 하는 가상환경의 이름을 쓰면 된다.
<code>python=3.10</code> 부분은 가상환경을 만들 때에 꼭 필요한 부분은 아니다. 만들 가상환경에서 쓰일 파이썬 버전을 지정하는 부분이다. 이 프로젝트에서는 추후 pyrebase library를 이용할 것이므로 python 버전을 3.10으로 지정해줬다. <del>(python3.11을 쓰면 라이브러리의 특정 부분에서 파이썬이 그 코드를 해석하지 못하는 오류가 발생했었다. 가상환경에서 이처럼 파이썬 버전을 지정하였음에도 다른 이유로 python3.11로 파일이 실행되는 문제가 있어 고생했다.)</del>
<code>conda create -n VENV_NAME python=3.10</code>을 입력하면 다음과 같은 화면이 뜬다.
<img src="https://velog.velcdn.com/images/didi_delos/post/3638923f-045e-4650-a96e-2728b8bc2090/image.png" alt="">
마지막에 다음과 같이 묻는다. <code>y</code>라고 입력하여 가상환경 설정을 마저 완료한다.
<img src="https://velog.velcdn.com/images/didi_delos/post/001675a2-e89f-4546-b527-7a61d726a834/image.png" alt="">
아래와 같이 뜨면 새로운 가상환경이 만들어진 것이다! 이제 이 가상환경에서 우리가 쓸 라이브러리를 설치할 것이다.
<img src="https://velog.velcdn.com/images/didi_delos/post/a1d42a06-1722-44fe-8c39-0d6e203984d6/image.png" alt="">
이제 <code>conda activate GUIDE(가상환경이름)</code>을 입력하여 새로 만든 가상환경을 실행해보자. 빨간색 밑줄이 현재 작업하고 있는 환경을 나타낸다. <code>base</code>, 기본 환경에서 <code>GUIDE</code>라는, 방금 새로 만든 가상환경으로 이동했다.
<img src="https://velog.velcdn.com/images/didi_delos/post/5d166f75-b938-45b6-bd71-37f966496a52/image.png" alt="">
여기에서 우리가 설치하려는 라이브러리들을 설치해주자.</p>
<h1 id="flask-설치">Flask 설치</h1>
<p>새로 만든 가상환경, GUIDE속에서 <code>conda install flask</code>를 입력한다.
<img src="https://velog.velcdn.com/images/didi_delos/post/e03d8e92-07ca-4015-8a3c-be8b99ff0cbb/image.png" alt="">
다시 <code>Proceed([y]/n)?</code>가 뜬다. 이번에도 <code>y</code>를 입력해주자.
<img src="https://velog.velcdn.com/images/didi_delos/post/08b3fa35-666d-4ebd-bc93-f6bebbd15040/image.png" alt="">
이것저것 열심히 설치하더니 마지막에는 <code>done</code> 하나만 뜬다.</p>
<h2 id="windows-powershell에서-실행-정책-변경하기">Windows Powershell에서 실행 정책 변경하기</h2>
<p>이제 Anaconda Powershell Prompt를 끄고 Windows Powershell을 <strong>관리자로 실행</strong>해준다.
<img src="https://velog.velcdn.com/images/didi_delos/post/45f3a8eb-74bd-4dcb-9092-d97acb80392d/image.png" alt="">
사용자의 어쩌고... 하는 경고창에서 &#39;예&#39;를 클릭하면 다음과 같은 파란 프롬프트 창이 뜬다.
<img src="https://velog.velcdn.com/images/didi_delos/post/7e052826-5d5c-4925-92ac-1c744914f67f/image.png" alt="">
<code>Set-ExecutionPolicy Unrestricted</code>를 실행한다. 띄어쓰기와 철자에 유의하라.
<img src="https://velog.velcdn.com/images/didi_delos/post/054ea82f-9566-4bb5-8ab7-08fd95ce01bb/image.png" alt="">
실행 정책을 변경하겠냐고 물어본다. Y라고 해준다.
그럼 이제 거의 다 끝났다!
Windows Powershell도 이만 닫아준다.</p>
<h2 id="vscode에서-python-interpreter-변경하기">vscode에서 python interpreter 변경하기</h2>
<p>다시 vscode로 이동하여 아래 캡쳐의 빨간 박스 부분에서 <code>&gt;</code>을 누르고 <code>Python: Select Interpreter</code>를 선택한다.
<img src="https://velog.velcdn.com/images/didi_delos/post/100e6f0c-ed71-488d-a346-b348d801cd25/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/didi_delos/post/d7fff43d-dbfc-4770-84f5-1d2925d3b384/image.png" alt="">
그러면 여러 가지 interpreter가 뜬다. 개중 우리가 방금 만든 가상환경을 선택해주자. 나는 <code>GUIDE</code>라는 이름의 가상환경을 만들었으므로 <code>GUIDE</code>를 선택한다.
<img src="https://velog.velcdn.com/images/didi_delos/post/7d8e521a-e042-4d09-a128-91d8e78410de/image.png" alt="">
찾아보니 이 과정은 코드에서 빨간줄이 안 생기게 하는 역할을 했다. 실제 코드 수행과는 상관 없을 수 있지만, 이왕이면 내가 실행할 가상환경과 python interpreter가 같으면 코드를 이해하기가 편할 것이다.</p>
<h2 id="vscode-인코딩-확인">vscode 인코딩 확인</h2>
<p><img src="https://velog.velcdn.com/images/didi_delos/post/edc2fd78-2c9f-4ba8-a1ab-3bca0a098375/image.png" alt="">
vscode의 왼쪽 바 하단에 있는 톱니바퀴를 클릭하여 설정(Settings)에 들어간다. 단축키는 <code>Ctrl+,</code>이다.
<img src="https://velog.velcdn.com/images/didi_delos/post/f427b046-5396-4bd5-857d-65f059d9b040/image.png" alt="">
<code>encoding</code>을 검색하여 인코딩이 UTF-8로 되어 있는지 확인한다. 다르다면 UTF-8로 변경해준다.
<img src="https://velog.velcdn.com/images/didi_delos/post/256e21ca-61d7-485d-b91a-517a7a9c9668/image.png" alt=""></p>
<h2 id="flask-app-실행">Flask app 실행</h2>
<p>아래는 학교 수업 실습 중에 만든 폴더이다. 다음과 같이 flask의 기본 구조인 static폴더, templates폴더, app.py파일을 만들고 작업한다.
<img src="https://velog.velcdn.com/images/didi_delos/post/64ac9a3b-a534-4431-9533-63e5cb42cc02/image.png" alt="">
그럼 플라스크 앱을 실행해보자. 플라스크 앱은 터미널 창에서 <code>flask run</code>을 입력하여 실행할 수 있다.
새 터미널(terminal)을 여는 단축키는 <code>Ctrl+Shift+`</code>이다.</p>
<p>다음은 여러 시행착오를 거쳐 얻은, 플라스크 앱이 정상적으로 실행되지 않았을 경우 체크해야 할 몇 가지 사항이다.</p>
<ol>
<li>터미널이 Command Prompt인가? Command Prompt라면 터미널 상단 바의 오른쪽에 &#39;cmd&#39;라고 쓰여 있을 것이다. 아니라면 bash, powershell 등등 다른 이름이 쓰여 있을 것이다.
<img src="https://velog.velcdn.com/images/didi_delos/post/59f9389a-e43e-46fe-b4a9-a08beacd48ad/image.png" alt="">
아래와 같이 여러 가지 터미널이 떠 있을 수도 있다. cmd 터미널로 옮겨서 실행하자.
<img src="https://velog.velcdn.com/images/didi_delos/post/610b1cac-ff3e-4a12-a7a7-00d4d48ef041/image.png" alt=""></li>
<li><code>app.py</code>가 위치한 폴더에서 실행하는가? app.py가 위치한 그 폴더에서 실행하지 않으면 flask가 app.py를 찾지 못한다. <code>cd</code>명령어를 이용하여 app.py가 있는 폴더로 이동하여 실행하자.
<img src="https://velog.velcdn.com/images/didi_delos/post/64c4f6c3-a217-45bb-94bc-4eb17585a772/image.png" alt=""></li>
</ol>
<p><img src="https://velog.velcdn.com/images/didi_delos/post/fbd7819a-a61c-4ed7-8a8a-f0ea3293acdc/image.png" alt="">
3. python 파일의 이름이 app.py인가? app.py여야만 실행된다!</p>
]]></description>
        </item>
    </channel>
</rss>