<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>byebye</title>
        <link>https://velog.io/</link>
        <description>폐쇄맨</description>
        <lastBuildDate>Tue, 22 Dec 2020 12:28:19 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. byebye. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/choonghee-lee" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Django - 가상 환경 설정]]></title>
            <link>https://velog.io/@choonghee-lee/Django-1.-%EA%B0%80%EC%83%81-%ED%99%98%EA%B2%BD-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@choonghee-lee/Django-1.-%EA%B0%80%EC%83%81-%ED%99%98%EA%B2%BD-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Tue, 22 Dec 2020 12:28:19 GMT</pubDate>
            <description><![CDATA[<h2 id="준비">준비</h2>
<p>파이썬 패키지 매니저 <code>pip</code>가 필요하다</p>
<h2 id="사용법">사용법</h2>
<h3 id="가상환경-생성">가상환경 생성</h3>
<pre><code class="language-bash">&gt; mkdir myproject
&gt; cd myproject
&gt; python -m venv env</code></pre>
<h3 id="가상환경-활성화">가상환경 활성화</h3>
<pre><code class="language-bash">$ source env/bin/activate

# shell의 환경에 따라 위의 커맨드가 동작하지 않을 때 아래의 명령어를 사용
$ . env/bin/activate</code></pre>
<h3 id="활성화-확인">활성화 확인</h3>
<pre><code class="language-bash">(env) $</code></pre>
<h3 id="가상환경-비활성화">가상환경 비활성화</h3>
<pre><code class="language-bash">(env) $ deactivate</code></pre>
<h3 id="설치">설치</h3>
<p>가상환경이 활성화된 상태에서 설치한다.</p>
<pre><code class="language-bash">(env) $ pip install &quot;Django~=3.0.0&quot;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바 코드 - 파일이나 디렉토리 삭제하기]]></title>
            <link>https://velog.io/@choonghee-lee/%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C-%ED%8C%8C%EC%9D%BC%EC%9D%B4%EB%82%98-%EB%94%94%EB%A0%89%ED%86%A0%EB%A6%AC-%EC%82%AD%EC%A0%9C%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@choonghee-lee/%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C-%ED%8C%8C%EC%9D%BC%EC%9D%B4%EB%82%98-%EB%94%94%EB%A0%89%ED%86%A0%EB%A6%AC-%EC%82%AD%EC%A0%9C%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 17 Dec 2020 12:00:15 GMT</pubDate>
            <description><![CDATA[<h3 id="단일-파일-삭제">단일 파일 삭제</h3>
<p><code>File#delete()</code> 메서드로 파일 또는 디렉토리를 삭제할 수 있다. 삭제에 성공한 경우 <code>true</code>, 실패하면 <code>false</code>를 반환한다.</p>
<pre><code class="language-java">File file = ...

if(!file.delete()) {
    ... 파일 삭제에 실패한 경우
}</code></pre>
<h3 id="디렉토리의-재귀적-삭제">디렉토리의 재귀적 삭제</h3>
<p>디렉토리를 삭제하는 경우 해당 디렉토리가 비어 있어야 한다. 디렉터리 내에 파일이 존재하는 경우는 다음과 같이 재귀적으로 삭제한다.</p>
<pre><code class="language-java">private void deleteDirectory(File dir){
    // 디렉토리 내부의 파일 삭제
    for(File file : dir.listFiles()) {
        if(file.isDirectory()) {
            // 디렉토리의 경우 재귀적으로 삭제
            deleteDirectory(file);
        } else {
            // 파일의 경우 삭제
            file.delete();
        }
    }
    // 디렉터리 삭제
    dir.delete();
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바 코드 - 파일이나 디렉토리의 존재 여부]]></title>
            <link>https://velog.io/@choonghee-lee/%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C-%ED%8C%8C%EC%9D%BC%EC%9D%B4%EB%82%98-%EB%94%94%EB%A0%89%ED%86%A0%EB%A6%AC%EC%9D%98-%EC%A1%B4%EC%9E%AC-%EC%97%AC%EB%B6%80</link>
            <guid>https://velog.io/@choonghee-lee/%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C-%ED%8C%8C%EC%9D%BC%EC%9D%B4%EB%82%98-%EB%94%94%EB%A0%89%ED%86%A0%EB%A6%AC%EC%9D%98-%EC%A1%B4%EC%9E%AC-%EC%97%AC%EB%B6%80</guid>
            <pubDate>Thu, 17 Dec 2020 11:54:50 GMT</pubDate>
            <description><![CDATA[<h3 id="파일의-존재-여부-조사">파일의 존재 여부 조사</h3>
<p><code>File#exists()</code> 메서드를 사용한다. 파일 또는 디렉터리가 존재하면 <code>true</code>, 존재하지 않으면 <code>false</code>를 반환한다.</p>
<p>```java
File file = new File(&quot;test.txt&quot;);</p>
<p>if(file.exists()) {
    ... 파일이 존재하는 경우
} else {
    ... 파일이 존재하지 않는 경우
}</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바 코드 - 현재 시간 구하기]]></title>
            <link>https://velog.io/@choonghee-lee/%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C-%ED%98%84%EC%9E%AC-%EC%8B%9C%EA%B0%84-%EA%B5%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@choonghee-lee/%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C-%ED%98%84%EC%9E%AC-%EC%8B%9C%EA%B0%84-%EA%B5%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 17 Dec 2020 11:51:59 GMT</pubDate>
            <description><![CDATA[<h3 id="date와-calendar로-현재-시간-구하기">Date와 Calendar로 현재 시간 구하기</h3>
<p><code>Date</code> 클래스의 생성자를 인수 없이 호출하면 현재 일시를 가진 <code>Date</code> 인스턴스가 생성된다. 또한, <code>Calendar#getInstance()</code> 메서드로 현재 일시의 달력을 생성하고 캘린더에서 <code>Date</code> 인스턴스를 구할 수 있다.</p>
<pre><code class="language-java">// 실행한 순간의 일시가 생성된다.
Date date1 = new Date();

// 현재 일시를 나타낸는 캘린더에서 Date 인스턴스를 생성
Calendar calendar = Calendar.getInstance();
Date date2 = calendar.getTime();</code></pre>
<p><code>Calendar#getInstance()</code> 메서드의 인수에는 <code>TimzeZone</code>을 지정할 수 있다. <code>TimeZone</code>은 세계의 시차를 나타내는 것으로 한국의 경우 <code>Asia/Seoul</code>이다. 기본으로 운영체제의 타임존이 설정된다.</p>
<pre><code class="language-java">// 디폴트 타임존, 로케일 정보를 가진 캘린더 클래스의 생성
Calendar calendar1 = Calendar.getInstance();

// 로케일이 US인 캘린더 생성
Calendar calendar2 = Calendar.getInstance(Locale.US);

// 타임존이 미국 로스엔젤레스인 캘린더 생성
TimeZone timezone = TimeZone.getTimeZone(&quot;America/Los_Angeles&quot;);
Calendar calendar3 = Calendar.getInstance(timezone);</code></pre>
<blockquote>
<p><code>TimeZone</code> 클래스의 <code>getAvailableDs()</code> 라는 static 메서드를 사용하면 이용 간으한 타임존의 목록을 String 배열로 받을 수 있다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바 코드 - 두 개의 값 비교]]></title>
            <link>https://velog.io/@choonghee-lee/%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C-%EB%91%90-%EA%B0%9C%EC%9D%98-%EA%B0%92-%EB%B9%84%EA%B5%90</link>
            <guid>https://velog.io/@choonghee-lee/%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C-%EB%91%90-%EA%B0%9C%EC%9D%98-%EA%B0%92-%EB%B9%84%EA%B5%90</guid>
            <pubDate>Thu, 17 Dec 2020 11:45:03 GMT</pubDate>
            <description><![CDATA[<h3 id="비교하기">비교하기</h3>
<p>기본형의 경우는 비교 연산자에서 값의 비교가 가능하지만 <strong>참조형인 경우 참조가 동일한지 비교한다</strong>. 값을 비교하려면 <code>equals()</code>와 <code>compareTo()</code> 등의 방법을 사용한다.</p>
<h4 id="equals">equals()</h4>
<p>값이 같은 경우 <code>true</code> 아니면 <code>false</code> 를 반환한다.</p>
<h4 id="compareto">compareTo()</h4>
<p>인수 쪽이 큰 경우 음수, 인수와 동일한 경우 0, 인수 쪽이 작은 경우 양수를 리턴한다.</p>
<h3 id="문자열의-비교">문자열의 비교</h3>
<pre><code class="language-java">String s1 = &quot;123&quot;;
String s2 = new String(&quot;123&quot;);

// 참조 비교
if(s1 == s2){
    System.out.println(&quot;s1과 s2는 참조가 같다&quot;);
}


// 값이 같은지 비교
if(s1.equals(s2)) {
    System.out.println(&quot;s1과 s2는 값이 같다&quot;);
}

// 값의 대소를 비교
int result = s1.compareTo(s2);
if(result == 0) {
    System.out.println(&quot;값이 같다&quot;);
} else if (result &lt; 0) {
    System.out.println(&quot;s1은 s2보다 작다&quot;);
} else if (result &gt; 0) {
    System.out.println(&quot;s1은 s2보다 크다&quot;);
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바 코드 - 파일 조작 개념]]></title>
            <link>https://velog.io/@choonghee-lee/%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C-%ED%8C%8C%EC%9D%BC-%EC%A1%B0%EC%9E%91-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@choonghee-lee/%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C-%ED%8C%8C%EC%9D%BC-%EC%A1%B0%EC%9E%91-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Wed, 16 Dec 2020 12:39:23 GMT</pubDate>
            <description><![CDATA[<h3 id="개요">개요</h3>
<p>Java 1.0 부터 있던 <code>java.io.File</code>과 Java 7 부터 도입된 <code>NIO2</code> 두 개의 API가 존재한다.</p>
<h3 id="nio2이-file보다-좋은점">NIO2이 File보다 좋은점</h3>
<ul>
<li>심볼릭 링크나 퍼미션을 다룰 수 있다.</li>
<li>파일 덮어쓰기, 이동 및 복사를 간단하게 할 수 있다.</li>
<li>파일의 변경을 감시할 수 있다.</li>
</ul>
<h3 id="javaiofile">java.io.File</h3>
<p>파일 경로를 지정하여 인스턴스를 생성한다. 파일이나 디렉터리 조작을 위한 다양한 메서드가 있다.</p>
<pre><code class="language-java">// 절대 경로
File file1 = new File(&quot;C:\\Users\\minsoo\\workspace\\abc.txt&quot;);

// 현재 디렉토리 기준
File file2 = new File(&quot;abc.txt&quot;);
File file3 = new File(&quot;..\\workspace\\abc.txt&quot;);

// 부모 디렉터리와 짬뽕
File parent = new File(&quot;C:\\Users\\minsoo&quot;);
File file4 = new File(parent, &quot;workspace\\abc.txt&quot;);

// 디렉토리 생성
File dir = new File(&quot;dir&quot;);
dir.mkdir();

// 파일 생성
File file = new File(dir, &quot;test.txt&quot;);
file.createNewFile();</code></pre>
<blockquote>
<p>경로 구분 문자는 윈도우즈의 경우 &quot;&quot;, 유닉스 계열은 &quot;/&quot;을 사용한다. 따라서 경로를 하드 코딩하지 않기위해 File.separator 속성을 사용한다.</p>
</blockquote>
<pre><code class="language-java">String sep = File.separator;</code></pre>
<h3 id="javaniofilepath">java.nio.file.Path</h3>
<p>NIO2에서는 <code>java.nio.file.Path</code>로 경로를 나타낸다. Path 객체의 생성은 <code>java.nio.file.FileSystem</code>의 <code>getPath()</code> 메서드를 사용한다.</p>
<pre><code class="language-java">// 파일 시스템 구하기
FileSystem fs = FileSystems.getDefault();

// 절대 경로
Path path1 = fs.getPath(&quot;C:\\Users\\minsoo\\workspace\\abc.txt&quot;);

// 경로 구분 문자 없이
Path path2 = fs.getPath(&quot;C:&quot;, &quot;Users&quot;, &quot;minsoo&quot;, workspace&quot;, &quot;abc.txt&quot;);

// 현재 디렉토리 기준
Path path3 = fs.getPath(&quot;dir&quot;, &quot;test.txt&quot;);

// java.nio.file.Paths로 Path 객체 생성
Path path = Paths.get(&quot;dir&quot;, &quot;test.txt&quot;);</code></pre>
<p>NIO2는 경로 자체를 나타내는 Path 클래스와 그 경로에 있는 파일이나 디렉토리 조작은 분리되어 있다. 파일이나 디렉터리에 대한 조작을 하려면 <code>java.nio.file.Files</code>의 static 메서드를 이용한다.</p>
<pre><code class="language-java">// 디렉토리 생성
Path dir = Paths.get(&quot;dir&quot;);
Files.createDirectory(dir);

// 파일을 작성
Path file = dir.resolve(&quot;test.txt&quot;);
Files.createFile(file);</code></pre>
<h3 id="file과-path의-상호-변환">File과 Path의 상호 변환</h3>
<p><code>java.io.File</code>과 <code>java.nio.file.Path</code>는 서로 변환이 가능하다.</p>
<pre><code class="language-java">File file = ...
Path path = file.toPath();

Paht path = ...
File file = path.toFile();</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바 코드 - java.time 개념]]></title>
            <link>https://velog.io/@choonghee-lee/%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C-java.time-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@choonghee-lee/%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C-java.time-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Wed, 16 Dec 2020 12:12:30 GMT</pubDate>
            <description><![CDATA[<h3 id="개요">개요</h3>
<p>Java 8 부터 <code>java.time</code> 패키지에서 <code>Date and Time API</code>를 제공한다. 해당 API는 ISO 8601이라는 날짜와 시간의 국제 규격에 맞춰져있고, 스레드 세이프하다. </p>
<h3 id="date-and-time-api의-주요-클래스">Date and Time API의 주요 클래스</h3>
<table>
<thead>
<tr>
<th>클래스 이름</th>
<th>개요</th>
<th>예</th>
</tr>
</thead>
<tbody><tr>
<td>LocalDate</td>
<td>타임존을 갖지 않는 날짜를 표시</td>
<td>2020-12-16</td>
</tr>
<tr>
<td>LocalDateTime</td>
<td>타임존을 갖지 않는 일시를 표시</td>
<td>2020-12-16T21:04:00</td>
</tr>
<tr>
<td>LocalTime</td>
<td>타임존을 갖지 않는 시간을 표시</td>
<td>21:04:00</td>
</tr>
<tr>
<td>OffsetDateTime</td>
<td>UTC의 시차를 가진 일시를 표시</td>
<td>2020-12-16T21:04:00-09:00</td>
</tr>
<tr>
<td>OffsetTime</td>
<td>UTC의 시차를 가진 시간을 표시</td>
<td>21:04:00-09:00</td>
</tr>
<tr>
<td>ZonedDateTime</td>
<td>타임존을 가진 일시를 표시</td>
<td>2020-12-16T12:04:00+01:00Europe/Paris</td>
</tr>
<tr>
<td>Duration</td>
<td>기간을 시간으로 표시</td>
<td>PT3600S</td>
</tr>
<tr>
<td>Perioid</td>
<td>기간을 날짜로 표시</td>
<td>P1Y2M3D</td>
</tr>
</tbody></table>
<h3 id="joda-time">Joda Time</h3>
<p>자바 8 이전에는 <code>Joda Time</code> 이라는 라이브러리를 사용하였으나, 8 이상은 <code>java.time</code> 패키지를 활용하는 것이 좋다. 자바 8 이전에는 사실상 표준이었으나, Joda Time 공식 홈페이지에서도 자바 8 이상은 <code>java.time</code> 으로 개발하는 것을 권장한다.</p>
<h3 id="immutable-api">Immutable API</h3>
<p><code>Date and Time API</code>의 클래스는 모두 변하지 않는 클래스이다. <code>java.util.Calendar</code>는 <code>set()</code> 메서드나 <code>add()</code> 메서드 등을 사용하여 해당 인스턴스의 날짜를 변경할 수 있다. 하지만 <code>java.time.LocalDateTime</code>은 <code>plus()</code> 메서드나 <code>minus()</code> 메서드로 날짜를 더하거나 뺄 수 있지만, 자신의 날짜를 바꾸지 않고 새로운 <code>LocalDateTime</code> 인스턴스를 생성하고 반환한다.</p>
<pre><code class="language-java">Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, 1);

LocalDateTime dateTime = LocalDateTime.now();
LocalDateTime result = dateTime.plusDays(1);</code></pre>
<blockquote>
<p>Immutable 클래스는 인스턴스가 생성되면 값이 바뀌지 않기 때문에 의도치 않은 수정에 의한 오류를 방지하여 코드의 안정성을 높일 수 있다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바 코드 - import]]></title>
            <link>https://velog.io/@choonghee-lee/%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C-import</link>
            <guid>https://velog.io/@choonghee-lee/%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C-import</guid>
            <pubDate>Wed, 16 Dec 2020 11:52:14 GMT</pubDate>
            <description><![CDATA[<h3 id="패키지-선언">패키지 선언</h3>
<p>패키지를 사용하여 클래스를 분류한다.</p>
<pre><code class="language-java">package example.hellopackage

class HelloWorld {
    ...
}</code></pre>
<h3 id="패키지-클래스-import">패키지, 클래스 import</h3>
<pre><code class="language-java">// 임포트 하지 않는 경우
java.util.List&lt;String&gt; list = new java.util.ArrayList&lt;&gt;();

// 임포트 하는 경우
import java.util.List;
import java.util.ArrayList;

List&lt;String&gt; list = new ArrayList&lt;&gt;();

// 와일드 카드 사용
import java.util.*;</code></pre>
<blockquote>
<p>java.lang 은 항상 임포트 되어 있다. (java.lang.String, java.lang.Integer ...)</p>
</blockquote>
<blockquote>
<p>java.util.Date, java.sql.Date 처럼 이름이 중복되는 경우 하나만 임포트하고 다른 하나는 전체를 기술해야 컴파일 에러가 없다.</p>
</blockquote>
<h3 id="static-멤버-import">static 멤버 import</h3>
<p><code>import static</code> 키워드로 스태틱 메서드나 필드를 임포트한다.</p>
<pre><code class="language-java">// static 메서드 호출
long value = Math.round(d);

// static import
import static java.lang.Math.round;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바 코드 - 커맨드 라인에서 컴파일하기]]></title>
            <link>https://velog.io/@choonghee-lee/%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C-%EC%BB%A4%EB%A7%A8%EB%93%9C-%EB%9D%BC%EC%9D%B8%EC%97%90%EC%84%9C-%EC%BB%B4%ED%8C%8C%EC%9D%BC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@choonghee-lee/%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C-%EC%BB%A4%EB%A7%A8%EB%93%9C-%EB%9D%BC%EC%9D%B8%EC%97%90%EC%84%9C-%EC%BB%B4%ED%8C%8C%EC%9D%BC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 15 Dec 2020 11:51:48 GMT</pubDate>
            <description><![CDATA[<h3 id="단일-파일-컴파일">단일 파일 컴파일</h3>
<pre><code class="language-bash">&gt; javac HelloWorld.java</code></pre>
<h3 id="여러-파일-컴파일">여러 파일 컴파일</h3>
<pre><code class="language-bash">&gt; javac HelloWorld.java HelloWorld2.java</code></pre>
<h3 id="와일드-카드-컴파일">와일드 카드 컴파일</h3>
<pre><code class="language-bash">&gt; javac *.java</code></pre>
<h3 id="class-파일-실행하기">.class 파일 실행하기</h3>
<pre><code class="language-bash">&gt; java HelloWorld</code></pre>
<h3 id="컴파일-또는-실행시-클래스-경로의-설정">컴파일 또는 실행시 클래스 경로의 설정</h3>
<p>JAR 파일이나 서드 파티 라이브러리를 사용하여 컴파일이나 실행하는 경우 <code>-cp</code> 옵션을 붙여 클래스 경로를 설정한다.</p>
<pre><code class="language-bash">&gt; javac -cp /home/lib/ojdbc7.jar:/home/workspace/* HelloWorld.java
&gt; java -cp /home/lib/ojdbc7.jar:/home/workspace/* HelloWorld</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flask - 2. Flask-RESTful]]></title>
            <link>https://velog.io/@choonghee-lee/Flask-2.-Flask-RESTful</link>
            <guid>https://velog.io/@choonghee-lee/Flask-2.-Flask-RESTful</guid>
            <pubDate>Wed, 09 Dec 2020 13:06:21 GMT</pubDate>
            <description><![CDATA[<h1 id="가상환경-설정">가상환경 설정</h1>
<pre><code class="language-bash">pip install vritualenv

# 활성화
source venv/bin/activate     # 맥, 리눅스
./venv/Scripts/activate.bat    # 윈도우즈

# 비활성화
deactivate</code></pre>
<h1 id="flask-restful">Flask-RESTful</h1>
<h2 id="설치">설치</h2>
<pre><code class="language-bash"># flask도 같이 설치된다.
pip install Flask-RESTful</code></pre>
<h2 id="사용">사용</h2>
<pre><code class="language-python">from flask import Flask, request
from flask_restful import Resource, Api

app = Flask(__name__)
api = Api(app)

class Item(Resource):
    def get(self, name): # Path Parameter
        ...
        return {&#39;result&#39;: name}, 200 # JSON return, Status Code

    def post(self, name):
        data = request.get_json() 
        pass

api.add_resource(Item, &#39;/item/&lt;string:name&gt;&#39;)

app.run(port=5000, debug=True)</code></pre>
<p><code>Resource</code>를 상속받은 클래스의 함수명은 HTTP Method의 역할을 한다.
<code>debug=True</code>를 설정하면 코드 수정 후 저장하면 알아서 재시작한다.
<code>request.get_json(silent=True)</code>는 JSON 파싱 에러가 나면 None을 리턴한다.</p>
<h1 id="잠깐-filter">잠깐 filter()</h1>
<pre><code class="language-python">filter(조건 함수, 순회 가능한 데이터)
filter(lambda x: x[&#39;name&#39;] == name, items)
next(filter(lambda x: x[&#39;name&#39;] == name, items)) </code></pre>
<p>이터레이터를 리턴하므로 <code>next()</code> 사용 가능</p>
<h1 id="flask-jwt">Flask-JWT</h1>
<h2 id="설치-1">설치</h2>
<pre><code class="language-bash">pip install Flask-JWT</code></pre>
<h2 id="사용-1">사용</h2>
<pre><code class="language-python"># security.py
from werkzeug.security import safe_str_cmp

def authenticate(username, password):
    user = DB에서 유저 가져오기
    if user and safe_str_cmp(user.password, password):
        return user

def identity(payload):
    user_id = payload[&#39;identity&#39;]
    return DB에서 유저 가져오기 by user_id

# app.py
from flask_jwt import JWT
from security import authenticate, identity

app.secret_key = 시크릿키설정

jwt = JWT(app, authenticate, identity)</code></pre>
<p><code>app.py</code>의 맨 마지막 코드는 자동으로 <code>/auth</code> 엔드포인트를 만들어준다. 사용자가 유저 이름과 패스워드를 보내면 <code>authenticate</code> 함수로 가서 인증을 하고 JWT 토큰을 리턴해준다. 사용자가 발급받은 토큰을 보내면 <code>identity</code> 함수로 가서 토큰에 담긴 정보 (여기서는 <code>user_id</code>)를 찾아 사용하면 된다.</p>
<blockquote>
<p>HTTP 헤더 Authorization에 <strong>JWT [토큰]</strong> 이라고 써서 보내주어야 한다!</p>
</blockquote>
<h2 id="로그인이-필요한-기능-설정">로그인이 필요한 기능 설정</h2>
<pre><code class="language-python">from flask_jwt import jwt_required

@jwt_required()
def get(self, name):
    pass</code></pre>
<h1 id="flask-restful-json-parser">Flask-RESTful JSON Parser</h1>
<pre><code class="language-python">from flask_restful import reqparse

class Item(Resource):
    parser = reqparse.RequestParser()
    parser.add_argument(&#39;price&#39;, type=float, 
        required=True, help=&#39;This field cannot be blank!&#39;)

    def put(self, name):
        data = Item.parser.parse_args()
        ...</code></pre>
<p><code>parser.add_argument()</code>에 등록된 키만 사용할 수 있다.</p>
<blockquote>
<p><a href="https://flask-restful.readthedocs.io/en/latest/reqparse.html">Flask-RESTful 공식 문서</a>에 버전 2.0 부터 모든 파서 기능이 사라질 수 있다고 경고되어 있다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flask - 1. REST API]]></title>
            <link>https://velog.io/@choonghee-lee/Flask-1.-REST-API</link>
            <guid>https://velog.io/@choonghee-lee/Flask-1.-REST-API</guid>
            <pubDate>Tue, 08 Dec 2020 13:34:27 GMT</pubDate>
            <description><![CDATA[<h1 id="flask-설치">Flask 설치</h1>
<pre><code class="language-bash">pip install flask</code></pre>
<h1 id="rest-api">REST API</h1>
<pre><code class="language-python">from flask import Flask

app = Flask(__name__)

@app.route(&#39;/&#39;)
def home():
    return &#39;Hello, World!&#39;

app.run(port=5000)</code></pre>
<h1 id="실행">실행</h1>
<pre><code class="language-bash">python app.py</code></pre>
<h1 id="엔드포인트-설정">엔드포인트 설정</h1>
<pre><code class="language-python">@app.route(&#39;/store&#39;, methods=[&#39;POST&#39;])
def create_store():
    pass

@app.route(&#39;/store/&lt;string:name&gt;&#39;) 
def get_store(name):
    pass</code></pre>
<h1 id="엔드포인트-리턴">엔드포인트 리턴</h1>
<pre><code class="language-python">from flask import jsonify

stores = [
    ....
]

@app.route(&#39;/store&#39;)
def get_stores():
    return jsonify({&#39;stores&#39;: stores})</code></pre>
<h1 id="json-처리">JSON 처리</h1>
<pre><code class="language-python">from flask import request

@app.route(&#39;/store&#39;, methods=[&#39;POST&#39;])
def create_store():
    request_data = request.get_json()
    name = request_data[&#39;name&#39;]
    ...</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django - select_related() & prefetch_related()]]></title>
            <link>https://velog.io/@choonghee-lee/Django-selectrelated-prefetchrelated</link>
            <guid>https://velog.io/@choonghee-lee/Django-selectrelated-prefetchrelated</guid>
            <pubDate>Tue, 17 Nov 2020 13:41:08 GMT</pubDate>
            <description><![CDATA[<h1 id="select_related">select_related()</h1>
<p>장고는 <code>select_related()</code>라는 QuerySet 메서드를 제공한다. 이는 one-to-many 관계의 객체를 검색할 때 사용한다. 이는 복잡한 QuerySet이 될 수 있지만, 연관 관계에 있는 필드에 접근하기 위해 추가적인 쿼리를 날릴 필요가 없게된다. <code>select_related()</code> 메서드는 <code>ForeignKey</code>와 <code>OneToOne</code> 필드에 사용한다. SQL의 <code>JOIN</code> 쿼리를 통해 연관 객체를 가져오게 된다.</p>
<pre><code class="language-python">class City(models.Model):
    # ...
    pass

class Person(models.Model):
    # ...
    hometown = models.ForeignKey(
        City,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )

class Book(models.Model):
    # ...
    author = models.ForeignKey(Person, on_delete=models.CASCADE)

# Hits the database with joins to the author and hometown tables.
b = Book.objects.select_related(&#39;author__hometown&#39;).get(id=4)
p = b.author         # Doesn&#39;t hit the database.
c = p.hometown       # Doesn&#39;t hit the database.

# Without select_related()...
b = Book.objects.get(id=4)  # Hits the database.
p = b.author         # Hits the database.
c = p.hometown       # Hits the database.</code></pre>
<p>만약 argument 없이 <code>select_related()</code>를 호출하면, 모든 <code>ForeignKey</code> 관계의 객체를 검색할 것이다. 그렇기 때문에, 항상 호출 이후에 접근할 관계에 대해서만 <code>select_related()</code>를 사용해야한다.</p>
<blockquote>
<p><code>select_related()</code>를 사용하는 것은 실행 시간을 굉장히 증가시킬 수 있기 때문에 조심해서 사용해야한다.</p>
</blockquote>
<h1 id="prefetch_related">prefetch_related()</h1>
<p><code>select_related()</code>가 one-to-many 관계의 연관 객체들을 검색하는데 도움을 준다. 하지만 <code>select_related()</code>는 many-to-many 또는 many-to-one 관계 (<code>ManyToMany</code> 또는 역(逆)<code>ForeignKey</code> 필드)에 대해서는 소용이 없다. 장고는 <code>prefetch_related()</code> 메서드를 제공한다. <code>prefetch_related()</code>는 각 관계에 대해서 lookup을 나눠서하고, 파이썬을 이용해서 결과를 합친다. 이 메소드는 <code>GenericRelation</code>과 <code>GenericForeignKey</code>에 대한 prefetching도 지원한다.</p>
<pre><code class="language-python">class Topping(models.Model):
    name = models.CharField(max_length=30)

class Pizza(models.Model):
    name = models.CharField(max_length=50)
    toppings = models.ManyToManyField(Topping)

class Restaurant(models.Model):
    pizzas = models.ManyToManyField(Pizza, related_name=&#39;restaurants&#39;)
    best_pizza = models.ForeignKey(Pizza, related_name=&#39;championed_by&#39;, on_delete=models.CASCADE)

&gt;&gt;&gt; Restaurant.objects.prefetch_related(&#39;pizzas__toppings&#39;)
# This will result in a total of 3 database queries - one for the restaurants, one for the pizzas, and one for the toppings.
&gt;&gt;&gt; Restaurant.objects.prefetch_related(&#39;best_pizza__toppings&#39;)
# 2 queries using select_related()
&gt;&gt;&gt; Restaurant.objects.select_related(&#39;best_pizza&#39;).prefetch_related(&#39;best_pizza__toppings&#39;)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[장고 Autocommit]]></title>
            <link>https://velog.io/@choonghee-lee/%EC%9E%A5%EA%B3%A0-Autocommit</link>
            <guid>https://velog.io/@choonghee-lee/%EC%9E%A5%EA%B3%A0-Autocommit</guid>
            <pubDate>Fri, 13 Nov 2020 10:17:53 GMT</pubDate>
            <description><![CDATA[<h1 id="장고는-왜-autocommit을-사용할까">장고는 왜 autocommit을 사용할까?</h1>
<p>SQL 표준에서, 각 SQL 쿼리는 하나의 트랜잭션을 시작하게 되어있다. 이런 표준 트랜잭션은 반드시 명시적으로 커밋되거나 롤백 되어야 한다.</p>
<p>이는 애플리케이션 개발자들에게 항상 편리한 기능이 아니다. 이런 불편한 것을 줄이기 위해, 대부분의 데이터베이스는 autocommit 모드를 지원한다. <strong>autocommit 모드에서, 어떤 트랜잭션도 active 아닐 때, 각 쿼리는 트랜잭션을 시작하고, 쿼리의 성공 여부에 따라서 자동으로 커밋되거나 롤백된다.</strong></p>
<p>PEP 249, 파이썬 데이터베이스 API 명세서 v2.0은 autocommit을 꺼둔 상태를 요구하지만, <strong>장고는 이를 오버라이드하여 디폴트로 autocommit 모드를 켜둔다.</strong></p>
<h1 id="트랜잭션-관리를-비활성화하기">트랜잭션 관리를 비활성화하기</h1>
<p>데이터베이스 옵션에서 AUTOCOMMIT 세팅을 False로 지정하면 장고의 트랜잭션 관리를 비활성화한다. 이렇게 하면, 장고는 어떠한 커밋도 실행하지 않는다.</p>
<pre><code class="language-python"># settings.py
DATABASES = {
    &#39;default&#39;: {
        &#39;ENGINE&#39;: &#39;django.db.backends.postgresql&#39;,
        &#39;NAME&#39;: &#39;mydatabase&#39;,
        &#39;USER&#39;: &#39;mydatabaseuser&#39;,
        &#39;PASSWORD&#39;: &#39;mypassword&#39;,
        &#39;HOST&#39;: &#39;127.0.0.1&#39;,
        &#39;PORT&#39;: &#39;5432&#39;,
        &#39;AUTOCOMMIT&#39;: False
    }
}</code></pre>
<p>장고나, 서드파티 라이브러리와 상관없이, 개발자는 모든 트랜잭션에 커밋을 명시적으로 해주어한다. 그래서 보통 커스텀 트랜잭션-제어 미들웨어를 할 때나 정말 이상한 짓을 하고 싶을 때 비활성화 하면 된다.</p>
<p>장고에서 직접 트랜잭션을 제어하고 싶다면 <a href="https://blueshw.github.io/2016/01/16/django-migration/">다음 블로그 내용</a>을 참조하길 바란다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[QuerySet은 언제 Evaluate될까?]]></title>
            <link>https://velog.io/@choonghee-lee/QuerySet%EC%9D%80-%EC%96%B8%EC%A0%9C-Evaluate%EB%90%A0%EA%B9%8C</link>
            <guid>https://velog.io/@choonghee-lee/QuerySet%EC%9D%80-%EC%96%B8%EC%A0%9C-Evaluate%EB%90%A0%EA%B9%8C</guid>
            <pubDate>Tue, 10 Nov 2020 04:47:31 GMT</pubDate>
            <description><![CDATA[<p>QuerySet 객체는 평가되기(Evaluated) 전 까지 데이터베이스에 직접 영향을 주지 않는다. QuerySet에 필터링을 하여 또 다른 QuerySet을 얻을 수 있지만, QuerySet이 평가되기 전까지 절대 데이터베이스 Hit를 하는 일이 없다. QuerySet이 평가될 때, SQL 쿼리를 데이터베이스에서 실행하게 된다.</p>
<p>다음과 같은 경우에 QuerySet은 평가된다:</p>
<ul>
<li>쿼리셋을 처음 반복(iterate)할 때</li>
<li>슬라이싱 할 때, Pizza.objects.all()[:3]</li>
<li>pickle 하거나 cache 할 때</li>
<li>repr() 이나 len()을 호출 할 때</li>
<li>list()를 명시적으로 호출할 때</li>
<li><em>bool()</em>, <em>or</em>, <em>and</em>, <em>if</em> 와 같은 문장에서 사용될 때</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[StockX, 2차 프로젝트 후기]]></title>
            <link>https://velog.io/@choonghee-lee/StockX-2%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@choonghee-lee/StockX-2%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Fri, 23 Oct 2020 07:42:03 GMT</pubDate>
            <description><![CDATA[<h1 id="👟-span-stylebackground-color0aa05dcolorfff프로젝트-소개span">👟 <span style="background-color:#0AA05D;color:#fff">&nbsp;&nbsp;프로젝트 소개&nbsp;&nbsp;</span></h1>
<p>판매자와 구매자를 연결해주는 <strong>신발, 패션 리셀링 경매 중개 사이트</strong>에요. 판매 상품이 정품인지 인증해주고, 구매자의 결제를 보증해줘요. 수많은 브랜드의 신발과 패션 상품이 등록되어 있어요. 사이즈별로 실시간 경매액을 알 수 있고, 판매자가 원하는 가격에 바로 구매할 수도 있어요! 적당한 가격에 최애 신발을 갖고 싶다구요? 지금 바로 <a href="https://stockx.com/">StockX</a>로 고고~ 👍</p>
<h2 id="팀원">팀원</h2>
<h3 id="frontend-4명">FrontEnd (4명)</h3>
<ul>
<li>이영섭</li>
<li>김동호</li>
<li>송다슬</li>
<li>류상욱</li>
</ul>
<h3 id="backend-3명">BackEnd (3명)</h3>
<ul>
<li><span style="color: #0D9F5D;">이충희 (바로 저에요!) </span></li>
<li>이태현</li>
<li>왕민욱</li>
</ul>
<h2 id="프로젝트-기간">프로젝트 기간</h2>
<p>2020.08.31 - 2020.09.11</p>
<h2 id="깃헙">깃헙</h2>
<ul>
<li>프론트: </li>
<li>백엔드: <a href="https://github.com/choonghee-lee/westock-backend">https://github.com/choonghee-lee/westock-backend</a></li>
</ul>
<h1 id="🎬-span-stylebackground-color0aa05dcolorfff데모-영상span">🎬 <span style="background-color:#0AA05D;color:#fff">&nbsp;&nbsp;데모 영상&nbsp;&nbsp;</span></h1>
<p>!youtube[w1PJ6CLfxf8]</p>
<h1 id="🖥-span-stylebackground-color0aa05dcolorfff기술-스택span">🖥 <span style="background-color:#0AA05D;color:#fff">&nbsp;&nbsp;기술 스택&nbsp;&nbsp;</span></h1>
<h2 id="개발-도구">개발 도구</h2>
<ul>
<li><strong>Django</strong> 웹 프레임워크로 API를 구현했어요.</li>
<li><strong>AQueryTool</strong> 을 이용하여 데이터베이스 모델링을 했어요.</li>
<li><strong>Selenium</strong> 을 통해 조던 신발 데이터를 크롤링했어요.</li>
<li>크롤링한 csv 파일 데이터를 <strong>MySQL</strong>에 저장해 두었어요.</li>
<li>상품이 너무 많아 일부를 캐쉬하는 <strong>Redis</strong>를 사용했어요.</li>
<li><strong>Docker</strong> 이미지 두 개를 사용하여 <strong>AWS EC2</strong> 서버에 가동했어요.</li>
</ul>
<h2 id="협업-도구">협업 도구</h2>
<ul>
<li><strong>Zoom</strong>, <strong>Google Meet</strong>을 통해 화상 회의를 수시로 하였어요.</li>
<li><strong>VSCode LiveShare</strong>로 서로의 오류를 체크!</li>
<li><strong>Git &amp; Github</strong> 는 개발자의 필수 도구! 💪</li>
<li><strong>Trello</strong> 로 전체적인 일정 관리와 팀원들의 작업 현황을 파악했어요.</li>
</ul>
<h1 id="🤔-span-stylebackground-color0aa05dcolorfff어떤-기능을-클로닝-했나요span">🤔 <span style="background-color:#0AA05D;color:#fff">&nbsp;&nbsp;어떤 기능을 클로닝 했나요?&nbsp;&nbsp;</span></h1>
<blockquote>
<p><span style="color: #0AA05D;"><strong>색깔이 들어간 내용</strong></span>은 직접 구현한 부분입니다!!</p>
</blockquote>
<p>백엔드 구현 사항을 말씀 드릴게요!</p>
<h2 id="모델링">모델링</h2>
<ul>
<li>모델링 URL : <a href="https://aquerytool.com:443/aquerymain/index/?rurl=ee256e1a-149a-4b2e-850b-0b961e7ad838&amp;">https://aquerytool.com:443/aquerymain/index/?rurl=ee256e1a-149a-4b2e-850b-0b961e7ad838&amp;</a>
Password : 786v0a</li>
</ul>
<h2 id="메인-페이지">메인 페이지</h2>
<ul>
<li>카카오 소셜 로그인</li>
<li><span style="color: #0AA05D;">구글 소셜 로그인</span></li>
<li><span style="color: #0AA05D;">신발 정보를 20개씩 검색해요</span>
<img src="https://images.velog.io/images/choonghee-lee/post/ba6a77b7-a643-497c-bb45-7e01713ebb41/search.png" alt="search"></li>
</ul>
<h2 id="상품-페이지">상품 페이지</h2>
<ul>
<li>쿼리 스트링을 이용한 상품 리스트 필터링!</li>
<li><span style="color: #0AA05D;">상품 상세 정보 조회 (아래는 사이즈 별로 판매자가 올린 가격의 변동 내역 입니다.)</span>
<img src="https://images.velog.io/images/choonghee-lee/post/54e4881b-a416-42d4-b2df-88091d274753/bid.png" alt=""></li>
<li><span style="color: #0AA05D;">상품 데이터 크롤링 + Mock 데이터 생성</span></li>
</ul>
<h2 id="마이-페이지">마이 페이지</h2>
<ul>
<li>사용자가 구매한 내역 조회</li>
</ul>
<h1 id="😎-span-stylebackground-color0aa05dcolorfff느낀-점span">😎 <span style="background-color:#0AA05D;color:#fff">&nbsp;&nbsp;느낀 점&nbsp;&nbsp;</span></h1>
<h2 id="1-크롤링-넘나-힘든-것">1. 크롤링 넘나 힘든 것...</h2>
<p><img src="https://images.velog.io/images/choonghee-lee/post/57835da8-033f-4f5b-9e62-73a215f7791c/prove-human.gif" alt=""></p>
<p>크롤링하고 싶은 사이트 띄워서 로그인하고 상품 정보, 사이즈, 이미지, 거래 내역, 경매 내역 등등... 하나의 상품에 딸린 정보가 많아서 시간이 오래걸렸어요. 아무튼, 동작이 잘하니 맥북을 켜두고 쿨하게 자고 일어났더니... 12개 밖에 된 것이 아니겠어요 🤨 ? 중간 중간에 휴먼임을 인증해달라는 페이지가 정말 랜덤하게 나타나서 고생을 좀 했네요. 그냥 크롤링 하지 말아달라고 사이트에서 경고하는 것 같아 가짜 데이터를 만들었어요. <strong>하지만 상품이 없으면 진도를 못나갈 팀원들을 생각하며 끝까지 해냈습니다!</strong></p>
<h2 id="2-레디스의-활용">2. 레디스의 활용</h2>
<p><img src="https://images.velog.io/images/choonghee-lee/post/6bbd61c3-038e-4054-921d-00723e4909e6/redis.png" alt="">
우리 팀의 민욱님이 쿼리스트링을 이용하여 상품 리스트 필터링을 만드셨어요. 그런데 상품의 수가 좀 많다보니, 조회하는데 시간이 4~5초 사이가 걸렸어요. 필터에 따라서 정렬을 달리해야하기 때문이지요. 발표가 이틀밖에 남지 않아 레디스를 사용하기로 결정했고, 민욱님께서 캐싱을 통해 0.02초로 만들어 와주셨어요. 캐싱의 힘을 직접 체감했고, <strong>저는 도커에 장고와 레디스 이미지를 AWS EC2 서버에 띄워서</strong> 무사히 발표를 잘 할 수 있었어요!</p>
<h1 id="🥳-span-stylebackground-color0aa05dcolorfff마무리span">🥳 <span style="background-color:#0AA05D;color:#fff">&nbsp;&nbsp;마무리&nbsp;&nbsp;</span></h1>
<p>2차 프로젝트 후기를 10월 말에 작성하고 있어서 기억이 많이 무뎌졌어요. 더 적으려면 더 적겠지만 기억을 미화시킬 것 같아서 그만하려고 해요. 두목이라고 장난스레 불렀지만 팀장으로 힘드셨을 <strong>영섭</strong>님, 묵묵히 할 일 해주신 <strong>다슬</strong>님, 프론트 엔드의 멘토 <strong>동호</strong>님, 구현 페이지를 자랑하시면서 재밌어하셨던 <strong>상욱</strong>님, 상품 필터링 40초 걸릴 때 마음고생 하셨을 <strong>민욱</strong>님, 회의 시간에 모든 걸 기록해 주셨던 <strong>태현</strong>님 모두 감사드려요 😊!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[브랜디 인턴십 후기]]></title>
            <link>https://velog.io/@choonghee-lee/%EB%B8%8C%EB%9E%9C%EB%94%94-%EC%9D%B8%ED%84%B4%EC%8B%AD-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@choonghee-lee/%EB%B8%8C%EB%9E%9C%EB%94%94-%EC%9D%B8%ED%84%B4%EC%8B%AD-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Wed, 21 Oct 2020 10:15:59 GMT</pubDate>
            <description><![CDATA[<h1 id="🏢-span-stylebackground-colorff385ecolorfff브랜디-소개span">🏢 <span style="background-color:#FF385E;color:#fff">&nbsp;&nbsp;브랜디 소개&nbsp;&nbsp;</span></h1>
<p><strong>브랜디</strong>는 20대 여성을 위한 패션/뷰티 앱으로 알려져 있어요. 하지만 남성 패션 앱 <strong>하이버</strong>, 쇼핑몰 무료 창업 서비스 <strong>헬피</strong>, 동대문 D2C(Direct to Consumer) 플랫폼 <strong>트랜디</strong>까지 동대문 패션 사업에 적극적으로 뛰어들고 있는 기업이랍니다!</p>
<p>주문한 옷이 반나절 만에 도착하는 &#39;하루 배송&#39; 서비스를 만들었고, 업계 최초로 100명 규모의 개발자 채용을 선언하는 등 빠른 성장을 위해 노력하고 있어요.</p>
<h1 id="🤔-span-stylebackground-colorff385ecolorfff무엇을-만들었나요span">🤔 <span style="background-color:#FF385E;color:#fff">&nbsp;&nbsp;무엇을 만들었나요?&nbsp;&nbsp;</span></h1>
<h2 id="프로젝트-정보">프로젝트 정보</h2>
<p>10명의 개발자가 한 팀이 되어, 브랜디의 스테이징 서비스 페이지와, 백오피스 페이지를 클로닝 하였어요. <strong>저는 백오피스의 백엔드 개발자로 참여했어요!</strong></p>
<h3 id="프로젝트-기간">프로젝트 기간</h3>
<p>2020-09-14 ~ 2020-10-15 (31일)</p>
<h3 id="깃허브">깃허브</h3>
<p><a href="https://github.com/choonghee-lee/Admin">https://github.com/choonghee-lee/Admin</a></p>
<h2 id="데모-영상">데모 영상</h2>
<p>!youtube[Ntau8sPeA-I]</p>
<h2 id="구현-사항">구현 사항</h2>
<blockquote>
<p><span style="color: #FF385E;"><strong>색깔이 들어간 내용</strong></span>은 직접 구현한 부분입니다!!</p>
</blockquote>
<h3 id="span-stylecolor-ff385e모델링-공통span"><span style="color: #FF385E;">모델링 (공통)</span></h3>
<p>AQueryTool을 이용해 모델링을 하였어요. 궁금하신 분들은 아래의 URL에 접속해보세요! </p>
<p>URL : <a href="https://aquerytool.com:443/aquerymain/index/?rurl=928d796a-adf1-4ced-b51a-7400d5e8aec3&amp;">https://aquerytool.com:443/aquerymain/index/?rurl=928d796a-adf1-4ced-b51a-7400d5e8aec3&amp;</a>
Password : vm0irt</p>
<h3 id="일반-회원--셀러-관리">일반 회원 / 셀러 관리</h3>
<ul>
<li>셀러 로그인 / 회원 가입</li>
<li>일반 회원 조회</li>
<li>셀러 회원 조회</li>
</ul>
<h3 id="span-stylecolor-ff385e상품-관리span"><span style="color: #FF385E;">상품 관리<span></h3>
<ul>
<li><span style="color: #FF385E;">상품 리스트 / 상세 조회<span></li>
<li><span style="color: #FF385E;">기간에 따른 전체 상품 / 선택 상품 엑셀 다운로드</span></li>
<li><span style="color: #FF385E;">상품 정보 등록 / 수정 (선분 이력)</span></li>
<li><span style="color: #FF385E;">상품 이미지 업로드 (Amazon S3)</span></li>
</ul>
<h3 id="주문-관리">주문 관리</h3>
<ul>
<li>주문의 상태(배송완료, 취소, 환불 등)에 따른 주문 조회</li>
<li>주문 상세 관리</li>
</ul>
<h3 id="span-stylecolor-ff385e쿠폰-관리span"><span style="color: #FF385E;">쿠폰 관리<span></h3>
<ul>
<li><span style="color: #FF385E;">쿠폰 리스트 / 상세 조회<span></li>
<li><span style="color: #FF385E;">쿠폰 시리얼 넘버 csv 다운로드<span></li>
<li><span style="color: #FF385E;">쿠폰 생성 / 수정<span></li>
<li><span style="color: #FF385E;">삭제 (Soft Delete)<span></li>
</ul>
<h3 id="기획전-관리">기획전 관리</h3>
<ul>
<li>기획전 리스트 / 상세 조회</li>
<li>기획전 생성 / 수정</li>
</ul>
<h2 id="기술-스택">기술 스택</h2>
<ul>
<li>요청과 응답을 위해 _<strong>Flask</strong>_를 사용했어요.</li>
<li><em><strong>MySQL</strong></em> 과 _<strong>RDS</strong>_를 사용하여 데이터베이스를 구축했어요.</li>
<li>_<strong>PyMySQL</strong>_을 이용하여 데이터베이스 커넥션 관리부터 SQL 사용까지 모두 직접했어요!</li>
<li><em><strong>AWS EC2</strong></em> 서버를 통해 배포하였어요.</li>
</ul>
<h1 id="span-stylebackground-colorff385ecolorfff1주차span-💪"><span style="background-color:#FF385E;color:#fff">&nbsp;&nbsp;1주차&nbsp;&nbsp;</span> 💪</h1>
<p><img src="https://images.velog.io/images/choonghee-lee/post/d177f92b-d0fa-494f-a71d-c1b5440e62c8/modeling-book.jpeg" alt=""></p>
<p>1주차는 모델링만 하면서 보냈어요. 백엔드 인턴 5명 중에 모델링에 대해 경험이 많고 지식이 풍부한 사람이 없어서, 클론할 백오피스 페이지만 보면서 모델링을 했어요. 그러다 보니 정보처리기사 자격증 취득할 때 달달 외웠던 &quot;도부이결다조&quot;의 필요성을 느끼게 되었고, 팀원들과 김기창님의 <a href="http://www.yes24.com/Product/Goods/91901049">&quot;관계형 데이터 모델링 프리미엄 가이드&quot;</a> 책을 구매하여 <strong>정규화</strong>, <strong>비정규화</strong>, <strong>이력 관리</strong>에 대해 읽어보며 우리의 모델링에 적용해보려고 했어요.  하지만 이론과 현실의 간극을 좁히지 못하고, &quot;어차피 개발하며 계속 바뀔 스키마, 일단 기존의 것과 비슷하게 가자&quot;라는 팀원의 의견을 받아들이기로 했어요. 모델링은 경험치가 중요한 것 같아요 😭.</p>
<h1 id="span-stylebackground-colorff385ecolorfff2주차span-📗"><span style="background-color:#FF385E;color:#fff">&nbsp;&nbsp;2주차&nbsp;&nbsp;</span> 📗</h1>
<p><img src="https://images.velog.io/images/choonghee-lee/post/3151240b-c095-4e79-b6fd-46b612231675/planner.JPG" alt="">
개인적으로 힘든 시기를 보내고 있어서 매일 아침 회의 시간에 저의 상태에 대해 이야기하며 하루를 시작했어요. 팀원들이 저에게 이야기 해주는 것에 진심이 느껴져서 항상 고마웠고 힘을 내지 않을 수가 없었어요 🤟! 팀원들을 위해 무엇을 할 수 있을까 생각해보니 <strong>&quot;내 할일 제 시간에 끝내서 다른 사람에게 부담주지 않는 것&quot;</strong> 이라 판단하고 시간 관리를 하기 위해 플래너를 작성하기 시작했어요. 그랬더니 <strong>앞으로 나아갈 수 있는 힘</strong>이 다시 생기고 플래너에 적힌 것을 완료할 때마다 보람도 느끼고, <strong>팀에 도움이 되고 있다는 긍정적인 생각</strong>도 다시하게 되었어요 😎!</p>
<h1 id="span-stylebackground-colorff385ecolorfff3주차span-🏙"><span style="background-color:#FF385E;color:#fff">&nbsp;&nbsp;3주차&nbsp;&nbsp;</span> 🏙</h1>
<p><img src="https://images.velog.io/images/choonghee-lee/post/431738eb-8131-445b-86fd-3cbf8020f391/s3.png" alt=""></p>
<p>4주차에 서버를 띄우고 발표 준비도 해야해서 상품 관련 엔드포인트를 모두 만들어야 했어요. 추석에 할머니댁에 가지 못하고 하루에 2~3개 정도의 API를 작성했어요 😅.</p>
<p>상품에서 가장 어려웠던 것은 이미지 수정이었어요. 이미지가 전부 5개라 그냥 다 지워버리고 다시 만들까 생각도 했지만... 셀러들이 <strong>이미지 등록을 더 늘려달라</strong>고 할 수도 있을 것 같아서, <strong>4가지 이미지 상태를 두어 처리</strong>하는 로직을 작성했어요.</p>
<ul>
<li>이미지가 없는 경우</li>
<li>기존의 이미지를 계속 사용하는 경우</li>
<li>기존의 이미지를 제거하는 경우</li>
<li><strong>새로운 이미지를 업로드하는 경우</strong></li>
</ul>
<p>새로운 이미지를 업로드하는 경우가 가장 어려웠는데, S3에 업로드된 이미지를 제거하고 DB 테이블의 로우를 제거한 후,  S3에 새로운 이미지를 업로드하고 DB 테이블에 S3 URL을 가지는 로우를 생성하는 방법을 사용했답니다. </p>
<h1 id="span-stylebackground-colorff385ecolorfff4주차span-👊"><span style="background-color:#FF385E;color:#fff">&nbsp;&nbsp;4주차&nbsp;&nbsp;</span> 👊</h1>
<p><img src="https://images.velog.io/images/choonghee-lee/post/246cde5a-20b2-44f2-83dc-ba1f39224862/serial-numbers.png" alt=""></p>
<p>일정에 착오가 있어서 5주차까지 해야한다는 소식을 듣고 잠시 정신이 몽롱해졌지만 ㅋㅋ... <strong>&quot;기왕 이렇게 된 거 계속해야지&quot;</strong> 하는 마음으로 팀원들과 추가 구현 회의를 하고 의지를 다졌어요.</p>
<p>쿠폰 관리를 맡게 되었어요. 기억나는 부분은 쿠폰 시리얼 넘버 CSV 파일 다운로드 였어요. 일단 사용자에게 파일을 주려면, 임시 파일을 만들어야 했어요. 해당 파일명을 중복되지 않게 (<strong>여러 요청이 동시에 올 경우 덮어 씌워질 경우가 있어요!</strong>) UUID로 작성하고 시리얼 넘버와 사용여부, 날짜 등을 파일에 적어서 저장하였어요. 임시파일은 공간만 차지하므로 사용자에게 보낼 수 있게 메모리에 올려두고 삭제했어요. </p>
<h1 id="span-stylebackground-colorff385ecolorfff5주차span-🤗"><span style="background-color:#FF385E;color:#fff">&nbsp;&nbsp;5주차&nbsp;&nbsp;</span> 🤗</h1>
<p><img src="https://images.velog.io/images/choonghee-lee/post/86acd34a-a2a5-41a4-af9b-bd181d8f831d/keynote.png" alt=""></p>
<p>AWS EC2 서버에 웹 애플리케이션을 배포하고 PPT 발표 준비를 했어요. <strong>저는 모든 코드를 보여드리기 보다 제가 중요하다고 느낀 코드에 어떤 포인트를 두고 작성하였는지 설명하였어요.</strong> 백오피스 프론트 페이지가 준비되지 않아 포스트맨으로 시연했던 점은 아쉬웠어요. 프론트와 실제로 붙여보면 보이지 않던 오류가 보이니까요.</p>
<p>모든 일정이 끝나고 동기들과 치킨, 피자, 맥주 파티를 했어요. <strong>오예~~ 🥳 🍺 🍕 !!!</strong></p>
<h1 id="span-stylebackground-colorff385ecolorfff감사합니다span-🙇🏻♂️"><span style="background-color:#FF385E;color:#fff">&nbsp;&nbsp;감사합니다&nbsp;&nbsp;</span> 🙇🏻‍♂️</h1>
<p><img src="https://images.velog.io/images/choonghee-lee/post/c81d420c-a289-41d5-b480-e3fd36f5ed14/team.JPG" alt=""></p>
<p>이틀마다 돌아오는 코드 리뷰 시간이 있어서 구현물을 항상 들고가야 한다는 부담도 있었지만, 가장 많이 배운 시간이었어요. 제가 생각한 로직의 허점을 깨닫고, 현업에서 어떤 방식을 더 선호하는지 배울 수 있었어요. 역시 회사에서 몸소 느끼며 배워야 한다더니... 인턴 한달만으로도 이렇게 느끼는데, <strong>어서 취직해서 더 많은 것을 해보고 싶어요!</strong></p>
<p><span style="color: #FF385E;">마지막으로, 저를 도와주신 많은 분들께 감사드린다는 말을 하고 싶어요. 힘들 때 본인만의 스트레스 해소법을 공유해 주신 분, 몸 챙기라고 건강 보조 식품을 주신 분, 동기 부여 영상을 찾아주신 분, 다운된 분위기를 항상 일으켜 주시는 분, 프로젝트 관련하여 기술적 피드백을 주신 분, 인턴십 이후의 있을 일을 논의하며 생각을 나누어 주신 분 등등... 정말 많은 분들이 함께해주셨기 때문에 한번 더 성장할 수 있었어요. 모두 사랑합니다 ❤️!</span></p>
<p><strong>나다 싶으면 내 사랑받아 xD</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[WATCHAPEDIA, 1차 프로젝트 후기]]></title>
            <link>https://velog.io/@choonghee-lee/WATCHAPEDIA-1%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@choonghee-lee/WATCHAPEDIA-1%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Sat, 05 Sep 2020 12:57:40 GMT</pubDate>
            <description><![CDATA[<h1 id="🙇🏻♂️-span-stylebackground-colorff0057colorfff시작하기-전에span">🙇🏻‍♂️ <span style="background-color:#ff0057;color:#fff">&nbsp;&nbsp;시작하기 전에&nbsp;&nbsp;</span></h1>
<p>엥? 어디서 본 것 같은 글이라구요? <a href="https://velog.io/@solmii/Dr.Martens">위코드 9기 강솔미님의 닥터마틴 1차 프로젝트 후기</a>를 오마주(?) 하였어요. 여윽시 센빠이 👍 감사합니다!</p>
<h1 id="👩🏻🏫-span-stylebackground-colorff0057colorfff프로젝트-소개span">👩🏻‍🏫 <span style="background-color:#ff0057;color:#fff">&nbsp;&nbsp;프로젝트 소개&nbsp;&nbsp;</span></h1>
<p><img src="https://images.velog.io/images/choonghee-lee/post/651e916b-cbe9-4cb6-a736-ccb77593dc59/watcha.png" alt=""></p>
<p><strong>왓챠피디아(WATCHAPEDIA)</strong> 는 영화 평가를 기반으로 사용자의 취향을 분석해주고, 이에 따라 추천까지 해주는 서비스에요. 개인적으로 1000개 이상의 영화를 평가했고, 저만의 영화 컬렉션으로 이용하고 있어요 ❤️! 최근에는 <strong>왓챠</strong>의 스트리밍 서비스와 함께 <strong>TV 프로그램, 도서</strong> 까지 서비스를 확장했답니다.</p>
<ul>
<li>팀원: <pre><code class="language-python">wecha = { 
&quot;frontend&quot;: [&quot;김동호&quot;, &quot;류상욱&quot;, &quot;박주엽&quot;],
&quot;backend&quot; : [&quot;이충희&quot;, &quot;이용민&quot;]
}</code></pre>
</li>
<li>개발기간: 2020.08.18 ~ 2020.08.28 (10일)</li>
<li><a href="https://github.com/wecode-bootcamp-korea/11-WeCha-frontend">Frontend Github</a></li>
<li><a href="https://github.com/wecode-bootcamp-korea/11-WeCha-backend">Backend Github</a></li>
</ul>
<h1 id="🎞-span-stylebackground-colorff0057colorfff데모-영상span">🎞 <span style="background-color:#ff0057;color:#fff">&nbsp;&nbsp;데모 영상&nbsp;&nbsp;</span></h1>
<p>조금 더 잘할 수 있었을 텐데, 아쉬워요.. 😓</p>
<p>!youtube[mmjKbSavkXs]</p>
<h1 id="🖥-span-stylebackground-colorff0057colorfff기술-스택span">🖥 <span style="background-color:#ff0057;color:#fff">&nbsp;&nbsp;기술 스택&nbsp;&nbsp;</span></h1>
<h3 id="개발-도구">개발 도구</h3>
<ul>
<li><em><strong>Django</strong></em> 웹 프레임워크로만 API를 구현했어요.</li>
<li><em><strong>AQueryTool</strong></em> 을 이용하여 데이터베이스 모델링을 했어요.</li>
<li><em><strong>Selenium</strong></em> 을 통해 영화 데이터를 크롤링했어요.</li>
<li>크롤링한 csv 데이터를 <em><strong>MySQL</strong></em> 에 저장해 두었어요.</li>
<li><em><strong>AWS EC2</strong></em> 서버에 <em><strong>RDS</strong></em> 인스턴스를 연결하여 서비스 하였어요.</li>
</ul>
<h3 id="협업-도구">협업 도구</h3>
<ul>
<li><em><strong>Git / Github</strong></em> 는 개발자의 필수 도구! 💪</li>
<li><em><strong>Trello</strong></em> 로 전체적인 일정 관리와 팀원들의 작업 현황을 파악했어요.</li>
<li><em><strong>Postman</strong></em> 으로 프론트엔드와 백엔드를 연결할때 <strong>소통하기 위해</strong> API 문서를 작성하였어요. - <a href="https://documenter.getpostman.com/view/12235507/T1LV8PVD">영화 API 문서</a>, <a href="https://documenter.getpostman.com/view/8738620/T1LV9PdQ">유저 API 문서</a></li>
</ul>
<h1 id="🤔-span-stylebackground-colorff0057colorfff어떤-기능을-클로닝-했나요span">🤔 <span style="background-color:#ff0057;color:#fff">&nbsp;&nbsp;어떤 기능을 클로닝 했나요?&nbsp;&nbsp;</span></h1>
<blockquote>
<p><span style="color: #ff0057;"><strong>색깔이 들어간 내용</strong></span>은 직접 구현한 부분입니다!!</p>
</blockquote>
<p>백엔드 구현 사항을 말씀드릴게요!</p>
<h3 id="메인-페이지">메인 페이지</h3>
<ul>
<li><strong><span style="color: #ff0057;">넷플릭스와 같은 서비스 제공자와 평균 별점으로 영화 랭킹을 제공해요. 서버에서 정렬해서 보내준답니다.</span></strong></li>
<li><strong><span style="color: #ff0057;">사용자의 평가를 기반으로 장르, 국가, 인물별 영화들을 추천해줘요. <em>쿼리 스트링</em> 으로 고르면 되요!</span></strong></li>
<li><strong><span style="color: #ff0057;">사용자들의 영화 컬렉션 리스트를 조회할 수 있어요.</span></strong></li>
<li><strong><span style="color: #ff0057;">영화 컬렉션의 상세 정보를 조회할 수 있어요. <em>페이지네이션</em> 을 구현하여 프론트엔드에서 무한 스크롤을 구현할 수 있게 도와줘요.
</span></strong></li>
</ul>
<h3 id="navbar--footer">Navbar &amp; Footer</h3>
<ul>
<li><strong><span style="color: #ff0057;">영화를 검색할 수 있어요. 자동완성 기능이 있답니다.</span></strong></li>
<li>전체 평가 수를 보여줘요.</li>
</ul>
<h3 id="로그인--회원가입-모달">로그인 &amp; 회원가입 모달</h3>
<ul>
<li>이메일로 회원가입을 해요. <em><strong>bcrypt</strong></em> 를 사용한 암호화는 필수!</li>
<li><em><strong>JWT</strong></em> 로그인을 구현했어요. 데코레이터를 이용한 _<strong>Validation</strong>_은 필수!</li>
</ul>
<h3 id="영화-상세-페이지">영화 상세 페이지</h3>
<ul>
<li><strong><span style="color: #ff0057;">영화 상세 정보를 보여줘요. 평균 별점, 출연진, 리뷰, 이미지 URL, 컬렉션 등을 포함하고 있어요.</span></strong></li>
<li>로그인된 유저는 평가를 하고 코멘트를 남길 수 있어요.</li>
</ul>
<h3 id="마이-페이지">마이 페이지</h3>
<ul>
<li>간단한 유저 정보를 보여줘요. 유저 이미지, 유저 네임, 설명 등을 포함해요.</li>
<li>유저가 평가한 영화 목록을 보여줘요.</li>
</ul>
<h1 id="😎-span-stylebackground-colorff0057colorfff잘한-점span">😎 <span style="background-color:#ff0057;color:#fff">&nbsp;&nbsp;잘한 점&nbsp;&nbsp;</span></h1>
<h3 id="1-친해졌다">1. 친해졌다!</h3>
<p><img src="https://images.velog.io/images/choonghee-lee/post/627c86af-c882-4083-a9a1-32e8fd57a689/%E1%84%82%E1%85%A1%E1%86%BE%E1%84%80%E1%85%A1%E1%84%85%E1%85%B5%E1%86%B7.jpg" alt="">
처음에 우리팀은 말수가 적은 사람들이 모여있어서 PM인 저로서는 조금 걱정이었어요. 그래서 매일 점심, 저녁 중 한 번은 식사를 꼭 같이해야 한다고 생각을 했어요. 다들 그렇게 생각하셨는지, 밥을 같이 먹으며 프로젝트에 대한 것 뿐만 아니라 개인적인 이야기를 서로 많이 했어요. <strong><span style="color: #ff0057;">어색하게 지내는 사람이 없었다는 것으로 성공적! 😍</span></strong></p>
<h3 id="2-목표한-만큼-구현했다">2. 목표한 만큼 구현했다!</h3>
<p><img src="https://images.velog.io/images/choonghee-lee/post/e3ee706f-040d-4c0e-9f56-588301afd217/giphy.gif" alt="applause">
10일이라는 짧은 기간 동안 <strong><span style="color: #ff0057;">우리팀은 각자가 맡은 필수구현 사항을 모두 구현했어요!</span></strong> 처음에 <em>&quot;이게 잘될까? 🤔&quot;</em> 의구심이 들었지만 시간이 지날수록 <em>&quot;오~ 왠지 다 할 수 있겠는걸?&quot;</em> 라는 생각으로 바뀌었어요. 특히 프론트 상욱님께서 무언가 구현할 때 마다 팀원들에게 자랑하는 모습을 보면서 정말로 흐뭇했어요. 한편, 백엔드인 저는 만든걸 보여줘도 팀원들은 시큰둥~ ㅋㅋㅋㅋㅋㅋㅋ 🤣</p>
<h1 id="😔-span-stylebackground-colorff0057colorfff아쉬운-점span">😔 <span style="background-color:#ff0057;color:#fff">&nbsp;&nbsp;아쉬운 점&nbsp;&nbsp;</span></h1>
<p>잘한게 있으면 그렇지 못한것도 있는게 인생이라지만, 이번 프로젝트는 아쉬운 점이 정말 많았어요. <strong><span style="color: #ff0057;">하지만 어쩌겠어요! 앞으로 잘하면 되는걸요 😊!</span></strong></p>
<h3 id="1-문서화의-부족">1. 문서화의 부족</h3>
<p><img src="https://images.velog.io/images/choonghee-lee/post/d939182a-cb82-47f6-afd3-afd60bacc6ec/doc.gif" alt="">
프로젝트 시작할 때, 사이트 분석을 오랜 시간 두고 버튼 하나하나 클릭을 해보면서 했어야 했는데... 😢 이런것들을 문서화 하지 않고 노트에 대충 적어 놓으니 까먹기도 하고 중간에 <em>&quot;충희님, 이런 기능도 있어요!!&quot;</em> 소리를 몇 번 듣기도 했어요. <strong><span style="color: #ff0057;">2차 프로젝트 때는 하루를 모두 써도 좋으니 꼭 문서화를 해야겠어요!!</span></strong></p>
<h3 id="2-나는-왜-이리도-침착하지-못했나">2. 나는 왜 이리도 침착하지 못했나</h3>
<p><img src="https://images.velog.io/images/choonghee-lee/post/768b334b-b7fd-4081-91f2-b7728abb155b/complex.jpg" alt="so complex">
코드가 길어지고 중복될 것 같다 싶으면 모듈화하고 함수를 만들고 이런 것들을 머리로 모두 알고 있지만, 막상 실제 코딩할 때는 지키지 못했던 것이 사실이에요. 멘토님에게 코드 리뷰를 받아, 가이드를 따라서 코드가 예뻐지긴 했지만, 처음에 작성한 코드를 생각하면 어휴... 😓 <strong><span style="color: #ff0057;">다음번에는 수정 요청 없이 바로 Merge되는 Pull Request 날리기 도-----전!</span></strong></p>
<h1 id="🥳-span-stylebackground-colorff0057colorfff보여주고-싶은-코드span-🥳">🥳 <span style="background-color:#ff0057;color:#fff">&nbsp;&nbsp;보여주고 싶은 코드&nbsp;&nbsp;</span> 🥳</h1>
<h3 id="1-재사용-가능한-함수-작성">1. 재사용 가능한 함수 작성</h3>
<p>이번 프로젝트에는 GET 요청 API를 많이 작성하다 보니 JSON 바디가 굉~<del>~</del>장히 길어지고 중복되는 경우가 많았어요. 그래서 아래와 같이 <strong><span style="color: #ff0057;">많은 수의 함수를 만들어두고 여러번 재사용</span></strong> 했답니다. <strong><span style="color: #ff0057;">리스트 컴프리헨션 사용 마스터는 덤! 😎</span></strong>
<img src="https://images.velog.io/images/choonghee-lee/post/cb97b20f-a68f-4d4d-abd7-4de2a61cfbec/code1.png" alt="코드1"></p>
<blockquote>
<p>위의 코드는 <a href="https://github.com/wecode-bootcamp-korea/11-WeCha-backend/blob/master/film/make_jsons.py">이곳에서</a> 확인이 가능해요!</p>
</blockquote>
<h3 id="2-db-hit를-줄이기-위한-노력">2. DB Hit를 줄이기 위한 노력!</h3>
<p>우리 백엔둥이들에게는 <strong><span style="color: #ff0057;">데이터베이스에 쿼리를 한번 더 날리냐 덜 날리느냐가 굉장히 중요해요!</span></strong> 장고에서는 <code>select_related()</code>, <code>prefetch_related()</code>와 같은 메서드를 이용하면 된답니다.</p>
<blockquote>
<p><code>select_related()</code> 의 공식 문서 번역이 <a href="https://velog.io/@choonghee-lee/%EB%B2%88%EC%97%AD-Django-selectrelated">저의 블로그</a> 에 있어요!</p>
</blockquote>
<p><img src="https://images.velog.io/images/choonghee-lee/post/d67b2f67-8d39-4919-add1-0195ec7aa1d1/code2.png" alt="코드2"></p>
<blockquote>
<p>위의 코드 또한 <a href="https://github.com/wecode-bootcamp-korea/11-WeCha-backend/blob/master/film/views.py">이곳에서</a> 확인이 가능해요!</p>
</blockquote>
<p>컬렉션에 담긴 영화 정보를 알고 싶은데 <code>prefetch_related()</code>가 없었다면 아마도 아래와 같은 코드를 작성했을거에요 😭. Oh là là..</p>
<pre><code class="language-python">collections = Collection.objects.all().order_by(&#39;?&#39;)[:limit]
for collection in collections:
    film_collections = FilmCollection.objects.filter(collection=collection)
    for film_collection in film_collections:
        print(film_collection.film.korean_title)</code></pre>
<h1 id="👋-span-stylebackground-colorff0057colorfff마무으리span">👋 <span style="background-color:#ff0057;color:#fff">&nbsp;&nbsp;마무으리&nbsp;&nbsp;</span></h1>
<p>먼저 부족한 PM을 커버해준 우리 팀원들에게 고맙다는 말을 해주고 싶어요. 그리고 항상 힘을 주는 위코드 동기들, 멘토님들이 있어서 다행이에요. 혼자였다면, View 하나 만들고 포기했을거에요...ㅋㅋㅋㅋㅋㅋ. <strong><span style="color: #ff0057;">함께해서 WeCode!</span></strong></p>
<p><img src="https://images.velog.io/images/choonghee-lee/post/ca841a19-8c11-42d4-b01d-db0fd9472469/wecode.png" alt=""></p>
<p>엥?? 3개월이요? <strong><span style="color: #ff0057;">저는 이미 개발자입니다만?</span></strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[번역] Decorators in Python]]></title>
            <link>https://velog.io/@choonghee-lee/%EB%B2%88%EC%97%AD-Decorators-in-Python</link>
            <guid>https://velog.io/@choonghee-lee/%EB%B2%88%EC%97%AD-Decorators-in-Python</guid>
            <pubDate>Sun, 23 Aug 2020 12:37:57 GMT</pubDate>
            <description><![CDATA[<h1 id="읽기전에">읽기전에..</h1>
<p>그냥 <a href="https://www.geeksforgeeks.org/decorators-in-python/">GeeksForGeeks</a>에서 데코레이터 페이지를 번역했다.</p>
<h1 id="서론">서론</h1>
<p>파이썬에서, 함수는 first class object 이다. 그 말인즉슨,</p>
<ul>
<li>함수는 객체이다; 변수에 저장되어 질 수 있고, 다른 함수에서 리턴값이 될 수도 있다.</li>
<li>함수는 다른 함수 내부에서 선언되어 질 수 있고, 다른 함수의 인자로 넘겨질 수도 있다.</li>
</ul>
<p>데코레이터는 프로그래머가 함수 또는 클래스의 behavior를 조작한다는 점에서 굉장히 파워풀하고 유용한 파이썬 문법이다. 데코레이터는 직접적으로 함수를 조작하지 않고 함수의 기능을 확장하기 위해 함수를 감싼다.</p>
<p>데코레이터에서 함수는 다른 함수의 인자로 넘겨져 다음 wrapper 함수 내부에서 호출된다.</p>
<h1 id="syntax-for-decorator">Syntax for Decorator</h1>
<pre><code class="language-python">@gfg_decorator
def hello_decorator(): 
    print(&quot;Gfg&quot;) 

&#39;&#39;&#39;위의 코드는 다음과 동일하다 - 

def hello_decorator(): 
    print(&quot;Gfg&quot;) 

hello_decorator = gfg_decorator(hello_decorator)&#39;&#39;&#39;</code></pre>
<p>위의 코드에서, <em>gfg_decorator</em> 는 callable 함수이고, 또 다른 callable 함수인 <em>hello_decorator</em> 의 상단에 위치하여 코드를 추가해주고, 그것의 wrapper 함수를 리런한다.</p>
<h1 id="decorator-can-modify-the-behavior">Decorator can modify the behavior</h1>
<pre><code class="language-python"># 데코레이터를 선언한다. 
def hello_decorator(func): 

    # inner1은 func 인자를 호출하는 Wrapper 함수이다.
    def inner1(): 
        print(&quot;Hello, this is before function execution&quot;) 

        # wrapper 함수의 내부에서 실제 함수를 호출한다. 
        func() 

        print(&quot;This is after function execution&quot;) 

    return inner1 


# wrapper 함수에서 호출될 함수를 선언한다.
def function_to_be_used(): 
    print(&quot;This is inside the function !!&quot;) 


# &#39;function_to_be_used&#39; 함수를 그 기능을 확장하기 위해 데코레이터에 인자로 넘겨준다.
function_to_be_used = hello_decorator(function_to_be_used) 


# 함수를 호출한다.
function_to_be_used() </code></pre>
<h3 id="결과">결과</h3>
<pre><code>Hello, this is before function execution
This is inside the function !!
This is after function execution</code></pre><p>&quot;function_to_be_used&quot;가 어떻게 호출되었지는 그 단계를 살펴보자
<img src="https://images.velog.io/images/choonghee-lee/post/c6262595-c220-4081-a4ea-d0672e3e9c1f/decorators_step.png" alt="">
<img src="https://images.velog.io/images/choonghee-lee/post/dc748421-a9e2-411a-ab00-0764909c20fa/decorators_step2.png" alt=""></p>
<p><strong>데코레이터를 사용하는 함수의 실행 시간을 확인</strong>하기 위한 다른 예제를 살펴보자.</p>
<pre><code class="language-python"># importing libraries 
import time 
import math 

# 인자로 들어온 함수의 실행시간을 계산하는 데코레이터
def calculate_time(func): 

    # 함수가 인자를 받는다면 다음과 같이 파라미터를 써준다.
    def inner1(*args, **kwargs): 

        # 함수가 실행되기 전에 시간을 체크한다.
        begin = time.time() 

        func(*args, **kwargs) 

        # 함수가 실행된 후 시간을 체크한다.
        end = time.time() 
        print(&quot;Total time taken in : &quot;, func.__name__, end - begin) 

    return inner1 


# 어떠한 함수도 시간 측정이 가능해졌다.
@calculate_time
def factorial(num): 

    # 2초간 sleep을 걸어준다.
    # 이렇게 하면 실제 차이를 볼 수 있다.
    time.sleep(2) 
    print(math.factorial(num)) 

# 함수를 호출한다.
factorial(10)</code></pre>
<h3 id="결과-1">결과</h3>
<pre><code>3628800
Total time taken in :  factorial 2.0061802864074707</code></pre><h1 id="what-if-a-function-returns-something-">What if a function returns something ...</h1>
<p>위의 모든 예제의 함수들은 아무것도 리턴하지 않았다. 리턴값이 있을 때의 예제를 살펴본다.</p>
<pre><code class="language-python">def hello_decorator(func): 
    def inner1(*args, **kwargs): 

        print(&quot;before Execution&quot;) 

        # 리턴된 값을 받는다.
        returned_value = func(*args, **kwargs) 
        print(&quot;after Execution&quot;) 

        # 그 값을 다시 리턴한다.
        return returned_value 

    return inner1 


# 함수에 데코레이터를 붙인다.
@hello_decorator
def sum_two_numbers(a, b): 
    print(&quot;Inside the function&quot;) 
    return a + b 

a, b = 1, 2

# 리턴값을 받는다.
print(&quot;Sum =&quot;, sum_two_numbers(a, b)) </code></pre>
<h3 id="결과-2">결과</h3>
<pre><code>before Execution
Inside the function
after Execution
Sum = 3</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[번역] Django - select_related()]]></title>
            <link>https://velog.io/@choonghee-lee/%EB%B2%88%EC%97%AD-Django-selectrelated</link>
            <guid>https://velog.io/@choonghee-lee/%EB%B2%88%EC%97%AD-Django-selectrelated</guid>
            <pubDate>Sun, 23 Aug 2020 01:01:42 GMT</pubDate>
            <description><![CDATA[<p>장고 공식문서에 있는 내용을 번역해본다.</p>
<h1 id="select_related">select_related()</h1>
<h2 id="select_relatedfields">select_related(*fields)</h2>
<p>쿼리가 실행될 때 related 객체 데이터를 추가적으로 SELECT 해오고, 외래키 관계를 &quot;따르는 (follow)&quot; 하나의 <strong>QuerySet</strong>을 리턴한다. 당장은 복잡한 쿼리를 만들지만 나중에 외래키 관계에 따른 추가적인 데이터베이스 쿼리를 생성하지 않게되는 장점이 있다.</p>
<p>아래의 예시는 일반적인 lookup과 <strong>select_related()</strong> lookup 사이의 차이를 보여준다. 여기에 일반적인 lookup이 있다:</p>
<pre><code class="language-python"># 데이터베이스에 쿼리를 날린다.
e = Entry.objects.get(id=5)

# Entry와 관계를 맺고 있는 Blog 객체를 가져오기 위해 다시 데이터베이스에 쿼리를 날린다.
b = e.blog</code></pre>
<p>그리고 여기 <strong>select_related()</strong> lookup이 있다:</p>
<pre><code class="language-python"># 데이터베이스에 쿼리를 날린다.
e = Entry.objects.select_related(&#39;blog&#39;).get(id=5)

# 데이터베이스에 쿼리를 날리지 않는다. e.blog는 이미 이전 쿼리에서 가져왔기 (prepopulated) 때문이다.
b = e.blog</code></pre>
<p>어떤 객체의 queryset에도 <strong>select_related()</strong> 를 사용 할 수 있다:</p>
<pre><code class="language-python">from django.utils import timezone

# 나중에 포스팅되도록 예정된 엔트리들을 가진 블로그들을 찾는다.
blogs = set()

for e in Entry.objects.filter(pub_date__gt=timezone.now()).select_related(&#39;blog&#39;):
    # select_related() 함수가 없었다면, 각 엔트리에 관계된 블로그를 fetch 하기 위해 각 루프 회차때 마다 데이터베이스 쿼리를 날려야한다.
    blogs.add(e.blog)</code></pre>
<p><strong>filter()</strong> 와 <strong>select_related()</strong> 함수 chaining의 순서는 중요하지 않다. 아래의 두 쿼리셋은 동일하다:</p>
<pre><code class="language-python">Entry.objects.filter(pub_date__gt=timezone.now()).select_related(&#39;blog&#39;)
Entry.objects.select_related(&#39;blog&#39;).filter(pub_date__gt=timezone.now())</code></pre>
<p>비슷한 방법으로 외래키의 객체가 포함하는 객체를 쿼리하기 위해 외래키 객체를 참조할 수도 있다. 만약 다음과 같은 모델들이 있다면:</p>
<blockquote>
<p>여기서 Book의 외래키는 Person. 외래키 Person의 객체는 City. 결국 City를 참조하기 위해 아래의 예시가 있다.</p>
</blockquote>
<pre><code class="language-python">from django.db import models

class City(models.Model):
    # ...
    pass

class Person(models.Model):
    # ...
    hometown = models.ForeignKey(
        City,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )

class Book(models.Model):
    # ...
    author = models.ForeignKey(Person, on_delete=models.CASCADE)</code></pre>
<p>... <strong>Book.objects.select_related(&#39;author__hometown&#39;).get(id=4)</strong> 의 실행은 연관된 Person과 City 객체를 캐쉬한다.</p>
<pre><code class="language-python"># Book의 author 필드와 Person의 hometown 필드로 조인문을 만들어 데이터베이스에 쿼리를 날린다.
b = Book.objects.select_related(&#39;author__hometown&#39;).get(id=4)
p = b.author         # 쿼리를 날리지 않는다.
c = p.hometown       # 쿼리를 날리지 않는다.

# select_related() 를 사용하지 않는다면...
b = Book.objects.get(id=4)  # 쿼리를 날린다.
p = b.author                # 쿼리를 날린다.
c = p.hometown              # 쿼리를 날린다.</code></pre>
<p><strong>select_related()</strong> 에 전달될 필드 리스트를 통해 <strong>ForeignKey</strong> 또는 <strong>OneToOneField</strong> 관계를 참조할 수 있다.</p>
<p><strong>select_related()</strong> 에 전달될 필드 리스트를 통해 <strong>OneToOneField</strong> 의 반대 방향도 참조할 수 있다. - 즉, OneToOneField 타입의 필드를 선언한 객체로 참조가 가능하다는 말이다. 그 필드의 이름을 명시하기 보다는, OneToOneField 타입의 필드를 선언할 때 <strong>related_name</strong> 옵션을 사용한다.</p>
<p>가끔은 객체 간의 관계가 복잡하게 얽혀있거나, 프로그래머가 그 모든 관계를 파악하지 파악하지 못하고 있을 때, <strong>select_related()</strong> 를 사용해야할 상황들이 있다. 이러한 경우에는 argument 없이 <strong>select_related()</strong> 를 호출한다. 그러면 찾을 수 있는 모든 non-null 외래키 데이터를 SELECT 하게 된다 - nullable 외래키들은 반드시 명시해야한다. 이것은 쿼리를 더 복잡하게 만들고 실제로 필요한 경우보다 많은 데이터를 반환할 수 있으므로 대부분의 경우 권장되는 방법은 아니다.</p>
<p><strong>QuerySet</strong>에서 과거 <strong>select_related()</strong> 호출에 의해 추가된 related 필드들을 지워야하는 경우, <strong>None</strong>을 파라미터로 넘겨줄 수 있다.</p>
<pre><code class="language-python">without_relations = queryset.select_related(None)</code></pre>
<p><strong>select_related()</strong> 를 chaining 하여 호출하는 것도 가능하다. <strong>select_related(&#39;foo&#39;, &#39;bar&#39;)</strong> 은 <strong>select_related(&#39;foo&#39;).select_related(&#39;bar&#39;)</strong> 과 동일하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django Shell을 이용한 Serialization & Deserialization]]></title>
            <link>https://velog.io/@choonghee-lee/Django-Shell%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-Serialization-Deserialization</link>
            <guid>https://velog.io/@choonghee-lee/Django-Shell%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-Serialization-Deserialization</guid>
            <pubDate>Wed, 19 Aug 2020 22:07:52 GMT</pubDate>
            <description><![CDATA[<h1 id="주의">주의</h1>
<blockquote>
<p>Django Rest Framework를 장고 프로젝트에 설정할 수 있어야한다!</p>
</blockquote>
<h1 id="모델-클래스">모델 클래스</h1>
<pre><code class="language-python">class Toy(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    name = models.CharField(max_length=150, blank=False, default=&#39;&#39;)
    description = models.CharField(max_length=250, blank=True, default=&#39;&#39;)
    toy_category = models.CharField(max_length=200, blank=False, default=&#39;&#39;)
    release_date = models.DateTimeField()
    was_included_in_home = models.BooleanField(default=False)

    class Meta:
        ordering = (&#39;name&#39;,)</code></pre>
<h1 id="serializer-클래스">Serializer 클래스</h1>
<pre><code class="language-python">from rest_framework import serializers
from toys.models import Toy

class ToySerializer(serializers.Serializer):
    pk = serializers.IntegerField(read_only=True)
    name = serializers.CharField(max_length=150)
    description = serializers.CharField(max_length=250)
    release_date = serializers.DateTimeField()
    toy_category = serializers.CharField(max_length=200)
    was_included_in_home = serializers.BooleanField(required=False)

    def create(self, validated_data):
        return Toy.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.name = validated_data.get(&#39;name&#39;, instance.name)
        instance.description = validated_data.get(&#39;description&#39;, instance.description)
        instance.release_date = validated_data.get(&#39;release_date&#39;, instance.release_date)
        instance.toy_category = validated_data.get(&#39;toy_category&#39;, instance.toy_category)
        instance.was_included_in_home = validated_data.get(&#39;was_included_in_home&#39;, instance.was_included_in_home)
        instance.save()
        return instance</code></pre>
<h1 id="장고-쉘-실행">장고 쉘 실행</h1>
<pre><code class="language-bash">python manage.py shell</code></pre>
<p>지금 부터 파이썬 쉘에 한줄 한줄 입력해야한다...</p>
<h1 id="imports">imports</h1>
<pre><code class="language-python">from datetime import datetime
from django.utils import timezone
from io import BytesIO
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from toys.models import Toy
from toys.serializers import ToySerializer</code></pre>
<h1 id="객체-생성">객체 생성</h1>
<pre><code class="language-python">toy_release_date = timezone.make_aware(datetime.now(), timezone.get_current_timezone())

toy1 = Toy(name=&#39;Snoopy talking action figure&#39;, 
    description=&#39;Snoopy speaks five languages&#39;, 
    release_date=toy_release_date, 
    toy_category=&#39;Action figures&#39;, 
    was_included_in_home=False)
toy1.save()</code></pre>
<h2 id="객체-확인">객체 확인</h2>
<pre><code class="language-python">print(toy1.pk)
print(toy1.name)
print(toy1.created)
print(toy1.was_included_in_home)</code></pre>
<h1 id="파이썬-객체-to-딕셔너리">파이썬 객체 to 딕셔너리</h1>
<p>모델 객체를 파이썬 딕셔너리로 변환한다.</p>
<pre><code class="language-python">serializer_for_toy1 = ToySerializer(toy1)
print(serializer_for_toy1.data)

{
  &#39;pk&#39;: 1,
  &#39;name&#39;: &#39;Snoopy talking action figure&#39;,
  &#39;description&#39;: &#39;Snoopy speaks five languages&#39;,
  &#39;release_date&#39;: &#39;2017-10-09T12:11:37.090335Z&#39;,
  &#39;toy_category&#39;: &#39;Action figures&#39;,
  &#39;was_included_in_home&#39;: False
}</code></pre>
<h1 id="serialization">Serialization</h1>
<p><em>JSONRenderer</em> 의 <code>render()</code> 메서드는 위에서 만든 딕셔너리를 바이너리 JSON 형태로 변환해준다.</p>
<pre><code class="language-python">json_renderer = JSONRenderer()
toy1_rendered_into_json = json_renderer.render(serializer_for_toy1.data)
print(toy1_rendered_into_json)

b&#39;{&quot;pk&quot;:1,&quot;name&quot;:&quot;Snoopy talking action figure&quot;, &quot;description&quot;: &quot;Snoopy speaks five languages&quot;, &quot;release_date&quot;: &quot;2017-10-09T12:11:37.090335Z&quot;, &quot;toy_category&quot;: &quot;Action figures&quot;, &quot;was_included_in_home&quot;: false}</code></pre>
<h1 id="deserialization">Deserialization</h1>
<p>JSON 문자열을 바이트 타입으로 바꾸고, ByteIO 객체로 바꾼다. <em>JSONParser</em> 의 <code>parser()</code> 메서드를 이용하여 딕셔너리 형태로 변환한다.</p>
<pre><code class="language-python">json_string_for_new_toy = &#39;{&quot;name&quot;:&quot;Clash Royale play set&quot;,&quot;description&quot;:&quot;6 figures from Clash Royale&quot;, &quot;release_date&quot;:&quot;2017-10-09T12:10:00.776594Z&quot;,&quot;toy_category&quot;:&quot;Playset&quot;,&quot;was_included_in_home&quot;:false}&#39;
json_bytes_for_new_toy = bytes(json_string_for_new_toy, encoding=&quot;UTF-8&quot;)
stream_for_new_toy = BytesIO(json_bytes_for_new_toy)
parser = JSONParser()
parsed_new_toy = parser.parse(stream_for_new_toy)
print(parsed_new_toy)

{
    &#39;name&#39;: &#39;Clash Royale play set&#39;,
    &#39;description&#39;: &#39;6 figures from Clash Royale&#39;,
    &#39;release_date&#39;: &#39;2017-10-09T12:10:00.776594Z&#39;,
    &#39;toy_category&#39;: &#39;Playset&#39;,
    &#39;was_included_in_home&#39;: False
}</code></pre>
<h2 id="저장">저장</h2>
<p>위에서 만든 딕셔너리를 Serializer 객체를 이용해 Toy 객체로 변환하고 데이터베이스에 저장한다.</p>
<pre><code class="language-python">new_toy_serializer = ToySerializer(data=parsed_new_toy)
if new_toy_serializer.is_valid():
    toy3 = new_toy_serializer.save()</code></pre>
<h2 id="장고-쉘-종료">장고 쉘 종료</h2>
<pre><code class="language-bash">quit()</code></pre>
]]></description>
        </item>
    </channel>
</rss>