diff options
Diffstat (limited to 'files/ko/learn/server-side')
27 files changed, 10448 insertions, 0 deletions
diff --git a/files/ko/learn/server-side/django/admin_site/index.html b/files/ko/learn/server-side/django/admin_site/index.html new file mode 100644 index 0000000000..086f4e99aa --- /dev/null +++ b/files/ko/learn/server-side/django/admin_site/index.html @@ -0,0 +1,357 @@ +--- +title: 'Django Tutorial Part 4: Django admin site' +slug: Learn/Server-side/Django/Admin_site +translation_of: Learn/Server-side/Django/Admin_site +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Models", "Learn/Server-side/Django/Home_page", "Learn/Server-side/Django")}}</div> + +<p class="summary"><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> website의 모델을 만들었으므로, 우리는 Django Admin 을 이용해서 "실제" book data를 추가할 것입니다. 첫째로 우리는 당신에게 관리자 사이트에 모델들을 등록는 방법과, 이후 어떻게 로그인해서 데이터를 만들지를 보여줄 것입니다. 끝으로는 Admin site를 더 개선할 수 있는 방법들에 대해서 알아볼 것입니다.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">사전 준비:</th> + <td>첫째로 완료하세요: <a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a>.</td> + </tr> + <tr> + <th scope="row">목적:</th> + <td>장고 관리자 사이트의 이득과 한계를 이해하고, 모델을 위한 여러 레코드들을 생성하기.</td> + </tr> + </tbody> +</table> + +<h2 id="개요">개요</h2> + +<p> 장고의 관리자 어플리케이션은 모델을 사용하여 당신이 만들고, 보고, 업데이트하고, 그리고 삭제하는 데에 사용할 수 있는 사이트 영역을 자동적으로 만드는 데에 사용할 수 있습니다. 이는 당신이 시간을 많이 절약할 수 있도록 돕고, 모델을 쉽게 테스트 할 수 있게 하며 당신이 정확한 데이터를 가지고 있다는 느낌을 가질수 있도록 돕습니다. 관리자 어플리케이션은 또한 웹 사이트의 유형에 따라 production의 데이터를 관리하는 데 유용합니다. 모델 중심 접근 방식은 모델에 관한 많은 불필요한 세부 사항을 사용자들에게 노출하는, 모든 사용자들에게 가장 좋은 방식이라고 말할 수 없기 때문에, 장고 프로젝트는 internal 데이터 관리만을(즉, 관리자, 또는 당신의 조직 안에 있는 사람들을 위한 사용만을) 위해서 사용하는 것을 추천합니다.</p> + +<p> 웹사이트 안에 관리자 어플리케이션을 포함시키기 위해 요구되는 모든 설정은 <a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">created the skeleton project</a>를 생성했을 때 자동적으로 완료됩니다(실제 종속성에 대한 정보는, 여기 <a href="https://docs.djangoproject.com/en/2.0/ref/contrib/admin/">Django docs here</a>를 확인하세요). 결과적으로, 모델을 관리자 어플리케이션에 추가하기 위해서 <strong>꼭 해야만</strong> 하는 것은 그것들을 등록하는(register) 것입니다. 이 글의 마지막에서 모델 데이터를 더 잘 나타내기 위한 관리자 영역의 추가적인 설정을 짧게 보여드리겠습니다.</p> + +<p>모델을 등록한 뒤로는 새로운 "superuser"를 만들어서 사이트에 로그인을 하며, 이후에는 books, 저자, book instances 그리고 장르를 만들 것입니다. 이것들은 다음 튜토리얼에서 만들기 시작할 뷰와 템플릿을 테스트할 때 유용할 것입니다.</p> + +<h2 id="Models_등록하기">Models 등록하기</h2> + +<p>첫째, catalog 어플리케이션 안의 <strong>admin.py</strong> 파일을 여세요 (<strong>/locallibrary/catalog/admin.py</strong>). 다음과 같을 겁니다 — <code>django.contrib.admin</code>을 이미 imoprt하고 있다는 것에 주의하세요:</p> + +<pre class="brush: python">from django.contrib import admin + +# Register your models here. +</pre> + +<p> 파일의 하단에 아래 텍스트를 복사 붙여넣기 해서 모델을 등록(register)하세요. 이 코드는 모델들을 import하고 그것들을 등록하기 위해 <code>admin.site.register</code> 를 호출합니다.</p> + +<pre class="brush: python">from catalog.models import Author, Genre, Book, BookInstance + +admin.site.register(Book) +admin.site.register(Author) +admin.site.register(Genre) +admin.site.register(BookInstance) +</pre> + +<div class="note"><strong> 주의</strong>: 책의 언어를 나타내기 위한 모델을 생성하는 도전과제를 수행했다면 (<a href="/en-US/docs/Learn/Server-side/Django/Models">see the models tutorial article</a>), 그 모델도 import 한 후 등록하세요!</div> + +<p> 이것은 모델이나 모델들을 사이트에 등록(register)하는 가장 간단한 방법입니다. 관리자 사이트는 커스터마이즈 범위가 넓고, 아래에서 모델을 등록하는 다른 방법들에 관해 다르겠습니다.</p> + +<h2 id="Superuser_만들기">Superuser 만들기</h2> + +<p> 관리자 사이트에 로그인하기 위해서는, 직원(<em>Staff)</em> 상태가 활성화 된 사용자 계정이 있어야 합니다. 레코드들을 보고 생성하기 위해서는 이 사용자가 모든 객체들을 관리하기 위한 허가가 있어야 합니다. 사이트에 대한 모든 접속 권한과 필요한 허가를 가진 "superuser" 계정을 manage.py를 사용해서만들 수 있습니다. </p> + +<p>superuser를 생성하기 위해<strong> manage.py</strong>와 같은 경로 안에서 아래 명령어를 호출하세요. username, 이메일 주소, 그리고 강력한 비밀번호를 입력해야 할 것입니다.</p> + +<pre class="brush: bash">python3 manage.py createsuperuser +</pre> + +<p> 이 명령어가 완료되면 새로운 superuser가 데이터베이스에 추가되었을 것입니다. 이제 로그인을 테스트할 수 있도록 개발 서버를 재시작 하세요:</p> + +<pre class="brush: bash">python3 manage.py runserver + +</pre> + +<h2 id="관리자_계정에_로그인하기">관리자 계정에 로그인하기</h2> + +<p> 사이트에 로그인하기 위해서는, /admin URL(예: <a href="http://127.0.0.1:8000/admin/">http://127.0.0.1:8000/admin</a>)을 열어서 새로운 superuser의 userid와 password를 입력하세요(세부 정보를 입력한 후에는 로그인 페이지로 리디렉션되고 /admin URL로 돌아갑니다).</p> + +<p> 이곳은 설치된 어플리케이션에 따라 그룹지어진 모든 모델들을 보여줍니다. 모델 이름을 클릭해서 모든 관련 레코드들의 목록을 볼 수 있고, 그 목록들을 클릭하여 수정할 수 있습니다. 또한 모델 오른쪽의 <strong>Add</strong> 링크를 클릭하여 그 타입의 레코드를 곧바로 생성할 수 있습니다.</p> + +<p><img alt="Admin Site - Home page" src="https://mdn.mozillademos.org/files/13975/admin_home.png" style="display: block; height: 634px; margin: 0px auto; width: 998px;"></p> + +<p><strong> </strong>Books 모델 오른쪽의<strong> Add</strong> 링크를 클릭해서 새로운 책을 만드세요(아래와 같은 화면이 보일 것입니다). 각 필드의 제목, 사용된 위젯의 타입 그리고 <code>help_text</code>(있는 경우)가 모델 안에서 지정한 값과 일치하는 방식에 유의하세요. </p> + +<p> 필드에 값들을 입력하세요. 해당 필드 옆에 있는 <strong>+</strong> 버튼을 눌러 새로운 저자나 장르를 생성할 수 있습니다 (이미 생성했다면 목록에서 값을 선택하세요). 모두 완료했다면 레코드를 저장하기 위해 <strong>SAVE</strong>, <strong>Save and add another</strong> 또는 <strong>Save and continue editing</strong>을 클릭할 수 있습니다.</p> + +<p><img alt="Admin Site - Book Add" src="https://mdn.mozillademos.org/files/13979/admin_book_add.png" style="border-style: solid; border-width: 1px; display: block; height: 780px; margin: 0px auto; width: 841px;"></p> + +<div class="note"> +<p><strong> 주의</strong>: 이 지점에서 어플리케이션에 몇 가지 책들, 저자 그리고 장르(예: 판타지)를 추가하면서 시간을 보내보세요. 각각 저자와 장르에 서로 다른 두 가지 책이 포함되어 있는지 확인하세요 (이렇게 하면 나중에 리스트(list)와 디테일 뷰(detail view)들을 구현할 때 그것들을 더욱 흥미롭게 만들어 줍니다).</p> +</div> + +<p> 책을 추가했다면, 상단 북마크 안의 <strong>Home</strong> 링크를 클릭해서 관리자 페이지로 되돌아가세요. 그리고 <strong>Books </strong>링크를 클릭해서 책들의 현재 목록을 나타내세요(아니면 다른 링크를 클릭해서 다른 모델의 목록을 보세요). 이제 몇 가지 책을 추가했으니, 목록은 아래 스크린샷과 비슷하게 보일 겁니다. 각 책의 제목이 보입니다; 이것은 지난 글에서 다뤘던 Book 모델의 <code>__str__()</code> 메소드가 반환한 값입니다.</p> + +<p><img alt="Admin Site - List of book objects" src="https://mdn.mozillademos.org/files/13935/admin_book_list.png" style="border-style: solid; border-width: 1px; display: block; height: 407px; margin: 0px auto; width: 1000px;"></p> + +<p> 이 리스트에서 원하지 않는 책의 왼쪽에 있는 체크박스를 선택하고, Action 드랍-다운 목록에서 delete... 동작을 선택한 후, <strong>Go</strong> 버튼을 클릭해 책을 삭제할 수 있습니다. 또한 <strong>ADD BOOK</strong> 버튼을 클릭해서 새로운 책들을 추가할 수 있습니다. </p> + +<p> 링크 안의 책 이름을 선택해서 책을 편집할 수 있습니다. 아래에 보여지는 책 모델의 편집 페이지는 "Add" 페이지와 거의 동일합니다. 페이지의 제목 (<em>Change book</em>)과 <strong>Delete</strong>, <strong>HISTORY</strong> 그리고 <strong>VIEW ON SITE</strong> 버튼들의 추가가 큰 차이점입니다(마지막 버튼은 모델에서 <code>get_absolute_url()</code> 메소드를 정의했기 때문에 나타납니다).</p> + +<p><img alt="Admin Site - Book Edit" src="https://mdn.mozillademos.org/files/13977/admin_book_modify.png" style="border-style: solid; border-width: 1px; display: block; height: 780px; margin: 0px auto; width: 841px;"></p> + +<p>이제 <strong>Home</strong> 페이지로 되돌아가서 (홈 링크를 사용해서) <strong>Author</strong> 와 <strong>Genre</strong> 목록들을 보세요 — 새로운 책들을 추가하면서 이미 몇 가지 목록들이 있겠지만, 조금 더 추가해도 됩니다.</p> + +<p> 아직 가지고 있지 않은 것은 Book Instance들입니다. 왜냐하면 Books에서 만들어지지 않았기 때문이죠( <code>BookInstance</code>에서 <code>Book</code>을 만들 수 있긴 하지만 — 이것은 <code>ForeignKey</code> 필드의 특성입니다). Home 페이지로 되돌아가서 연관된<strong> 추가(Add)</strong>버튼을 눌러 아래의 Add book Instance 화면을 나타내세요. 크고 전역적으로 고유한 id에 주의하세요. 이것은 도서관에서 한 권의 책을 개별적으로 식별하는 데 사용할 수 있습니다.</p> + +<p><img alt="Admin Site - BookInstance Add" src="https://mdn.mozillademos.org/files/13981/admin_bookinstance_add.png" style="border-style: solid; border-width: 1px; display: block; height: 514px; margin: 0px auto; width: 863px;"></p> + +<p> 각각의 책마다 여러 개의 레코드를 만드세요. 상태(Status)를 최소한 몇 개의 레코드는 대여 가능(Available)로 설정하고, 나머지는 대여 중(On loan)으로 설정하세요. 만약 상태가 대여 불가능(<strong>not</strong> Available)이면, 만기 날짜도 같이 설정하세요.</p> + +<p>이게 끝입니다! 관리자 사이트를 어떻게 설정하고 사용하는지 배웠습니다. 또한 <code>Book</code>, <code>BookInstance</code>, <code>Genre</code>, 그리고 <code>Author</code> 에 대한 레코드들을 생성했고, 뷰와 탬플릿을 생성할 때 이 레코드들을 사용할 수 있을 것입니다.</p> + +<h2 id="추가_설정(Advanced_configuration)">추가 설정(Advanced configuration)</h2> + +<p> 장고는 등록된(registered) 모델의 정보를 사용하여 기본적인 관리자 사이트를 만드는 일을 꽤 잘합니다:</p> + +<ul> + <li>각각의 모델은 모델의 <code>__str__()</code> 메소드로 생성된 문자열로 식별되는, 그리고 편집을 위한 세부 정보(detail views)/양식(forms)에 링크되어 있는 개별적인 레코드들의 목록을 가지고 있습니다. 기본적으로, 이 화면은 레코드에 대한 대량 삭제 작업을 수행할 수 있는 액션 메뉴를 상단에 가지고 있습니다.</li> + <li>레코드의 편집과 추가를 위한 모델 디테일 레코드 양식들은 모델 안의 모든 필드를 포함하고 있고, 그것들의 선언 순서에 따라 수직으로 배치되어 있습니다. </li> +</ul> + +<p> 사용하기 더욱 편하도록 인터페이스를 추가적으로 사용자화 할 수 있습니다. 당신이 할 수 있는 몇 가지 일은:</p> + +<ul> + <li>목록 뷰(List views): + <ul> + <li>각각의 레코드에 표시되는 추가적인 필드/정보를 추가하기. </li> + <li>날짜나 다른 선택 값(예: 책의 대여 상태)에 기초해서 어떤 레코드들이 목록지어질 지 선택하는 필터들 추가하기.</li> + <li>목록 뷰(list views) 안의 동작 메뉴(actions menu)에 추가적인 옵션을 추가하고 이 메뉴가 양식 위의 어디에 보여져야 할 지 선택하기.</li> + </ul> + </li> + <li>세부 뷰(Detail views): + <ul> + <li>표시할(혹은 제외할)필드, 따를 순서, 그루핑, 편집 가능할지 여부, 사용될 위젯, 방향(orientation) 등등을 선택하기.</li> + <li>인라인 편집(inline editing)기능을 넣기 위해 관련된 필드들을 레코드에 추가하기(예: 책 레코드의 저자 레코드를 생성하는 동안 책 레코드를 추가하고 편집할 수 있는 기능을 추가하기).</li> + </ul> + </li> +</ul> + +<p> 이 글에서는 LocalLibrary의 인터페이스를 향상시키기 위한 몇 가지 변경점을 알아볼 겁니다. 그것들은 <code>Book</code>과 <code>Author</code> 모델 목록에 더 많은 정보를 추가하고, 그리고 그것들의 편집 보기(edit views)의 레이아웃을 향상시키는 것을 포함합니다. <code>Language</code> 와 <code>Genre</code> 모델은 변경하지 않을 겁니다. 그들은 각각 하나의 필드만을 갖고 있기 때문에 변경하는 것에 대한 이득이 없습니다!</p> + +<p> 모든 관리자 사이트 사용자화(customisation) 선택들(choices)의 완벽한 레퍼런스(reference)를 <a href="https://docs.djangoproject.com/en/2.0/ref/contrib/admin/">The Django Admin site</a>(장고 문서)에서 찾을 수 있습니다.</p> + +<h3 id="ModelAdmin_클래스_등록하기">ModelAdmin 클래스 등록하기</h3> + +<p>관리자 인터페이스에서 모델이 보여지는 방식을 바꾸고 싶다면 <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#modeladmin-objects">ModelAdmin</a> 클래스(레이아웃을 나타내는)를 정의한 후 모델 안에 등록합니다.</p> + +<p> <code>Author</code> 모델부터 시작해 봅시다. catalog 어플리케이션의 <strong>admin.py</strong> 파일을 여세요 (<strong>/locallibrary/catalog/admin.py</strong>). 원래 있던 <code>Author</code> 모델에 대한 등록(registration)을 주석처리(#를 앞에 붙여서) 하세요:</p> + +<pre class="brush: js"># admin.site.register(Author)</pre> + +<p>이제 아래와 같이 새로운 <code>AuthorAdmin</code> 과 그것의 등록(registration)을 추가하세요.</p> + +<pre class="brush: python"># Define the admin class +class AuthorAdmin(admin.ModelAdmin): + pass + +# Register the admin class with the associated model +admin.site.register(Author, AuthorAdmin) +</pre> + +<p> 이제 우리는<code> Book</code> 그리고 <code>BookInstance</code>를 위한 <code>ModelAdmin</code> 클래스를 추가할 겁니다. 또다시 원본 등록(registrations)를 주석처리 해야 합니다:</p> + +<pre class="brush: js"># admin.site.register(Book) +# admin.site.register(BookInstance)</pre> + +<p>이제 새로운 모델들을 생성하고 등록하기 위해서; 이것의 설명을 위해, 우리는 모델들을 등록(register)하기 위해 <code>@register</code> 데코레이터(decorator)를 대신 사용합니다(이것은 <code>admin.site.register()</code> 구문과 정확히 똑같은 일을 합니다):</p> + +<pre class="brush: python"># Register the Admin classes for Book using the decorator +@admin.register(Book) +class BookAdmin(admin.ModelAdmin): + pass + +# Register the Admin classes for BookInstance using the decorator +@admin.register(BookInstance) +class BookInstanceAdmin(admin.ModelAdmin): + pass +</pre> + +<p> 현재 모든 관리자 클래스들은 비어있기 때문에("<code>pass"</code>를 보세요) 관리자 행동은 바뀌지 않았을 것입니다! 이제 우리는 모델 고유의 관리자 행동(behaviour)을 정의하기 위해 이것들을 확장할 수 있습니다.</p> + +<h3 id="목록_뷰(list_view)들을_설정하기">목록 뷰(list view)들을 설정하기</h3> + +<p>현재 Locallibrary는 모델의 <code>__str__()</code> 메소드에서 생성되는 객체 이름을 사용하여 모든 저자들을 목록짓습니다. 이것은 단지 몇 명의 저자만 있을 때는 괜찮지만, 수많은 저자가 있을 때는 겹치는 이름의 서로 다른 저자가 있을 겁니다. 그것들을 구분하거나, 아니면 그저 각각의 저자마다 흥미로운 정보를 보여주고 싶다면, <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_display">list_display</a> 를 사용해서 view에 추가적인 필드를 추가할 수 있습니다. </p> + +<p>아래 코드로 <code>AuthorAdmin</code> 클래스를 대체하십시오. 목록에 선언될 이름들은 필요한 순서대로 튜플(tuple)로 선언됩니다, 아래에 보시는 것 처럼요(이것들은 원래 모델에서 특정된 것과 같은 이름들입니다).</p> + +<pre class="brush: python">class AuthorAdmin(admin.ModelAdmin): + list_display = ('last_name', 'first_name', 'date_of_birth', 'date_of_death') +</pre> + +<p> 이제 웹사이트 안의 저자 목록으로 이동하세요. 위의 필드들이 아래와 같이 보여질 것입니다:</p> + +<p><img alt="Admin Site - Improved Author List" src="https://mdn.mozillademos.org/files/14023/admin_improved_author_list.png" style="border-style: solid; border-width: 1px; display: block; height: 302px; margin: 0px auto; width: 941px;"></p> + +<p>우리의 <code>Book</code> 모델을 위해서 추가적으로 <code>author</code> 그리고 <code>genre</code>를 표시해 봅시다. <code>author</code>는 <code>Foreignkey</code> 필드 관계(일-대-다)이기 때문에, 관련된 레코드의 <code>__str__()</code> 값에 의해 나타내어질 것입니다. 아래의 버전으로 <code>BookAdmin</code> 클래스를 대체하세요.</p> + +<pre class="brush: python">class BookAdmin(admin.ModelAdmin): + list_display = ('title', 'author', 'display_genre') +</pre> + +<p> 불행하게도 <code>genre</code> 필드는 <code>ManyToManyField</code>이기 때문에 <code>list_display</code>에 직접적으로 특정할 수 없습니다.(거대한 데이터베이스 접근 "비용"이 발생할 수 있기 때문에 장고는 이것을 방지합니다). 대신 정보를 문자열로 받기 위해서 <code>display_genre</code> 함수를 정의할 겁니다(이것이 위에서 호출한 함수입니다; 아래에서 함수를 정의하겠습니다).</p> + +<div class="note"> +<p><strong>주의</strong>: <code>genre</code> 를 가져오는 것은 좋은 생각이 아닙니다. 왜냐하면 데이터베이스 작동의 "비용"이 너무 높기 때문입니다. 모델 안의 호출 함수들이 다른 이유로도 굉장히 유용하기 때문에 — 예를 들자면 리스트 안의 모든 항목에 delete 링크를 추가하는 것 처럼요.</p> +</div> + +<p>아래 코드를 <code>Book</code> 모델(<strong>models.py</strong>) 안에 추가하세요. 이것은 <code>genre</code> 필드의 첫 세 가지 값들의 문자열을 생성합니다(만약 존재한다면요) 그리고 이 메소드를 위해 관리자 사이트에서 사용될 수 있는 <code>short_description</code> 을 만듭니다.</p> + +<pre class="brush: python"> def display_genre(self): + """Create a string for the Genre. This is required to display genre in Admin.""" + return ', '.join(genre.name for genre in self.genre.all()[:3]) + + display_genre.short_description = 'Genre' +</pre> + +<p> 모델을 저장하고 관리자를 업데이트 했다면, 웹사이트를 열어서 Books 목록 페이지로 이동하세요; 아래와 같은 책 리스트가 있을 것입니다:</p> + +<p><img alt="Admin Site - Improved Book List" src="https://mdn.mozillademos.org/files/14025/admin_improved_book_list.png" style="border-style: solid; border-width: 1px; display: block; height: 337px; margin: 0px auto; width: 947px;"></p> + +<p><code>Genre</code> 모델 (그리고 <code>Language</code> 모델도, 정의했다면) 은 단일 필드를 갖고 있기 때문에, 추가적인 필드를 보여주기 위해 추가적인 모델을 생성하는 것은 의미가 없습니다.</p> + +<div class="note"> +<p><strong>주의</strong>: 최소한 상태 그리고 예상되는 반납일을 보여주는 <code>BookInstance</code> 모델 목록을 업데이트 해봅시다 . 이것은 이 글의 마지막에 도전 과제로 추가되어 있습니다!</p> +</div> + +<h3 id="목록_필터_추가하기">목록 필터 추가하기</h3> + +<p> 목록 안에 있는 항목들이 아주 많다면, 그 중에 어떤 항목들만이 표시될 지 정할 수 있는 필터를 추가하는 것이 유용합니다. 이것은 <code>list_filter</code> 속성 안에서 필드들을 목록지어서 완료됩니다. 현재 <code style="font-style: normal; font-weight: normal;">BookInstanceAdmin</code> 클래스를 아래 코드 조각으로 대체하십시오.</p> + +<pre class="brush: python">class BookInstanceAdmin(admin.ModelAdmin): +<strong> list_filter = ('status', 'due_back')</strong> +</pre> + +<p> 목록 뷰(list view)는 이제 오른쪽에 필터 상자를 갖고 있을 겁니다. 값을 필터링하기 위해 날짜와 상태를 선택하는 방법에 주의하세요:</p> + +<p><img alt="Admin Site - BookInstance List Filters" src="https://mdn.mozillademos.org/files/14037/admin_improved_bookinstance_list_filters.png" style="height: 528px; width: 960px;"></p> + +<h3 id="세뷰_보기_레이아웃(detail_view_layout)_조직하기">세뷰 보기 레이아웃(detail view layout) 조직하기</h3> + +<p> 기본적으로, detail view들은 모든 필드들을 모델에 선언된 순서대로, 수직적으로 배치합니다. 당신은 선언 순서, 표시될(혹은 제외될) 필드들, 섹션들이 정보들을 조직하는 데 사용될 지 여부, 필드들이 수직적 또는 수평적으로 표시될 지 여부 그리고 심지어 관리자 양식에서 어떤 편집 위젯이 사용될 지 까지 변경할 수 있습니다.</p> + +<div class="note"> +<p><strong>주의</strong>: LocalLibrary 모델은 상대적으로 간단하기 때문에 레이아웃을 변경할 큰 필요성이 없습니다; 그러나 방법을 보여드리기 위해, 몇 가지 변경점을 만들 것입니다.</p> +</div> + +<h4 id="어떤_필드들이_보여지고_배치될_지_제어하기">어떤 필드들이 보여지고 배치될 지 제어하기</h4> + +<p>아래와 같이 <code>fields</code> 줄(굵은 글씨)을 추가하기 위해 <code>AuthorAdmin</code> 클래스를 업데이트 하세요:</p> + +<pre class="brush: python">class AuthorAdmin(admin.ModelAdmin): + list_display = ('last_name', 'first_name', 'date_of_birth', 'date_of_death') +<strong> fields = ['first_name', 'last_name', ('date_of_birth', 'date_of_death')]</strong> +</pre> + +<p><code>fields</code> 속성은 양식에 보여져야 할 필드들만을 순서대로 목록짓습니다. 필드들은 기본적으로 수직적으로 표시되지만, 추가적으로 그것들을 튜플 안에 그룹짓는다면 수평적으로 표시됩니다(위의 "date" 필드처럼요).</p> + +<p>웹사이트 안에서 저자 세부 사항 뷰로 이동하세요 — 이제 아래와 같이 보일겁니다:</p> + +<p><img alt="Admin Site - Improved Author Detail" src="https://mdn.mozillademos.org/files/14027/admin_improved_author_detail.png" style="border-style: solid; border-width: 1px; display: block; height: 282px; margin: 0px auto; width: 928px;"></p> + +<div class="note"> +<p><strong>주의</strong>: 당신은 또한 양식(form)에서 제외되어야 할 속성들의 목록을 선언하기 위해 <code>exclude</code> 속성을 사용할 수 있습니다 (모델 안의 다른 모든 속성들이 표시될 겁니다). </p> +</div> + +<h4 id="세부_뷰_구역_나누기(Sectioning_the_detail_view)">세부 뷰 구역 나누기(Sectioning the detail view)</h4> + +<p><a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.fieldsets">fieldsets</a> 속성을 사용하여, 세부 양식 안의 연관된 모델 정보를 그룹화하기 위해 "sections"를 추가할 수 있습니다.</p> + +<p> <code>BookInstance</code> 안에서, 우리는 책이 무엇인지 (i.e. <code>name</code>, <code>imprint</code>, and <code>id</code>) 그리고 이것을 언제 빌릴 수 있는지 (<code>status</code>, <code>due_back</code>)에 대한 정보를 가지고 있습니다. 우리는 <code>BookInstanceAdmin</code> 클래스에 굵게 표시된 텍스트를 추가하여 이것들을 각각 다른 구역에 추가할 수 있습니다. </p> + +<pre class="brush: python">@admin.register(BookInstance) +class BookInstanceAdmin(admin.ModelAdmin): + list_filter = ('status', 'due_back') + +<strong> fieldsets = ( + (None, { + 'fields': ('book', 'imprint', 'id') + }), + ('Availability', { + 'fields': ('status', 'due_back') + }), + )</strong></pre> + +<p>각각의 섹션엔 고유한 제목(또는 제목을 원하지 않을 경우, <code>None</code>) 그리고 사전의 필드와 관련된 튜플이 있습니다 — 양식(format)은 묘사하기 복잡하지만, 바로 위의 코드 조각을 본다면 이해하기 상당히 쉽습니다.</p> + +<p>이제 웹사이트 안의 book instance view로 이동하세요; 이제 아래와 같은 양식이 보일겁니다:</p> + +<p><img alt="Admin Site - Improved BookInstance Detail with sections" src="https://mdn.mozillademos.org/files/14029/admin_improved_bookinstance_detail_sections.png" style="border-style: solid; border-width: 1px; display: block; height: 580px; margin: 0px auto; width: 947px;"></p> + +<h3 id="연관_레코드들의_인라인_편집(Inline_editing_of_associated_records)">연관 레코드들의 인라인 편집(Inline editing of associated records)</h3> + +<p>때때로 연관된 레코드들을 동시에 추가하는 것이 가능할 때도 있습니다. 예를 들어, 세부 사항 페이지에서 책 정보 그리고 특정한 복사본에 대한 정보 둘 다 가질 수도 있습니다.</p> + +<p>이것은 <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.inlines">inlines</a>를 선언해서 할 수 있고, <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.TabularInline">TabularInline</a> (수평적 레이아웃)타입 또는 <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.StackedInline">StackedInline</a> (기본 모델 레이아웃과 같은 수직적 레이아웃)을 선택할 수 있습니다. 아래 <code>BookAdmin</code> 근처의 굵게 표시된 줄을 추가하여 <code>BookInstance</code> 정보 인라인을 <code>Book</code> 세부 사항(detail)에 추가할 수 있습니다:</p> + +<pre class="brush: python"><strong>class BooksInstanceInline(admin.TabularInline): + model = BookInstance</strong> + +@admin.register(Book) +class BookAdmin(admin.ModelAdmin): + list_display = ('title', 'author', 'display_genre') +<strong> inlines = [BooksInstanceInline]</strong> +</pre> + +<p> 이제 웹사이트의 <code>Book</code> 을 위한 화면으로 이동하세요 — 화면의 하단에서 이제 이 책과 연관된 책 인스턴스(book instances)를 볼 수 있을 겁니다(책의 장르 필드 바로 아래에):</p> + +<p><img alt="Admin Site - Book with Inlines" src="https://mdn.mozillademos.org/files/14033/admin_improved_book_detail_inlines.png" style="border-style: solid; border-width: 1px; display: block; height: 889px; margin: 0px auto; width: 937px;"></p> + +<p> 이 경우 우리는 인라인된 모델의 모든 필드들을 추가하는, 테이블화된 인라인 클래스를 선언했습니다. 레이아웃을 위해 모든 종류의 추가적인 정보들을 지정할 수 있습니다. 표시할 필드, 그것들의 순서, 그것들이 읽기 전용인지 아닌지, 등등을 포함해서요. (더 많은 정보를 위해 <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.TabularInline">TabularInline</a> 를 보세요). </p> + +<div class="note"> +<p><strong>주의</strong>: 이 기능에는 몇 가지 고통스러운 한계가 있습니다! 위의 스크린샷에서 우리는 이미 존재하는 세 개의 책 인스턴스(book instance)와 그 아래에 새로운 책 인스턴스를 위한 세 개의 플레이스홀더(placeholder)를 가지고 있습니다(아주 비슷하게 보이지요!). 기본값으로 예비 책 인스턴스를 위한 플레이스를 홀더를 가지지 <strong>않고 </strong>새로운 북 인스턴스마다 <strong>새로운 북 인스턴스 링크 하나씩</strong> 추가하는 것이 좋습니다. 또는 <code>BookInstance</code>를 여기서는 읽기 불가(non-readable) 링크로 목록화하는 것도 좋구요. 전자는<code> BookInstanceInline</code> 모델 안의 <code>extra</code> 속성을 0으로 설정하여 완료할 수 있습니다. 직접 해보세요.</p> +</div> + +<h2 id="도전_과제">도전 과제</h2> + +<p>이 섹션에서 많은 것을 배웠기 때문에, 이젠 직접 몇 가지를 도전해 볼 차례입니다.</p> + +<ol> + <li><code>BookInstance</code> 목록 뷰에 책, 상태, 만기 날짜, 그리고 id(book, status, due back date, id)를 표시하기 위한 코드를 추가해 보세요(기본<code> __str__()</code> 텍스트 가 아닌).</li> + <li>Book/BookInstance에서 했던 것고 같은 접근법을 사용해서 Author 세부 사항 뷰에 Book 항목들의 인라인 목록(Inline listing)을 추가해 보세요.</li> +</ol> + +<ul> +</ul> + +<h2 id="요약">요약</h2> + +<p>이게 답니다! 이제 관리자 사이트를 어떻게 설정하는지에 대해 가장 간단한 방법으로 그리고 발전된 방법으로도 배웠습니다. superuser 만드는 방법, 관리자 사이트와 뷰들을 이동하는 방법, 레코드를 삭제 및 업데이트하는 방법 등등을 배웠습니다. 지금까지 여러 Books, BookInstances, Genres 그리고 Authors를 만들었고, 이것들은 우리가 우리만의 view와 탬플릿들을 만들었을 때 그곳에 목록화하여 나타낼 수 있을 것입니다.</p> + +<h2 id="추가_자료">추가 자료</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/intro/tutorial02/#introducing-the-django-admin">Writing your first Django app, part 2: Introducing the Django Admin</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/contrib/admin/">The Django Admin site</a> (Django Docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Models", "Learn/Server-side/Django/Home_page", "Learn/Server-side/Django")}}</p> + + + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> diff --git a/files/ko/learn/server-side/django/authentication/index.html b/files/ko/learn/server-side/django/authentication/index.html new file mode 100644 index 0000000000..44bb932043 --- /dev/null +++ b/files/ko/learn/server-side/django/authentication/index.html @@ -0,0 +1,707 @@ +--- +title: 'Django Tutorial Part 8: User authentication and permissions' +slug: Learn/Server-side/Django/Authentication +translation_of: Learn/Server-side/Django/Authentication +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Sessions", "Learn/Server-side/Django/Forms", "Learn/Server-side/Django")}}</div> + +<p class="summary">이 튜토리얼에서는, 당신의 사이트에 그들의 계정으로 로그인을 어떻게 허락할 것인지, 그리고 그들의 로그인 여부와 그들에게 허가한 퍼미션에 따라서 사이트에서 무엇을 할 수 있게 하거나, 볼 수 있게 할 것인지에 대해서 알려줄 것입니다. 또한 예시의 일부분으로, 우리는 이 LocalLibrary website를 확장시켜서, 로그인, 로그아웃 페이지를 덧붙이고, 사용자와 staff들이 그들이 빌려간 책들을 볼 수 있는 특별한 페이지들도 추가할 것입니다.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">선행학습:</th> + <td>앞의 모든 튜토리얼을 모두 끝내세요. up to and including <a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a>.</td> + </tr> + <tr> + <th scope="row">목표:</th> + <td>사용자 인증과 허가를 어떻게 셋업하고, 이용하는 지에 대해 이해하기</td> + </tr> + </tbody> +</table> + +<h2 id="개요">개요</h2> + +<p>Django는 인증과 권위 부여 ("허가") 시스템을 제공합니다. 이것은 <a href="/en-US/docs/Learn/Server-side/Django/Sessions">previous tutorial</a> 에서 논의된 session framework에서 찾아볼 수 있는데, 사용자의 credentials을 검증하고 사용자들이 행동들의 허가 여부를 정의합니다. 이 프레임워크는 <code>Users</code> 와 <code>Groups</code> (한 번에 한 명 이상의 유저에게 허가를 적용하는 일반적인 방법)을 위한 빌트인 모델을 포함하는데, permissions/flags는 that designate whether 사용자들이 일이나 , forms, 로그인 한 유저들의 뷰, 그리고 컨텐츠를 제한하는 뷰 툴을 조정합니다.</p> + +<div class="note"> +<p><strong>Note</strong>: Django의 인증시스템은 매우 일반적인 것을 목표로하기 때문에, 다른 웹사이트 인증시스템에서 제공하는 특정한 기능들을 제공하지 않습니다. 이런 문제에 대한 해결방법은 서드파티 솔루션을 이용하는 것입니다. 예를 들면, 로그인 시도 제한과 제3자에 대한 인증(e.g.OAuth)과 같은 기능들은 제공하지 않습니다.</p> +</div> + +<p>이 튜토리얼에서 우리는 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> website안에서의 유저 인증을 어떻게 활성화하는지 보여주고, 당신의 로그인, 로그아웃 페이지를 만들며 당신의 모델과 페이지에 대한 권한 및 그 조정에 대해서 살펴볼 것입니다. 우리는 인증과 허가(권한)을 사용해서 사용자나 사서가 빌린 책들을 표시할 것입니다.</p> + +<p>이 인증시스템은 매우 유연하므로 당신이 원한다면 단지 제공된 로그인 API를 호출하는 것만으로 scratch에서 온 당신의 URLs, forms, views와 templates를 작성할 수 있습니다. 그렇지만, 이 단계에서는 Django가 "보유한" 인증 views와 forms를 이용하여 우리의 로그인과 로그아웃페이지를 만들 것입니다. 우리는 여전히 어떤 템플릿들을 만들어야 합니다. 그러나, 이것들은 꽤 쉽습니다.</p> + +<p>우리는 어떻게 퍼미션을 만드는지, 그리고 로그인 상태와 퍼미션을 views와 templates에서 어떻게 체크하는지에 대해서도 보여줄 것입니다.</p> + +<h2 id="인증하도록_하기">인증하도록 하기</h2> + +<p>Authentication은 우리가 이미 <a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">skeleton website</a> 을 생성했을 때 (in tutorial 2) 자동적으로 생서되었으므로 이 점에서는 할 것이 없습니다..</p> + +<div class="note"> +<p><strong>Note</strong>: 설정을 위해 필요한 것들은 모두 django-admin startproject 명령을 이용하여 앱을 생성했을 때 끝났습니다. 사용자들과 모델 퍼미션을 위한 데이터베이스 테이블들은 우리가 처음으로 python manage.py migrate을 실행했을 때 만들어졌습니다.</p> +</div> + +<p>아래에서 보여주는 것과 같이, 설정은 settings.py파일에서 INSTALLED_APPS과 MIDDLEWARE 부분을 셋업하는 것입니다.</p> + +<pre class="brush: python notranslate">INSTALLED_APPS = [ + ... +<strong> 'django.contrib.auth', </strong>#Core authentication framework and its default models. +<strong> 'django.contrib.contenttypes', #</strong>Django content type system (allows permissions to be associated with models). + .... + +MIDDLEWARE = [ + ... +<strong> 'django.contrib.sessions.middleware.SessionMiddleware',</strong> #Manages sessions across requests + ... +<strong> 'django.contrib.auth.middleware.AuthenticationMiddleware',</strong> #Associates users with requests using sessions. + .... +</pre> + +<h2 id="users와_groups_만들기">users와 groups 만들기</h2> + +<p><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django admin site</a>를 tutorial 4에서 봤을 때 이미 당신은 첫번째 유저를 만들었습니다. 이는 슈퍼유저로서 <code>python manage.py createsuperuser</code>라는 명령으로 만들었죠. 우리의 슈퍼 유저는 이미 인증이 되어있고 모든 허가 권한을 가지고 있으므로, 우리는 일반적인 사이트 유저를 대표하는 테스트 유저를 만들 것입니다. 우리의 <em>locallibrary</em> groups과 website logins을 위해서 우리는 admin 사이트를 이용할 것인데, 이것이 가장 빠른 방법 중에 하나입니다.</p> + +<div class="note"> +<p>Note: 당신은 아래에서 보여주는 것처럼 프로그램적으로 사용자를 추가할 수 있습니다. 만약에 사용자가 로그인하는 사이트를 개발한다면, 당신은 이것을 해야합니다. (사용자들이 admin site를 접근하게 해서는 안됩니다.)</p> + +<pre class="brush: python notranslate">from django.contrib.auth.models import User + +# Create user and save to the database +user = User.objects.create_user('myusername', 'myemail@crazymail.com', 'mypassword') + +# Update fields and then save again +user.first_name = 'John' +user.last_name = 'Citizen' +user.save() +</pre> +</div> + +<p>아래에서 우리는 첫번째 그룹과 그 그룹의 사용자를 만들 것입니다. 아직까지는 우리의 도서관 멤버로서 아무런 퍼미션도 부여하지는 않습니다. 그러나 나중에 필요하다면, 개인들에게 각각 하는 것보다는 그룹에 한번에 하는 것이 휠씬 쉬운 일입니다.</p> + +<p>개발서버를 시작하고, 웹브라우저를 통해 admin site(<a href="http://127.0.0.1:8000/admin/">http://127.0.0.1:8000/admin/</a>)에 접속하십시오. 그리고 당신의 superuser 계정으로 로그인하십시오. admin site의 최상위 단계에서는 "Django application"에 의해 소트된 당신의 모델들이 있습니다. Authentication and Authorisation 섹션에 있는 Users or Groups 링크를 클릭하여 현재의 등록된 기록들을 볼 수 있습니다.</p> + +<p><img alt="Admin site - add groups or users" src="https://mdn.mozillademos.org/files/14091/admin_authentication_add.png" style="border-style: solid; border-width: 1px; display: block; height: 364px; margin: 0px auto; width: 661px;"></p> + +<p>첫번째로 우리 도서관 멤버를 위한 새 그룹을 만듭시다.</p> + +<ol> + <li>Add버튼(Group 다음에 있는)을 클릭하여 새 그룹을 만듭니다; "Library Members"라는 이름을 넣으세요.</li> +</ol> + +<p><img alt="Admin site - add group" src="https://mdn.mozillademos.org/files/14093/admin_authentication_add_group.png" style="border-style: solid; border-width: 1px; display: block; height: 561px; margin: 0px auto; width: 800px;"></p> + + + +<ol> + <li>우리는 그룹을 위한 어떠한 권한도 필요하지 않습니다. 그러므로 <strong>SAVE </strong>를 누르세요. (you will be taken to a list of groups).</li> +</ol> + + + +<p>자 이제 사용자(user)를 만들어봅시다 :</p> + +<ol> + <li>admin 사이트의 홈페이지로 돌아가주세요.</li> + <li>Users 옆 <strong>Add</strong> 버튼을 클릭하여 user dialog를 열어줍니다.<img alt="Admin site - add user pt1" src="https://mdn.mozillademos.org/files/14095/admin_authentication_add_user_prt1.png" style="border-style: solid; border-width: 1px; display: block; height: 409px; margin: 0px auto; width: 800px;"></li> + <li>당신의 테스트 사용자(user)를 위해 적절한 사용자이름(<strong>Username)</strong> 과 비밀번호(<strong>Password</strong>/<strong>Password confirmation)</strong> 를 입력해주세요.</li> + <li>사용자(user)를 만들기 위해<strong> SAVE</strong> 를 눌러줍시다.<br> + <br> + 관리자 사이트는 새로운 유저를 만들고, <strong>username</strong>을 바꿀 수 있고 유저모델의 선택 필드에 정보를 추가할 수 있는 <em>Change user화면으로 즉각 당신에게 보여줄 것입니다. 이 필드들은 이름, 성, 이메일 주소, 유저 상태 및 권한 (오직 </em><strong>Active </strong>표시만 가능합니다)를 포함합니다. 더 밑으로 내려가면 당신의 그룹과 권한 유저와 관련된 중요한 날짜들(예를 들어 가입일과 마지막 로그인 날짜)을 기입할 수 있습니다. <img alt="Admin site - add user pt2" src="https://mdn.mozillademos.org/files/14097/admin_authentication_add_user_prt2.png" style="border-style: solid; border-width: 1px; display: block; height: 635px; margin: 0px auto; width: 800px;"></li> + <li>그룹 섹션에서, Available groups목록에서 <strong>Library Member</strong> 를 선택하고두 박스 사이에 있는<strong> 오른쪽 화살표</strong>를 누르면 Chosen groups box로 이동이 될 거에요<img alt="Admin site - add user to group" src="https://mdn.mozillademos.org/files/14099/admin_authentication_user_add_group.png" style="border-style: solid; border-width: 1px; display: block; height: 414px; margin: 0px auto; width: 933px;"></li> + <li>여기서는 아무것도 필요치 않습니다, 그저 <strong>SAVE</strong> 를 선택하고, 유저 목록으로 가십시오.</li> +</ol> + +<p>다됬습니다! 이제 당신은 테스트를 위해 사용할 수 있는 "normal library member" 계정을 갖게 되었습니다(이들이 로그인할 수 있도록 페이지를 만들 때 말이죠).</p> + +<div class="note"> +<p><strong>Note</strong>: 다른 도서관 유저 만들기를 시도해보아야 합니다. 또한 사서들을 위한 그룹을 만들고 유저를 추가해보세요!</p> +</div> + +<h2 id="authentication_views_세팅하기">authentication views 세팅하기</h2> + +<p>Django는 authentication pages에서 login, log out, and password 조정을 위한 거의 모든 것을 제공해 줍니다. "out of the box". 이것은 URL mapper, views와 forms들을 포함하지만 templates은 포함하지 않습니다 — 우리가 만들어야 하죠!</p> + +<p>여기서, 우리는 기본 시스템들에 LocalLibrary를 통합하고, template들을 만들 수 있는 지를 볼 거에요.그리고 이것들을 프로젝트 메인 URL들에 넣을 것입니다.</p> + +<div class="note"> +<p><strong>Note</strong>: 어떤 코드도 사용하지 않으셔도 되지만, 아마 스스로 원할 가능성이 높아요. 더 쉽게 해주거든요. 만약 user model을 바꾸고자 한다면, form을 다루는 코드를 바꾸게 될 가능성이 아주 높아요. (앞으로 나올 주제에요!) 그렇다고 하더라도, stock view 함수들은 사용할 수 있어야 합니다.</p> +</div> + +<div class="note"> +<p><strong>Note: </strong>이 경우에, catalog application에 URL과 템플릿을 포함해서 인authentication page들을 넣는게 합리적입니다. 그러나 많은 application들을 가지고 있다면, 공통적으로 로그인 해야하는 것을 분리하고 사이트 전체에서 로그인하는 것을 가능하게끔하는 게 좋을겁니다. 이게 우리가 여기서 볼려고하는 것이죠!</p> +</div> + +<h3 id="Project_URLs">Project URLs</h3> + +<p> (<strong>locallibrary/locallibrary/urls.py</strong>) 파일에 다음 코드를 추가해주세요:</p> + +<pre class="brush: python notranslate">#Add Django site authentication urls (for login, logout, password management) +urlpatterns += [ + path('accounts/', include('django.contrib.auth.urls')), +] +</pre> + +<p>이 URL( <a href="http://127.0.0.1:8000/accounts/">http://127.0.0.1:8000/accounts/</a>)로 접속하세요. URL ('/'의 연결에 주의하세요!) 그러면 장고는 이 URL을 찾을 수 없기 때문에 에러메세지를 보여줄겁니다. 그리고 그 URL들을 어디에서 찾았는지 시도했던 모든 리스트들도 보여줍니다. 이것으로부터 당신은 예를들면, 어떤 URL들이 작동되는지 볼 수 있습니다.</p> + +<div class="note"> +<p><strong>Note: </strong>Using the above method adds the following URLs with names in square brackets, which can be used to reverse the URL mappings. You don't have to implement anything else — the above URL mapping automatically maps the below mentioned URLs.</p> +</div> + +<div class="note"> +<pre class="brush: python notranslate">accounts/ login/ [name='login'] +accounts/ logout/ [name='logout'] +accounts/ password_change/ [name='password_change'] +accounts/ password_change/done/ [name='password_change_done'] +accounts/ password_reset/ [name='password_reset'] +accounts/ password_reset/done/ [name='password_reset_done'] +accounts/ reset/<uidb64>/<token>/ [name='password_reset_confirm'] +accounts/ reset/done/ [name='password_reset_complete']</pre> +</div> + +<p>Now try to navigate to the login URL (<a href="http://127.0.0.1:8000/accounts/login/">http://127.0.0.1:8000/accounts/login/</a>). This will fail again, but with an error that tells you that we're missing the required template (<strong>registration/login.html</strong>) on the template search path. You'll see the following lines listed in the yellow section up the top:</p> + +<pre class="brush: python notranslate">Exception Type: TemplateDoesNotExist +Exception Value: <strong>registration/login.html</strong></pre> + +<p>The next step is to create a registration directory on the search path and then add the <strong>login.html</strong> file.</p> + +<h3 id="Template_directory">Template directory</h3> + +<p>The URLs (and implicitly views) that we just added expect to find their associated templates in a directory <strong>/registration/</strong> somewhere in the templates search path.</p> + +<p>For this site, we'll put our HTML pages in the <strong>templates/registration/</strong> directory. This directory should be in your project root directory, i.e the same directory as the <strong>catalog</strong> and <strong>locallibrary</strong> folders). Please create these folders now.</p> + +<div class="note"> +<p><strong>Note:</strong> Your folder structure should now look like the below:<br> + locallibrary (Django project folder)<br> + |_catalog<br> + |_locallibrary<br> + |_templates <strong>(new)</strong><br> + |_registration</p> +</div> + +<p>To make these directories visible to the template loader (i.e. to put this directory in the template search path) open the project settings (<strong>/locallibrary/locallibrary/settings.py</strong>), and update the <code>TEMPLATES</code> section's <code>'DIRS'</code> line as shown.</p> + +<pre class="brush: python notranslate">TEMPLATES = [ + { + ... +<strong> 'DIRS': ['./templates',],</strong> + 'APP_DIRS': True, + ... +</pre> + +<h3 id="Login_template">Login template</h3> + +<div class="warning"> +<p><strong>Important</strong>: The authentication templates provided in this article are a very basic/slightly modified version of the Django demonstration login templates. You may need to customise them for your own use!</p> +</div> + +<p>Create a new HTML file called /<strong>locallibrary/templates/registration/login.html</strong>. give it the following contents:</p> + +<pre class="brush: html notranslate">{% extends "base_generic.html" %} + +{% block content %} + +{% if form.errors %} + <p>Your username and password didn't match. Please try again.</p> +{% endif %} + +{% if next %} + {% if user.is_authenticated %} + <p>Your account doesn't have access to this page. To proceed, + please login with an account that has access.</p> + {% else %} + <p>Please login to see this page.</p> + {% endif %} +{% endif %} + +<form method="post" action="{% url 'login' %}"> +{% csrf_token %} + +<div> + <td>\{{ form.username.label_tag }}</td> + <td>\{{ form.username }}</td> +</div> +<div> + <td>\{{ form.password.label_tag }}</td> + <td>\{{ form.password }}</td> +</div> + +<div> + <input type="submit" value="login" /> + <input type="hidden" name="next" value="\{{ next }}" /> +</div> +</form> + +{# Assumes you setup the password_reset view in your URLconf #} +<p><a href="{% url 'password_reset' %}">Lost password?</a></p> + +{% endblock %}</pre> + +<p id="sect1">This template shares some similarities with the ones we've seen before — it extends our base template and overrides the <code>content</code> block. The rest of the code is fairly standard form handling code, which we will discuss in a later tutorial. All you need to know for now is that this will display a form in which you can enter your username and password and that if you enter invalid values you will be prompted to enter correct values when the page refreshes.</p> + +<p>Navigate back to the login page (<a href="http://127.0.0.1:8000/accounts/login/">http://127.0.0.1:8000/accounts/login/</a>) once you've saved your template, and you should see something like this:</p> + +<p><img alt="Library login page v1" src="https://mdn.mozillademos.org/files/14101/library_login.png" style="border-style: solid; border-width: 1px; display: block; height: 173px; margin: 0px auto; width: 441px;"></p> + +<p>If you try to log in that will succeed and you'll be redirected to another page (by default this will be <a href="http://127.0.0.1:8000/accounts/profile/">http://127.0.0.1:8000/accounts/profile/</a>). The problem here is that by default Django expects that after login you will want to be taken to a profile page, which may or may not be the case. As you haven't defined this page yet, you'll get another error!</p> + +<p>Open the project settings (<strong>/locallibrary/locallibrary/settings.py</strong>) and add the text below to the bottom. Now when you log in you should be redirected to the site homepage by default.</p> + +<pre class="brush: python notranslate"># Redirect to home URL after login (Default redirects to /accounts/profile/) +LOGIN_REDIRECT_URL = '/' +</pre> + +<h3 id="Logout_template">Logout template</h3> + +<p>If you navigate to the logout URL (<a href="http://127.0.0.1:8000/accounts/logout/">http://127.0.0.1:8000/accounts/logout/</a>) then you'll see some odd behaviour — your user will be logged out sure enough, but you'll be taken to the <strong>Admin</strong> logout page. That's not what you want, if only because the login link on that page takes you to the Admin login screen (and that is only available to users who have the <code>is_staff</code> permission).</p> + +<p>Create and open /<strong>locallibrary/templates/registration/logged_out.html</strong>. Copy in the text below:</p> + +<pre class="brush: html notranslate">{% extends "base_generic.html" %} + +{% block content %} + <p>Logged out!</p> + <a href="{% url 'login'%}">Click here to login again.</a> +{% endblock %}</pre> + +<p>This template is very simple. It just displays a message informing you that you have been logged out, and provides a link that you can press to go back to the login screen. If you go to the logout URL again you should see this page:</p> + +<p><img alt="Library logout page v1" src="https://mdn.mozillademos.org/files/14103/library_logout.png" style="border-style: solid; border-width: 1px; display: block; height: 169px; margin: 0px auto; width: 385px;"></p> + +<h3 id="Password_reset_templates">Password reset templates</h3> + +<p>The default password reset system uses email to send the user a reset link. You need to create forms to get the user's email address, send the email, allow them to enter a new password, and to note when the whole process is complete.</p> + +<p>The following templates can be used as a starting point.</p> + +<h4 id="Password_reset_form">Password reset form</h4> + +<p>This is the form used to get the user's email address (for sending the password reset email). Create <strong>/locallibrary/templates/registration/password_reset_form.html</strong>, and give it the following contents:</p> + +<pre class="brush: html notranslate">{% extends "base_generic.html" %} + +{% block content %} + <form action="" method="post"> + {% csrf_token %} + {% if form.email.errors %} + \{{ form.email.errors }} + {% endif %} + <p>\{{ form.email }}</p> + <input type="submit" class="btn btn-default btn-lg" value="Reset password"> + </form> +{% endblock %} +</pre> + +<h4 id="Password_reset_done">Password reset done</h4> + +<p>This form is displayed after your email address has been collected. Create <strong>/locallibrary/templates/registration/password_reset_done.html</strong>, and give it the following contents:</p> + +<pre class="brush: html notranslate">{% extends "base_generic.html" %} + +{% block content %} + <p>We've emailed you instructions for setting your password. If they haven't arrived in a few minutes, check your spam folder.</p> +{% endblock %} +</pre> + +<h4 id="Password_reset_email">Password reset email</h4> + +<p>This template provides the text of the HTML email containing the reset link that we will send to users. Create <strong>/locallibrary/templates/registration/password_reset_email.html</strong>, and give it the following contents:</p> + +<pre class="brush: html notranslate">Someone asked for password reset for email \{{ email }}. Follow the link below: +\{{ protocol}}://\{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} +</pre> + +<h4 id="Password_reset_confirm">Password reset confirm</h4> + +<p>This page is where you enter your new password after clicking the link in the password reset email. Create <strong>/locallibrary/templates/registration/password_reset_confirm.html</strong>, and give it the following contents:</p> + +<pre class="brush: html notranslate">{% extends "base_generic.html" %} + +{% block content %} + {% if validlink %} + <p>Please enter (and confirm) your new password.</p> + <form action="" method="post"> + {% csrf_token %} + <table> + <tr> + <td>\{{ form.new_password1.errors }} + <label for="id_new_password1">New password:</label></td> + <td>\{{ form.new_password1 }}</td> + </tr> + <tr> + <td>\{{ form.new_password2.errors }} + <label for="id_new_password2">Confirm password:</label></td> + <td>\{{ form.new_password2 }}</td> + </tr> + <tr> + <td></td> + <td><input type="submit" value="Change my password" /></td> + </tr> + </table> + </form> + {% else %} + <h1>Password reset failed</h1> + <p>The password reset link was invalid, possibly because it has already been used. Please request a new password reset.</p> + {% endif %} +{% endblock %} +</pre> + +<h4 id="Password_reset_complete">Password reset complete</h4> + +<p>This is the last password-reset template, which is displayed to notify you when the password reset has succeeded. Create <strong>/locallibrary/templates/registration/password_reset_complete.html</strong>, and give it the following contents:</p> + +<pre class="brush: html notranslate">{% extends "base_generic.html" %} + +{% block content %} + <h1>The password has been changed!</h1> + <p><a href="{% url 'login' %}">log in again?</a></p> +{% endblock %}</pre> + +<h3 id="Testing_the_new_authentication_pages">Testing the new authentication pages</h3> + +<p>Now that you've added the URL configuration and created all these templates, the authentication pages should now just work!</p> + +<p>You can test the new authentication pages by attempting to log in and then logout your superuser account using these URLs:</p> + +<ul> + <li><a href="http://127.0.0.1:8000/accounts/login/">http://127.0.0.1:8000/accounts/login/</a></li> + <li><a href="http://127.0.0.1:8000/accounts/logout/">http://127.0.0.1:8000/accounts/logout/</a></li> +</ul> + +<p>You'll be able to test the password reset functionality from the link in the login page. <strong>Be aware that Django will only send reset emails to addresses (users) that are already stored in its database!</strong></p> + +<div class="note"> +<p><strong>Note</strong>: The password reset system requires that your website supports email, which is beyond the scope of this article, so this part <strong>won't work yet</strong>. To allow testing, put the following line at the end of your settings.py file. This logs any emails sent to the console (so you can copy the password reset link from the console).</p> + +<pre class="brush: python notranslate">EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' +</pre> + +<p>For more information, see <a href="https://docs.djangoproject.com/en/2.0/topics/email/">Sending email</a> (Django docs).</p> +</div> + +<h2 id="authenticated_users_테스트_하기">authenticated users 테스트 하기</h2> + +<p>This section looks at what we can do to selectively control content the user sees based on whether they are logged in or not.</p> + +<h3 id="Testing_in_templates">Testing in templates</h3> + +<p>당신은 템플릿에서 <code>\{{ user }}</code> 라는 템플릿 변수로 현재 로그인한 사용자에 대한 정보를 얻을 수 있습니다 (이것은 우리의 스켈레톤을 만들때 프로젝트에서 세팅했을 때 템플릿 컨텍스트의 기본 값으로 추가된 것입니다).</p> + +<p>보통 처음으로 <code>\{{ user.is_authenticated }}</code> 라는 템플릿 변수를 통해서 당신은 사용자가 특정 내용을 볼 수 있는 지여부에 대해서 테스트하게 될 것입니다. 이를 시험하기 위해서, 우리는 사이트바에 로그인와 로그아웃 링크를 업데이트 할 것입니다.</p> + +<p>Open the base template (<strong>/locallibrary/catalog/templates/base_generic.html</strong>) and copy the following text into the <code>sidebar</code> block, immediately before the <code>endblock</code> template tag.</p> + +<pre class="brush: html notranslate"> <ul class="sidebar-nav"> + + ... + + <strong>{% if user.is_authenticated %}</strong> + <li>User: <strong>\{{ user.get_username }}</strong></li> + <li><a href="{% url 'logout'%}?next=\{{request.path}}">Logout</a></li> + <strong>{% else %}</strong> + <li><a href="{% url 'login'%}?next=\{{request.path}}">Login</a></li> + <strong>{% endif %} </strong> + </ul></pre> + +<p>As you can see, we use <code>if</code>-<code>else</code>-<code>endif</code> template tags to conditionally display text based on whether <code>\{{ user.is_authenticated }}</code> is true. If the user is authenticated then we know that we have a valid user, so we call <strong>\{{ user.get_username }} </strong>to display their name.</p> + +<p>We create the login and logout link URLs using the <code>url</code> template tag and the names of the respective URL configurations. Note also how we have appended <code>?next=\{{request.path}}</code> to the end of the URLs. What this does is add a URL parameter <font face="Consolas, Liberation Mono, Courier, monospace">next</font> containing the address (URL) of the <em>current</em> page, to the end of the linked URL. After the user has successfully logged in/out, the views will use this "<code>next</code>" value to redirect the user back to the page where they first clicked the login/logout link.</p> + +<div class="note"> +<p><strong>Note</strong>: Try it out! If you're on the home page and you click Login/Logout in the sidebar, then after the operation completes you should end up back on the same page.</p> +</div> + +<h3 id="Testing_in_views">Testing in views</h3> + +<p>If you're using function-based views, the easiest way to restrict access to your functions is to apply the <code>login_required</code> decorator to your view function, as shown below. If the user is logged in then your view code will execute as normal. If the user is not logged in, this will redirect to the login URL defined in the project settings (<code>settings.LOGIN_URL</code>), passing the current absolute path as the <code>next</code> URL parameter. If the user succeeds in logging in then they will be returned back to this page, but this time authenticated.</p> + +<pre class="brush: python notranslate">from django.contrib.auth.decorators import login_required + +@login_required +def my_view(request): + ...</pre> + +<div class="note"> +<p><strong>Note:</strong> You can do the same sort of thing manually by testing on <code>request.user.is_authenticated</code>, but the decorator is much more convenient!</p> +</div> + +<p>Similarly, the easiest way to restrict access to logged-in users in your class-based views is to derive from <code>LoginRequiredMixin</code>. You need to declare this mixin first in the superclass list, before the main view class.</p> + +<pre class="brush: python notranslate">from django.contrib.auth.mixins import LoginRequiredMixin + +class MyView(LoginRequiredMixin, View): + ...</pre> + +<p>This has exactly the same redirect behaviour as the <code>login_required</code> decorator. You can also specify an alternative location to redirect the user to if they are not authenticated (<code>login_url</code>), and a URL parameter name instead of "<code>next</code>" to insert the current absolute path (<code>redirect_field_name</code>).</p> + +<pre class="brush: python notranslate">class MyView(LoginRequiredMixin, View): + login_url = '/login/' + redirect_field_name = 'redirect_to' +</pre> + +<p>For additional detail, check out the <a href="https://docs.djangoproject.com/en/2.0/topics/auth/default/#limiting-access-to-logged-in-users">Django docs here</a>.</p> + +<h2 id="Example_—_listing_the_current_users_books">Example — listing the current user's books</h2> + +<p>Now that we know how to restrict a page to a particular user, let's create a view of the books that the current user has borrowed.</p> + +<p>Unfortunately, we don't yet have any way for users to borrow books! So before we can create the book list we'll first extend the <code>BookInstance</code> model to support the concept of borrowing and use the Django Admin application to loan a number of books to our test user.</p> + +<h3 id="Models">Models</h3> + +<p>First, we're going to have to make it possible for users to have a <code>BookInstance</code> on loan (we already have a <code>status</code> and a <code>due_back</code> date, but we don't yet have any association between this model and a User. We'll create one using a <code>ForeignKey</code> (one-to-many) field. We also need an easy mechanism to test whether a loaned book is overdue.</p> + +<p>Open <strong>catalog/models.py</strong>, and import the <code>User</code> model from <code>django.contrib.auth.models</code> (add this just below the previous import line at the top of the file, so <code>User</code> is available to subsequent code that makes use of it):</p> + +<pre class="brush: python notranslate">from django.contrib.auth.models import User +</pre> + +<p>Next, add the <code>borrower</code> field to the <code>BookInstance</code> model:</p> + +<pre class="brush: python notranslate">borrower = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True) +</pre> + +<p>While we're here, let's add a property that we can call from our templates to tell if a particular book instance is overdue. While we could calculate this in the template itself, using a <a href="https://docs.python.org/3/library/functions.html#property">property</a> as shown below will be much more efficient.</p> + +<p>Add this somewhere near the top of the file:</p> + +<pre class="brush: python notranslate">from datetime import date</pre> + +<p class="brush: python">Now add the following property definition to the <code>BookInstance</code> class:</p> + +<pre class="brush: python notranslate">@property +def is_overdue(self): + if self.due_back and date.today() > self.due_back: + return True + return False</pre> + +<div class="note"> +<p><strong>Note</strong>: We first verify whether <code>due_back</code> is empty before making a comparison. An empty <code>due_back</code> field would cause Django to throw an error instead of showing the page: empty values are not comparable. This is not something we would want our users to experience!</p> +</div> + +<p>Now that we've updated our models, we'll need to make fresh migrations on the project and then apply those migrations:</p> + +<pre class="brush: bash notranslate">python3 manage.py makemigrations +python3 manage.py migrate +</pre> + +<h3 id="Admin">Admin</h3> + +<p>Now open <strong>catalog/admin.py</strong>, and add the <code>borrower</code> field to the <code>BookInstanceAdmin</code> class in both the <code>list_display</code> and the <code>fieldsets</code> as shown below. This will make the field visible in the Admin section so that we can assign a <code>User</code> to a <code>BookInstance</code> when needed.</p> + +<pre class="brush: python notranslate">@admin.register(BookInstance) +class BookInstanceAdmin(admin.ModelAdmin): + list_display = ('book', 'status'<strong>, 'borrower'</strong>, 'due_back', 'id') + list_filter = ('status', 'due_back') + + fieldsets = ( + (None, { + 'fields': ('book','imprint', 'id') + }), + ('Availability', { + 'fields': ('status', 'due_back'<strong>,'borrower'</strong>) + }), + )</pre> + +<h3 id="Loan_a_few_books">Loan a few books</h3> + +<p>Now that its possible to loan books to a specific user, go and loan out a number of <code>BookInstance</code> records. Set their <code>borrowed</code> field to your test user, make the <code>status</code> "On loan" and set due dates both in the future and the past.</p> + +<div class="note"> +<p><strong>Note</strong>: We won't spell the process out, as you already know how to use the Admin site!</p> +</div> + +<h3 id="On_loan_view">On loan view</h3> + +<p>Now we'll add a view for getting the list of all books that have been loaned to the current user. We'll use the same generic class-based list view we're familiar with, but this time we'll also import and derive from <code>LoginRequiredMixin</code>, so that only a logged in user can call this view. We will also choose to declare a <code>template_name</code>, rather than using the default, because we may end up having a few different lists of BookInstance records, with different views and templates.</p> + +<p>Add the following to catalog/views.py:</p> + +<pre class="brush: python notranslate">from django.contrib.auth.mixins import LoginRequiredMixin + +class LoanedBooksByUserListView(LoginRequiredMixin,generic.ListView): + """Generic class-based view listing books on loan to current user.""" + model = BookInstance + template_name ='catalog/bookinstance_list_borrowed_user.html' + paginate_by = 10 + + def get_queryset(self): + return BookInstance.objects.filter(borrower=self.request.user).filter(status__exact='o').order_by('due_back')</pre> + +<p>In order to restrict our query to just the <code>BookInstance</code> objects for the current user, we re-implement <code>get_queryset()</code> as shown above. Note that "o" is the stored code for "on loan" and we order by the <code>due_back</code> date so that the oldest items are displayed first.</p> + +<h3 id="URL_conf_for_on_loan_books">URL conf for on loan books</h3> + +<p>Now open <strong>/catalog/urls.py</strong> and add a<code>path()</code> pointing to the above view (you can just copy the text below to the end of the file).</p> + +<pre class="brush: python notranslate">urlpatterns += [ + path('mybooks/', views.LoanedBooksByUserListView.as_view(), name='my-borrowed'), +]</pre> + +<h3 id="Template_for_on-loan_books">Template for on-loan books</h3> + +<p>Now, all we need to do for this page is add a template. First, create the template file <strong>/catalog/templates/catalog/bookinstance_list_borrowed_user.html</strong> and give it the following contents:</p> + +<pre class="brush: python notranslate">{% extends "base_generic.html" %} + +{% block content %} + <h1>Borrowed books</h1> + + {% if bookinstance_list %} + <ul> + + {% for bookinst in bookinstance_list %} + <li class="{% if bookinst.is_overdue %}text-danger{% endif %}"> + <a href="{% url 'book-detail' bookinst.book.pk %}">\{{bookinst.book.title}}</a> (\{{ bookinst.due_back }}) + </li> + {% endfor %} + </ul> + + {% else %} + <p>There are no books borrowed.</p> + {% endif %} +{% endblock %}</pre> + +<p>This template is very similar to those we've created previously for the <code>Book</code> and <code>Author</code> objects. The only thing "new" here is that we check the method we added in the model <code>(bookinst.is_overdue</code>) and use it to change the colour of overdue items.</p> + +<p>When the development server is running, you should now be able to view the list for a logged in user in your browser at <a href="http://127.0.0.1:8000/catalog/mybooks/">http://127.0.0.1:8000/catalog/mybooks/</a>. Try this out with your user logged in and logged out (in the second case, you should be redirected to the login page).</p> + +<h3 id="Add_the_list_to_the_sidebar">Add the list to the sidebar</h3> + +<p>The very last step is to add a link for this new page into the sidebar. We'll put this in the same section where we display other information for the logged in user.</p> + +<p>Open the base template (<strong>/locallibrary/catalog/templates/base_generic.html</strong>) and add the line in bold to the sidebar as shown.</p> + +<pre class="brush: python notranslate"> <ul class="sidebar-nav"> + {% if user.is_authenticated %} + <li>User: \{{ user.get_username }}</li> +<strong> <li><a href="{% url 'my-borrowed' %}">My Borrowed</a></li></strong> + <li><a href="{% url 'logout'%}?next=\{{request.path}}">Logout</a></li> + {% else %} + <li><a href="{% url 'login'%}?next=\{{request.path}}">Login</a></li> + {% endif %} + </ul> +</pre> + +<h3 id="What_does_it_look_like">What does it look like?</h3> + +<p>When any user is logged in, they'll see the <em>My Borrowed</em> link in the sidebar, and the list of books displayed as below (the first book has no due date, which is a bug we hope to fix in a later tutorial!).</p> + +<p><img alt="Library - borrowed books by user" src="https://mdn.mozillademos.org/files/14105/library_borrowed_by_user.png" style="border-style: solid; border-width: 1px; display: block; height: 215px; margin: 0px auto; width: 530px;"></p> + +<h2 id="허가">허가</h2> + +<p>Permissions 는 모델과 연관되어 허가를 가진 유저에 의한 model instance 작업들을 정의하게 됩니다. 기본적으로 Django 자동적으로 <em>add</em>, <em>change</em>, 그리고 <em>delete</em> permissions 을 모든 모델에 제공하는데, 유저가 관리자 사이트를 통해서 (권한)허가를 가지고 연고나도니 작업들을 하게 합니다. 당신은 권한을 모델이나 특정 유저에게 부여하도록 정의할 수 있습니다. You can also change the permissions associated with different instances of the same model.</p> + +<p>Testing on permissions in views and templates is then very similar for testing on the authentication status (and in fact, testing for a permission also tests for authentication).</p> + +<h3 id="Models_2">Models</h3> + +<p>Defining permissions is done on the model "<code>class Meta</code>" section, using the <code>permissions</code> field. You can specify as many permissions as you need in a tuple, each permission itself being defined in a nested tuple containing the permission name and permission display value. For example, we might define a permission to allow a user to mark that a book has been returned as shown:</p> + +<pre class="brush: python notranslate">class BookInstance(models.Model): + ... + class Meta: + ... +<strong> permissions = (("can_mark_returned", "Set book as returned"),) </strong> </pre> + +<p>We could then assign the permission to a "Librarian" group in the Admin site.</p> + +<p>Open the <strong>catalog/models.py</strong>, and add the permission as shown above. You will need to re-run your migrations (call <code>python3 manage.py makemigrations</code> and <code>python3 manage.py migrate</code>) to update the database appropriately.</p> + +<h3 id="Templates">Templates</h3> + +<p>The current user's permissions are stored in a template variable called <code>\{{ perms }}</code>. You can check whether the current user has a particular permission using the specific variable name within the associated Django "app" — e.g. <code>\{{ perms.catalog.can_mark_returned }}</code> will be <code>True</code> if the user has this permission, and <code>False</code> otherwise. We typically test for the permission using the template <code>{% if %}</code> tag as shown:</p> + +<pre class="brush: python notranslate">{% if perms.catalog.<code>can_mark_returned</code> %} + <!-- We can mark a BookInstance as returned. --> + <!-- Perhaps add code to link to a "book return" view here. --> +{% endif %} +</pre> + +<h3 id="Views">Views</h3> + +<p>Permissions can be tested in function view using the <code>permission_required</code> decorator or in a class-based view using the <code>PermissionRequiredMixin</code>. The pattern and behaviour are the same as for login authentication, though of course, you might reasonably have to add multiple permissions.</p> + +<p>Function view decorator:</p> + +<pre class="brush: python notranslate">from django.contrib.auth.decorators import permission_required + +@permission_required('catalog.<code>can_mark_returned</code>') +@permission_required('catalog.<code>can_edit</code>') +def my_view(request): + ...</pre> + +<p>A permission-required mixin for class-based views.</p> + +<pre class="brush: python notranslate">from django.contrib.auth.mixins import PermissionRequiredMixin + +class MyView(PermissionRequiredMixin, View): + permission_required = 'catalog.<code>can_mark_returned</code>' + # Or multiple permissions + permission_required = ('catalog.<code>can_mark_returned</code>', 'catalog.can_edit') + # Note that 'catalog.can_edit' is just an example + # the catalog application doesn't have such permission!</pre> + +<h3 id="Example">Example</h3> + +<p>We won't update the <em>LocalLibrary</em> here; perhaps in the next tutorial!</p> + +<h2 id="Challenge_yourself">Challenge yourself</h2> + +<p>Earlier in this article, we showed you how to create a page for the current user listing the books that they have borrowed. The challenge now is to create a similar page that is only visible for librarians, that displays <em>all</em> books that have been borrowed, and which includes the name of each borrower.</p> + +<p>You should be able to follow the same pattern as for the other view. The main difference is that you'll need to restrict the view to only librarians. You could do this based on whether the user is a staff member (function decorator: <code>staff_member_required</code>, template variable: <code>user.is_staff</code>) but we recommend that you instead use the <code>can_mark_returned</code> permission and <code>PermissionRequiredMixin</code>, as described in the previous section.</p> + +<div class="warning"> +<p><strong>Important</strong>: Remember not to use your superuser for permissions based testing (permission checks always return true for superusers, even if a permission has not yet been defined!). Instead, create a librarian user, and add the required capability.</p> +</div> + +<p>When you are finished, your page should look something like the screenshot below.</p> + +<p><img alt="All borrowed books, restricted to librarian" src="https://mdn.mozillademos.org/files/14115/library_borrowed_all.png" style="border-style: solid; border-width: 1px; display: block; height: 283px; margin: 0px auto; width: 500px;"></p> + +<ul> +</ul> + +<h2 id="Summary">Summary</h2> + +<p>Excellent work — you've now created a website that library members can log in into and view their own content and that librarians (with the correct permission) can use to view all loaned books and their borrowers. At the moment we're still just viewing content, but the same principles and techniques are used when you want to start modifying and adding data.</p> + +<p>In our next article, we'll look at how you can use Django forms to collect user input, and then start modifying some of our stored data.</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/auth/">User authentication in Django</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/auth/default//">Using the (default) Django authentication system</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/intro/#decorating-class-based-views">Introduction to class-based views > Decorating class-based views</a> (Django docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Sessions", "Learn/Server-side/Django/Forms", "Learn/Server-side/Django")}}</p> + + + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> diff --git a/files/ko/learn/server-side/django/deployment/index.html b/files/ko/learn/server-side/django/deployment/index.html new file mode 100644 index 0000000000..9ea3f870e5 --- /dev/null +++ b/files/ko/learn/server-side/django/deployment/index.html @@ -0,0 +1,685 @@ +--- +title: 'Django 튜토리얼 파트 11: Django 웹사이트 공개하기' +slug: Learn/Server-side/Django/Deployment +tags: + - Django deployment + - django + - heroku + - whitenoise + - 웹 서버 + - 장고 + - 장고 배포 +translation_of: Learn/Server-side/Django/Deployment +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Testing", "Learn/Server-side/Django/web_application_security", "Learn/Server-side/Django")}}</div> + +<p class="summary">당신은 이제 놀라운 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 웹사이트를 (테스트도 마치고) 만들었으니, 도서관 스태프나 회원들이 인터넷을 통해 이용할 수 있도록 공개된 웹 서버에 설치하길 원할 것이다. 이번 장에서는 웹사이트를 배포할수 있는 호스트를 살펴보는 방법에 대한 개요와 사이트를 실제 운운영하기위해 필요한 것들에 대해 설명한다.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">사전학습:</th> + <td>아래 파트를 포함하여 앞선 모든 튜토리얼 파트의 학습을 완료할 것. <a href="/en-US/docs/Learn/Server-side/Django/Testing">Django 튜토리얼 파트 10: Django 웹 어플리케이션 테스트하기</a></td> + </tr> + <tr> + <th scope="row">학습목표:</th> + <td>Django 앱을 공개 운영할 수 있는 네트워크상의 장소와 방법을 배우기.</td> + </tr> + </tbody> +</table> + +<h2 id="개요">개요</h2> + +<p>일단 사이트 개발이 완료되면( 혹은 공개 테스트를 시작할 정도로 "충분히" 완료되었다면 ) 개인 컴퓨터 환경보다는 좀더 공개되고 접근성이 있는 네트워크상의 장소가 필요할 것이다.</p> + +<p>이제까지는, Django 개발용 웹 서버를 사용하여 사이트를 로컬 브라우저/네트워크 범위 내에서 공개하고, 암호를 포함한 개인정보및 디버깅정보가 노출되는 (보안이 부실한) 개발용 환경 설정으로 웹사이트를 실행하면서, 개발환경에서만 작업을 해왔다. 외부에서 웹사이트를 운영하기 전에 해야할 일은 다음과 같다:</p> + +<ul> + <li>프로젝트 설정에서 몇가지를 고쳐야 한다.</li> + <li>Django 앱을 호스팅할 환경을 선택해야 한다.</li> + <li>정적 파일들(static files)을 호스팅할 환경을 선택해야 한다.</li> + <li>웹 사이트를 서비스할 공개단계 레벨의 기반환경을 구축한다.</li> +</ul> + +<p>이 튜토리얼은 호스팅사이트를 선택하기위한 몇가지 지침과 Django 앱을 공개하는데 대비하기 위해 필요한 사항에 대한 간단한 개요및 LocalLibrary 웹사이트를 <a href="https://www.heroku.com/">Heroku</a> 클라우드 호스팅 서비스 위에서 운영되도록 설치하는 방법에 대해 동작하는 예제를 제공한다.</p> + +<h2 id="운영환경_(Production_envrionment)_이란">운영환경 (Production envrionment) 이란?</h2> + +<p>운영환경은 외부공개를 위한 웹사이트를 운영하는 서버 컴퓨터로 부터 제공되는 환경이다. 운영환경은 다음 요소로 구성된다 :</p> + +<ul> + <li>웹사이트가 실행되는 컴퓨터 하드웨어.</li> + <li>Linux, Windows와 같은 운영체제.</li> + <li>웹 사이트를 작성한 프로그래밍 언어 구동 환경(runtime)과 프레임웍 라이브러리.</li> + <li>콘텐츠및 페이지를 서비스 하기위한 Nginx, Apache와 같은 웹 서버.</li> + <li>Django 웹사이트와 웹서버간에 "동적인" request 를 전달하기위한 어플리케이션 서버.</li> + <li>웹사이트의 정보를 저장하는 데이타베이스.</li> +</ul> + +<div class="note"> +<p><strong>주목할점</strong>: 운영환경의 설정에 따라 역방향 프록시(reverse proxy)나 로드 밸런서(load balancer)등이 추가될 수도 있다.</p> +</div> + +<p>서버 컴퓨터는 빠른 인터넷으로 연결되어 당신의 사무실내에 위치할 수도 있지만, 보통 "클라우드 상에서" 사용하는것이 훨씬 더 흔한 방법이다. 이말은 실제로는 당신의 코드가 호스팅 업체의 데이터센터에 위치한 어떤 원격 컴퓨터에서(혹은 아마도 "가상환경의" 컴퓨터상에서) 실행된다는 것을 의미한다. 원격지 서버는 대개는 어느 정도 검증된 수준의 컴퓨터 자원(예를들면, CPU, 램, 저장 메모리 공간등)과 가격대별 인터넷 연결을 제공한다.</p> + +<p>이런 종류의 원격 접속가능한 컴퓨터/네트워크 하드웨어를 IaaS ( <em>Infrastructure as a Service) 라고 줄여 부른다</em>. 많은 IaaS 업체들은 미리 설치된 특정 OS 환경에 대한 여러가지 옵션을 제공하는데 당신은 그 위에 운영환경에 필요한 것들을 설치해야 한다. 또 다른 업체는 장고와 웹서버등을 포함하여 완전히 갖추어진 환경을 선택할수 있도록 옵션을 제공하기도 한다.</p> + +<div class="note"> +<p><strong>주목할점:</strong> 미리 구성된 환경에서는 환경설정에 대한 부담을 덜 수 있어서 웹사이트 구성을 매우 쉽게할 수 있지만, 익숙하지 않은 서버환경이나 다른 콤포넌트 때문에, 혹은 구형 OS 버전 때문에 선택가능한 구성수단이 줄어들 수도 있다. 많은 경우에, 당신이 원하는 결과를 얻기 위해, 당신이 직접 필요한 콤포넌트를 설치하는 것이 낫다. 그러면 시스템 업그레이드가 필요한 경우에, 어떻게 시작해야 하는지 알아챌 수 있을 것이다 !</p> +</div> + +<p>다른 호스팅 업체들은 Django를 플랫폼의 일부분으로 지원하는데 이런 업체를 PaaS( <em>Platform as a Service)로 줄여 부른다</em>. 이런 종류의 호스팅에서는 호스트 플랫폼이 (어플리케이션의 규모의 변화에 따라 조정해야하는 거의 모든 것을 포함하여) 대신 관리해주므로 운영환경(웹 서버, 어플리케이션 서버, 로드 밸런서등)에 대해 거의 고민할 필요가 없다. 이 경우, 다른 서버 기반환경에 신경쓸 필요없이, 웹 어플리케이션에만 집중할수 있기 때문에 웹사이트 배포가 엄청 쉽다.</p> + +<p>어떤 개발자들은 PaaS에 비해 좀더 높은 자유도를 가진 IaaS를 선택하는 반면, 다른 개발자들은 관리부담이 덜하고 웹사이트 규모를 쉽게 조정할수 있는 PaaS를 선택할 것이다. 당신은 이제 막 개발을 시작했기에, PaaS에 웹사이트를 설정하는 것이 더 쉬울 것이다. 그리고 그것이 이 튜토리얼에서 우리가 공부할 내용이다.</p> + +<div class="note"> +<p><strong>한가지 팁:</strong> Python/Django 친화적인 호스팅 업체를 선정했다면, 웹서버,어플리케이션 서버, 역방향 프록시( 혹시 PaaS 업체를 선정했다면 별로 신경쓸 필요없는 내용임)의 여러가지 설정을 이용한 Django 웹사이트를 구성하는 방법에 대한 설명이 제공될 것이다. 예를 들면, 이곳 ( <a href="https://www.digitalocean.com/community/tutorials?q=django">Digital Ocean Django community docs</a> ) 에는 다양한 설정에 대한 단계적인 가이드가 있다.</p> +</div> + +<h2 id="호스팅_업체_선정하기">호스팅 업체 선정하기</h2> + +<p>Django에 대한 지원이 활발하게 이루어지거나 Django가 잘 동작한다고 알려진 호스팅 업체는 약 100여곳이 있다. ( 꽤 광범위한 목록을 <a href="http://djangofriendly.com/hosts/">Djangofriendly hosts</a> 에서 찾아볼 수 있다.) 이 업체들은 서로 다른 타입(IaaS, PaaS)이거나 서로 다른 가격대에 여러가지 수준의 컴퓨팅/네트워크 자원을 제공한다.</p> + +<p>호스팅 업체를 선정할 때 고려해야할 몇가지는 다음과 같다:</p> + +<ul> + <li>당신의 사이트가 얼마나 바쁘게 돌아갈 것인지와 이 요구조건을 감당할수 있는 데이터와 컴퓨팅 자원의 비용 수준.</li> + <li>수평적인(같은 머신을 더 많이 사용하기) 혹은 수직적인(좀더 고성능의 머신을 사용하기) 사이트 규모조정 수준과 이 작업에 필요한 비용.</li> + <li>공급자의 데이터 센터가 위치한 장소와 그에 따른 가장 빠르게 접속할 수 있는 장소.</li> + <li>호스트의 역대 가동시간및 가동중지시간 상의 성능.</li> + <li>사이트 관리용으로 제공되는 도구 - 가 사용하기 쉬운지와 안전한지 ( 예를들면 SFTP vs FTP )</li> + <li>당신의 서버를 모니터링 하기위하 내장 프레임워크.</li> + <li>알려진 제한사항들. 어떤 호스트 업체는 의도적 특정 서비스(예를 들면 email)를 제한한다. 또 다른 업체는 특정 가격대에 일정 시간의 "가동시간"이나 일정량의 저장공간만을 제공한다.</li> + <li>추가적인 혜택들. 어떤 업체들은 다른곳에서는 유료로 지원되는 도메인 네임과 SSL인증에 대한 지원을 무료로 제공한다. </li> + <li>당신이 사용하던 "무료"계정이 시간초과로 중지되거나 좀더 비싼 계정으로 이전하는데 비용들거나 하게된다는 것은 처음에 선택한 호스팅 서비스와 다른 선택을 했다면 웹사이트가 좀더 성공적일 수도 있었다는 것을 의미한다! </li> +</ul> + +<p>당신이 개발을 시작했다면 좋은 소식은 "평가용", "개발자용"혹은 "취미개발용" 컴퓨터 환경을 "무료"로 제공하는 업체가 꽤 있다는 것이다. 이들은 항상 자원을 아주 제한적으로만 사용할 수 있고, 처음 신규 가입 기간이 지나 중지되지 않는지 주의를 기울여야 한다. 하지만, 트래픽이 낮은 사이트를 실제 운영환경에서 테스트 하기에 좋으며, 사이트가 좀더 바빠질 경우 더 많은 자원을 제공하는 유료환경으로 쉽게 이전할 수 있다.이런 종류의 환경으로 인기있는 곳은 <a href="https://www.heroku.com/">Heroku</a>, <a href="https://www.pythonanywhere.com/">Python Anywhere</a>, <a href="http://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/billing-free-tier.html">Amazon Web Services</a>, <a href="https://azure.microsoft.com/en-us/pricing/details/app-service/">Microsoft Azure</a> 등등이다.</p> + +<p>많은 업체가 좀더 쓸만한 수준의 컴퓨팅 파워와 제한사항을 완화한 "기본"계정을 가지고 있다. <a href="https://www.digitalocean.com/">Digital Ocean</a> 과 <a href="https://www.pythonanywhere.com/">Python Anywhere</a> 는 상대적으로 비용이 낮은( 1달에 5~10 USD 가격의) 기본 계정을 제공하는 인기있는 호스팅 업체의 대표적인 예이다.</p> + +<div class="note"> +<p><strong>주목할점:</strong> 사용가격이 유일한 선택기준이 아니라는 것을 명심하라. 당신의 웹사이트가 성공적이라면 사이트의 확장성이 가장 중요한 기준이 될것이다.</p> +</div> + +<h2 id="웹사이트에서_공개(publish)준비_하기">웹사이트에서 공개(publish)준비 하기</h2> + +<p>django-admin과 manage.py 도구로 생성된 <a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django 뼈대 웹사이트</a>는 개발환경에 최적화 되어 설정되었다. 개발환경이 아닌 운영환경에서는 ( <strong>settings.py</strong>에 설정되어 있는 ) Django 프로젝트 설정의 많은 부분이, 보안상 혹은 성능상 이유로, 변경되어야 한다.</p> + +<div class="note"> +<p><strong>한가지 팁:</strong> 운영환경용으로 별도의 <strong>settings.py</strong> 파일을 유지하고 민감한 설정들은 별도의 파일이나 시스템의 환경변수에 저장하는 것이 일반적이다. 소스코드의 다른 부분은 공개된 저장소에 보관하더라도, 이 파일은 격리되어 보호되어야 한다. </p> +</div> + +<p>반드시 체크되어야 할 가장 중요한 설정은 다음과 같다 :</p> + +<ul> + <li><code>DEBUG</code>. 이 설정은 운영환경에서 <code>False</code> 로 설정되어야 한다 (<code>DEBUG = False</code>). 이 설정은 민감하고 / 보호가 필요한 디버그 정보나 변수정보가 외부로 보여지지 않도록 막는다.</li> + <li><code>SECRET_KEY</code>. CRSF 보안등을 위해 사용되는 큰 숫자의 랜덤 값이다. 운영환경에서 사용되는 key 값이 형상관리툴에 등록되지 않도록 하거나 운영 서버 밖에서 접근할 수 없도록 관리하는 것이 중요하다. Django 문서에서는 이 값을 환경 변수에서 로딩하거나 serve-only 파일에서 읽어오도록 제안하고 있다. + <pre># Read SECRET_KEY from an environment variable +import os +SECRET_KEY = os.environ['SECRET_KEY'] + +#OR + +#Read secret key from a file +with open('/etc/secret_key.txt') as f: + SECRET_KEY = f.read().strip()</pre> + </li> +</ul> + +<p><em> </em><code>SECRET_KEY</code><em> 와 </em><code>DEBUG</code><em> 변수를, 이 변수들이 정의되어 있다면, 시스템의 환경 변수에서 읽어오도록 LocalLibrary</em> 어플리케이션을 수정하자. 정의되어 있지 않다면 설정 파일의 디폴트 값을 사용하도록 한다.</p> + +<p><strong>/locallibrary/settings.py</strong> 파일을 열고, 기존의 <code>SECRET_KEY</code> 설정을 비활성화 하고 아래 코드에서 <strong>bold</strong>체로 보이는 부분을 추가한다. 개발 과정중에는 보통 key와 관련하여 환경변수가 설정되지 않으므로 초기값이 사용되고 있을것이다. ( 키가 노출되면 당신이 그 키를 운영환경에 사용하지는 않을것이므로 여기서 어떤키를 사용하는지는 중요하지 않다).</p> + +<pre class="brush: python"># SECURITY WARNING: keep the secret key used in production secret! +# SECRET_KEY = 'cg#p$g+j9tax!#a3cup@1$8obt2_+&k3q+pmu)5%asj6yjpkag' +<strong>import os</strong> +<strong>SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'cg#p$g+j9tax!#a3cup@1$8obt2_+&k3q+pmu)5%asj6yjpkag')</strong> +</pre> + +<p>그리고나서 기존의 <code>DEBUG</code> 설정을 주석처리 하고 신규 라인을 아래와 같이 추가 하라.</p> + +<pre class="brush: python"># SECURITY WARNING: don't run with debug turned on in production! +# DEBUG = True +<strong>DEBUG = bool( os.environ.get('DJANGO_DEBUG', True) )</strong> +</pre> + +<p><code>DEBUG</code> 의 초기값은 <code>True</code> 이지만, <code>DJANGO_DEBUG</code> 환경변수가 비어있는 문자열로 설정 되면 (즉, <code>DJANGO_DEBUG=''</code> 와 같이) <code>False</code> 로 설정될 것이다.</p> + +<div class="note"> +<p><strong>주목할 점</strong>: <code>DJANGO_DEBUG</code> 환경변수를 "값을 가진 문자열"이나 "빈 문자열" 로 설정하기 보다, 그냥 <code>True</code> 나 <code>False</code> 로 (각각) 직접 설정할 수 있다면 이런 방식이 좀더 직관적으로 보일것이다. 하지만 불행히도 환경변수는 파이썬 문자열로 저장되며, <code>False</code> 로 평가받을수 있는 문자열은 "빈 문자열"이 유일하다.</p> + +<p>역자주: os.environ.get의 사용법에 대해서는 이 <a href="https://bugs.python.org/msg277165">링크</a>를 참조 하라</p> +</div> + +<p>변경해야할 설정의 전체 체크리스트는 <a href="https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/">배포 체크리스트</a> (장고 문서) 로 제공된다. 아래 터미널 명령으로도 몇몇 목록을 뽑을 수 있다:</p> + +<pre class="brush: python">python3 manage.py check --deploy +</pre> + +<h2 id="예제_LocalLibrary를_Heroku에_설치하기">예제: LocalLibrary를 Heroku에 설치하기</h2> + +<p>이번 섹션에서는 <a href="http://heroku.com">Heroku PaaS 클라우드</a> 에 LocalLibrary를 설치하는 방법에 대한 실제적인 예제를 제공한다.</p> + +<h3 id="왜_Heroku_인가">왜 Heroku 인가?</h3> + +<p>Heroku는 현재까지 가장 오래 운영된 서비스들중 하나이며, 인기있는 클라우드기반 PaaS 서비스이다. 원래는 Ruby 앱들만 지원했지만, 현재는 많은 프로그래밍 환경의 앱들을 지원할수 있으며, 여기에는 Django 또한 포함된다!</p> + +<p>우리는 다음과 같은 이유로 Heroku 를 사용하기로 결정했다:</p> + +<ul> + <li>Heroku는 정말 돈 낼 필요가 없는 <a href="https://www.heroku.com/pricing">무료 단계 (free tier)</a> 를 지원한다. (물론 약간의 제약이 있긴하다).</li> + <li>Heroku는 PaaS 개념의 서비스로 많은 웹 기반환경의 관리를 제공한다. 서버관리, 로드 밸런싱, 역방향 프록시등 여러가지 웹 기반환경들을 Heroku가 내부적으로 모두 제공하므로 이에 대한 걱정을 덜고 쉽게 개발을 시작할수 있다.</li> + <li>제약이 있긴하지만, 이 제약들은 우리가 진행하려는 어플리케이션 개발에는 영향이 없을 것이다. 예를 들면 다음과 같다: + <ul> + <li>무료 단계의 Heroku는 활성주기가 짧은 저장공간을 제공하므로 유저가 업로드한 파일을 Heroku 자체에 안전하게 저장할 수는 없다.</li> + <li>무료 단계에서는 30분동안 아무런 요청도 없다면 웹 앱은 비활성화 될 것이다. 이 후에 요청이 오면 응답하는데 몇 초정도 약간의 시간이 더 필요하게 될것이다. </li> + <li>무료 단계에서는 웹 사이트의 동작 가능 시간이 매월 특정시간 만큼으로 제한된다 ( 사이트가 "비활성(asleep)"상태인 경우의 시간은 제외된다). 이 제약은 사용빈도가 낮고/ 예제 확인용 사이트인 경우는 문제가 되지 않지만, 계속 활성화 상태가 필요한 앱의 경우에는 적합하지 않을것이다.</li> + <li>다른 제약 사항은 <a href="https://devcenter.heroku.com/articles/limits">Limits</a> (Heroku 문서) 에서 찾아볼 수 있다.</li> + </ul> + </li> + <li>대부분의 경우 문제없이 사용할 수 있을것이고, 당신이 결국 Heroku에 만족하게 되었다면, 당신의 앱을 확장하는 것도 매우 쉽다.</li> +</ul> + +<p>Heroku로 이 예제를 호스팅 하는데는 부족함이 없겠지만, 당신이 개발하고자 하는 실제 웹사이트의 요구조건에는 부족할 수도 있다. Heroku는 설치하여 사용하고 확장하기는 쉽지만 대신 모든 요구조건을 충족할 정도로 유연하지는 않으며 일단 무료 단계를 벗어나면 당신의 요구조건을 만족시키기 위해 좀더 비싼 비용을 요구할 가능성이 잠재되어있다.</p> + +<h3 id="Heroku는_어떻게_동작하는가">Heroku는 어떻게 동작하는가?</h3> + +<p>Heroku는 Django 웹사이트를 한 개이상의 "<a href="https://devcenter.heroku.com/articles/dynos">다이노(Dyno</a>)"에서 실행한다. 다이노란 고립적이고, 가상화된 Unix 콘테이너이며 어플리케이션을 실행하는데 필요한 환경을 제공한다. 다이노는 완전히 고립적이며 <em>ephemeral </em>( <em>다이노가 재시작될 때마다 깨끗이 비워지는 단기 수명의 )</em> 파일 시스템을 가지고 있다. 다이노간에 공유하는 유일한 항목은 어플리케이션 <a href="https://devcenter.heroku.com/articles/config-vars">설정 변수</a> 이다. Heroku 는 웹 트래픽을 모든 "웹" 다이노들로 분배하기 위해 내부적으로 로드 밸런서를 사용한다. 다이노들간에 공유하는것이 없기때문에 Heroku는 단지 다이노를 좀더 추가하는 것으로 앱을 수평적으로 확장할 수있다. ( 사실은 당연히 추가적인 접속자를 받기위해 다이노 뿐만 아니라 데이타베이스도 확장할 필요가 있긴 하다).</p> + +<p>파일 시스템이 단기 수명의 특성이 있어서, 어플리케이션에 필요한 서비스(즉, 데이타베이스, 큐, 캐싱 시스템, 저장공간, 이메일 서비스 등등) 를 직접 설치할 수는 없다. 대신에 Heroku 웹 어플리케이션들은 Heroku나 써드파티로 부터 제공되는 독립적인 "애드온"들로 부터의 지원 서비스를 이용한다. 일단 애드온이 웹 어플리케이션에 부착되면, 다이노는 어플리케이션 설정 변수에 포함된 정보를 사용하여 서비스에 접속한다. </p> + +<p>어플리케이션을 실행하기위해서, Heroku는 적절한 환경과 의존성을 셋업하고 어떻게 런칭되는지 이해할 필요가 있다. Django 앱에 대해서는 아래 정보를 몇개의 텍스트 파일로 제공한다.</p> + +<ul> + <li><strong>runtime.txt</strong>:<strong> </strong>프로그래밍 언어와 사용 버전.</li> + <li><strong>requirements.txt</strong>: Django를 포함한 파이썬 관련 라이브러리 의존성.</li> + <li><strong>Procfile</strong>: 웹 어플리케이션을 구동하기 위해 실행되어야 하는 프로세스의 목록. 장고의 예를 들자면, Gunicorn 웹 어플리케이션 서버( .wsgi 스크립트와 함께) 가 될것이다. </li> + <li><strong>wsgi.py</strong>: Heroku 환경에서 Django 어플리케이션을 호출 하기 위한 <a href="http://wsgi.readthedocs.io/en/latest/what.html">WSGI</a> 설정.</li> +</ul> + +<p>개발자들은 Unix bash 스크립트와 매우 유사한, 특별한 클라이언트 앱/터미널로 Heroku와 통신한다. 이 도구는 git 저장소에 보관된 코드를 업로드 할수 있도록 지원하며, 실행중인 프로세스의 모니터링과, 로그를 보고 환경변수를 설정하는등 그외 많은 일을 할수 있도록 지원한다!</p> + +<p>Heroku 상에서 어플리케이션을 실행하기 위해서는 Django 웹 어플리케이션을 git 저장소에 보관하고, 위에서 언급한 파일을 추가하며, 데이터베이스 애드온과 통합하고, 스태틱 파일을 다룰수 있도록 수정할 필요가 있다.</p> + +<p>일단 Heroku 계정에서 준비할수 있는 모든것을 완료 했으니, Heroku 클라이언트를 다운로드 받아서 웹 사이트를 설치하라.</p> + +<div class="note"> +<p><strong>주목할 점:</strong> 아래 지시사항은 이 글을 쓸 당시의 Heroku로 작업하는 방법을 반영했다. Heroku 서비스의 절차가 상당히 많이 바뀐다면, 이글 대신 다음링크의 Heroku 문서를 참조하는 것이 좋다: <a href="https://devcenter.heroku.com/articles/getting-started-with-python#introduction">Django로 Heroku 시작하기</a>.</p> +</div> + +<p>이것으로 시작을 하기위한 준비를 모두 마친다. ( 좀 더 포괄적인 이해를 위해서는 <a href="https://devcenter.heroku.com/articles/how-heroku-works">Heroku의 동작 방식</a>(Heroku 문서) 가이드를 참고하라).</p> + +<h3 id="Github에_애플리케이션_저장소(repository)_생성하기"> Github에 애플리케이션 저장소(repository) 생성하기</h3> + +<p>Heroku는 <strong>git</strong> 형상관리 시스템과 밀접하게 통합되어있는데, git을 이용하여 활성화된 시스템에 수정사항의 업로드및 동기화를 수행한다. git은 Heroku 클라우드 상에서 당신의 소스코드 저장소를 가리키도록 이름붙여진 신규 heroku "원격" 저장소를 추가함으로 이 작업을 수행한다. 개발기간동안 "master" 저장소에 변경사항을 저장하기 위에 git을 사용하게된다. 사이트를 배포할 때가 되면, Heroku 저장소에 수정사항을 동기화 한다.</p> + +<div class="note"> +<p><strong>주목할 점:</strong> 좋은 소프트웨어 개발 예제를 따라서 작성하는데 익숙하다면, 아마도 당신은 이미 git 이나 다른 SCM 도구를 사용하고 있을 것이다. 당신이 이미 git 저장소를 소유하고 있다면, 이 단계를 건너뛰어도 좋다.</p> +</div> + +<p>git 으로 작업하는 수많은 방법이 있지만, <a href="https://github.com/">Github</a>에 계정을 생성하여, 저장소를 생성하고, 로컬 컴퓨터와 동기화 하는것이 가장 쉬운 방법중 하나이다 :</p> + +<ol> + <li>이곳( <a href="https://github.com/">https://github.com/</a> )을 방문하여 계정을 생성하라.</li> + <li>로그인 해서 꼭대기 툴바의 <strong>+</strong> 링크를 클릭하여 <strong>New repository</strong>를 선택하라.</li> + <li>이 폼의 모든 필드에 기입하라. 그렇지 않으면 진행이 불가한 것은 아니지만, 모두 채울것을 강력 추천한다. + <ul> + <li>새로운 저장소 이름 (예시: <em>django_local_library</em>)과 설명 (예시: "Local Library website written in Django")을 입력하라.</li> + <li>"<em>Add .gitignore" 선택 목록에서 "</em><strong>Python</strong><em>"을 선택하라</em>.</li> + <li>"<em>Add a license" 선택 목록에서 선호하는 라이센스 유형을 선택하라</em>.</li> + <li>"<strong>Initialize this repository with a README</strong>." 체크박스에 체크하라</li> + </ul> + </li> + <li>" <strong>Create repository" </strong>버튼을 누른다</li> + <li>신규 저장소 페이지에서 초록색의 "<strong>Clone or download</strong>" 버튼을 클릭한다.</li> + <li>아래와 같은 형태의 URL 값을 다이얼로그 박스내의 텍스트 필드에서 복사한다.(예시: <strong>https://github.com/<em><your_git_user_id></em>/django_local_library.git</strong>).</li> +</ol> + +<p>이제 원격 저장소 ("repo")가 생성되었으니 로컬 컴퓨터에 복제(clone)하길 원할 것이다:</p> + +<ol> + <li><em>git을 로컬 컴퓨터에 설치하라</em> (플랫폼별 버전은 <a href="https://git-scm.com/downloads">이곳</a> 에서 찾을 수 있다).</li> + <li>커맨드 프롬프트/터미널 을 열고 위에서 복사한 URL을 이용하여 저장소 내용을 복제(clone) 한다 : + <pre class="brush: bash">git clone https://github.com/<strong><em><your_git_user_id></em></strong>/django_local_library.git +</pre> + 이 명령은 현재 프롬프트의 위치에 저장소를 생성할 것이다.</li> + <li>새로운 저장소 위치로 이동한다. + <pre class="brush: bash">cd django_local_library.git</pre> + </li> +</ol> + +<p>마지막 단계는 어플리케이션을 복사하여 git을 이용해 저장소에 파일을 추가하는 것이다 :</p> + +<ol> + <li>이 폴더에 Django 어플리케이션을 복사해 넣는다. (locallibrary 폴더를 포함한 위치가 아니라 <strong>manage.py</strong> 와 그 하위 폴더와 같은 위치의 모든 파일에 대해 작업한다).</li> + <li><strong>.gitignore</strong> 파일을 열어서, 아래 코드를 맨 밑에 복사하고, 저장하라 ( 이 파일은 기본 설정에 의해 git에 저장되지 말아야할 파일을 구분하는데 사용된다). + <pre># Text backup files +*.bak + +#Database +*.sqlite3</pre> + </li> + <li>커맨드 프로프트/터미널을 열고 <code>add</code>명령으로 모든 파일을 git에 등록한다. + <pre class="brush: bash">git add -A +</pre> + </li> + <li>status 명령을 사용하여 등록하고자 하는 파일이 맞는지 확인한다 ( 당신은 소스파일만 등록 하길 원하고 바이너리나 임시 파일은 원치 않을 것이다). 명령을 실행하면 아래와 유사하게 나온다. + <pre>> git status +On branch master +Your branch is up-to-date with 'origin/master'. +Changes to be committed: + (use "git reset HEAD <file>..." to unstage) + + modified: .gitignore + new file: catalog/__init__.py + ... + new file: catalog/migrations/0001_initial.py + ... + new file: templates/registration/password_reset_form.html</pre> + </li> + <li>위의 결과에 만족했다면, commit 명령으로 파일의 로컬 저장소 등록을 확정한다: + <pre class="brush: bash">git commit -m "First version of application moved into github"</pre> + </li> + <li>다음 명령을 이용하여 Github 웹사이트와 로컬 저장소를 동기화 한다: + <pre>git push origin master</pre> + </li> +</ol> + +<p>이런 작업이 완료된 후, repo를 생성한 Github 페이지로 다시 가서, 페이지를 새로 로딩하여, 전체 어플리케이션이 모두 업로드된것인지 확인할 수 있어야 한다. 이후로 파일 변경 사항이 발생하면 add/commit/push 순서로 명령을 사용하여 저장소를 업데이트 할 수 있다.</p> + +<div class="note"> +<p><strong>한가지 팁:</strong> 이쯤에서 당신의 "변형 없이 순수한(<a href="https://en.wikipedia.org/wiki/Vanilla_software">vanilla</a>)" 프로젝트를 백업하는것이 좋다. - 이어지는 섹션에서 해볼 작업들은 어떤 플랫폼(혹은 개발작업) 에서는 유용하지만 다른 곳에서는 그렇지 않을 수 있기 때문이다.</p> + +<p>백업을 하는 가장 좋은 방법은 git을 사용하여 관리하는 것이다. git을 사용하면, 특정 구 버전으로 갈수 있을 뿐만 아니라, 이것을 운영관련 변경사항으로 부터 분리된 "브랜치(branch)"로 유지보수할 수있고, 운영 브랜치와 개발 브랜치간에 좋은 부분만 선별하여 적용할 수 있다. <a href="https://help.github.com/articles/good-resources-for-learning-git-and-github/">Git 공부하기</a> 는 수고를 들여 배울가치가 있지만 이 글의 주제를 벗어난다.</p> + +<p>백업을 하는 가장 쉬운방법은 단순히 파일을 다른 위치에 복제하는 것이다. 당신의 git 관련 지식에 맞춰, 어떤 방법이든 사용하도록 하자 !</p> +</div> + +<h3 id="Heroku에_맞춰_앱을_수정하기">Heroku에 맞춰 앱을 수정하기</h3> + +<p>이번 섹션에서는 LocalLibray 어플리케이션이 Heroku상에서 작동할 수 있도록 수정할 필요가 있는 변경사항에 대해 설명한다. Heroku 문서인 <a href="https://devcenter.heroku.com/articles/getting-started-with-python#introduction">Django로 Heroku 시작해보기</a> 에서 나온 지시사항들은 당신이 로컬 개발 환경의 실행 또한 Heroku 클라이언트를 사용할 것으로 가정하고 있지만, 우리가 여기서 제시할 변경사항은 기존의 Django 개발 서버및 이제껏 배워온 작업방식과 호환될 것이다.</p> + +<h4 id="Procfile_작성하기">Procfile 작성하기</h4> + +<p>어플리케이션의 프로세스 타입과 엔트리 포인트를 선언하기 위해 GiHub 저장소의 root 폴더에 <code>Procfile</code> 파일을 (확장자 없이 ) 생성한다. 아래 문장을 해당 파일에 작성한다:</p> + +<pre>web: gunicorn locallibrary.wsgi --log-file -</pre> + +<p>"<code>web:</code>" 구문은 Heroku에게 이것이 웹 다이노이며 HTTP 트래픽을 받을수 있다는 것을 알려준다. 이 다이노에서 시작할 프로세스는 <em>gunicorn 인데 Heroku가 추천하는 인기있는 웹 어플리케이션 서버이다. </em><code>locallibrary.wsgi</code><em> ( 어플리케이션 뼈대로 생성된: </em><strong>/locallibrary/wsgi.py</strong>) <em>모듈의 설정 정보를 이용하여 </em>Gunicorn을 구동시킨다.</p> + +<h4 id="Gunicorn_설치하기">Gunicorn 설치하기</h4> + +<p><a href="http://gunicorn.org/">Gunicorn</a> 은 Django와 함께 사용되는 용도로 Heroku에서 추천되는 HTTP server 이다 (바로 위의 Procfile 에서 미리 본 바와 같다). 하나의 다이노에서 여러개의 Python 동시 프로세스를 실행할 수 있는 WSGI 어플리케이션을 위한 순수 Python으로 작성된 HTTP server 다. ( 추가 정보를 얻으려면 <a href="https://devcenter.heroku.com/articles/python-gunicorn">Gunicorn으로 Python 어플리케이션 배포하기</a> ( Heroku 문서) 참고하라).</p> + +<p>개발기간중에는 LocalLibrary 어플리케이션을 서비스하기 위해 Gunicorn을 필요로 하진 않겠지만, Heroku에서 원격 서버를 셋업하기 위한 <a href="https://developer.mozilla.org/ko/docs/Learn/Server-side/Django/Deployment$edit#requirements">요구조건</a> 의 일부이므로 Gunicorn을 설치할 것이다. </p> + +<p>아래와 같이 커맨드라인에서 pip를 이용하여 <em>Gunicorn</em> 설치한다 (pip는 <a href="/en-US/docs/Learn/Server-side/Django/development_environment">개발 환경 구축하기</a> 단계에서 설치했다):</p> + +<pre class="brush: bash">pip3 install gunicorn +</pre> + +<h4 id="Database_설정하기">Database 설정하기</h4> + +<p>디폴트로 설정되었던 SQLite 데이타베이스는 Heroku에서는 사용할 수 없다. 이유는 SQLite가 파일 베이스로 동작하는 시스템인데, Heroku는 단기-수명(ephemeral) 파일 시스템을 사용하므로 어플리케이션이 재시작되면 파일이 삭제되기 때문이다 ( 보통 하루에 한 번, 어플리케이션이나 그에 딸린 설정 변수가 변경되면 재시작 된다) . </p> + +<p>이런 상황에 대처하는 Heroku의 메커니즘은 <a href="https://elements.heroku.com/addons#data-stores">database 애드온</a>을 사용하고 애드온에 의해 설정되는 환경 <a href="https://devcenter.heroku.com/articles/config-vars">설정 변수</a>로 부터의 정보를 이용하여 웹 어플리케이션을 설정하는 것이다. 많은 데이타베이스 옵션이 있지만, Heroku postgres 데이터베이스의 <a href="https://devcenter.heroku.com/articles/heroku-postgres-plans#plan-tiers">Hobby tier</a>를 사용할것인데, 이것은 무료이고, Django가 지원하며, 무료 hobby dyno plan tier를 사용할 때 신규 Heroku 앱에 자동으로 추가된다.</p> + +<p>데이터베이스 연결 정보는 <code>DATABASE_URL</code> 라는 설졍변수를 사용해 웹 다이노에 제공된다. Heroku는 , 이 정보를 Django에 하드 코딩 해넣기 보다는, 개발자들이 <a href="https://warehouse.python.org/project/dj-database-url/">dj-database-url</a> 패키지를 사용하여 <code>DATABASE_URL</code> 환경 변수를 분석하여 자동적으로 Django가 원하는 설정 형식으로 변환하는 것을 추천한다. <em>dj-database-url</em> 패키지를 설치하는것에 외에도, Django에서 Postgres 데이터베이스로 작업하기 위해서는 <a href="http://initd.org/psycopg/">psycopg2</a> 도 설치해야 한다.</p> + +<h5 id="dj-database-url_(환경_변수를_통한_Django_데이터베이스_설정_)_설치하기">dj-database-url (환경 변수를 통한 Django 데이터베이스 설정 ) 설치하기</h5> + +<p>Heroku에서 원격 서버에 설치하기 위한 <a href="https://developer.mozilla.org/ko/docs/Learn/Server-side/Django/Deploymentt#requirements">요구조건</a>의 일부가 되었으니, <em>dj-database-url</em> 를 로컬에 설치한다:</p> + +<pre>$ pip3 install dj-database-url +</pre> + +<h5 id="settings.py_수정하기">settings.py 수정하기</h5> + +<p><strong>/locallibrary/settings.py</strong> 를 열고 아래 설정코드를 파일의 맨 밑에 복사해 넣는다:</p> + +<pre># Heroku: Update database configuration from $DATABASE_URL. +import dj_database_url +db_from_env = dj_database_url.config(conn_max_age=500) +DATABASES['default'].update(db_from_env)</pre> + +<div class="note"> +<p><strong>주목할 점:</strong></p> + +<ul> + <li><code>DATABASE_URL</code> 환경변수가 개발용 컴퓨터에는 없을것이므로 개발단계에서는 계속 SQLite를 사용한다.</li> + <li> <code>conn_max_age=500</code> 설정을 하면 연결상태가 지속될 수 있는데, 매번 요청 주기에 연결을 새로 하는것보다 이렇게 하는 것이 훨신 효율적이다. 하지만 이것은 옵션이며 불 필요하면 제거해도 된다.</li> +</ul> +</div> + +<h5 id="psycopg2_(Python_Postgres_데이터베이스_지원용)_설치하기">psycopg2 (Python Postgres 데이터베이스 지원용) 설치하기</h5> + +<p>Django에서 Postgres 데이터베이스로 작업하기 위해서는 <em>psycopg2</em> 가 필요하므로 Heroku에 원격 서버를 생성하기 위해서는( 아래 요구조건 섹션에 논의된 바와 같이) <a href="#requirements">requirements.txt</a> 파일에 이 항목을 추가할 필요가 있다.</p> + +<p>환경 변수가 로컬 환경에서 존재하지 않기 때문에, 로컬에서 Django는 디폴트로 SQLite를 사용할 것이다. 당신이 Postgres로 완전히 전환해서 개발과 운영 모두 Heroku 무료 단계 데이터베이스를 사용하길 원한다면 그렇게 할 수 있다. 예를 들면, Linux 기반 시스템에 psycopg2와 관련 모듈을 설치하려면, 아래 bash/터미널 명령을 사용하면 된다:</p> + +<pre class="brush: bash"><code>sudo apt-get install python-pip python-dev libpq-dev postgresql postgresql-contrib</code> +pip3 install psycopg2 +</pre> + +<p>다른 플랫폼에 대한 설치방법은 <a href="http://initd.org/psycopg/docs/install.html">이곳 psycopg2 웹사이트</a> 에서 찾아볼 수 있다.</p> + +<p>하지만, 당신이 Heroku에 사이트를 적용하기 위한 ( <code>requirements.txt</code> (아래에 나옴)에서) 요구조건 으로서만 맞추려고 한다면, 굳이 이렇게 PostGreSQL을 로컬 컴퓨터에 설치할 필요까지는 없다.</p> + +<h4 id="운영환경에서_정적_파일(static_file)_지원하기">운영환경에서 정적 파일(static file) 지원하기</h4> + +<p>개발 단계동안 Django와 Django 개발용 웹 서버를 사용하여 정적 파일을 지원해 왔다 (CSS, JavaScript, 등등.). 운영 환경에서는 보통 콘텐츠 전달 네트웍 ( CDN) 이나 웹서버에서 정적 파일을 지원한다.</p> + +<div class="note"> +<p><strong>주목할 점:</strong> Django/웹 어플리케이션으로 직접 정적 파일을 지원하는 것은 비효율적이다. 웹서버에 의해 직접 조작되거나 CDN으로 완전히 분리하는 것에 비해, 불필요한 추가적인 코드가 요청(requests)에 추가되기 때문이다. 이런 사항은 로컬에서 진행되는 개발단계에서는 영향이 크진 않지만, 운영 단계에서 같은 방식으로 사용하려 한다면 심각한 성능저하 상황이 발생할 수 있다. </p> +</div> + +<p>Django 웹 어플리케이션으로부터 분리하여 정적파일을 쉽게 호스팅하기 위해, Django는 개발용 정적 파일을 수집하는 collectstatic 도구를 제공한다 (collectstatic이 실행될 때 어느곳에서 파일이 수집되어야 하는지 정의한 설정 변수들이 있다). Django 템플릿은 정적 파일 종류의 호스팅 위치를 <code>STATIC_URL</code> 설정 변수로 알려주며, 정적 파일의 위치가 다른 호스트/서버로 이동되면 여기서 바꿀 수 있도록 한다.</p> + +<p>관련 설정 변수는 다음과 같다:</p> + +<ul> + <li><code>STATIC_URL</code>: 이것은 베이스 URL 위치인데 이곳에서 정적 파일들이 지원된다. 예를 들면 CDN과 같은곳이다. 베이스 템플릿에서 접근하는 정적 템플릿 변수에 사용된다. ( <a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django 튜토리얼 파트 5: 홈페이지 작성하기</a> 를 참고하라).</li> + <li><code>STATIC_ROOT</code>: 이것은 Django의 "collectstatic" 도구로 템플릿에서 참조하는 모든 정적 파일을 모집하는 디렉토리로 가는 절대 경로이다. 일단 수집되면, 이것들은 파일이 어떤곳에서 호스팅되든지 단체로 업로드 될 수 있다.</li> + <li><code>STATICFILES_DIRS</code>: 이것은 Django의 colletstatic 도구가 정적 파일을 탐색할 추가적인 디렉토리를 나열한다.</li> +</ul> + +<h5 id="settings.py_수정하기_2">settings.py 수정하기</h5> + +<p><strong>/locallibrary/settings.py</strong> 파일을 열고 아래 설정을 파일의 맨 밑으로 복사한다. <code>BASE_DIR</code> 는 파일에 이미 정의되어 있었어야 한다. ( <code>STATIC_URL</code> 도 설정파일이 생성될 때 정의되었을 것이다. 중복되는 이전의 설정은, 지우지 않아도 문제는 없겠지만, 제거하는 것이 좋다).</p> + +<pre># Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/2.0/howto/static-files/ + +# The absolute path to the directory where collectstatic will collect static files for deployment. +STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') + +# The URL to use when referring to static files (where they will be served from) +STATIC_URL = '/static/' +</pre> + +<p>실제로는, 다음 섹션에서 설치하고 설정할, <a href="https://warehouse.python.org/project/whitenoise/">WhiteNoise</a> 라는 이름의 라이브러리를 이용하여 파일을 관리할 것이다.</p> + +<p>더 많은 정보가 필요하면 <a href="https://devcenter.heroku.com/articles/django-assets">Django and Static Assets</a> (Heroku docs) 를 참고한다.</p> + +<h4 id="Whitenoise_적용하기">Whitenoise 적용하기</h4> + +<p>운영환경에서 정적 파일을 관리하기 위한 수많은 방법이 있다 (바로 앞 섹션에서 관련된 Django 설정을 봤다). Heroku는 <a href="https://warehouse.python.org/project/whitenoise/">WhiteNoise</a> 프로젝트를 이용하여 운영환경의 Gunicorn상에서 직접 정적 자원을 관리하는 것을 추천한다.</p> + +<div class="note"> +<p><strong>주목할 점: </strong>Heroku는 자동적으로 collectstatic을 호출하고 사용자 어플리케이션을 업로드한 후 WhiteNoise로 정적파일을 사용할 수 있도록 준비한다. WhiteNoise의 동작 방식과 이 도구를 적용하는 것이 좀더 효율적인 이유에 대한 설명은 <a href="https://warehouse.python.org/project/whitenoise/">WhiteNoise</a> 문서를 참조한다.</p> +</div> + +<p>본 프로젝트에 <em>WhiteNoise를 적용하는 단계적 방법은 다음과 같다 </em>:</p> + +<h5 id="WhiteNoise_설치하기">WhiteNoise 설치하기</h5> + +<p>아래 명령으로 whitenoise를 로컬에 설치한다 :</p> + +<pre>$ pip3 install whitenoise +</pre> + +<h5 id="settings.py_수정하기_3">settings.py 수정하기</h5> + +<p>사용자 Django 어플리케이션에 WhiteNoise를 설치하기 위해, <strong>/locallibrary/settings.py</strong> 를 열고, <code>MIDDLEWARE</code> 설정을 찾아서 <code>SecurityMiddleware</code> 바로 밑의 목록의 윗부분에 <code>WhiteNoiseMiddleware</code> 를 추가 한다 :</p> + +<pre>MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + <strong>'whitenoise.middleware.WhiteNoiseMiddleware',</strong> + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] +</pre> + +<p>선택 사항으로, 파일이 서빙될 때 정적 파일의 크기를 줄일 수 있다. (이 방식이 좀더 효율적이다). Just add the following to the bottom of <strong>/locallibrary/settings.py</strong>:</p> + +<pre># Simplified static file serving. +# https://warehouse.python.org/project/whitenoise/ +STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' +</pre> + +<h4 id="파이썬_관련_라이브러리_(Requirements)_설치하기">파이썬 관련 라이브러리 (Requirements) 설치하기</h4> + +<p>웹 어플리케이션의 Python 관련 라이브러리들은 저장소의 루트에 위치한 <strong>requirements.txt</strong> 라는 파일에 저장되어야 한다. 그러면 Heroku는 환경을 재구성할 때 이 패키지들을 자동적으로 설치할 것이다. 커맨드 라인에서 pip 명령을 이용해 이 파일을 생성할 수 있다. ( 아래 명령을 repo 루트 디렉토리에서 실행한다):</p> + +<pre class="brush: bash">pip3 freeze > requirements.txt</pre> + +<p>여러가지 관련 라이브러리를 위에서 설치했다면, <strong>requirements.txt</strong> 파일은 최소한 아래 나열된 항목들을 가지고 있어야 한다. ( 버전 숫자는 다를수도 있다). 명확히 이 어플리케이션을 위해 설치한것이 아니라면 , 아래 목록을 제외한 라이브러리는 지우는 것이 좋다.</p> + +<pre>dj-database-url==0.4.1 +Django==2.0 +gunicorn==19.6.0 +<strong>psycopg2==2.6.2</strong> +whitenoise==3.2.2 +</pre> + +<div class="note"> +<p>위 목록에 <strong>psycopg2</strong> 줄이 있다는 것을 주의해야 한다! 로컬환경에서 이 라이브러리를 설치한 적이 없더라도 이 줄은 <strong>requirements.txt</strong> 파일에 추가해야 한다.</p> +</div> + +<h4 id="Runtime_파일_추가하기">Runtime 파일 추가하기</h4> + +<p><strong>runtime.txt</strong> 파일이 존재한다면 Heroku에게 웹사이트에서 사용할 프로그래밍 언어를 알려준다. 아래 문구를 추가하여 저장소의 루트에 runtime.txt 파일을 생성한다:</p> + +<pre>python-3.6.4</pre> + +<div class="note"> +<p><strong>주목할 점:</strong> Heroku가 지원하는 <a href="https://devcenter.heroku.com/articles/python-support#supported-python-runtimes">Python 실행버전</a> ( 이 글을 쓰는 당시에는 위 버전을 포함한다)은 그리 많지 않다. Heroku는 이 파일에 지정된 버전에 개의치 않고 지원되는 실행버전을 사용할 것이다.</p> +</div> + +<h4 id="Github에_변경사항을_저장하고_테스트_다시하기">Github에 변경사항을 저장하고 테스트 다시하기</h4> + +<p>다음으로 모든 변경사항을 Github에 저장하자. 아래 명령을 ( 저장소 범위내 위치에서) 터미널에 입력한다 :</p> + +<pre class="brush: bash">git add -A +git commit -m "Added files and changes required for deployment to heroku" +git push origin master</pre> + +<p>계속 진행하기전에, 로컬에서 사이트를 다시 테스트 해서 위의 변경사항에 의해 영향받은부분이 없는지 확인한다. 지금까지 해온 것 처럼 개발용 웹 서버를 실행하고 브라우저 상에서 여전히 기대한 대로 동작하는지 체크한다.</p> + +<pre class="brush: bash">python3 manage.py runserver</pre> + +<p>이제 Heroku에 LocalLibrary의 배포를 시작할 준비가 다 되었다.</p> + +<h3 id="Heroku_계정_생성하기">Heroku 계정 생성하기</h3> + +<p>Heroku 이용을 시작 하려면 계정을 한 개 생성해야 한다 :</p> + +<ul> + <li><a href="https://www.heroku.com/">www.heroku.com</a> 사이트로 가서 <strong>SIGN UP FOR FREE</strong> 버튼을 클릭한다.</li> + <li>세부 정보를 입력하고 <strong>CREATE FREE ACCOUNT</strong> 버튼을 누른다. 당신은 가입 이메일에서 계정을 확인 하도록 요청받을 것이다.</li> + <li>가입 이메일에서 계정 활성화 요청 링크 (account activation link) 를 클릭하면 웹 브라우저 상에서 당신의 계정 위치로 이동될 것이다.</li> + <li>당신의 암호를 입력하고 <strong>SET PASSWORD AND LOGIN</strong> 버튼을 클릭하라</li> + <li>로그인이 되면 Heroku 대시보드로 이동될 것이다: <a href="https://dashboard.heroku.com/apps">https://dashboard.heroku.com/apps</a>.</li> +</ul> + +<h3 id="client_설치하기">client 설치하기</h3> + +<p>여기의 <a href="https://devcenter.heroku.com/articles/getting-started-with-python#set-up">Heroku 지시사항</a> 에 따라 Heroku 클라이언트를 다운로드하고 설치하라.</p> + +<p>클라이언트를 설치한후에 명령어를 실행할 수 있다. 예를 들면, 클라이언트에 대한 도움말을 얻고 싶다면 아래 명령을 실행한다:</p> + +<pre class="brush: bash">heroku help +</pre> + +<h3 id="웹사이트를_생성하고_업로드하기">웹사이트를 생성하고 업로드하기</h3> + +<p>앱을 생성하기 위해 "create" 명령을 저장소의 루트 디렉토리에서 실행한다. 이명령은 로컬 컴퓨터의 git 환경에 heroku라는 이름의 git의 원격 저장소(remote) ("원격 저장소의 지정자(pointer)")를 생성한다.</p> + +<pre class="brush: bash">heroku create</pre> + +<div class="note"> +<p><strong>주목할 점:</strong> 원한다면 원격 저장소(remote)에 이름을 붙일 수 있는데 . "create" 다음에 값을 추가하면 된다. 아무것도 붙이지 않으면 랜덤으로 생성된 이름을 가진다. 이 이름은 기본 URL로 사용된다.</p> +</div> + +<p>그다음에 아래와 같은 명령으로 앱을 Heroku 저장소에 등록(push)할 수 있다. 이 명령은 앱을 업로드하고, 다이노안에 앱을 포장(pakage)하며, colletstatic을 실행하고 사이트가 시작 되도록 한다.</p> + +<pre class="brush: bash">git push heroku master</pre> + +<p>운이 좋다면, 앱은 이제 사이트상에서 "실행(running)" 상태에 있게되겠지만, 어플리케이션을 위한 데이터베이스 테이블을 구성하지 않았기 때문에 제대로 동작하지 않을 것이다. 이 작업을 위해 <code>heroku run</code> 명령을 사용해 migrate 작업을 수행할 "<a href="https://devcenter.heroku.com/articles/deploying-python#one-off-dynos">one off 다이노</a>" 를 시작시켜야 한다. 아래 명령을 터미널에 입력하라 :</p> + +<pre class="brush: bash">heroku run python manage.py migrate</pre> + +<p>책과 저자도 추가할 수 있어야 하니까 다시 one-off 다이노를 이용하여 관리자 아이디 또한 생성하자:</p> + +<pre class="brush: bash">heroku run python manage.py createsuperuser</pre> + +<p>일단 이 작업이 완료되면, 사이트를 볼 수 있다. 아직 아무 책도 없긴 하지만 사이트가 동작한다. 신규 웹사이트를 브라우저로 보고 싶으면 아래 명령을 사용하라 :</p> + +<pre class="brush: bash">heroku open</pre> + +<p>admin 사이트에서 책 몇개를 생성하고 사이트가 기대한대로 동작하는지 확인하라.</p> + +<h3 id="애드온_관리하기">애드온 관리하기</h3> + +<p><code>heroku addons</code> 명령으로 앱의 애드온 적용에 대해 확인할 수 있다. 이 명령은 모든 애드온을 나열하고, 가격과 상태를 보여준다.</p> + +<pre class="brush: bash">>heroku addons + +Add-on Plan Price State +───────────────────────────────────────── ───────── ───── ─────── +heroku-postgresql (postgresql-flat-26536) hobby-dev free created + └─ as DATABASE</pre> + +<p>여기서 단 한개의 애드온만 있는데, 바로 postgres SQL 데이터베이스 이다. 이것은 무료이고, 앱을 생성할 때 자동적으로 생성되었다. 데이터베이스 애드온을 ( 다른 어떤 애드온이라도) 좀 더 자세히 조사하기 위해 아래 명령으로 웹 페이지 열 수 있다 (open) :</p> + +<pre class="brush: bash">heroku addons:open heroku-postgresql +</pre> + +<p>애드온을 생성하고(create), 제거하고(destroy), 업그레이드하고(upgrade), 다운그레이드하는(downgrade) 다른 명령들도 있다. ( 여는 명령(open)과 유사한 문법을 사용한다). 좀 더 자세한 정보는 <a href="https://devcenter.heroku.com/articles/managing-add-ons">Managing Add-ons</a> (Heroku 문서) 를 참조하라.</p> + +<h3 id="구성_변수_(_configuration_variables)_설정하기">구성 변수 ( configuration variables) 설정하기</h3> + +<p><code>heroku config</code> 명령을 이용하여 사이트의 구성 변수를 확인할 수 있다. 아래에서 현재 한개의 변수만 가지는 것을 확인할 수있는데, <code>DATABASE_URL</code> 변수는 데이터베이스를 설정하는 데 사용된다.</p> + +<pre class="brush: bash">>heroku config + +=== locallibrary Config Vars +DATABASE_URL: postgres://uzfnbcyxidzgrl:j2jkUFDF6OGGqxkgg7Hk3ilbZI@ec2-54-243-201-144.compute-1.amazonaws.com:5432/dbftm4qgh3kda3</pre> + +<p>위에서 공부한 <a href="#Getting_your_website_ready_to_publish">웹 사이트 공개(publish) 준비하기</a> 섹션을 다시 떠올려보면, <code>DJANGO_SECRET_KEY</code>와 <code>DJANGO_DEBUG</code> 환경 변수를 설정해야 한다. 지금 이 작업을 하자.</p> + +<div class="note"> +<p><strong>주목할 점:</strong> secret key는 정말로 비밀이 유지되어야 한다! 새로운 키를 만들어 내는 한가지 방법은 새로운 Django 프로젝트를 생성하고 (<code>django-admin startproject someprojectname</code>) 신규 프로젝트의 <strong>settings.py</strong>에서 생성된 키를 가져오는 것이다.</p> +</div> + +<p><code>DJANGO_SECRET_KEY</code> 를 <code>config:set</code> 명령을 통해 설정한다(아래 예제와 같이). 당신 자신의 secret key를 사용해야 한다는 것을 기억하자!</p> + +<pre class="brush: bash">>heroku config:set DJANGO_SECRET_KEY=eu09(ilk6@4sfdofb=b_2ht@vad*$ehh9-)3u_83+y%(+phh&= + +Setting DJANGO_SECRET_KEY and restarting locallibrary... done, v7 +DJANGO_SECRET_KEY: eu09(ilk6@4sfdofb=b_2ht@vad*$ehh9-)3u_83+y%(+phh +</pre> + +<p><code>DJANGO_DEBUG</code> 도 유사하게 설정한다 :</p> + +<pre class="brush: bash">>heroku config:set <code>DJANGO_DEBUG= + +Setting DJANGO_DEBUG and restarting locallibrary... done, v8</code></pre> + +<p>지금 웹사이트를 방문한다면 "Bad request" 에러를 만나게 되는데 이것은 (보안 조치상) <code>DEBUG=False</code> 로 설정되어 있여서 <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#allowed-hosts">ALLOWED_HOSTS</a> 설정이 요구되기 때문이다. <strong>/locallibrary/settings.py</strong> 파일을 열고 아래와 같이 <code>ALLOWED_HOSTS</code> 설정을 수정하여 베이스 앱 url과 (예를들면, 'locallibrary1234.herokuapp.com') 로컬 개발 환경 웹서버에서 지금까지 사용해온 URL을 추가하라.</p> + +<pre class="brush: python">ALLOWED_HOSTS = ['<your app URL without the https:// prefix>.herokuapp.com','127.0.0.1'] +# For example: +# ALLOWED_HOSTS = ['fathomless-scrubland-30645.herokuapp.com','127.0.0.1'] +</pre> + +<p>그다음 지금 설정한 것들을 저장하고 Github 저장소와 Herok에 반영한다 :</p> + +<pre class="brush: bash">git add -A +git commit -m 'Update ALLOWED_HOSTS with site and development server URL' +git push origin master +git push heroku master</pre> + +<div class="note"> +<p>Heroku에 사이트 업데이트하는 것을 완료한후, 존재하지 않는 주소URL(예를 들면, <strong>/catalog/doesnotexist/</strong>) 을 입력해 보자. 이전에는 이런 경우 자세한 설명이 담긴 디버그 페이지를 보여주었었지만, 이제는 단순한 "Not Found" 페이지가 보일것이다.</p> +</div> + +<h3 id="디버깅_하기">디버깅 하기</h3> + +<p>Heroku 클라이언트는 디버깅을 위한 몇가지 도구를 제공한다 :</p> + +<pre class="brush: bash">heroku logs # Show current logs +heroku logs --tail # Show current logs and keep updating with any new results +heroku config:set DEBUG_COLLECTSTATIC=1 # Add additional logging for collectstatic (this tool is run automatically during a build) +heroku ps #Display dyno status +</pre> + +<p>이것들보다 더 자세한 정보가 필요하다면 <a href="https://docs.djangoproject.com/en/2.0/topics/logging/">Django Logging</a> 을 자세히 살펴보는 것이 좋을것이다.</p> + +<ul> +</ul> + +<h2 id="요약">요약</h2> + +<p>이렇게 Django 앱을 운영환경에서 구성해보는 튜토리얼이 끝났고. 지금까지 공부해온 Django 튜토리얼 시리즈도 이것으로 마무리가 되었다. 이 시리즈가 당신에 유용한 자료이길 바란다. 앞에서 설명한 모든 단계를 커버하는 버전의, <a href="https://github.com/mdn/django-locallibrary-tutorial">Github상의 소스 코드는 이곳에서</a> 확인할 수 있다.<br> + <br> + 다음 단계는 마지막으로 몇개의 기사를 읽어보고, 평가작업을 완성하는 것이다.</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/howto/deployment/">Deploying Django</a> (Django docs) + + <ul> + <li><a href="https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/">Deployment checklist</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/howto/static-files/deployment/">Deploying static files</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/">How to deploy with WSGI</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/modwsgi/">How to use Django with Apache and mod_wsgi</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/gunicorn/">How to use Django with Gunicorn</a> (Django docs)</li> + </ul> + </li> + <li>Heroku + <ul> + <li><a href="https://devcenter.heroku.com/articles/django-app-configuration">Configuring Django apps for Heroku</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/getting-started-with-python#introduction">Getting Started on Heroku with Django</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/django-assets">Django and Static Assets</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/python-concurrency-and-database-connections">Concurrency and Database Connections in Django</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/how-heroku-works">How Heroku works</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/dynos">Dynos and the Dyno Manager</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/config-vars">Configuration and Config Vars</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/limits">Limits</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/python-gunicorn">Deploying Python applications with Gunicorn</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/deploying-python">Deploying Python and Django apps on Heroku</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/search?q=django">Other Heroku Django docs</a></li> + </ul> + </li> + <li>Digital Ocean + <ul> + <li><a href="https://www.digitalocean.com/community/tutorials/how-to-serve-django-applications-with-uwsgi-and-nginx-on-ubuntu-16-04">How To Serve Django Applications with uWSGI and Nginx on Ubuntu 16.04</a></li> + <li><a href="https://www.digitalocean.com/community/tutorials?q=django">Other Digital Ocean Django community docs</a></li> + </ul> + </li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Testing", "Learn/Server-side/Django/web_application_security", "Learn/Server-side/Django")}}</p> + +<p> </p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> + +<p> </p> diff --git a/files/ko/learn/server-side/django/development_environment/index.html b/files/ko/learn/server-side/django/development_environment/index.html new file mode 100644 index 0000000000..0e31882131 --- /dev/null +++ b/files/ko/learn/server-side/django/development_environment/index.html @@ -0,0 +1,439 @@ +--- +title: Django 개발 환경 세팅하기 +slug: Learn/Server-side/Django/development_environment +tags: + - 개발환경 + - 들어가기 + - 설치 + - 장고 + - 초심자 + - 파이썬 +translation_of: Learn/Server-side/Django/development_environment +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Introduction", "Learn/Server-side/Django/Tutorial_local_library_website", "Learn/Server-side/Django")}}</div> + +<p class="summary">이제 장고가 무엇인지 알았으니, 윈도우, 리눅스(우분투), 맥 OS X에서 어떻게 장고 개발환경을 세팅하는지, 설치 후에는 어떻게 테스트하는지 살펴보겠습니다. 즉 이 문서를 통해서는 사용하고 있는 운영체제가 무엇인지와 상관없이 장고 어플리케이션 개발을 시작하기 위해 필요한 것들을 배우게 됩니다.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">미리 필요한 것:</th> + <td> + <p>터미널 또는 커맨드 창을 열 수 있어야 합니다. 또, 자신이 사용하는 PC의 운영체제에 따라 PC에 소프트웨어 패키지를 설치할 수 있어야 합니다.</p> + </td> + </tr> + <tr> + <th scope="row">목표:</th> + <td>장고가 컴퓨터에서 실행될 수 있도록 개발 환경을 세팅합니다.</td> + </tr> + </tbody> +</table> + +<h2 id="장고_개발_환경_개요">장고 개발 환경 개요</h2> + +<p>장고는 개발 환경을 세팅하고 웹 어플리케이션을 개발하는 것이 매우 쉽습니다. 이 섹션에서는 개발 환경이 제공하는 것들과, 개발 환경 세팅 시 옵션 사항을 알아봅니다. 또 우분투, 맥 OS X, 윈도우에서 장고 개발 환경을 설치하는 방법과 설치 후 테스트하는 방법을 설명합니다.</p> + +<h3 id="장고_개발_환경이란">장고 개발 환경이란?</h3> + +<p>장고 개발 환경이란, 장고를 로컬 컴퓨터에 설치하여 장고 어플리케이션을 개발, 실행, 테스트할 수 있는 환경을 말합니다. 로컬 컴퓨터는 자신이 어플리케이션을 개발하는데 사용하는 컴퓨터입니다. 어플리케이션을 실제 배포하기 전에 로컬 컴퓨터 위에서 어플리케이션을 실행 및 테스트할 수 있습니다.</p> + +<p>장고 자체가 제공하는 주요 툴에는 장고 프로젝트를 생성하고 작업하기 위한 파이썬 스크립트들과 심플한 개발용 웹 서버가 있습니다. 이 개발용 웹 서버로 우리는 로컬 컴퓨터에서 개발한 장고 어플리케이션을 같은 로컬 컴퓨터에서 테스트해 볼 수 있습니다. 예를 들면, 자신의 PC에서 개발한 장고 웹 어플리케이션을 크롬 브라우저와 같은 웹 브라우저 상에서 실행하고 테스트해볼 수 있습니다.</p> + +<p>앞서 설명한 것 외에도 장고 개발 환경은 여러 툴을 제공합니다. 코드 작성을 돕는 텍스트 에디터와 IDE, 소스코드의 버전을 안전하게 관리하기 위한 Git과 같은 소스 관리 도구와 같은 것들이 있습니다. 그러나 이것들은 여기서는 다루지 않습니다. 또 여기서는 미리 텍스트 에디터를 설치했다고 가정할 것입니다. 그러므로 텍스트 에디터를 아직 설치하지 않았다면 설치해주세요. 자주 사용하는 텍스트 에디터로는 <a href="https://www.sublimetext.com/3">Sublime Text 3</a>, <a href="https://wiki.gnome.org/Apps/Gedit#Download">Gedit</a>, <a href="https://atom.io/">Atom</a> 등이 있습니다.</p> + +<h3 id="장고_설치_옵션">장고 설치 옵션</h3> + +<p>장고는 설치 및 구성에서 매우 유연합니다.</p> + +<p>장고는 다음 사항이 가능합니다:</p> + +<ul> + <li>여러 운영 체제에서 설치가 가능합니다.</li> + <li>소스에서, 파이썬 패키지 인덱스(PyPi)에서, 그리고 많은 경우 호스트 컴퓨터의 패키지 매니저 어플리케이션에서 설치가 가능합니다.</li> + <li>별도로 설치 및 구성되어있어야 하는 여러가지 데이터베이스 중 하나를 사용하도록 설정할 수 있습니다.</li> + <li>메인 시스템의 파이썬 환경 또는 별도의 파이썬 가상 환경에서 실행됩니다.</li> +</ul> + +<p>이러한 각각의 옵션들은 모두 조금씩 다른 구성과 설치가 필요합니다. 이어지는 세부 내용에서 몇 가지 선택 사항을 설명합니다. 이하 글에서는 몇 가지 운영체제에서 장고를 설치 및 설정하는 방법을 보여주고, 나머지 튜토리얼에서는 모두 이 설정을 가정해서 진행됩니다.</p> + +<div class="note"> +<p><strong>주의</strong>: 공식 장고 문서에서 다른 설치 옵션을 찾을 수 있습니다. 링크 : <a href="#furtherreading">appropriate documents below</a>.</p> +</div> + +<h4 id="어떤_운영체제가_지원되나요">어떤 운영체제가 지원되나요?</h4> + +<p>장고는 파이썬 3 프로그래밍 언어를 실행할 수 있는 거의 모든 기계에서 실행될 수 있습니다: 윈도우, 맥 OS X, 리눅스/유닉스, 솔라리스 등등. 거의 모든 컴퓨터가 개발 중에 장고를 실행할 수 있는 성능을 갖고 있습니다.</p> + +<p>이 글에서는 윈도우, 맥 OS X, 리눅스/유닉스에 관해 설명하도록 하겠습니다.</p> + +<h4 id="파이썬은_어느_버전을_사용해야_할까">파이썬은 어느 버전을 사용해야 할까?</h4> + +<p>가능한 최신 버전을 사용할 것을 권장합니다. 이 글을 작성할 때 가장 최신 버전은 파이썬 3.7입니다.</p> + +<p>필요에 따라 Python 3.4 혹은 그 이후의 버전이 사용될 수 있습니다. (파이썬 3.4는 차후에 지원이 안될 수도 있습니다)</p> + +<div class="note"> +<p><strong>주의</strong>: 파이썬 2.7은 장고 2.0에서 사용할 수 없습니다. (장고 1.11.x 버전에서 마지막으로 파이썬 2.7을 지원했습니다)</p> +</div> + +<h4 id="장고는_어디서_다운로드할_수_있나요">장고는 어디서 다운로드할 수 있나요?</h4> + +<p>장고를 다운로드하는 세가지 방법 :</p> + +<ul> + <li>pip 도구를 이용한 PyPi(Python Package Repository)에서 설치. 이 방법이 장고의 최신 버전을 받을 수 있는 최적의 방법입니다.</li> + <li>본인 컴퓨터의 패키지 매니저에 있는 버전을 사용하세요. 운영체제와 함께 제공되는 장고는 친숙한 설치 방법을 제공합니다. 다만 이것은 상당히 오래된 버전일 것이며, (아마 그렇게 원하지 않을) 시스템 파이썬 환경에만 설치될 수 있다는 것에 유의하세요.</li> + <li>소스에서 설치하기. 소스에서 장고의 최신버전을 다운로드하여 설치할 수 있습니다. 초심자에게는 추천하지 않지만, 당신이 장고에게 기여할 수 있는 준비가 됐다면 필요합니다.</li> +</ul> + +<p>아래 글은 최신의 안정된 버전을 얻기 위해 PyPi에서 장고를 설치하는 방법을 보여줍니다.</p> + +<h4 id="어떤_Database를_써야_하나요">어떤 Database를 써야 하나요?</h4> + +<p>장고는 네 가지 메인 데이터베이스(PostgreSQL, MySQL, Oracle 그리고 SQLite)를 지원합니다. 그리고 다른 인기있는 SQL과 NoSQL 데이터베이스들을 다양한 레벨로 지원하는 커뮤니티 라이브러리가 있습니다. 우리는 생산과 개발에 동일한 데이터베이스를 선택하는 것을 추천합니다(장고는 ORM(Object-Relational Mapper)을 사용해 데이터베이스간의 차이 대부분을 추상화하긴 하지만, 아직 피하는게 나은 <a href="https://docs.djangoproject.com/en/2.0/ref/databases/">잠재적 문제들</a>이 있습니다.</p> + +<p>이 글에서(그리고 이 모듈의 거의 모든 부분에서) 우리는 데이터를 파일로 저장하는 SQLite 데이터베이스를 사용할 것 입니다. SQLite는 가벼운 데이터베이스로 사용하기에 적합하며 높은 수준의 동시성을 지원하지 않습니다. 그렇지만 주로 읽기 전용인 응용 프로그램을 위해서는 아주 좋은 선택입니다.</p> + +<div class="note"> +<p><strong>주의</strong>: 장고는 <em>django-admin과 같이 웹사이트를 만드는 표준 도구를 사용하면</em> SQLite가 기본 값으로 설정되어 있습니다. 이는 추가적인 설정이 필요하지 않으므로 시작하기에 좋습니다. </p> +</div> + +<h4 id="시스템_전체에_설치할까요_파이썬_가상환경에_설치할까요">시스템 전체에 설치할까요, 파이썬 가상환경에 설치할까요?</h4> + +<p>파이썬 3를 설치하면 모든 파이썬 3 코드가 공유하는 하나의 글로벌 환경이 만들어집니다. 그 환경에 원하는 어떤 파이썬 패키지라도 설치할 수 있지만, 각 패키지의 하나의 버전만 설치할 수 있습니다.</p> + +<div class="note"> +<p><strong>주의</strong>: 글로벌 환경에 설치된 파이썬 응용 프로그램들은 서로 충돌할 가능성이 있습니다. (예: 같은 패키지의 다른 버전일 경우)</p> +</div> + +<p>만약 장고를 기본/전역 환경에 설치한다면 컴퓨터에서 하나의 장고 버전만을 대상으로 지정할 수 있습니다. 이것은 당신이 옛날 버전으로 작동하는 웹사이트를 관리하면서 최신 버전의 장고를 이용한 새로운 웹사이트를 만들고 싶을 때 문제가 됩니다.</p> + +<p>결과적으로, 경험있는 파이썬/장고 개발자들은 일반적으로 독립적인 파이썬 환경에서 파이썬 앱들을 실행합니다. 이것은 여러 다른 장고 환경이 하나의 컴퓨터에서 작동 가능하게 합니다. 장고 개발팀에서도 당신이 파이썬 가상 환경을 사용하는 것을 추천합니다!</p> + +<p>이 모듈은 당신이 장고를 가상 환경에 설치했다고 가정합니다. 아래에서 어떻게 설치하는지 알려드리겠습니다.</p> + +<h2 id="파이썬_3_설치">파이썬 3 설치</h2> + +<p>장고를 사용하기 위해서 파이썬을 설치해야 합니다. 파이썬 3을 사용하는 경우 장고와 다른 파이썬 앱에서 사용하는 파이썬 패키지 및 라이브러리를 설치, 업데이트, 제거하는 데 사용되는 <a href="https://pypi.org/">pip3 (Python Package Index)</a> 도구도 필요합니다.</p> + +<p>이번 섹션에서는 현재 당신의 파이썬 버전이 무엇인지 확인하고, 필요에 따라 운영체제(Ubuntu Linux 16.04, macOS X, and Windows 10)별로 새로운 버전 설치 방법을 간단하게 설명합니다.</p> + +<div class="note"> +<p><strong>주의</strong>: 플랫폼에 따라 운영 체제의 자체 패키지 관리자 또는 다른 메커니즘을 통해 Python / pip를 설치할 수도 있습니다. 대부분의 플랫폼의 경우 <a href="https://www.python.org/downloads/">https://www.python.org/downloads/</a>에서 필요한 설치 파일을 다운로드하여 적절한 플랫폼 별 방법을 사용하여 설치할 수 있습니다.</p> +</div> + +<h3 id="우분투_16.04">우분투 16.04</h3> + +<p>우분투 리눅스 18.04 LTS는 파이썬 3.6.6을 기본적으로 포함하고 있습니다. bash 터미널에서 아래 코드를 실행하여 이것을 확인할 수 있습니다.:</p> + +<pre class="brush: bash notranslate"><span style="line-height: 1.5;">python3 -V + Python 3.6.6</span></pre> + +<p>그러나 파이썬3의 패키지 설치를 위한 Python Package Index tool(장고를 포함해)는 기본적으로 설치되어있지 않습니다. bash 터미널에서 아래 코드를 사용하여 pip3를 설치할 수 있습니다:</p> + +<pre class="brush: bash notranslate">sudo apt install python3-pip +</pre> + +<h3 id="맥OS_X">맥OS X</h3> + +<p>맥OS X "엘 캐피탄" 이후의 최신 버전에서는 파이썬3를 포함하고 있지 않습니다. bash 터미널에서 아래 코드를 실행해서 확인할 수 있습니다.:</p> + +<pre class="brush: bash notranslate"><span style="line-height: 1.5;">python3 -V + </span>-bash: python3: command not found</pre> + +<p>당신은<a href="https://www.python.org/"> python.org</a>에서 파이썬3를(pip3 도구도 함께) 쉽게 설치할 수 있습니다:</p> + +<ol> + <li>필요한 설치 파일을 다운로드하세요: + <ol> + <li><a href="https://www.python.org/downloads/">https://www.python.org/downloads/</a> 로 가세요.</li> + <li><strong>Download Python 3.7.0</strong> 버튼을 선택하세요 (정확한 마이너 버전 숫자는 다를 수도 있습니다).</li> + </ol> + </li> + <li>파인더를 통해 파일을 찾아, 패키지 파일을 더블클릭 하세요. 그리고선 설치 과정을 따릅니다.</li> +</ol> + +<p>이제 아래와 같이 파이썬3의 성공적인 설치를 확인할 수 있습니다:</p> + +<pre class="brush: bash notranslate"><span style="line-height: 1.5;">python3 -V + Python 3.7.0</span> +</pre> + +<p>가능한 패키지들의 목록을 불러옴으로써 pip3가 설치된 것을 확인할 수 있습니다:</p> + +<pre class="brush: bash notranslate">pip3 list</pre> + +<h3 id="윈도우_10">윈도우 10</h3> + +<p>윈도우는 파이썬을 기본적으로 포함하고 있지 않지만, <a href="https://www.python.org/">python.org</a>에서(pip3 도구와 함께) 쉽게 설치할 수 있습니다:</p> + +<ol> + <li>필요한 설치 파일을 다운로드하세요: + <ol> + <li><a href="https://www.python.org/downloads/">https://www.python.org/downloads/</a> 로 가세요</li> + <li><strong>Download Python 3.7.1 </strong>버튼을 선택하세요 (정확한 마이너 버전 숫자는 다를 수도 있습니다).</li> + </ol> + </li> + <li>다운로드된 파일을 더블클릭해서 파이썬을 설치하세요.</li> +</ol> + +<p>명령 프롬프트에서 아래 텍스트를 입력해서 파이썬3가 설치된 것을 확인할 수 있습니다:</p> + +<pre class="brush: bash notranslate"><span style="line-height: 1.5;">py -3 -V + Python 3.7.1</span> +</pre> + +<p>윈도우 버전의 설치 파일은 pip3(파이썬 패키지 관리자)가 기본적으로 포함되어 있습니다. 아래 코드로 설치된 패키지 목록을 볼 수 있습니다.</p> + +<pre class="brush: bash notranslate"><span style="line-height: 1.5;">pip3 list</span> +</pre> + +<div class="note"> +<p><strong>주의</strong>: 설치 파일은 위 코드들이 실행되기 위한 모든 것을 설치해줄 것입니다. 만약 파이썬을 찾을 수 없다는 메시지가 나오면, 파이썬을 당신의 시스템 경로에 추가하는 것을 깜빡했을 수가 있습니다. 당신은 설치 파일을 다시 실행해서 'Modify'를 선택 후 두 번째 페이지에 있는 "Add Python to environment variables" 박스에 체크함으로써 시스템 경로에 파이썬을 추가할 수 있습니다.</p> +</div> + +<h2 id="파이썬_가상_환경에서_장고_사용하기">파이썬 가상 환경에서 장고 사용하기</h2> + +<p>우리가 가상 환경을 만드는 데 사용할 라이브러리들은 <a href="https://virtualenvwrapper.readthedocs.io/en/latest/index.html">virtualenvwrapper</a> (리눅스와 맥 OS X) 그리고 <a href="https://pypi.python.org/pypi/virtualenvwrapper-win">virtualenvwrapper-win</a> (윈도우)입니다. 둘 다 <a href="https://developer.mozilla.org/en-US/docs/Python/Virtualenv">virtualenv</a> 도구를 사용하죠. wrapper 도구는 모든 플랫폼의 인터페이스를 관리하기 위한 일관적인 인터페이스를 생성합니다.</p> + +<h3 id="가상_환경_소프트웨어_설치하기">가상 환경 소프트웨어 설치하기</h3> + +<h4 id="우분투_가상_환경_셋업">우분투 가상 환경 셋업</h4> + +<p>파이썬과 pip를 설치한 후에 (virtualenv를 포함하는)virtualenvwrapper를 설치할 수 있습니다. 공식 설치 가이드는 <a href="http://virtualenvwrapper.readthedocs.io/en/latest/install.html">여기</a>엣서 찾을 수 있습니다. 아니면 아래 설명을 따라오세요.</p> + +<p>pip3를 사용해서 그 도구를 설치하세요:</p> + +<pre class="brush: bash notranslate"><code>sudo pip3 install virtualenvwrapper</code></pre> + +<p>그리고 당신의 shell 스타트업 파일(이것은 당신의 홈 디렉토리에 있는 숨겨진 <strong>.bashrc</strong> 파일 이름입니다)의 끝에 아래 코드를 추가하세요. 이 코드들은 가상 환경이 활동할 위치, 당신의 개발 프로젝트 디렉토리 위치, 그리고 이 패키지와 함께 설치된 스크립트의 위치를 설정합니다:</p> + +<pre class="brush: bash notranslate"><code>export WORKON_HOME=$HOME/.virtualenvs +export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3 +export VIRTUALENVWRAPPER_VIRTUALENV_ARGS=' -p /usr/bin/python3 ' +export PROJECT_HOME=$HOME/Devel +source /usr/local/bin/virtualenvwrapper.sh</code></pre> + +<div class="note"> +<p><strong>Note</strong>:<code>VIRTUALENVWRAPPER_PYTHON</code> 와 <code>VIRTUALENVWRAPPER_VIRTUALENV_ARGS </code>변수는 파이썬3의 일반적인 설치 위치를 가리킵니다. 그리고 <code>source /usr/local/bin/virtualenvwrapper.sh</code> 는 <code>virtualenvwrapper.sh</code> 스크립트의 일반적인 위치를 가리킵니다. 만약 테스트 중에 virtualenv가 작동하지 않는다면, 확인해야 할 일 중 하나는 파이썬과 스크립트가 알맞은 위치에 있는지 입니다(그리고 스타트업 파일을 그에 맞게 바꾸세요).<br> + <br> + <code>which virtualenvwrapper.sh</code> 와 <code>which python3</code>커맨드를 사용해서 당신의 시스템에 알맞은 위치를 찾을 수 있습니다.</p> +</div> + +<p>그리고 아래 코드를 터미널에서 실행하여 스타트업 파일을 다시 불러오세요:</p> + +<pre class="brush: bash notranslate"><code>source ~/.bashrc</code></pre> + +<p>이 시점에서 아래와 같이 한 다발의 스크립트가 실행되는 걸 볼 수 있습니다 :</p> + +<pre class="brush: bash notranslate"><code>virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/premkproject +virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/postmkproject +... +virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/preactivate +virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/postactivate +virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/get_env_details</code></pre> + +<p>이제 <code>mkvirtualenv</code>명령으로 새로운 가상 환경을 생성할 수 있습니다.</p> + +<h4 id="맥OS_X_가상_환경_설정">맥OS X 가상 환경 설정</h4> + +<p>맥OS X에서 <em>virtualenvwrapper</em>를 설정하는 것은 우분트와 거의 다를바가 없습니다. (다시 말하지만, <a href="http://virtualenvwrapper.readthedocs.io/en/latest/install.html">공식 설치 가이드</a> 를 따라하거나 아래 내용을 따라해도 됩니다).</p> + +<p>아래와 같이 pip를 이용해 <em>virtualenvwrapper</em> (와 동봉된 <em>virtualenv</em>)를 설치하세요.</p> + +<pre class="brush: bash notranslate"><code>sudo pip3 install virtualenvwrapper</code></pre> + +<p>그리고 쉘 시작 파일(shell startup file)의 맨 아랫쪽에 아래 코드를 추가하세요.</p> + +<pre class="brush: bash notranslate"><code>export WORKON_HOME=$HOME/.virtualenvs +export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3 +export PROJECT_HOME=$HOME/Devel +source /usr/local/bin/virtualenvwrapper.sh</code> +</pre> + +<div class="note"> +<p><strong>참고사항</strong>: <code>VIRTUALENVWRAPPER_PYTHON</code> 변수는 파이썬3의 일반적인 설치 위치를 가리키며, <code>source /usr/local/bin/virtualenvwrapper.sh</code>는 <code>virtualenvwrapper.sh</code>스크립트의 일반적인 위치를 가리킵니다. 당신이 테스트할 때 <em>virtualenv</em> 가 동작하지 않는다면, 한가지 체크해볼 것은 파이썬과 해당 스크립트가 위에서 가리키는 위치에 있는지 여부입니다( 다르다면 startup 파일을 적절하게 수정해야 합니다).</p> + + + +<p>예를 들어, 맥OS상의 어떤 시스템의 설치 테스트에서는 startup 파일에 아래와 같은 코드를 추가할 필요가 있었습니다 :</p> + +<pre class="notranslate"><code>export WORKON_HOME=$HOME/.virtualenvs +export VIRTUALENVWRAPPER_PYTHON=/Library/Frameworks/Python.framework/Versions/3.7/bin/python3 +export PROJECT_HOME=$HOME/Devel +source /Library/Frameworks/Python.framework/Versions/3.7/bin/virtualenvwrapper.sh</code></pre> + +<p><code>which virtualenvwrapper.sh</code>와 <code>which python3</code>.명령을 이용하여 당신 시스템 환경에서의 정확한 위치를 찾을 수 있습니다.</p> + + +</div> + +<p>이 코드들은 우분트에서도 같은 코드이지만, startup 파일은 당신의 홈 디렉토리에 위치하며 다른 이름 <strong>.bash_profile</strong>을 가진 숨겨진 파일입니다.</p> + +<div class="note"> +<p><strong>Note</strong>: 파인더(finder)에서<strong>.bash-profile</strong> 파일을 찾을 수 없다면, 터미널에서 nano를 이용해 이 파일을 열 수 있습니다. </p> + +<p>터미널 명령은 대체로 아래와 같습니다 :</p> + +<pre class="notranslate"><code>cd ~ # Navigate to my home directory +ls -la #List the content of the directory. YOu should see .bash_profile +nano .bash_profile # Open the file in the nano text editor, within the terminal +# Scroll to the end of the file, and copy in the lines above +# Use Ctrl+X to exit nano, Choose Y to save the file.</code> +</pre> + + +</div> + +<p>그 다음엔, 터미널에서 아래 명령을 호출하여 startup 파일을 재실행 하세요 :</p> + +<pre class="brush: bash notranslate"><code>source ~/.bash_profile</code></pre> + +<p>이 시점에서 한 다발의 스크립트가 실행되는 걸 볼 수 있습니다( Ubuntu 설치때와 같은 스크립트 입니다). 이제 <code>mkvirtualenv</code> 명령으로 새로운 가상환경을 생성할 수 있어야 합니다.</p> + +<h4 id="윈도우_10_가상_환경_설정">윈도우 10 가상 환경 설정</h4> + +<p><a href="https://pypi.python.org/pypi/virtualenvwrapper-win">virtualenvwrapper-win</a> 를 설치하는것이 virtualenvwrapper를 설치하는 것보다 훨씬 쉬운데, 가상 환경 정보를 어디에 저장해야할지 설정할 필요가 없기 때문입니다 (기본값이 있습니다). 아래 명령을 명령 프롬프트에서 실행하는 것이 당신이 해야할 전부입니다:</p> + +<pre class="notranslate"><code>pip3 install virtualenvwrapper-win</code></pre> + +<p>이제 <code>mkvirtualenv</code> 명령으로 새로운 가상환경을 생성할 수 있습니다.</p> + +<h3 id="가상_환경_생성하기">가상 환경 생성하기</h3> + +<p>일단 virtualenvwrapper 나 virtualenvwrapper-win 을 설치했다면 가상 환경으로 작업하는 것은 모든 플랫폼별에서 차이가 거의 없습니다.</p> + +<p>이제 mkvirtualenv 명령으로 새로운 가상 환경을 생성할 수 있습니다. 이 명령이 수행될 때 환경이 설정되는 과정을 보게됩니다( 플랫폼에 따라 보이는 것이 다릅니다). 명령이 완료되면 새로운 가상환경이 활성화 됩니다 — 괄호내에 있는 가상환경의 이름으로 프롬프트가 시작하는 것으로 알 수 있습니다 (아래는 우분투의 경우인데, 마지막 라인은 윈도우/맥OS 도 유사합니다).</p> + +<pre class="notranslate"><code>$ mkvirtualenv my_django_environment + +Running virtualenv with interpreter /usr/bin/python3 +... +virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/t_env7/bin/get_env_details +(my_django_environment) ubuntu@ubuntu:~$</code> +</pre> + +<p>이제 당신은 가상환경내에 있으며 장고를 설치하고 개발을 시작할 수 있습니다.</p> + +<div class="note"> +<p><strong>주의</strong>: 이 시점부터 이 기사 ( 정확히는 이 모듈)에서 실행되는 모든 명령은 위에서 우리가 설정한 파이썬 가상환경내에서 실행되는 것으로 간주합니다.</p> +</div> + +<h3 id="가상_환경_사용하기">가상 환경 사용하기</h3> + +<p>당신이 알아야 하는 명령이 몇 가지 더 있다.(도구 문서에는 더 많이 있긴하지만, 아래 명령이 꾸준히 사용하게될 명령들이다):</p> + +<ul> + <li><code>deactivate</code> — 활성화된 파이썬 가상 환경을 비활성화한다</li> + <li><code>workon</code> — 사용가능한 가상 환경 목록을 보여준다</li> + <li><code>workon name_of_environment</code> — 특정 파이썬 가상 환경을 활성화한다</li> + <li><code>rmvirtualenv name_of_environment</code> — 특정 환경을 제거한다.</li> +</ul> + +<h2 id="장고_설치하기">장고 설치하기</h2> + +<p>일단 가상 환경을 하나 생성하고, 진입하기 위해 <code>workon</code> 을 호출하면 장고를 설치하기 위해 pip3를 사용할 수 있다. </p> + +<pre class="brush: bash notranslate">pip3 install django +</pre> + +<p>아래 명령을 실행하여 장고가 설치되었는지 테스트할 수 있다 (이 명령은 단지 파이썬이 django 모듈을 찾을 수 있는지 테스트한다):</p> + +<pre class="brush: bash notranslate"># Linux/macOS X +python3 -m django --version + 2.0 + +# Windows +py -3 -m django --version + 2.0 +</pre> + +<div class="note"> +<p><strong>주의</strong>: 위의 윈도우 명령이 django 모듈이 존재하는지 보여주지 않으면, 아래 명령을 시도해보세요:</p> + +<pre class="notranslate"><code>py -m django --version</code></pre> + +<p>당신의 설치 방법에 따라 변할수도 있긴 하지만, 윈도우에서는 파이썬 3 스트립트는 <code>py -3</code>을 명령앞에 붙여야 실행됩니다. 명령 실행에 문제가 있으면 <code>-3</code>옵션을 빼 보세요. 리눅스/맥OS X 에서는 <code>python3</code>명령입니다.</p> +</div> + +<div class="warning"> +<p><strong>중요사항</strong>: 이 <strong>모듈</strong> 의 나머지부분에서는 파이썬 3를 실행하는 명령으로 리눅스 명령 (<code>python3</code>) 을 사용합니다. 당신이 윈도우에서 진행중이라면 단지 명령 앞부분을 <code>py -3</code>로 변경하면 됩니다.</p> +</div> + +<h2 id="설치한_것_확인하기">설치한 것 확인하기</h2> + +<p>위 테스트는 성공해도 그리 재미있는 작업은 아니었습니다. 더 흥미있는 테스트는 기초적인 프로젝트를 생성해서 동작하는것을 보는것입니다. 이것을 해보기 위해, 명령 프롬프트/터미널에서 장고 앱을 저장할 부모폴더로 이동하세요. 테스트 사이트용 폴더를 생성하고 그 폴더안으로 이동하세요.</p> + +<pre class="brush: bash notranslate">mkdir django_test +cd django_test +</pre> + +<p>그 다음 아래와 같이 <strong>django-admin</strong> 도구를 이용해 "<em>mytestsite</em>" 라는 사이트의 기본 토대를 생성할 수 있습니다. 사이트를 생성한 이후 그 폴더로 가면 해당 프로젝트를 관리할수 있는 <strong>manage.py</strong> 라는 이름의 메인 스크립트파일을 발견할 것입니다.</p> + +<pre class="brush: bash notranslate">django-admin startproject mytestsite +cd mytestsite</pre> + +<p>이 폴더내에서 <code>runserver</code> 명령과 <strong>manage.py</strong> 를 이용하여 아래와 같이 개발용 웹 서버를 실행할 수 있습니다.</p> + +<pre class="brush: bash notranslate">$ python3 manage.py runserver +Performing system checks... + +System check identified no issues (0 silenced). + +You have 15 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions. +Run 'python manage.py migrate' to apply them. + +October 26, 2018 - 07:06:30 +Django version 2.1.2, using settings 'mytestsite.settings' +Starting development server at http://127.0.0.1:8000/ +Quit the server with CONTROL-C. +</pre> + +<div class="note"> +<p><strong>주의</strong>: 위 명령은 Linux/macOS X 명령을 보여준다. 지금 시점에서는 "15 unapplied migration(s)" 의 경고 문구는 무시해도 됩니다 !</p> +</div> + +<p>일단 서버가 실행중이면 당신 시스템의 웹 브라우저로 아래 URL에 가서 만들어진 사이트를 볼 수 있습니다: <code>http://127.0.0.1:8000/</code>. 방문한 사이트에서 아래와 같은 모습이 보여야 합니다:</p> + +<p><img alt="Django Skeleton App Homepage" src="https://mdn.mozillademos.org/files/15728/Django_Skeleton_Website_Homepage.png"></p> + +<ul> +</ul> + +<h2 id="요약">요약</h2> + +<p>당신은 이제 장고 개발 환경을 구축하고 당신의 컴퓨터에서 실행중입니다.</p> + +<p>마지막 확인 섹션에서 <code>django-admin startproject</code> 명령을 이용해 어떻게 새로운 장고 웹사이트를 생성할 수 있는지 간단하게 확인했습니다. 그리고 개발용 웹 서버를 이용해 당신의 브라우저로 웹사이트를 실행했습니다(<code>python3 manage.py runserver</code>). 다음 튜토리얼에서는 간단하지만 완전한 웹 어플리케이션을 구축하는 이 과정을 좀 더 상세히 설명합니다.</p> + +<h2 id="더불어_보기">더불어 보기</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/intro/install/">빠른 설치 가이드</a> (Django 문서)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/install/">Django 설치하는 법 — 완벽 가이드</a> (Django 문서) - Django를 제거하는 방법도 포함됨</li> + <li><a href="https://docs.djangoproject.com/en/2.0/howto/windows/">윈도우에 장고 설치하기</a> (Django 문서)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Introduction", "Learn/Server-side/Django/Tutorial_local_library_website", "Learn/Server-side/Django")}}</p> + +<h2 id="이_모듈에_포함된_튜토리얼">이 모듈에 포함된 튜토리얼</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> diff --git a/files/ko/learn/server-side/django/forms/index.html b/files/ko/learn/server-side/django/forms/index.html new file mode 100644 index 0000000000..e8b51c3bf5 --- /dev/null +++ b/files/ko/learn/server-side/django/forms/index.html @@ -0,0 +1,682 @@ +--- +title: 'Django 튜토리얼 파트 9: 폼(form)으로 작업하기' +slug: Learn/Server-side/Django/Forms +translation_of: Learn/Server-side/Django/Forms +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/authentication_and_sessions", "Learn/Server-side/Django/Testing", "Learn/Server-side/Django")}}</div> + +<p class="summary">이 튜토리얼에서 우리는 Django에서 HTML Form 작업 방법을 보여주고 특히 model Instance를 생성,수정,제거 하는 Form을 작성하는 가장 쉬운 방법을 보여줄 것이다. 이 예제의 일부분으로 우리는 도서관직원이 (admin 앱을 이용하기 보다) 우리가 만든 form을 이용하여 책 대여기간을 연장하거나 작가 정보를 생성,수정,제거할 수 있도록 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 웹사이트를 확장할 것이다.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">사전학습:</th> + <td>아래 파트를 포함하여 앞선 모든 튜토리얼 파트의 학습을 완료할것 <a href="/en-US/docs/Learn/Server-side/Django/authentication_and_sessions">Django 튜토리얼 파트 8: 사용자 인증과 이용권한</a>.</td> + </tr> + <tr> + <th scope="row">학습목표:</th> + <td>사용자로 부터 정보를 얻고 database를 수정하는 form을 작성하는 방법을 이해하기. 일반 클래스 기반 form 편집용 view가 단독 model로 동작하는 form을 작성할 때 얼마나 많이 단순화할 수 있는지 이해하기. </td> + </tr> + </tbody> +</table> + +<h2 id="개요">개요</h2> + +<p><a href="/ko/docs/Web/Guide/HTML/Forms">HTML 폼(Form)</a> 은 웹 페이지상에서 한개 이상의 필드나 위젯들의 묶음을 말하며, 사용자로부터 정보를 수집하여 서버에 제출하는데 사용된다. 다양한 종류의 데이타 입력을 지원하는 위젯들( 텍스트 박스, 체크 박스, 라디오 버튼, 날짜 선택기 등등)이 많이 존재하기 때문에, 폼은 사용자 입력을 수집하는데 유연한 장치라고 할 수 있다. 폼은 또한, 교차 사이트 요청 위조 방지(CSRF protection, cross-site request forgery protection)와 함께 <code>POST</code>요청으로 데이타를 보낼수 있도록 지원하므로, 데이타를 서버와 공유하는데 있어서 비교적 안전한 방법이다.</p> + +<p>지금까지 이 튜토리얼에서 우리가 직접 폼을 생성한 적은 없지만, Django 관리 사이트에서 이미 경험해 보았다. 예를 들면, 아래 스크린 샷에서 <a href="/ko/docs/Learn/Server-side/Django/Models">Book</a> 모델중 하나를 편집하는 폼을 보여주고 있는데, 몇개의 선택 목록과 텍스트 에디터를 볼 수 있다.</p> + +<p><img alt="Admin Site - Book Add" src="https://mdn.mozillademos.org/files/13979/admin_book_add.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<p>폼을 개발하는 것은 복잡한 작업이 될수도 있다! 개발자는 일단, 폼을 위한 HTML을 작성해야 하며, 서버로 입력된 (아마도 브라우저로도 입력된) 데이타의 유효성을 검증하고 적절하게 수정하도록 하고, 유효하지 않은 입력에 대해서 사용자가 알 수 있도록 폼을 에러 메시지와 함께 다시 표시해야하며,성공적으로 제출된 데이타를 적절히 처리하고, 마지막으로 성공했을 경우 사용자가 알수 있게 응답하도록 개발 해야 한다. <em>Django 폼은 다음과 같은 기능의 프레임워크를 제공하여 이 모든 단계중 많은 부분을 덜어내 준다. 이 프레임워크는 폼과 그에 연관된 필드를 프로그램적으로 정의하여 객체를 만들고, 폼 HTML 코드를 작성하는 작업과 데이타 유효성 검증과 사용자 상호작용에 이 객체들을 사용한다.</em></p> + +<p>이번 튜토리얼에서는, 폼을 생성하고 폼으로 작업하는 몇가지 방법을 보여줄 것이다. 특히, 모델을 처리하는 폼을 작성하는데 필요한 작업량을, generic 편집 폼 view를 이용하여 어떻게 획기적으로 줄일 수 있는지 보여줄 것이다. 그 과정에서, 도서관 사서들이 도서관 책 상태를 갱신할 수 있는 폼을 추가하고 책과 저자를 생성, 편집, 삭제할수 있는 페이지를 생성할 것이다. (즉, 위와 같이 책을 편집하는 폼의 기본적인 버전을 다시 개발하는 것이다).</p> + +<h2 id="HTML_폼Form_이란">HTML 폼(Form) 이란?</h2> + +<p>첫번째로 <a href="/en-US/docs/Learn/HTML/Forms">HTML 폼(Form</a>)에 대한 간단한 개요이다. 어떤 "team"의 이름을 입력하는 단일 텍스트 필드와 관련 라벨을 가진 간단한 HTML 폼을 생각해보자:</p> + +<p><img alt="Simple name field example in HTML form" src="https://mdn.mozillademos.org/files/14117/form_example_name_field.png" style="border-style: solid; border-width: 1px; display: block; height: 44px; margin: 0px auto; width: 399px;"></p> + +<p>폼은 HTML에서 적어도 한 개 이상의 <code>type="submit"</code>인 <code>input</code> 요소를 포함하는 <code><form>...</form></code> 태그 사이의 요소들의 집합으로 정의된다.</p> + +<pre class="brush: html"><form action="/team_name_url/" method="post"> + <label for="team_name">Enter name: </label> + <input id="team_name" type="text" name="name_field" value="Default name for team."> + <input type="submit" value="OK"> +</form></pre> + +<p>위 코드에서는 팀 이름을 입력하기 위한 텍스트 필드를 단지 한개만 가지는데, 폼이 가질수 있는 입력 요소와 관련 라벨의 갯수에는 제한이 없다. 필드의 <code>type</code> 속성은 어떤 종류의 위젯이 표시될지 정의한다. 필드의 <code>name</code>과 <code>id</code> 가 JavaScript/CSS/HTML에 있는 필드를 확인하는데 사용되고 <code>value</code>는 필드가 처음 표시될 때의 초기값을 정의한다. 관련 팀 라벨은 <code style="font-style: normal; font-weight: normal;">label</code>태그( 위 코드에서 "Enter name"을 확인)를 이용해 명시된다. 여기서 <code style="font-style: normal; font-weight: normal;">for</code>필드는 관련된 <code style="font-style: normal; font-weight: normal;">input</code>의 <code style="font-style: normal; font-weight: normal;">id</code>값을 포함하고 있다. </p> + +<p><code>submit</code> 타입의 input 태그는 (기본적으로) 사용자가 누를수 있는 버튼으로 표시되는데, 버튼의 동작에 의해 폼의 다른 모든 input 요소의 데이터가 서버로 업로드된다 (위의 경우는 <code>team_name</code>만 업로드된다). 폼 속성으로는 데이터를 보내기 위해 사용되는 HTTP <code>method</code>와 서버상에서 데이타의 목적지를 ( <code>action</code>으로) 정의한다:</p> + +<ul> + <li><code>action</code>: 폼이 제출(submit)될 때 처리가 필요한 데이타를 전달받는 곳의 자원/URL 주소. 설정이 안되면 (혹은 빈 문자열로 설정되면), 폼은 현재 페이지 URL로 다시 제출된다.</li> + <li><code>method</code>: 데이터를 보내는데 사용되는 HTTP 메소드: <em>post</em> 이거나 <em>get 이다.</em> + <ul> + <li><code>POST</code> 메소드는 사이트간 요청 위조 공격에 좀 더 저항성이 좋게 만들 수 있기 때문에, 관련 데이터에 의해 서버 데이터베이스가 변경될 경우에는 항상 사용 되어야 한다. </li> + <li><code>GET</code> 메소드는 사용자 데이터를 변경하지 않는 폼(예를 들면 , 탐색 폼)에서만 사용되어야 한다. URL을 북마크하길 원하거나 공유하기를 원하는 경우에 추천한다. </li> + </ul> + </li> +</ul> + +<p>서버의 역할은 첫번째로 - 필드를 비워두거나 초기값으로 채워두도록 - 초기 폼 상태를 표시하는 것이다. 사용자가 제출 버튼을 누른후에, 서버는 웹 브라우저로부터 폼의 데이타를 념겨 받고, 데이타의 유효성 검증을 해야한다. 폼이 유효하지 않은 데이타를 담고 있다면, 서버는 폼을 다시 표기해야 하는데, 이번에는 사용자가 입력한 유효한 데이타는 그대로 표시하며, 유효하지 않은 필드만 경고 메시지와 함께 표기해야 한다. 일단 모든 필드의 데이타가 유효한 폼 데이타의 제출요청을 서버가 받게 되면, 서버는 적절한 동작(예를 들면, 데이타를 저장하거나, 검색결과를 반화하거나, 파일을 업로딩하는 등등의 작업)을 수행하고 사용자에게 알려주게된다.</p> + +<p>당신이 상상할 수 있듯이, HTML을 작성하고, 입력된 데이타의 유효성을 검증하고, 필요시에 입력된 데이타를 검증 결과와 함게 다시 표시하며, 유효한 데이타에 대해 요구되는 동작을 수행하는 것은 "올바르게 하기"위해서는 꽤 많은 노력이 필요한 작업이다. Django는 일부 과중한 작업과 반복 코드를 줄여줌으로서, 이 작업을 훨씬 쉽게 만든다!</p> + +<h2 id="Django_폼_처리_과정">Django 폼 처리 과정</h2> + +<p>Django의 폼 처리 과정은 (모델에 대한 정보를 보여주는데 있어서) 우리가 앞선 튜토리얼에서 배웠던 것과 같은 기술을 사용한다. : 뷰는 요청을 받고, 모델로 부터 데이타를 읽는것을 포함한 요구되는 동작을 수행한다. 그런 다음, (보여줄 데이타를 포함한 context를 전달받은 템플릿으로 부터) HTML page를 생성하고 반환한다. 서버 또한 사용자가 입력한 데이타를 처리가능 해야 하며, 에러가 있으면 그 페이지를 다시 보여줄 필요가 있기 때문에 상황을 더욱 복잡하게 만든다. </p> + +<p>아래에 Django가 어떻게 요청읕 처리하는지 보여주는 플로우 차트가 있다. 폼을 포함하는 페이지에 대한 요청 (초록색으로 표시함) 으로 시작하고 있다. </p> + +<p><img alt="Updated form handling process doc." src="https://mdn.mozillademos.org/files/14205/Form%20Handling%20-%20Standard.png" style="display: block; height: 569px; margin: 0px auto; width: 800px;"></p> + +<p>위의 다이어그램에 기반하여, Django 폼이 주요하게 다루는 것은 다음과 같다. :</p> + +<ol> + <li>사용자가 처음으로 폼을 요청할 때 기본 폼을 보여준다. + <ul> + <li>폼은 비어있는 필드가 있을 수 있다 (예를 들면, 새로운 책을 등록할 경우) 아니면 초기값으로 채워진 필드가 있을 수도 있다. ( 예를 들면, 기존의 책을 수정하거나, 흔히 사용하는 초기값이 있을경우)</li> + <li>이 시점의 폼은 (초기값이 있긴해도) 유저가 입력한 값에 연관되지 않았기에 unbound 상태라고 불린다.</li> + </ul> + </li> + <li>제출 요청으로 부터 데이타를 수집하고 그것을 폼에 결합한다. + <ul> + <li>데이타를 폼에 결합(binding) 한다는 것은 사용자 입력 데이타와 유효성을 위반한 경우의 에러메시지가 폼을 재표시할 필요가 있을 때 준비되었다는 의미이다.</li> + </ul> + </li> + <li>데이타를 다듬어서 유효성을 검증한다. + <ul> + <li>데이타를 다듬는다는 것은 사용자 입력을 정화(sanitisation) 하고 (예를 들면, 잠재적으로 악의적인 콘덴츠를 서버로 보낼수도 있는 유효하지 않은 문자를 제거하는 것) python에서 사용하는 타입의 데이타로 변환하는 것이다.</li> + <li>유효성검증은 입력된 값이 해당 필드에 적절한 값인지 검사한다. (예를 들면, 데이타가 허용된 범위에 있는 값인지, 너무 짧거나 길지 않은지 등등) </li> + </ul> + </li> + <li>입력된 어떤 데이타가 유효하지 않다면, 폼을 다시 표시하는데 이번에는 초기값이 아니라 유저가 입력한 데이타와 문제가 있는 필드의 에러 메시지와 함께 표시한다.</li> + <li>입력된 모든 데이타가 유효하다면, 요청된 동작을 수행한다. (예를 들면, 데이타를 저장하거나, 이메일을 보내거나, 검색결과를 반환하거나, 파일을 업로딩하는 작업 등등)</li> + <li>일단 모든 작업이 완료되었다면, 사용자를 새로운 페이지로 보낸다.</li> +</ol> + +<p>Django는 위에 설명된 작업을 도와줄 수많은 도구와 접근법을 제공한다. 가장 기초적인 것은 <code>Form</code> 클래스 인데 form HTML의 생성과 데이터 정화와 유효성검증을 간단하게 만든다. 다음 단계에서는, 도서관 사서가 책의 대여갱신을 할수 있도록 해주는 페이지의 실제적인 예제를 이용해 폼이 어떻게 동작하는지 살펴보도록 한다.</p> + +<div class="note"> +<p><strong>참고사항:</strong> <code>Form</code> 이 어떻게 사용되는지 이해해두면 Django의 "고급 레벨" 폼 프레임워크 클래스를 논의하는데 도움이 된다.</p> +</div> + +<h2 id="책_대여갱신_form과_함수_view">책 대여갱신 form과 함수 view</h2> + +<p>다음으로 도서관직원이 대여기간을 갱신할수 있는 페이지를 추가할 것이다. 이 작업을 위해 사용자가 날짜 정보를 입력할 수 있는 form을 생성할 것이다. 그 필드는 현재날짜로 부터 3주의 기간 (일반적인 대여기간)으로 초기화될 것이다. 그리고 도서관직원이 과거날짜를 입력하거나 너무 긴 대여기간을 입력하지 않도록 유효성 체크기능을 추가할 것이다. 유효 날짜가 입력되면, 현재 record의 <code>BookInstance.due_back</code> 필드에 써넣을 것이다.</p> + +<p>아래 예제는 함수기반 view와 <code>Form</code> 클래스를 이용할 것이다. 이어지는 내용에서 form 동작 방법과 현재진행중인 LocalLibray 프로젝트에서 변경할 내용을 설명한다.</p> + +<h3 id="Form_작성하기">Form 작성하기</h3> + +<p><code>Form</code> 클래스는 Django form 관리 시스템의 핵심이다. <code>Form</code> 클래스는 form내 field들, field 배치, 디스플레이 widget, 라벨, 초기값, 유효한 값과 (유효성 체크이후에) 비유효 field에 관련된 에러메시지를 결정한다. <code>Form</code> 클래스는 또한 미리 정의된 포맷(테이블, 리스트 등등) 의 템플릿으로 그자신을 렌더링하는 method나 (세부 조정된 수동 렌더링을 가능케하는) 어떤 요소의 값이라도 얻는 method를 제공한다.</p> + +<h4 id="Form_선언하기">Form 선언하기</h4> + +<p><code>Form</code> 을 선언하는 문법은 <code>Model</code>을 선언하는 것과 많이 닮았으며, 같은 필드타입을 사용한다. ( 또한 일부 매개변수도 유사하다) . 두가지 경우 모두 각 필드가 데이타에 맞는 (유효성 규칙에 맞춘) 타입인지 확인할 필요가 있고, 각 필드가 보여주고 문서화할 description을 가진다는 것에서 Form과 Model이 유사한 문법으로 구성된다는 점을 납득할 수 있다. </p> + +<p>Form 데이타는 어플리케이션 디렉토리 안의 forms.py 파일에 저장되어야 한다. <strong>locallibrary/catalog/forms.py</strong> 파일을 생성하고 열어보자. <code>Form</code>을 생성하기 위해, <code>Form</code>클래스에서 파생된, <code>forms</code>라이브러리를 import 하고 폼 필드를 생성한다. 아래는 도서관 책 갱신 폼에 대한 아주 기본적인 폼 클래스이며 이를 생성한 파일에 추가하자.</p> + +<pre class="brush: python">from django import forms + +class RenewBookForm(forms.Form): + renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).") +</pre> + +<h4 id="Form_필드">Form 필드</h4> + +<p>우리가 구현할 구체적인 내용은 다음과 같다. 대여갱신 날짜를 입력할 한 개의 <code><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#datefield">DateField</a></code> 를 가지는데, 이 필드는 "Renewal date:"라는 라벨로 초기값 없이 빈 칸으로 HTML에 표시되게 된다. 그리고 다음과 같은 도움문구가 추가 된다: "<em>Enter a date between now and 4 weeks (default 3 weeks).</em>" 따로 추가지정할 선택사항 없이, 이 필드는 Django의 <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#django.forms.DateField.input_formats">input_formats</a>: YYYY-MM-DD (2016-11-06), MM/DD/YYYY (02/26/2016), MM/DD/YY (10/25/16) 을 이용하여 날짜를 입력받는다. 그리고 Django의 기본 <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#widget">widget</a>: <a href="https://docs.djangoproject.com/en/2.0/ref/forms/widgets/#django.forms.DateInput">DateInput</a> 를 이용하여 표시될 것이다.</p> + +<p>다음과 같이, 대응되는 모델 필드와 유사성 때문에, 여러분이 의미를 대체로 알만한 수많은 종류의 폼필드가 있다 : <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#booleanfield"><code>BooleanField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#charfield"><code>CharField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#choicefield"><code>ChoiceField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#typedchoicefield"><code>TypedChoiceField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#datefield"><code>DateField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#datetimefield"><code>DateTimeField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#decimalfield"><code>DecimalField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#durationfield"><code>DurationField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#emailfield"><code>EmailField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#filefield"><code>FileField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#filepathfield"><code>FilePathField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#floatfield"><code>FloatField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#imagefield"><code>ImageField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#integerfield"><code>IntegerField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#genericipaddressfield"><code>GenericIPAddressField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#multiplechoicefield"><code>MultipleChoiceField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#typedmultiplechoicefield"><code>TypedMultipleChoiceField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#nullbooleanfield"><code>NullBooleanField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#regexfield"><code>RegexField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#slugfield"><code>SlugField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#timefield"><code>TimeField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#urlfield"><code>URLField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#uuidfield"><code>UUIDField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#combofield"><code>ComboField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#multivaluefield"><code>MultiValueField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#splitdatetimefield"><code>SplitDateTimeField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#modelmultiplechoicefield"><code>ModelMultipleChoiceField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#modelchoicefield"><code>ModelChoiceField</code></a> .</p> + +<p>대부분의 필드에 공통적인 인자들은 아래와 같다. ( 이들은 적절한 기본값을 가지고 있다 ):</p> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#required">required</a>: <code>True</code> 로 설정되면, 필드를 빈칸으로 두거나 <code>None</code> 값을 줄 수 없게된다. 보통필드는 required는 True로 기본 설정되므로, 폼에서 빈 칸을 허용하기 위해서는<code>required=False</code> 로 설정해야 한다. </li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#label">label</a>: HTML에서 필드를 렌더링할때 사용하는 레이블이다. <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#label">label</a> 이 지정되지 않으면, Django는 필드 이름에서 첫번째 문자를 대문자로, 밑줄을 공백으로 변형한 레이블을 새로 생성할 것이다. (예를 들면, renewal_date --> <em>Renewal date</em>).</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#label-suffix">label_suffix</a>: 기본적으로, 콜론(:)이 레이블 다음에 표시된다. (예를 들면, Renewal date<strong>:</strong>). 이 인자는 다른 문자(들)를 포함한 접미사를 지정할 수 있도록 해준다.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#initial">initial</a>: 폼이 나타날 때 해당 필드의 초기 값.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#widget">widget</a>: 사용할 디스플레이 위젯.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#help-text">help_text</a> (위의 예에서 봤듯이): 필드 사용법을 보여주는 추가적인 문구.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#error-messages">error_messages</a>: 해당 필드의 에러 메시지 목록. 필요하면 문구를 수정할 수 있다.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#validators">validators</a>: 해당 필드가 유효한 값을 가질 때 호출되는 함수의 목록.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#localize">localize</a>: 폼 데이타 입력의 현지화(localisation)를 허용함 (자세한 정보는 해당 링크 참조).</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#disabled">disabled</a>: 이 옵션이 <code>True</code> 일때 해당 필드를 볼 수는 있지만 편집이 안됨. 기본 값은 <code>False</code>.</li> +</ul> + +<h4 id="유효성_체크">유효성 체크</h4> + +<p>Django는 데이타의 유효성을 체크할 수 있는 수많은 지점을 제공한다. 어떤 필드의 유효성을 체크하는 가장 쉬운 방법은 해당 필드의 <code>clean_<strong><fieldname></strong>()</code> 메소드를 덮어쓰는 것이다. 예를 들면, 입력된 <code>renewal_date</code> 값이 현재로 부터 4 주이후 사이에 있는지는, <code>clean_<strong>renewal_date</strong>()</code> 를 아래와 같이 구현하여 유효성 체크를 수행할 수 있다.</p> + + + +<p>forms.py 파일을 업데이트 하면 아래와 같은 모습이 된다:</p> + + + +<pre class="brush: python"><code><strong>import datetime</strong> + +from django import forms +<strong>from django.core.exceptions import ValidationError +from django.utils.translation import ugettext_lazy as _ +</strong> +class RenewBookForm(forms.Form): + renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).") + +<strong> def clean_renewal_date(self): + data = self.cleaned_data['renewal_date'] + + # Check if a date is not in the past. + if data < datetime.date.today(): + raise ValidationError(_('Invalid date - renewal in past')) + + # Check if a date is in the allowed range (+4 weeks from today). + if data > datetime.date.today() + datetime.timedelta(weeks=4): + raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead')) + + # Remember to always return the cleaned data. + return data</strong></code></pre> + +<p>주목해야할 지점이 두개 있다. 첫 번째 지점은 <code>self.cleaned_data['renewal_date']</code> 를 이용하여 데이타를 획득하고 이 데이타의 수정여부에 상관없이 함수가 끝나면 이 데이타를 반환한 다는 것이다. 이 단계는 기본 유효성 검사도구를 이용해 입력값을 "다듬고(cleaned)" 잠재적으로 안전하지 않을 수 있는 입력 값을 정화하며 , 해당 입력값에 맞는 표준 형식으로 변환해준다. ( 이 경우에는 Python <code>datetime.datetime</code> 객체 형식이다.).</p> + +<p>두 번째 지점은 입력값이 지정한 범위를 벗어날 경우 <code>ValidationError</code> 에러를 발생시키고, 유효하지 않은 입력값일 때 폼에 보여주고자 하는 에러 문구를 지정하는 부분이다. 위의 예에서는, <a href="https://docs.djangoproject.com/en/2.0/topics/i18n/translation/">Django의 번역 함수들</a> 중하나인 <code>ugettext_lazy()</code> (<code>_()</code> 로 import 됨)로 이 문구를 감싸고 있는데, 당신의 사이트를 나중에 번역하고자 한다면 좋은 예제가 된다.</p> + +<div class="note"> +<p><strong>참고사항:</strong> <a href="https://docs.djangoproject.com/en/2.0/ref/forms/validation/">폼과 필드 유효성 체크</a> (장고 문서임) 에 폼의 유효성 체크에 대한 수많은 다른메소드및 예제가 있다. 예를 들면, 서로 의존관계에 있는 여러개의 필드가 있을 경우, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/api/#django.forms.Form.clean">Form.clean()</a> 함수를 덮어써서, <code>ValidationError</code> 를 다시 발생시킬수도 있다.</p> +</div> + +<p>여기까지가 본 예제에서 필요한 폼에 대한 모든 내용이다!</p> + +<h3 id="URL_Configuration_작성하기">URL Configuration 작성하기</h3> + +<p>뷰를 생성하기 전에, 책 대여갱신 페이지를 위해 URL 설정을 추가 하자. 아래 설정코드를 <strong>locallibrary/catalog/urls.py </strong>아랫 부분에 복사하라.</p> + +<pre class="brush: python">urlpatterns += [ + path('book/<uuid:pk>/renew/', views.renew_book_librarian, name='renew-book-librarian'), +]</pre> + +<p>위 URL 설정코드는 <strong>/catalog/book/<em><bookinstance id></em>/renew/</strong> 형식의 URL을 <strong>views.py</strong> 에 있는 <code>renew_book_librarian()</code> 라는 이름의 함수를 호출하고 <code>BookInstance</code> id를 <code>pk</code>라고 이름지은 매개변수로 전송한다. 위 패턴은 <code>pk</code>가 정확히 <code>uuid</code>의 형식일때만 일치한다.</p> + +<div class="note"> +<p><strong>주목할점</strong>: 추출된 URL 데이타 "<code>pk</code>" 는 당신 마음대로 이름을 정할 수 있다. 왜냐하면 view 함수에 대해서는 어떤 조작이라도 가능하기 때문이다. ( 특정 이름을 기대하는 매개변수를 가진 Generic detail view 클래스를 사용하지 않고 있다.) 하지만 <code>pk</code>는 "primary key"의 약자으로 합리적인 관례상 이름이다 !</p> +</div> + +<h3 id="View_작성하기">View 작성하기</h3> + +<p>위의 <a href="#django_form_handling_process">Django 폼 처리 과정</a> 에서 설명된대로, 위의 폼 뷰는 첫번째로 호출될 때는 기본 폼을 표시해야 한다. 그리고 나서 데이터가 유효하지 않은 경우 에러 메시지를 재 표시하고, 데이터가 유효한 경우에는 데이타를 처리하고 새로운 페이지를 표시해야 한다. 이런 서로 다른 동작을 수행하기 위해, 해당 뷰가 기본 폼을 표시하도록 현재 첫번째로 호출되고 있는지, 데이터 유효성을 체크하기 위해 연속되어 이어지는 호출인지 알 수 있어야 한다. </p> + +<p>서버에 정보를 제출하는 <code>POST</code>리퀘스트를 사용하는 폼에 대해서, 가장 흔한 패턴은 뷰에서 <code>POST</code> 요청 타입 인지 판단 (<code>if request.method == 'POST':</code>) 하여 유효한 요청 여부를 확인하고 <code>GET</code> ( <code>else</code> 조건으로 ) 요청 타입인 경우 초기 폼 생성을 요청한다. <code>GET</code>요청으로 데이터를 제출하려고 한다면 첫 번째 뷰 호출인지 두 번째 이상의 뷰 호출인지 판단하는 전형적인 접근 방법은 폼 데이터를 읽어보는 (즉 폼에서 숨겨진 값을 읽는)것이다.</p> + +<p>책 대여갱신 과정은 데이터베이스에 결과를 보내기 때문에, 관례상 <code>POST</code>요청 방법을 사용한다. 아래 코드는 이런 종류의 function 뷰에 대해 가장 기본적인 형식을 보여준다.</p> + +<pre class="brush: python">import datetime + +from django.shortcuts import get_object_or_404 +from django.http import HttpResponseRedirect +from django.urls import reverse + +from catalog.forms import RenewBookForm + +def renew_book_librarian(request, pk): + book_instance = get_object_or_404(BookInstance, pk=pk) + + # POST 요청이면 폼 데이터를 처리한다 +<strong> if request.method == 'POST':</strong> + + # 폼 인스턴스를 생성하고 요청에 의한 데이타로 채운다 (binding): + book_renewal_form = RenewBookForm(request.POST) + + # 폼이 유효한지 체크한다: + <strong>if book_renewal_form.is_valid():</strong> + # form.cleaned_data 데이타를 요청받은대로 처리한다(여기선 그냥 모델 due_back 필드에 써넣는다) + book_instance.due_back = book_renewal_form.cleaned_data['renewal_date'] + book_instance.save() + + # 새로운 URL로 보낸다: + return HttpResponseRedirect(reverse('all-borrowed') ) + + # GET 요청 (혹은 다른 메소드)이면 기본 폼을 생성한다. +<strong> else:</strong> + proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3) + book_renewal_form = RenewBookForm(initial={'renewal_date': proposed_renewal_date}) + + context = { + 'form': book_renewal_form, + 'book_instance': book_instance, + } + + return render(request, 'catalog/book_renew_librarian.html', context)</pre> + +<p>첫부분에서는 미리 작성된 폼 (<code>RenewBookForm</code>)을 import 하고 뷰 함수의 내부에서 쓰일 유용한 객체나 메소드를 import 한다:</p> + +<ul> + <li><code><a href="https://docs.djangoproject.com/en/2.0/topics/http/shortcuts/#get-object-or-404">get_object_or_404()</a></code>: 해당 모델의 기본 키(primary key) 값에 연결되는 특정 객체를 반환하거나 해당 record가 없을경우 <code>Http404</code>예외를 발생시킨다. </li> + <li><code><a href="https://docs.djangoproject.com/en/2.0/ref/request-response/#django.http.HttpResponseRedirect">HttpResponseRedirect</a></code>: 특정 URL로의 재전송을 생성한다. (HTTP status code 302). </li> + <li><code><a href="https://docs.djangoproject.com/en/2.0/ref/urlresolvers/#django.urls.reverse">reverse()</a></code>: URL 설정(configuration) 의 이름과 전달인자들로 부터 URL을 만들어낸다. 템플릿에서 사용했던 <code>url</code>태그에 해당하는 파이썬 형식의 동일 표현이다.</li> + <li><code><a href="https://docs.python.org/3/library/datetime.html">datetime</a></code>: 날짜와 시간을 다루는 파이썬 라이브러리 이다. </li> +</ul> + +<p>뷰 코드는 첫번째로 현재 <code>BookInstance</code>를 얻기위해 <code>get_object_or_404()</code>함수에 <code>pk</code> 전달인자를 사용한다( <code>BookInstance</code>가 없으면 뷰는 그 즉시 완료되며 페이지에는 "발견 하지 못함" 에러가 뜨게된다). <code>POST</code>요청이아니라면 ( <code>else</code>절로 처리되어) <code>renewal_date</code>필드에 대해 <code>initial</code>값을 넘겨주는 기본 폼을 생성한다. ( 기본 값은 아래 코드에서 볼드체로 표시된대로, 현재 날짜로 부터 3주후이다). </p> + +<pre class="brush: python"> book_instance = get_object_or_404(BookInstance, pk=pk) + + # GET 요청(혹은 다른 메소드)이면 기본 폼을 생성한다. + <strong>else:</strong> + proposed_renewal_date = datetime.date.today() + datetime.timedelta(<strong>weeks=3</strong>) + <strong>book_renewal_form = RenewBookForm(initial={'</strong>renewal_date<strong>': </strong>proposed_renewal_date<strong>})</strong> + + context = { + 'form': book_renewal_form, + 'book_instance': book_instance, + } + + return render(request, 'catalog/book_renew_librarian.html', context)</pre> + +<p>폼을 생성한이후, HTML 페이지를 생성하기 위해 <code>render()</code>를 호출하는데, 이 함수에서 템플릿과 폼을 포함하는 context를 특정한다. 이 경우에 context는 <code>BookInstance</code> 또한 포함하는데, <code>BookInstance</code>는 갱신하고자 하는 책의 정보를 템플릿에 제공하는데 사용한다.</p> + +<p>하지만 <code>POST</code>요청이라면, <code>form</code>객체를 생성하고 <code>POST</code>요청에서의 데이터로 <code>form</code>을 채운다. 이 처리과정은 "binding"으로 불리며 폼의 유효성 체크를 할수 있도록 해준다. 여기에서 모든 필드에 관련된 유효성 체크 코드 - 날짜필드가 실제상황에서 유효한 값을 가지는지 체크하는 일반적인 코드와 날짜가 정해진 범위의 값을 가지는지 체크하는 폼의 특별한 함수인 <code>clean_renewal_date()</code> 를 포함하는 코드 - 를 실행하며 폼의 데이타가 유효한지 체크한다. </p> + +<pre class="brush: python"> book_instance = get_object_or_404(BookInstance, pk=pk) + + # POST 요청이면 폼 데이터를 처리한다 + if request.method == 'POST': + + # 폼 인스턴스를 생성하고 요청에 의한 데이타로 채운다 (binding): +<strong> book_renewal_form = RenewBookForm(request.POST)</strong> + + # 폼이 유효한지 체크한다: + if book_renewal_form.is_valid(): + # form.cleaned_data 데이타를 요청받은대로 처리한다(여기선 그냥 모델 due_back 필드에 써넣는다) + book_inst.due_back = form.cleaned_data['renewal_date'] + book_inst.save() + + # 새로운 URL로 보낸다: + return HttpResponseRedirect(reverse('all-borrowed') ) + + context = { + 'form': book_renewal_form, + 'book_instance': book_instance, + } + + return render(request, 'catalog/book_renew_librarian.html', context)</pre> + +<p>폼의 데이터가 유효하지 않다면 <code>render()</code>함수가 다시 호출된다. 하지만 이번에 context로 넘겨지는 폼의 값에는 에러메시지가 포함될 것이다. </p> + +<p>폼의 데이터가 유효하다면, <code>form.cleaned_data</code>속성을 통해 데이타 사용을 시작할수 있다(즉, 다음과 같다. <code>data = form.cleaned_data['renewal_date']</code>). 여기에서는 단지 폼 데이터를 <code>BookInstance</code>객체에 관련된 <code>due_back</code>변수에 저장했다. </p> + +<div class="warning"> +<p><strong>중요사항</strong>: 'request'객체를 통해 직접 폼 데이터를 가져올수는 있으나 ( 예를 들면 <code>request.POST['renewal_date']</code>나 GET 요청인경우 <code>request.GET['renewal_date']</code>처럼), 이 방식은 <strong>절대</strong> 추천하지 않는다. 위 코드에서 깔끔한 데이타(cleaned_data)란 것은 정제되고(sanitised), 유효성체크가되고, 파이썬에서 많이쓰는 타입의 데이타이다.</p> +</div> + +<p>뷰에서 폼 처리의 마지막 단계는 , 대개는 "Success" 페이지라는 다른 페이지로 주소를 바꾸는 것이다. 여기서는 <code>'all-borrowed'</code>라는 뷰( 이 뷰는 <a href="/en-US/docs/Learn/Server-side/Django/authentication_and_sessions#Challenge_yourself">Django 튜토리얼 파트 8: 사용자 인증과 사용권한</a> 파트에서 "도전과제로" 생성했었다) 로 주소를 바꾸기 위해 <code>HttpResponseRedirect</code>와 <code>reverse()</code>를 사용한다. 당신이 이 페이지를 생성하지 않았다면 URL 주소가 '/'인 홈페이지로 주소를 변경하는 것을 고려해보자.</p> + +<p>여기까지가 폼을 다루기 위해 필요한 모든 것이지만, 해당 폼 뷰의 사용권한을 도서관사서로 한정해야 하는 문제가 남아있다. <code>BookInstance</code>모델에 "<code>can_renew</code>"라는 새로운 사용권한을 추가해야 하겠지만, 작업을 간단하게 하기위해 그냥 기존의 사용권한<code>can_mark_returned</code>에 함수 데코레이터<code>@permission_required</code>를 사용하도록 하겠다.</p> + +<p>그러므로 최종 뷰의 코드는 다음과 같다. 이 코드를 <strong>locallibrary/catalog/views.py</strong> 의 아랫부분에 복사해넣어라.</p> + +<pre class="brush: python">import datetime + +<strong>from django.contrib.auth.decorators import permission_required</strong> +from django.shortcuts import get_object_or_404 +from django.http import HttpResponseRedirect +from django.urls import reverse + +from catalog.forms import RenewBookForm + +<strong>@permission_required('catalog.<code>can_mark_returned</code>')</strong> +def renew_book_librarian(request, pk): + """도서관 사서에 의해 특정 BookInstance를 갱신하는 뷰 함수.""" + book_instance = get_object_or_404(BookInstance, pk=pk) + + # POST 요청이면 폼 데이터를 처리한다 + if request.method == 'POST': + + # 폼 인스턴스를 생성하고 요청에 의한 데이타로 채운다 (binding): + book_renewal_form = RenewBookForm(request.POST) + + # 폼이 유효한지 체크한다: + if book_renewal_form.is_valid(): + # book_renewal_form.cleaned_data 데이타를 요청받은대로 처리한다(여기선 그냥 모델 due_back 필드에 써넣는다) + book_inst.due_back = book_renewal_form.cleaned_data['renewal_date'] + book_inst.save() + + # 새로운 URL로 보낸다: + return HttpResponseRedirect(reverse('all-borrowed') ) + + # GET 요청(혹은 다른 메소드)이면 기본 폼을 생성한다. + else: + proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3) + book_renewal_form = RenewBookForm(initial={'renewal_date': proposed_renewal_date}) + + context = { + 'form': book_renewal_form, + 'book_instance': book_instance, + } + + return render(request, 'catalog/book_renew_librarian.html', context) +</pre> + +<h3 id="Template_작성하기">Template 작성하기</h3> + +<p>뷰 에서 참조되는 템플릿 (<strong>/catalog/templates/catalog/book_renew_librarian.html</strong>)을 생성하고 아래 코드를 복사해넣어라 :</p> + +<pre class="brush: html">{% extends "base_generic.html" %} + +{% block content %} + <h1>Renew: \{{ book_instance.book.title }}</h1> + <p>Borrower: \{{ book_instance.borrower }}</p> + <p{% if book_instance.is_overdue %} class="text-danger"{% endif %}>Due date: \{{book_instance.due_back}}</p> + +<strong> <form action="" method="post"> + {% csrf_token %} + \{{ form.as_table }} + <input type="submit" value="Submit"> + </form></strong> +{% endblock %}</pre> + +<p>이 작업의 대부분은 앞선 튜토리얼에서 익숙해진 작업이다. 우리는 베이스 템플릿을 확장하고 콘텐츠 블럭을 재설정한다. <code>\{{bookinst}}</code>(와 그에 따른 변수) 가 <code>render()</code> 함수 내의 컨텍스트 객체로 넘겨졌기 때문에 <code>\{{bookinst}}</code>를 참조할수 있다. 이들을 이용해 책 제목, 대여자 그리고 이전 대여마감일의 목록을 열거한다.</p> + +<p>폼 코드는 상대적으로 간단하다. 우선 form이 어디에 제출될 것인지(<code>action</code>)(POST인지 PUT인지) 명시하여 <code>form</code> 태그를 선언하고, 데이터를 제출하는 <code>method</code> 를 명시한다(이 경우에는 "HTTP POST") — 해당 페이지 위 쪽의 <a href="#HTML_forms">HTML Forms</a> overview에서 보았듯이, <code>action</code>을 비워 놓았는데, 이렇게 하면 form 데이터가 현재 URL페이지로 다시 POST 된다(지금 우리가 하고자 하는 것입니다!). <code>form</code> 태그 안에는 <code>submit</code> input 태그 또한 만들어서 페이지 사용자가 눌러서 데이터를 제출(submit)할 수 있도록 한다. <code>form</code> 태그 안에정의된 또 다른 하나인 <code>{% csrf_token %}</code>는 Django의 cross-site 위조 방지의 방식 중 하나이다. </p> + +<div class="note"> +<p><strong>Note:</strong> Add the <code>{% csrf_token %}</code> to every Django template you create that uses <code>POST</code> to submit data. This will reduce the chance of forms being hijacked by malicious users.</p> +</div> + +<p>마지막으로 템플릿에 context라는 dictionary형 데이터로 넘기는 <code>\{{form}}</code> 변수가 남았다. 별로 놀랍지 않을 수 있지만, 아래처럼 하면 form의 모든 field의 필드, 위젯, 도움말을 함께 렌더링하는 기본 렌더링기능을 사용할 수 있다 — 렌더링된 결과는 다음과 같다.</p> + +<pre class="brush: html"><tr> + <t><label for="id_renewal_date">Renewal date:</label></th> + <td> + <input id="id_renewal_date" name="renewal_date" type="text" value="2016-11-08" required /> + <br /> + <span class="helptext">Enter date between now and 4 weeks (default 3 weeks).</span> + </td> +</tr> +</pre> + +<div class="note"> +<p><strong>Note:</strong> 필드가 하나만 있기 때문에 분명하지는 않지만 기본적으로 모든 필드는 자체 테이블 행에 정의되어 있습니다. 템플릿 변수 <code>\{{ form.as_table }}</code>을 참조하면이 동일한 렌더링이 제공됩니다. </p> +</div> + +<p>유효하지 않은 날짜를 입력하는 경우 페이지에서 렌더링 된 오류 목록 (아래 굵게 표시)을 얻게됩니다.</p> + +<pre class="brush: html"><tr> + <th><label for="id_renewal_date">Renewal date:</label></th> + <td> +<strong> <ul class="errorlist"> + <li>Invalid date - renewal in past</li> + </ul></strong> + <input id="id_renewal_date" name="renewal_date" type="text" value="2015-11-08" required /> + <br /> + <span class="helptext">Enter date between now and 4 weeks (default 3 weeks).</span> + </td> +</tr></pre> + +<h4 id="Form_template_variable을_사용하는_다른_방법">Form template variable을 사용하는 다른 방법</h4> + +<p>위와 같이 <code>\{{form.as_table</code><code>}}</code> 을 사용하면 각 필드가 테이블 행으로 렌더링됩니다. 또한 각 필드를 <code>\{{form.as_ul}}</code> 을 사용하여 목록항목(list item)으로 렌더링하거나 <code>\{{form.as_p}}</code>를 사용하여 단락(paragraph)으로 렌더링 할 수도 있습니다.</p> + +<p>또한 dot notation을 사용하여 form 속성을 인덱싱하여 각 부분 렌더링을 완벽하게 제어 할 수도 있습니다. 예를 들어, <code>renewal_date</code> 필드에 대한 여러 개의 개별 항목에 접근 할 수 있습니다.</p> + +<ul> + <li><code>\{{form.renewal_date}}:</code> The whole field.</li> + <li><code>\{{form.renewal_date.errors}}</code>: The list of errors.</li> + <li><code>\{{form.renewal_date.id_for_label}}</code>: The id of the label.</li> + <li><code>\{{form.renewal_date.help_text}}</code>: The field help text.</li> +</ul> + +<p>템플릿의 양식을 수동으로 렌더링하고 템플릿 필드를 동적으로 반복하는 방법에 대한 자세한 예제는, <a href="https://docs.djangoproject.com/en/2.0/topics/forms/#rendering-fields-manually">Working with forms > Rendering fields manually</a> (Django docs)를 참고.</p> + +<h3 id="Page를_시험하기">Page를 시험하기</h3> + +<p>If you accepted the "challenge" in <a href="/en-US/docs/Learn/Server-side/Django/authentication_and_sessions#Challenge_yourself">Django Tutorial Part 8: User authentication and permissions</a> you'll have a list of all books on loan in the library, which is only visible to library staff. We can add a link to our renew page next to each item using the template code below.</p> + +<pre class="brush: html">{% if perms.catalog.can_mark_returned %}- <a href="{% url 'renew-book-librarian' bookinst.id %}">Renew</a> {% endif %}</pre> + +<div class="note"> +<p><strong>Note</strong>: Remember that your test login will need to have the permission "<code>catalog.can_mark_returned</code>" in order to access the renew book page (perhaps use your superuser account).</p> +</div> + +<p>You can alternatively manually construct a test URL like this — <a href="http://127.0.0.1:8000/catalog/book/<bookinstance id>/renew/">http://127.0.0.1:8000/catalog/book/<em><bookinstance_id></em>/renew/</a> (a valid bookinstance id can be obtained by navigating to a book detail page in your library, and copying the <code>id</code> field).</p> + +<h3 id="What_does_it_look_like">What does it look like?</h3> + +<p>If you are successful, the default form will look like this:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14209/forms_example_renew_default.png" style="border-style: solid; border-width: 1px; display: block; height: 292px; margin: 0px auto; width: 680px;"></p> + +<p>The form with an invalid value entered, will look like this:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14211/forms_example_renew_invalid.png" style="border-style: solid; border-width: 1px; display: block; height: 290px; margin: 0px auto; width: 658px;"></p> + +<p>The list of all books with renew links will look like this:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14207/forms_example_renew_allbooks.png" style="border-style: solid; border-width: 1px; display: block; height: 256px; margin: 0px auto; width: 613px;"></p> + +<h2 id="ModelForms">ModelForms</h2> + +<p>Creating a <code>Form</code> class using the approach described above is very flexible, allowing you to create whatever sort of form page you like and associate it with any model or models.</p> + +<p>However if you just need a form to map the fields of a <em>single</em> model then your model will already define most of the information that you need in your form: fields, labels, help text, etc. Rather than recreating the model definitions in your form, it is easier to use the <a href="https://docs.djangoproject.com/en/2.0/topics/forms/modelforms/">ModelForm</a> helper class to create the form from your model. This <code>ModelForm</code> can then be used within your views in exactly the same way as an ordinary <code>Form</code>.</p> + +<p>A basic <code>ModelForm</code> containing the same field as our original <code>RenewBookForm</code> is shown below. All you need to do to create the form is add <code>class Meta</code> with the associated <code>model</code> (<code>BookInstance</code>) and a list of the model <code>fields</code> to include in the form (you can include all fields using <code>fields = '__all__'</code>, or you can use <code>exclude</code> (instead of <code>fields</code>) to specify the fields <em>not </em>to include from the model).</p> + +<pre class="brush: python">from django.forms import ModelForm +from .models import BookInstance + +class RenewBookModelForm(ModelForm): +<strong> class Meta: + model = BookInstance + fields = ['due_back',]</strong> +</pre> + +<div class="note"> +<p><strong>Note</strong>: This might not look like all that much simpler than just using a <code>Form</code> (and it isn't in this case, because we just have one field). However if you have a lot of fields, it can reduce the amount of code quite significantly!</p> +</div> + +<p>The rest of the information comes from the model field definitions (e.g. labels, widgets, help text, error messages). If these aren't quite right, then we can override them in our <code>class Meta</code>, specifying a dictionary containing the field to change and its new value. For example, in this form we might want a label for our field of "<em>Renewal date</em>" (rather than the default based on the field name: <em>Due date</em>), and we also want our help text to be specific to this use case. The <code>Meta</code> below shows you how to override these fields, and you can similarly set <code>widgets</code> and <code>error_messages</code> if the defaults aren't sufficient.</p> + +<pre class="brush: python">class Meta: + model = BookInstance + fields = ['due_back',] +<strong> labels = { 'due_back': _('Renewal date'), } + help_texts = { 'due_back': _('Enter a date between now and 4 weeks (default 3).'), } </strong> +</pre> + +<p>To add validation you can use the same approach as for a normal <code>Form</code> — you define a function named <code>clean_<em>field_name</em>()</code> and raise <code>ValidationError</code> exceptions for invalid values. The only difference with respect to our original form is that the model field is named <code>due_back</code> and not "<code>renewal_date</code>".</p> + +<pre class="brush: python">from django.forms import ModelForm +from .models import BookInstance + +class RenewBookModelForm(ModelForm): +<strong> def clean_due_back(self): + data = self.cleaned_data['due_back'] + + #Check date is not in past. + if data < datetime.date.today(): + raise ValidationError(_('Invalid date - renewal in past')) + + #Check date is in range librarian allowed to change (+4 weeks) + if data > datetime.date.today() + datetime.timedelta(weeks=4): + raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead')) + + # Remember to always return the cleaned data. + return data +</strong> + class Meta: + model = BookInstance + fields = ['due_back',] + labels = { 'due_back': _('Renewal date'), } + help_texts = { 'due_back': _('Enter a date between now and 4 weeks (default 3).'), } +</pre> + +<p>The class <code>RenewBookModelForm</code> below is now functionally equivalent to our original <code>RenewBookForm</code>. You could import and use it wherever you currently use <code>RenewBookForm</code>.</p> + +<h2 id="Generic_editing_views">Generic editing views</h2> + +<p>The form handling algorithm we used in our function view example above represents an extremely common pattern in form editing views. Django abstracts much of this "boilerplate" for you, by creating <a href="https://docs.djangoproject.com/en/2.0/ref/class-based-views/generic-editing/">generic editing views</a> for creating, editing, and deleting views based on models. Not only do these handle the "view" behaviour, but they automatically create the form class (a <code>ModelForm</code>) for you from the model.</p> + +<div class="note"> +<p><strong>Note: </strong>In addition to the editing views described here, there is also a <a href="https://docs.djangoproject.com/en/2.0/ref/class-based-views/generic-editing/#formview">FormView</a> class, which lies somewhere between our function view and the other generic views in terms of "flexibility" vs "coding effort". Using <code>FormView</code> you still need to create your <code>Form</code>, but you don't have to implement all of the standard form-handling pattern. Instead you just have to provide an implementation of the function that will be called once the submitted is known to be be valid.</p> +</div> + +<p>In this section we're going to use generic editing views to create pages to add functionality to create, edit, and delete <code>Author</code> records from our library — effectively providing a basic reimplementation of parts of the Admin site (this could be useful if you need to offer admin functionality in a more flexible way that can be provided by the admin site).</p> + +<h3 id="Views">Views</h3> + +<p>Open the views file (<strong>locallibrary/catalog/views.py</strong>) and append the following code block to the bottom of it:</p> + +<pre class="brush: python">from django.views.generic.edit import CreateView, UpdateView, DeleteView +from django.urls import reverse_lazy +from .models import Author + +class AuthorCreate(CreateView): + model = Author + fields = '__all__' + initial={'date_of_death':'05/01/2018',} + +class AuthorUpdate(UpdateView): + model = Author + fields = ['first_name','last_name','date_of_birth','date_of_death'] + +class AuthorDelete(DeleteView): + model = Author + success_url = reverse_lazy('authors')</pre> + +<p>As you can see, to create the views you need to derive from <code>CreateView</code>, <code>UpdateView</code>, and <code>DeleteView</code> (respectively) and then define the associated model.</p> + +<p>For the "create" and "update" cases you also need to specify the fields to display in the form (using in same syntax as for <code>ModelForm</code>). In this case we show both the syntax to display "all" fields, and how you can list them individually. You can also specify initial values for each of the fields using a dictionary of <em>field_name</em>/<em>value</em> pairs (here we arbitrarily set the date of death for demonstration purposes — you might want to remove that!). By default these views will redirect on success to a page displaying the newly created/edited model item, which in our case will be the author detail view we created in a previous tutorial. You can specify an alternative redirect location by explicitly declaring parameter <code>success_url</code> (as done for the <code>AuthorDelete</code> class).</p> + +<p>The <code>AuthorDelete</code> class doesn't need to display any of the fields, so these don't need to be specified. You do however need to specify the <code>success_url</code>, because there is no obvious default value for Django to use. In this case we use the <code><a href="https://docs.djangoproject.com/en/2.0/ref/urlresolvers/#reverse-lazy">reverse_lazy()</a></code> function to redirect to our author list after an author has been deleted — <code>reverse_lazy()</code> is a lazily executed version of <code>reverse()</code>, used here because we're providing a URL to a class-based view attribute.</p> + +<h3 id="Templates">Templates</h3> + +<p>The "create" and "update" views use the same template by default, which will be named after your model: <em>model_name</em><strong>_form.html</strong> (you can change the suffix to something other than <strong>_form</strong> using the <code>template_name_suffix</code> field in your view, e.g. <code>template_name_suffix = '_other_suffix'</code>)</p> + +<p>Create the template file <strong>locallibrary/catalog/templates/catalog/author_form.html</strong> and copy in the text below.</p> + +<pre class="brush: html">{% extends "base_generic.html" %} + +{% block content %} + +<form action="" method="post"> + {% csrf_token %} + <table> + \{{ form.as_table }} + </table> + <input type="submit" value="Submit" /> + +</form> +{% endblock %}</pre> + +<p>This is similar to our previous forms, and renders the fields using a table. Note also how again we declare the <code>{% csrf_token %}</code> to ensure that our forms are resistant to CSRF attacks.</p> + +<p>The "delete" view expects to find a template named with the format <em>model_name</em><strong>_confirm_delete.html</strong> (again, you can change the suffix using <code>template_name_suffix</code> in your view). Create the template file <strong>locallibrary/catalog/templates/catalog/author_confirm_delete</strong><strong>.html</strong> and copy in the text below.</p> + +<pre class="brush: html">{% extends "base_generic.html" %} + +{% block content %} + +<h1>Delete Author</h1> + +<p>Are you sure you want to delete the author: \{{ author }}?</p> + +<form action="" method="POST"> + {% csrf_token %} + <input type="submit" action="" value="Yes, delete." /> +</form> + +{% endblock %} +</pre> + +<h3 id="URL_configurations">URL configurations</h3> + +<p>Open your URL configuration file (<strong>locallibrary/catalog/urls.py</strong>) and add the following configuration to the bottom of the file:</p> + +<pre class="brush: python">urlpatterns += [ + path('author/create/', views.AuthorCreate.as_view(), name='author_create'), + path('author/<int:pk>/update/', views.AuthorUpdate.as_view(), name='author_update'), + path('author/<int:pk>/delete/', views.AuthorDelete.as_view(), name='author_delete'), +]</pre> + +<p>There is nothing particularly new here! You can see that the views are classes, and must hence be called via <code>.as_view()</code>, and you should be able to recognise the URL patterns in each case. We must use <code>pk</code> as the name for our captured primary key value, as this is the parameter name expected by the view classes.</p> + +<p>The author create, update, and delete pages are now ready to test (we won't bother hooking them into the site sidebar in this case, although you can do so if you wish).</p> + +<div class="note"> +<p><strong>Note</strong>: Observant users will have noticed that we didn't do anything to prevent unauthorised users from accessing the pages! We leave that as an exercise for you (hint: you could use the <code>PermissionRequiredMixin</code> and either create a new permission or reuse our <code>can_mark_returned</code> permission).</p> +</div> + +<h3 id="Testing_the_page">Testing the page</h3> + +<p>First login to the site with an account that has whatever permissions you decided are needed to access the author editing pages.</p> + +<p>Then navigate to the author create page: <a href="http://127.0.0.1:8000/catalog/author/create/">http://127.0.0.1:8000/catalog/author/create/</a>, which should look like the screenshot below.</p> + +<p><img alt="Form Example: Create Author" src="https://mdn.mozillademos.org/files/14223/forms_example_create_author.png" style="border-style: solid; border-width: 1px; display: block; height: 184px; margin: 0px auto; width: 645px;"></p> + +<p>Enter values for the fields and then press <strong>Submit</strong> to save the author record. You should now be taken to a detail view for your new author, with a URL of something like <em>http://127.0.0.1:8000/catalog/author/10</em>.</p> + +<p>You can test editing records by appending <em>/update/</em> to the end of the detail view URL (e.g. <em>http://127.0.0.1:8000/catalog/author/10/update/</em>) — we don't show a screenshot, because it looks just like the "create" page!</p> + +<p>Last of all we can delete the page, by appending delete to the end of the author detail-view URL (e.g. <em>http://127.0.0.1:8000/catalog/author/10/delete/</em>). Django should display the delete page shown below. Press <strong>Yes, delete.</strong> to remove the record and be taken to the list of all authors.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14221/forms_example_delete_author.png" style="border-style: solid; border-width: 1px; display: block; height: 194px; margin: 0px auto; width: 561px;"></p> + + + +<h2 id="Challenge_yourself">Challenge yourself</h2> + +<p>Create some forms to create, edit and delete <code>Book</code> records. You can use exactly the same structure as for <code>Authors</code>. If your <strong>book_form.html</strong> template is just a copy-renamed version of the <strong>author_form.html</strong> template, then the new "create book" page will look like the screenshot below:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14225/forms_example_create_book.png" style="border-style: solid; border-width: 1px; display: block; height: 521px; margin: 0px auto; width: 595px;"></p> + +<ul> +</ul> + +<h2 id="Summary">Summary</h2> + +<p>Creating and handling forms can be a complicated process! Django makes it much easier by providing programmatic mechanisms to declare, render and validate forms. Furthermore, Django provides generic form editing views that can do <em>almost all</em> the work to define pages that can create, edit, and delete records associated with a single model instance.</p> + +<p>There is a lot more that can be done with forms (check out our See also list below), but you should now understand how to add basic forms and form-handling code to your own websites.</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/forms/">Working with forms</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/intro/tutorial04/#write-a-simple-form">Writing your first Django app, part 4 > Writing a simple form</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/api/">The Forms API</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/">Form fields</a> (Django docs) </li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/validation/">Form and field validation</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/generic-editing/">Form handling with class-based views</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/forms/modelforms/">Creating forms from models</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/class-based-views/generic-editing/">Generic editing views</a> (Django docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/authentication_and_sessions", "Learn/Server-side/Django/Testing", "Learn/Server-side/Django")}}</p> + + + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> diff --git a/files/ko/learn/server-side/django/generic_views/index.html b/files/ko/learn/server-side/django/generic_views/index.html new file mode 100644 index 0000000000..f3f496f97c --- /dev/null +++ b/files/ko/learn/server-side/django/generic_views/index.html @@ -0,0 +1,623 @@ +--- +title: 'Django Tutorial Part 6: Generic list and detail views' +slug: Learn/Server-side/Django/Generic_views +translation_of: Learn/Server-side/Django/Generic_views +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Home_page", "Learn/Server-side/Django/Sessions", "Learn/Server-side/Django")}}</div> + +<p class="summary">이 튜토리얼은 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> website에 책과 저자의 목록과 세부 페이지를 추가 하여 확장할 것입니다. 이 글에서 우리는 제네릭 클래스-기반 뷰(generic class-based views)에 대해 배울 것이며, 그것이 일반적인 사용 사례를 위해 작성해야 하는 코드들을 줄이는 방법을 보여줄 것입니다. 우리는 또한 URL 처리에 대해 더 세부적으로 알아볼 것이며, 기본 패턴 매칭(basic pattern matching)을 수행하는 방법을 보여줄 것입니다.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">사전 준비:</th> + <td><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a>를 포함한 모든 이전 튜토리얼을 완료하세요.</td> + </tr> + <tr> + <th scope="row">목표:</th> + <td>제네릭 클래스-기반 뷰를 어디 그리고 어떻게 사용하는지, 그리고 어떻게 URL들로부터 패턴들을 추출하여 정보를 view들에게 전달하는지에 대해 이해하기.</td> + </tr> + </tbody> +</table> + +<h2 id="개요">개요</h2> + +<p>이 튜토리얼에서 우리는 책과 저자에 대한 목록과 세부 페이지(detail page)를 추가하여 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 웹사이트의 첫 번째 버전을 완성할 것입니다(더 정확하게는, 우리는 책 페이지들을 구현하는 방법을 보여주고, 저자 페이지는 스스로 완성하도록 할 것입니다!)</p> + +<p>이 과정은 이전 튜토리얼에서 보여준, 색인 페이지(index page)를 만드는 과정과 비슷합니다. 우리는 여전히 URL 맵들, view들, 그리고 탬플릿들을 만들어야 합니다. 주요한 차이점은 세부 페이지(detail pages)에서, 우리는 URL 안의 패턴에서 정보를 추출해서 view로 전달하는 추가적인 도전을 가진다는 점입니다. </p> + +<p>이 튜토리얼의 마지막에서는 제네릭 클래스-기반 뷰를 사용할 때 데이터의 페이지를 매기는 방법을 보여드리겠습니다.</p> + +<h2 id="책_목록_페이지">책 목록 페이지</h2> + +<p>책 목록 페이지는 모든 사용 가능한 책 레코드들의 목록을 페이지 안에 나타낼 것이며, 다음 URL을 사용하여 접근합니다: <code>catalog/books</code>/. 이 페이지는 각 레코드마다 제목과 저자를 나타낼 것이며, 제목은 관련된 책 페이지로 이동하는 하이퍼링크 처리됩니다. 이 페이지는 사이트의 다른 페이지들과 같은 구조와 내비게이션을 가지고 있어서, 우리는 이전 튜토리얼에서 만들었던 템플릿(<strong>base_generic.html</strong>)을 확장해서 사용하면 됩니다.</p> + +<h3 id="URL_mapping">URL mapping</h3> + +<p><strong>/catalog/urls.py </strong>파일을 열고 아래 코드박스의 굵은 글씨를 복사해 붙여넣으세요. 인덱스 페이지처럼 <code>path()</code> 함수는 URL('<strong>books/'</strong>)과 매치되는 패턴, URL이 매치될 때 호출되는 view 함수<code>(views.BookListView.as_view())</code>, 그리고 이 특정 매핑에 대한 이름을 정의합니다.</p> + +<pre class="brush: python">urlpatterns = [ + path('', views.index, name='index'), +<strong> </strong>path<strong>('books/', views.BookListView.as_view(), name='books'),</strong> +]</pre> + +<p>이전 튜토리얼에서 URL은 이미 <code>/catalog</code> 와 매치되었을 것입니다. 그래서 사실상 view는 <code>/catalog/books/</code> URL에 접속하면 호출됩니다.</p> + +<p>View함수는 이전과 다른 형태를 가집니다. 왜냐면 이 view는 사실 클래스로 구현이 되기 때문입니다. 우리는 이 view를 직접 구현하지 않고, 이미 존재하는 generic view 함수를 상속받아 view 함수를 구현할 것입니다. 이 generic view 함수는 우리가 구현하고 싶은 기능들을 거의 다 가지고 있습니다.</p> + +<p>Django의 클래스 기반 view에서는, <code>as_view()</code>클래스 메소드를 호출해 적절한 view 함수에 접근할 수 있습니다. 이건 클래스의 인스턴스를 생성하는 작업과 모든 HTTP 요청을 처리하는 핸들러 메소드가 제대로 동작하는지를 처리합니다.</p> + +<h3 id="뷰(클래스_기반)">뷰(클래스 기반)</h3> + +<p>표준 function으로 우리는 꽤나 쉽게 book list view를 만들 수 있는데 (마치 이전 우리의 index view같이), 모든 책에 대한 데이터 베이스 쿼리를 만들어서 <code>render()</code> 함수를 불러 특정 템플릿에 리스트를 보냅니다. 대신 우리는 class-based generic list view (<code>ListView</code>) 를 사용하는데— 존재하는 뷰로부터 상속받아온 클래스입니다. generic view가 이미 우리가 필요한 대부분의 기능성을 실행하면서 동시에 Django best-practice이기 때문에, 우리는 코드의 양과 반복을 줄이고 궁극적으로 유지보수에 수고가 덜 드는 견고한 리스트 뷰를 만들 수 있습니다.</p> + +<p><strong>catalog/views.py </strong>파일을 열고, 아래의 코드를 views.py 파일 가장 아래에 붙여넣기하세요.</p> + +<pre class="brush: python">from django.views import generic + +class BookListView(generic.ListView): + model = Book</pre> + +<p>이게 전부입니다! Generic view는 명시된 모델(Book)의 모든 레코드를 가져오기 위해 데이터베이스를 쿼리할 것이고, <strong>/locallibrary/catalog/templates/catalog/book_list.html</strong>(아래에서 만들 예정)경로에 있는 템플릿을 렌더링합니다. 템플릿 안에서 우리는 <code>object_list</code>나 <code>book_list</code>라는 템플릿 변수를 사용해 도서 목록에 접근할 수 있습니다. (일반적으로 "<code><em>the_model_name</em>_list</code>").</p> + +<div class="note"> +<p><strong>Note</strong>: 이 어색한 템플릿 경로는 오타가 아닙니다. Generic views는 <code>/<em>application_name</em>/<em>the_model_name</em>_list.html</code>(지금 상황에서는 <code>catalog/book_list.html</code>)에서 템플릿을 찾습니다. 이 경로는 애플리캐이션의 <code>/<em>application_name</em>/templates/</code> 디렉토리 안에 있습니다(<code>/catalog/templates/)</code>.</p> +</div> + +<p>속성이나 디폴트 동작을 추가할 수도 있습니다. 예를 들어, 같은 모델을 사용하지만 여러 개의 뷰를 사용해야 되는 경우 다른 템플릿 파일을 명시할 수 있습니다. 혹은 <code>book_list</code> 템플릿 변수명이 직관적이지 않다고 생각해 다른 템플릿 변수명을 사용하고 싶을지도 모릅니다. 아마 가장 유용한 바리에이션은 리턴 값의 결과를 바꾸거나 필터링하는 것입니다. 따라서 모든 책을 나열하는 대신, 유저가 읽은 순으로 5개의 책을 나열할 수도 있습니다.</p> + +<pre class="brush: python">class BookListView(generic.ListView): + model = Book + context_object_name = 'my_book_list' # your own name for the list as a template variable + queryset = Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war + template_name = 'books/my_arbitrary_template_name_list.html' # Specify your own template name/location</pre> + +<h4 id="클래스_기반_뷰의_메소드_오버라이딩">클래스 기반 뷰의 메소드 오버라이딩</h4> + +<p>굳이 여기서 할 필요는 없지만, 클래스 메소드 오버라이딩을 할 수도 있습니다.</p> + +<p>예를 들어, 우리는 <code>get_queryset()</code>메소드를 오버라이딩해 반환되는 레코드의 리스트들을 바꿀 수 있습니다. 이건 우리가 이전에 했던 <code>queryset</code> 속성을 지정하는 방법보다 더 유연한 방법입니다. (비록 지금 상황에서 별 이득이 되지는 않지만요.)</p> + +<pre class="brush: python">class BookListView(generic.ListView): + model = Book + + def get_queryset(self): + return Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war +</pre> + +<p>우리는 템플릿에 추가적인 컨텍스트 변수들을 전달하기 위해 <code>get_context_data()</code>를 오버라이딩할 수도 있습니다. (도서 목록이 디폴트로 전달됩니다.) 아래의 코드는 <code>some_data</code> 라는 이름의 변수를 어떻게 컨텍스트에 추가하는지를 보여줍니다. (이렇게 하면 템플릿 변수로 사용할 수 있습니다.)</p> + +<pre class="brush: python">class BookListView(generic.ListView): + model = Book + + def get_context_data(self, **kwargs): + # Call the base implementation first to get the context + context = super(BookListView, self).get_context_data(**kwargs) + # Create any data and add it to the context + context['some_data'] = 'This is just some data' + return context</pre> + +<p>이렇게 할 때에는, 아래의 패턴을 따르는 것이 중요합니다:</p> + +<ul> + <li>첫째로, 슈퍼클래스에서 기존 컨텍스트를 가져옵니다.</li> + <li>그리고 새로운 컨텍스트 정보를 추가합니다. </li> + <li>마지막으로 새롭게 업데이트된 컨텍스트를 리턴합니다.</li> +</ul> + +<div class="note"> +<p><strong>Note</strong>: <a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/generic-display/">Built-in class-based generic views</a> (Django docs)를 방문해 다양한 예제를 살펴볼 수 있습니다.</p> +</div> + +<h3 id="리스트_뷰_템플릿_생성하기">리스트 뷰 템플릿 생성하기</h3> + +<p><strong>/locallibrary/catalog/templates/catalog/book_list.html</strong> 경로에 HTML 파일을 만든 다음, 아래의 코드를 복사, 붙여넣기 하세요. 이전에 설명한 것처럼, 이건 제네릭 클래스 기반 리스트 뷰에서 예상되는 기본 템플릿 파일입니다. (<code>catalog</code> 애플리케이션 내의 <code>Book</code> 모델)</p> + +<p>제네릭 뷰의 템플릿은 다른 템플릿과 비슷합니다 (물론 템플릿에 전달되는 컨텍스트나 정보는 다를지도 모르지만요). 다른 index 템플릿처럼, 우리는 첫번째 줄에 base 템플릿을 넣어 확장한 다음, <code>content</code>라는 이름의 블록으로 교체합니다.</p> + +<pre class="brush: html">{% extends "base_generic.html" %} + +{% block content %} + <h1>Book List</h1> + <strong>{% if book_list %}</strong> + <ul> + {% for book in book_list %} + <li> + <a href="\{{ book.get_absolute_url }}">\{{ book.title }}</a> (\{{book.author}}) + </li> + {% endfor %} + </ul> + <strong>{% else %}</strong> + <p>There are no books in the library.</p> + <strong>{% endif %} </strong> +{% endblock %}</pre> + +<p>뷰는 <code>object_list</code>나 <code>book_list</code>라는 디폴트 aliases로 컨텍스트(도서 목록)를 전달합니다. <code>object_list</code>나 <code>book_list</code> 둘 중 어느 것을 적어도 상관이 없습니다.</p> + +<h4 id="조건부_실행">조건부 실행</h4> + +<p><code><a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#if">if</a></code>, <code>else</code> 그리고 <code>endif</code> 라는 템플릿 태그들은 <code>book_list</code> 이 정의되었는지, 그리고 존재하는지 체크합니다. 만약 <code>book_list</code>가 없다면, 책이 존재하지 않는다는 <code>else</code> 절의 텍스트 문구가 표시될 것입니다. 만약 <code>book_list</code>가 존재한다면, 도서 목록의 갯수만큼 반복만큼 반복해서 실행합니다.</p> + +<pre class="brush: html"><strong>{% if book_list %}</strong> + <!-- code here to list the books --> +<strong>{% else %}</strong> + <p>There are no books in the library.</p> +<strong>{% endif %}</strong> +</pre> + +<p>위의 조건문은 단 하나의 상황만 체크합니다. 하지만 <code>elif</code>라는 템플릿 태그를 사용해 추가적인 조건을 걸어 테스트할 수 있습니다. (예를 들면 <code>{% elif var2 %}</code>) 조건부 연산자에 대한 자세한 내용은 다음 링크에서 확인할 수 있습니다: <a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#if">if</a>, <a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#ifequal-and-ifnotequal">ifequal/ifnotequal</a>, and <a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#ifchanged">ifchanged</a> in <a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins">Built-in template tags and filters</a> (Django Docs).</p> + +<h4 id="반복_구문">반복 구문</h4> + +<p><a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#for">for</a> 와 <code>endfor</code> 라는 템플릿 태그들은 아래와 같이 도서 목록을 살펴보는 루프를 위해 사용합니다. 각각의 반복은 <code>book</code> 템플릿 변수에 현재 리스트 아이템에 대한 정보를 채웁니다.</p> + +<pre class="brush: html">{% for <strong>book</strong> in book_list %} + <li> <!-- code here get information from each <strong>book</strong> item --> </li> +{% endfor %} +</pre> + +<p>여기에서는 사용되지 않지만, 반복 구문 내에서 Django는 반복을 추적할 수 있는 다른 변수들을 만들 수 있습니다. 예를 들어, <code>forloop.last</code>라는 변수로 루프의 마지막 실행에 대한 조건부 처리을 할 수 있습니다.</p> + +<h4 id="변수_접근하기">변수 접근하기</h4> + +<p>반복 구문 내에서의 코드는 각각의 책에 대한 리스트 아이템을 생성합니다. 이 리스트 아이템은 타이틀(아직 작성되지 않은 상세 뷰의 링크)과 작가의 이름을 나타냅니다.</p> + +<pre class="brush: html"><a href="\{{ book.get_absolute_url }}">\{{ book.title }}</a> (\{{book.author}}) +</pre> + +<p>우리는 점 표기법(dot notation)을 사용해서 연관된 책의 레코드(예를 들어 <code>book.title</code> 과 <code>book.author</code>)에 대한 필드에 접근이 가능합니다. <code>book</code> 다음의 텍스트는 모델에 정의되어있는 필드의 이름입니다.</p> + +<p>우리는 템플릿 안에 모델에서 정의한 함수를 불러올 수도 있습니다. 이 경우, 우리는 <code>Book.get_absolute_url()</code> 함수를 호출해 연관된 세부 레코드를 표시하는 URL을 가져옵니다. 이 작업은 함수가 아무 인자를 가지지 않을 때 제공됩니다 (여기에는 인자를 넘길 방법이 없습니다!).</p> + +<div class="note"> +<p><strong>Note</strong>: 우리는 템플릿 내에서 함수를 호출할 때 발생하는 부작용을 조금 조심해야 합니다. 여기서 우리는 그저 표시하기 위해 URL을 얻었지만, 함수는 그보다 더한 작업도 할 수 있습니다 — (예를 들면) 우리는 그냥 템플릿을 렌더링한다고 해서 우리의 데이터베이스를 삭제하고 싶지 않을 것입니다.</p> +</div> + +<h4 id="베이스_템플릿_업데이트">베이스 템플릿 업데이트</h4> + +<p>베이스 템플릿(<strong>/locallibrary/catalog/templates/<em>base_generic.html</em></strong>)을 열고 <strong>All books</strong>의 URL 링크 부분에 <strong>{% url 'books' %}</strong> 코드를 삽입하세요. 이건 모든 페이지 링크에 적용될 것입니다 (우리는 "books" url mapper를 만들었으니 이렇게 넣을 수 있습니다.)</p> + +<pre class="brush: python"><li><a href="{% url 'index' %}">Home</a></li> +<strong><li><a href="{% url 'books' %}">All books</a></li></strong> +<li><a href="">All authors</a></li></pre> + +<h3 id="어떻게_보일까요">어떻게 보일까요?</h3> + +<p>우리는 아직 도서 목록을 표시할 수 없습니다. 왜냐면 디펜던시(dependency)가 없기 떄문이죠. 책의 상세 페이지에 대한 URL이 필요한데, 이 URL은 각 도서에 대한 하이퍼 링크입니다. 다음 섹션 이후에는 목록보기와 상세보기가 모두 표시될 것입니다.</p> + +<h2 id="Book_상세_페이지">Book 상세 페이지</h2> + +<p>URL <code>catalog/book/<em><id></em></code> ( <code><em><id></em></code> 는 book의 primary key입니다)에 접근해서, Book의 상세 페이지는 특정 책의 정보를 보여줄 것입니다. <code>Book</code> 모델 (author, summary, ISBN, language, 그리고 genre) 같은 필드에 추가로, 우리는 가능한 복사본(<code>BookInstances</code>) 의 상세부분, 즉 상태와 기대하는 반납일, 기록 그리고 아이디 등을 리스트화 할 것입니다. 이렇게 하면 독자들이 책의 리스트를 확인할 뿐만 아니라, 언제 책을 대여할 수 있는지에 대한 여부를 확인할 수 있게 해줍니다.</p> + +<h3 id="URL_매핑">URL 매핑</h3> + +<p><strong>/catalog/urls.py</strong> 파일을 열고 아래 두꺼운 글씨로 된 <strong>book-detail </strong>URL mapper를 추가하세요. <code>path()</code> 함수는 연관된 제네릭 클래스 기반의 상세 뷰와 이름에 대한 패턴을 정의합니다.</p> + +<pre class="brush: python">urlpatterns = [ + path('', views.index, name='index'), + path('books/', views.BookListView.as_view(), name='books'), +<strong> path('book/<uuid:pk>', views.BookDetailView.as_view(), name='book-detail'),</strong> +]</pre> + +<p><em>book-detail URL 패턴은 우리가 원하는 책의 id를 캡처하기 위해 특별한 구문을 사용합니다. 구문은 간단합니다: 꺾쇠 괄호는 </em>캡처하는 URL의 일부를 정의하고 뷰가 캡처 된 데이터에 액세스하는 데 사용할 수있는 변수의 이름을 지정합니다. <em>예를 들어, </em><strong style="font-size: 1rem; letter-spacing: -0.00278rem;"><something></strong><span style="font-size: 1rem; letter-spacing: -0.00278rem;">은 패턴을 캡처해서 "something"이라는 변수에 데이터를 담아 전달합니다. 우리는 선택적으로 변수 이름 앞에 데이터 형식 (int, str, slug, uuid, path)을 정의하는 </span><a href="https://docs.djangoproject.com/en/2.0/topics/http/urls/#path-converters" style="font-size: 1rem; letter-spacing: -0.00278rem;">converter specification</a><span style="font-size: 1rem; letter-spacing: -0.00278rem;">을 사용할 수 있습니다.</span></p> + +<p>여기에서 우리는 book id을 캡쳐하기 위해 <code>'<uuid:pk>'</code><strong> </strong> 라는 특별히 포맷화된 문자열을 활용할 것입니다. 그리고 <code>pk</code> (primary key의 단축어)라는 이름의 파라미터로서 뷰로 넘겨줄 것입니다. This is the id that is being used to store the book uniquely in the database, as defined in the Book Model.</p> + +<p>(번역 봉사자 주: uuid를 읽지 못한다면[NoReverseMatch] <int:pk>로 해보십시오.)</p> + +<div class="note"> +<p><strong>Note</strong>: 앞에서 언급했듯이, 관련된 URL 은 실제로는 <code>catalog/book/<digits></code> 입니다.(우리가 <strong>catalog</strong> application 에 있기때문에, <code>/catalog/</code> 를 가정합니다).</p> +</div> + +<div class="warning"> +<p><strong>명심</strong>: 통상 class-based detail view 는 <strong>pk </strong>라는 이름을 가진 파라미터로 전달됩니다. 만일 자체적으로 function view 를 만든다면 어떤 이름이라도 사용 가능합니다. 혹은 이름이 없는 argument 에 정보를 넣어 전달 할 수도 있습니다.</p> +</div> + +<h4 id="Regular_expression_을_이용한_고급_path_matching">Regular expression 을 이용한 고급 path matching</h4> + +<div class="note"> +<p><strong>Note</strong>: 튜터리얼과는 관련 없습니다. 하지만 향후 Django 스타일로 개발하기 위해선 매우 유용한 팁입니다.</p> +</div> + +<p><code>path()</code> 를 이용한 패턴 검색은 간단하고 일반적인 경우 - 예를 들어 단지 특정 문자열이나 숫자가 있는지 - 매우 유용합니다. 만일 좀더 세밀한 조건 - 예를 들어 특정 문자열 길이를 갖는 문자열 검색 - 으로 검색 하고자 한다면 . <a href="https://docs.djangoproject.com/en/2.0/ref/urls/#django.urls.re_path">re_path()</a> 를 사용하길 권고 드립니다.</p> + +<p>re_path() 는 <a href="https://docs.python.org/3/library/re.html">Regular expression</a> 을 사용한다는 점만 빼고 path() 와 똑 같습니다. 예를 들어 앞서 서술한 path 는 다음과 같이 re_path 로 대체 사용 가능합니다.</p> + +<pre class="brush: python"><strong>re_path(r'^book/(?P<pk>\d+)$', views.BookDetailView.as_view(), name='book-detail'),</strong> +</pre> + +<p><em>Regular expressions</em> 은 정말로 파워풀한 매핑 툴 입니다. 하지만 솔직히 직관적이지 못하고 초보자에게는 두려운 존재입니다. 아래는 간단한 지침서 입니다!</p> + +<p>첫번째로 regular expressions 은 the raw string literal syntax 로 선언되어야 합니다 (즉, 다음처럼 '< >' 로 닫혀 있어야 한다는 겁니다: <strong>r'<your regular expression text goes here>'</strong>).</p> + +<p>패턴 매칭을 정의하기 위해 알아야 될 문법의 핵심적인 부분들입니다:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">Symbol</th> + <th scope="col">Meaning</th> + </tr> + </thead> + <tbody> + <tr> + <td>^</td> + <td>기술된 text 로 그 문자열이 시작되는지</td> + </tr> + <tr> + <td>$</td> + <td>기술된 text 로 그 문자열이 끝나는지</td> + </tr> + <tr> + <td>\d</td> + <td>숫자(0, 1, 2, ... 9) 인지</td> + </tr> + <tr> + <td>\w</td> + <td>word character 인지. 즉, 대소문자, 숫자, underscore character (_) 로만 구성된 단어인지.</td> + </tr> + <tr> + <td>+</td> + <td>하나 이상의 선행 문자가 있는지. 예를 들어, 하나 이상의 숫자와 매치한다면 <code>\d+</code>.를 하나 이상의 'a' 문자와 매치 한다면 <code>a+</code></td> + </tr> + <tr> + <td>*</td> + <td>매치되는 문자열이 없거나 많은 경우, 예를 들어 매칭이 안되거나 한 단어를 찾고자 할 경우 <code>\w*</code></td> + </tr> + <tr> + <td>( )</td> + <td>괄호안에 있는 패턴의 일부를 선택할때. 선택된 값은 unnamed parameter 로 view 에게 전달된다. (만일 여러 패턴들이 선택 되었다면 선택된 순서대로 연관된 파라미터로써 전달 될것입니다.</td> + </tr> + <tr> + <td>(?P<<em>name</em>>...)</td> + <td>(...에 표기된) 패턴을 명명한 variable로 변환합니다(이 경우에는 "name" 입니다). 변환한 이름을 view 에 지정한 이름으로 넘깁니다. 그러므로 당신의 view 에서는 반드시 argument명을 동일하게 해주어야 합니다!</td> + </tr> + <tr> + <td>[ ]</td> + <td>집합 set 안에 있는 글자중 한개와 매치 될때. 예를 들어 [abc] 는 'a',혹은 'b' 혹은 'c' 와 매치되는지. [-\w] 는 '-' 한 글자 인지 혹은 '-'를 포함한 단어와 매치 하는지를 나타냅니다.</td> + </tr> + </tbody> +</table> + +<p>대부분의 다른 글자들은 문자 그대로 받아 들여 집니다!</p> + +<p>몇 가지 실제 패턴 예제를 보도록 합시다: </p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">Pattern</th> + <th scope="col">Description</th> + </tr> + </thead> + <tbody> + <tr> + <td><strong>r'^book/(?P<pk>\d+)$'</strong></td> + <td> + <p>이것은 우리가 URL mapper에서 사용한 Regular Expression입니다. 이 표현식은 먼저 문자열이 <code>book/</code> 으로 시작하는지 검사하고 (<strong>^book/</strong>), 그 다음에 한 개이상의 숫자가 오는지 (<code>\d+</code>), 그리고 문자열이 끝나기 전에 숫자가 아닌 문자가 들어 있지는 않는 지 검사합니다.</p> + + <p>또한 이 표현식은 모든 숫자들을 변환하고 <strong>(?P<pk>\d+)</strong> 변환된 값을 view 에 'pk'라는 이름의 parameter로 넘깁니다.<strong> 변환된 값은 항상 String type으로 넘어갑니다</strong><strong>!</strong></p> + + <p>예를 들어, 이 표현식은 <code>book/1234</code> 을 매칭합니다. 그리고 변수 <code>pk='1234'</code> 를 view에 넘깁니다.</p> + </td> + </tr> + <tr> + <td><strong>r'^book/(\d+)$'</strong></td> + <td>이 표현식은 위의 표현식과 동일한 URL들을 매칭합니다. 변환된 정보는 명명되지 않은 argument로 view에 전달됩니다.</td> + </tr> + <tr> + <td><strong>r'^book/(?P<stub>[-\w]+)$'</strong></td> + <td> + <p>이 표현식은 문자열 처음 부분에 <code>book/</code> 으로 시작하는지 검사하고 (<strong>^book/</strong>), 그리고 한 개 또는 그 이상의 '-' 나 word character가 오고 (<strong>[-\w]+</strong>), 그렇게 끝내는지를 매칭합니다. 이 표현식 또한 매칭된 부분을 변환하고 view 에 'stub' 라는 이름의 parameter로 전달합니다.</p> + + <p>This is a fairly typical pattern for a "stub". Stubs are URL-friendly word-based primary keys for data. You might use a stub if you wanted your book URL to be more informative. For example <code>/catalog/book/the-secret-garden</code> rather than <code>/catalog/book/33</code>.</p> + </td> + </tr> + </tbody> +</table> + +<p>당신은 다양한 패턴들을 한번의 매칭을 통해 변환시킬 수 있습니다. 그러므로 다양한 정보들을 URL안에 인코딩할 수 있습니다.</p> + +<div class="note"> +<p><strong>Note</strong>: 추가적으로, 특정 날짜에 출간된 책 목록을 URL에 인코딩할 수 있을지 생각해보세요. 그리고 어떤 Regular Expression이 해당 URL을 매칭할 수 있을까요?</p> +</div> + +<h4 id="Passing_additional_options_in_your_URL_maps">Passing additional options in your URL maps</h4> + +<p>우리가 여태까지 사용하지는 않았지만, 쓸모있을만한 한 기능은 당신이 <a href="https://docs.djangoproject.com/en/2.0/topics/http/urls/#views-extra-options">additional options</a> 을 정의내리고 view에 전달할 수 있다는 것입니다. 이 option들은 dictionary 형태로 정의하고 <code>path()</code> 함수의 3번째 명명되지 않은 argument로 전달됩니다. 이 방식은 만약, 당신이 동일한 view 를 다른 곳에서 재활용하려고 하거나, 각 상황에 맞게 조절하려고 할 때 유용합니다. (이 경우, 우리는 각 경우에 따라 다른 template 을 제공합니다).</p> + +<pre class="brush: python">path('url/', views.my_reused_view, <strong>{'my_template_name': 'some_path'}</strong>, name='aurl'), +path('anotherurl/', views.my_reused_view, <strong>{'my_template_name': 'another_path'}</strong>, name='anotherurl'), +</pre> + +<div class="note"> +<p><strong>Note:</strong> 추가된 options 과 변환된 패턴들 중 명명된 것들은 view 에 명명된 arguments로 전달됩니다. 만약 당신이<strong> 동일한 이름을</strong> 변환된 패턴들과 추가적인 option에 사용한다면, 오직 변환된 패턴들만이 view에 보내지게 됩니다. ( 추가된 option들에 있는 값들은 모두 버려집니다). </p> +</div> + +<h3 id="뷰_(클래스_기반)">뷰 (클래스 기반)</h3> + +<p><strong>catalog/views.py </strong>을 열고, 다음 코드를 파일 끝에 넣으세요:</p> + +<pre class="brush: python">class BookDetailView(generic.DetailView): + model = Book</pre> + +<p>다됬습니다! 이제 해야될 일은 <strong>/locallibrary/catalog/templates/catalog/book_detail.html </strong>template를 만들면, view는 template에 URL mapper에 의해 찾고자 하는 데이터베이스에 있는 특정 <code>Book </code> 레코드의 정보를 전달할 겁니다. template 안에서 template 변수 <code>object</code> 또는 <code>book</code>(즉, 일반적으로는 "<font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">해당_모델_명</span></font>") 으로 책 목록에 접근할 수 있습니다.</p> + +<p>만약 필요하다면, 사용하고 있는 template 또는 template 안에서 book을 참조하는 데 사용되는 context object의 이름을 바꿀 수 있습니다. 또한, 예를 들어 context에 정보를 추가하는 식으로, 메서드를 오버라이드 할 수도 있습니다.</p> + +<h4 id="만약_해당_레코드가_존재하지_않는다면_무슨_일이_일어날까요">만약 해당 레코드가 존재하지 않는다면 무슨 일이 일어날까요?</h4> + +<p>만약 요청된 레코드가존재하지 않는다면, 제네릭 클래스 기반의 detail view는 <code>Http404</code> exception 이 저절로 발생할 것입니다. — 만들어질 때, 적절한 "resource not found" 페이지를 알아서 보여줄 것입니다. 만약 원한다면 당신이 해당 페이지를 수정할 수 있겠죠.</p> + +<p>이런 일이 어떻게 발생하는지 원리를 조금 알려줄게요. 밑에 있는 코드는 만약 당신이 제네릭 클래스 기반의 detail view를 <strong>사용하지 않는다면</strong>, 클래스 기반의 view를 어떻게 함수 형태로 표현 할 수 있는지 보여줍니다.</p> + +<pre class="brush: python">def book_detail_view(request, primary_key): + try: + book = Book.objects.get(pk=primary_key) + except Book.DoesNotExist: + raise Http404('Book does not exist') + + return render(request, 'catalog/book_detail.html', context={'book': book}) +</pre> + +<p>view는 먼저 model로 부터 특정 book 의 레코드를 얻으려고 할 것입니다. 이 시도가 실패하면, view 는 "해당 책이 존재하지 않음"을 알려주면서 <code>Http404</code> exception가 발생할 것입니다. 그리고 마지막 과정은 ,평소처럼, book 정보를 <code>context</code> parameter (dictionary 형태로)에 집어 넣고 template 이름과 함께 <code>render()</code> 함수를 호출 할 것입니다.</p> + +<p>혹, 만약 해당 레코드를 찾지 못한다면, 우리는 손쉬운 방법으로 <code>Http404</code> exception을 발생하기 위해<code>get_object_or_404()</code> 함수를 사용할 수도 있어요.</p> + +<pre class="brush: python">from django.shortcuts import get_object_or_404 + +def book_detail_view(request, primary_key): + book = get_object_or_404(Book, pk=primary_key) + return render(request, 'catalog/book_detail.html', context={'book': book})</pre> + +<h3 id="상세_뷰_템플릿_생성하기">상세 뷰 템플릿 생성하기</h3> + +<p><strong>/locallibrary/catalog/templates/catalog/book_detail.html </strong>파일을 만들고, 아래 텍스트를 복사 붙여넣기 하세요. 위에서 설명한대로, 이 파알명은 제네릭 클래스 기반 상세 뷰의 디폴트 파일명입니다. (<code>catalog</code> 애플리케이션의 <code>Book</code> 모델을 위한 상세 뷰)</p> + +<pre class="brush: html">{% extends "base_generic.html" %} + +{% block content %} + <h1>Title: \{{ book.title }}</h1> + + <p><strong>Author:</strong> <a href="">\{{ book.author }}</a></p> <!-- author detail link not yet defined --> + <p><strong>Summary:</strong> \{{ book.summary }}</p> + <p><strong>ISBN:</strong> \{{ book.isbn }}</p> + <p><strong>Language:</strong> \{{ book.language }}</p> + <p><strong>Genre:</strong> {% for genre in book.genre.all %} \{{ genre }}{% if not forloop.last %}, {% endif %}{% endfor %}</p> + + <div style="margin-left:20px;margin-top:20px"> + <h4>Copies</h4> + + {% for copy in book.bookinstance_set.all %} + <hr> + <p class="{% if copy.status == 'a' %}text-success{% elif copy.status == 'm' %}text-danger{% else %}text-warning{% endif %}">\{{ copy.get_status_display }}</p> + {% if copy.status != 'a' %} + <p><strong>Due to be returned:</strong> \{{copy.due_back}}</p> + {% endif %} + <p><strong>Imprint:</strong> \{{copy.imprint}}</p> + <p class="text-muted"><strong>Id:</strong> \{{copy.id}}</p> + {% endfor %} + </div> +{% endblock %}</pre> + +<ul> +</ul> + +<div class="note"> +<p>이 템플릿의 작가 링크는 비어있는 URL입니다. 왜냐면 우리는 아직 작가 상세 페이지를 만들지 않았기 때문이죠. 만약 페이지가 존재한다면, URL을 아래와 같이 업데이트 해야합니다.</p> + +<pre><a href="<strong>{% url 'author-detail' book.author.pk %}</strong>">\{{ book.author }}</a> +</pre> +</div> + +<p>조금 더 커졌지만, 이 템플릿의 대부분의 것들은 이미 언급된 것들입니다:</p> + +<ul> + <li>content 블럭을 오버라이드 해서 우리의 기본 템플릿을 extend하였습니다.</li> + <li>우리는 조건 판단 처리를 해서 특정 컨텐츠가 있을지 없는지 체크하여 표시합니다.</li> + <li><code>for</code> 루프를 활용하여 objects들의 리스트를 표현합니다.</li> + <li>우리는 context fields를 dot notation를 활용합니다 (왜냐하면 우리는 detail generic view를 사용하는데, context의 이름은 <code>book</code> 이기에 우리는 "<code>object</code>"를 사용할 수 있습니다)</li> +</ul> + +<p>우리가 본 적 없는 한가지 흥미로운 점은 바로 <code>book.bookinstance_set.all()</code> 함수입니다. 이 메소드는 Django에 의해 자동으로 만들어진 메소드입니다. 이 메소드는 <code>Book</code> 과 관련된 <code>BookInstance</code> 레코드 집합을 반환합니다. </p> + +<pre class="brush: python">{% for copy in book.bookinstance_set.all %} + <!-- code to iterate across each copy/instance of a book --> +{% endfor %}</pre> + +<p>이 메소드는 관계의 한쪽("one")에만 <code>ForeignKey</code>(one-to many) 필드를 선언했기 때문에 필요합니다. 다른("many") 모델에서 아무것도 선언하지 않았기 때문에 관련 레코드 집합을 가져올 필드가 없습니다. 이 문제를 해결하기 위해, Django는 지금 우리가 사용하고 있는 "reverse lookup"이라는 적당한 이름의 함수를 만들었습니다. 이 함수의 이름은 <code>ForeignKey</code>가 선언되어있는 모델 이름을 소문자로 만들고, 그 뒤에 <code>_set</code>을 붙이면 됩니다. (따라서 <code>Book</code>에서 만든 함수는 <code>bookinstance_set()</code>가 되겠죠.)</p> + +<div class="note"> +<p><strong>Note</strong>: 여기서 우리는 모든 레코드를 가져오기 위해 <code>all()</code> 을 사용했습니다(기본값이죠). 반대로 당신은 <code>filter()</code> 메서드를 사용해서 레코드의 부분 집합을 가져올 수 있지만, 당신은 template에서 이걸 직접할 수는 없어요. 왜냐하면 함수의 arguments를 정할 수 없으니까요.</p> + +<p>만약 순서를 정의하지 않는다면 (클래스 기반 view 또는 model에서), 당신은 개발 서버로 부터 다음과 같은 에러를 받게 될 거라는 것 또한 알아두세요.</p> + +<pre>[29/May/2017 18:37:53] "GET /catalog/books/?page=1 HTTP/1.1" 200 1637 +/foo/local_library/venv/lib/python3.5/site-packages/django/views/generic/list.py:99: UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list: <QuerySet [<Author: Ortiz, David>, <Author: H. McRaven, William>, <Author: Leigh, Melinda>]> + allow_empty_first_page=allow_empty_first_page, **kwargs) +</pre> + +<p>이것은 <a href="https://docs.djangoproject.com/en/2.0/topics/pagination/#paginator-objects">paginator object</a> 가 데이터베이스에서 ORDER BY 명령어가 실행되었을 것이라고 예상하기 때문에 발생하는 것입니다. 이러한 것이 없다면, 레코드들이 정확한 순서로 반환되었는지 알 수가 없어요!<strong> </strong></p> + +<p>이 튜토리얼은 아직 <strong>Pagination</strong> 에 도달하지는 않았습니다.(곧 하게될 거에요) <code>sort_by()</code> 에 parameter를 전달하여 사용하는 것은 (위에서 이야기했던 <code>filter()</code> 와 동일한 역할을 합니다.) 사용할 수 없기 때문에, 당신은 3개의 선택권중에 하나를 골라야합니다:</p> + +<ol> + <li>Add a <code>ordering</code> inside a <code>class Meta</code> declaration on your model.</li> + <li>Add a <code>queryset</code> attribute in your custom class-based view, specifying a <code>order_by()</code>.</li> + <li>Adding a <code>get_queryset</code> method to your custom class-based view and also specify the <code>order_by()</code>.</li> +</ol> + +<p>만약 <code>Author</code> model에 <code>class Meta</code> 사용하기를 결정했다면 (커스터마이징 된 클래스 기반 view만큼 유연하진 않겠지만, 쉬운 방법입니다), 아마 밑에 코드와 비슷하게 끝날 거에요:</p> + +<pre>class Author(models.Model): + first_name = models.CharField(max_length=100) + last_name = models.CharField(max_length=100) + date_of_birth = models.DateField(null=True, blank=True) + date_of_death = models.DateField('Died', null=True, blank=True) + + def get_absolute_url(self): + return reverse('author-detail', args=[str(self.id)]) + + def __str__(self): + return f'{self.last_name}, {self.first_name}' + +<strong> class Meta: + ordering = ['last_name']</strong></pre> + +<p>물론, 해당 field는 <code>last_name</code> 이어야 할 필요는 없습니다: 다른 어떤 것도 될 수 있어요.</p> + +<p>마지막으로, 그러나 적어도 우리는 정렬(sort)을 attribute/column에 따라 해줘야 합니다. unique 여부와 상관없이 당신의 database의 퍼포먼스 이슈를 위해서 필요합니다. 물론, 이것은 여기서 필요한 것이 아니며 어떻게 보면 약간 진도를 앞서나가는 것 같지만 이런 작은 사용자와 프로젝트에서도 미래의 프로젝트를 위해서 미리 명심해 두는 것이 좋습니다.</p> +</div> + +<h2 id="어떻게_보일까요_2">어떻게 보일까요?</h2> + +<p>이 시점에서 책 목록 페이지와 책 세부 사항 페이지를 보기 위한 모든 것을 만들어 놓았어야 합니다. 명령어 (<code>python3 manage.py runserver</code>) 를 입력하여 서버를 실행하고 브라우저를 열어 <a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a> 에 접속해보세요.</p> + +<div class="warning"> +<p><strong>주의!:</strong> 아직 작가 목록 페이지나 작가 세부 사항 페이지를 클릭하시면 안됩니다. — 당신은 challenge에서 이것들을 만들어보게 될 거에요!</p> +</div> + +<p>책 목록을 보기 위해 <strong>All books</strong> 링크를 클릭하세요. </p> + +<p><img alt="Book List Page" src="https://mdn.mozillademos.org/files/14049/book_list_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; height: 216px; margin: 0px auto; width: 823px;"></p> + +<p>그리고 당신의 책들 중 하나를 클릭해보세요. 만약, 현재까지의 과정을 성공적으로 따라왔다면, 당신은 다음과 같은 스크린샷을 볼 수 있을 겁니다.</p> + +<p><img alt="Book Detail Page" src="https://mdn.mozillademos.org/files/14051/book_detail_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; height: 783px; margin: 0px auto; width: 926px;"></p> + +<h2 id="Pagination">Pagination</h2> + +<p>만약 레코드가 몇 개 없다면, 현재의 책 목록 페이지도 괜찮습니다. 하지만 수십,수백개의 레코드를 갖고 있다면, 페이지는 가져오는 데 점차 시간이 늘어날 겁니다(양이 너무 많아 탐색하는게 체감적으로 힘들어질 정도로요). 해결 방법은 당신의 list view에 pagination을 추가하는 것입니다, 그리고 pagination은 각 페이지마다 보여주는 항목들을 감소시켜줄 것입니다.</p> + +<p>장고는 pagination에 대한 훌륭한 지원을 하고 있습니다. 더욱 좋은 점은,이 지원은 제네릭 클래스 기반 list view에 내장되어 있어서, 당신이 pagination을 하기 위해 해야될 것이 많지 않다는 것입니다!</p> + +<h3 id="Views">Views</h3> + +<p><strong>catalog/views.py</strong>열고, 밑에 굵은 글씨로 작성되어있는 <code>paginate_by</code> 줄을 추가해주세요.</p> + +<pre class="brush: python">class BookListView(generic.ListView): + model = Book + <strong>paginate_by = 10</strong></pre> + +<p>이 것이 추가됨에 따라, 당신이 10개 이상의 레코드를 갖게 되면 view는 template에 보내는 데이터에 pagination 하는 것을 시작할 것입니다. 다른 page들을 GET parameter들을 통해 접근할 수 있습니다— 2 페이지에 접속하려면, 다음과 같은 URL을 사용하세요: <code>/catalog/books/<strong>?page=2</strong></code>.</p> + +<h3 id="Templates">Templates</h3> + +<p>자료들이 pagination되었습니다, 우리는 template에 결과들을 훑어볼 수 있도록 만들어야합니다. 우리는 이러한 기능이 모든 list view들에 필요할 수 있으므로, 우리는 base template에 추가하는 방향으로 작업을 진행하려고 합니다. </p> + +<p><strong>/locallibrary/catalog/templates/<em>base_generic.html</em></strong> 열고 content block 밑에 다음과 같은 pagination block을 복사하여 추가하세요 (아래에 굵은 글씨로 강조표시를 해놓았습니다). 첫번째로 코드는 현재 페이지가 pagination이 가능한지 체크합니다. 만약 가능하다면, 다음 페이지와 전 페이지를 적절히 추가합니다 (현재 페이지 넘버도요). </p> + +<pre class="brush: python">{% block content %}{% endblock %} + +<strong>{% block pagination %} + {% if is_paginated %} + <div class="pagination"> + <span class="page-links"> + {% if page_obj.has_previous %} + <a href="\{{ request.path }}?page=\{{ page_obj.previous_page_number }}">previous</a> + {% endif %} + <span class="page-current"> + <p>Page \{{ page_obj.number }} of \{{ page_obj.paginator.num_pages }}.</p> + </span> + {% if page_obj.has_next %} + <a href="\{{ request.path }}?page=\{{ page_obj.next_page_number }}">next</a> + {% endif %} + </span> + </div> + {% endif %} +{% endblock %} </strong></pre> + +<p>만약 현재 페이지에 pagination이 사용중이라면, <code>page_obj</code> 가 <a href="https://docs.djangoproject.com/en/2.0/topics/pagination/#paginator-objects">Paginator</a> 오브젝트 로서 존재합니다. 해당 오브젝트는 현재 페이지, 전 페이지, 페이지 수는 얼마나 되는 지등의 모든 정보를 제공합니다.</p> + +<p>pagination 링크를 만들기 위해 우리는 <code>\{{ request.path }}</code> 를 이용하여 현재 페이지의 URL을 가져오도록 할 겁니다. 우리가 pagination을 하는 객체와 독립적이기 때문에 유용합니다.</p> + +<p>다됬네요!</p> + +<h3 id="어떻게_보일_까요">어떻게 보일 까요?</h3> + +<p>밑에 있는 스크린샷은 pagination이 어떻게 보이는지를 알려줍니다 — 만약 에디터베이스에 10개가 넘는 제목을 추가하지 않았다면, <strong>catalog/views.py</strong> 파일에 있는 <code>paginate_by</code> 줄에 있는 숫자를 낮춤으로써 쉽게 테스트할 수 있습니다. 밑에 있는 결과는 우리가 <code>paginate_by = 2</code>로 바꾼 겨로가입니다.</p> + +<p>next/previous 링크와 함께 보이는 밑에 pagination 링크는 당신이 어느 페이지에 있느냐에 따라 달리 표시가 됩니다. </p> + +<p><img alt="Book List Page - paginated" src="https://mdn.mozillademos.org/files/14057/book_list_paginated.png" style="border-style: solid; border-width: 1px; display: block; height: 216px; margin: 0px auto; width: 924px;"></p> + +<h2 id="도전과제">도전과제</h2> + +<p>이번 글의 과제는 프로젝트를 완수하기 위해 필요한 작가 세부 사항과 목록 view들을 만드는 것입니다. 이 과제를 통해 해당 URL들을 사용가능하게 만들어야 해요:</p> + +<ul> + <li><code>catalog/authors/</code> — 작가 목록.</li> + <li><code>catalog/author/<em><id></em></code><em> </em>—<em><code><id></code></em>라는 이름의 기본키를 이용한 작가의 세부 사항</li> +</ul> + +<p>URL mappers에 필요한 코드들과 view들은 ,사실상, 우리가 위에서 만들었던 <code>Book</code> 목록과 세부 사항 view들과 동일해야 합니다. template들은 다르겠지만, 비슷한 동작을 가지고 있을 겁니다.</p> + +<div class="note"> +<p><strong>Note</strong>:</p> + +<ul> + <li>작가 목록 페이지를 위한 URL mapper를 만들고나면, <strong>All authors</strong> base template에 있는 <strong>All authors </strong>링크 또한 업데이트 해야될 필요를 느끼게 될 겁니다. 우리가 <strong>All books </strong>링크 업데이트 때 했던, <a href="#Update_the_base_template">수행 과정</a>을 따라해주세요.</li> + <li>작가 세부 사항 페이지에 대한 URL mapper를 만들고나면, 당신은 <a href="#Creating_the_Detail_View_template">book detail view template</a> (<strong>/locallibrary/catalog/templates/catalog/book_detail.html</strong>) 또한 업데이트 해야 합니다. 그래야 작가 링크가 당신이 새로 만든 작가 세부 사항 페이지를 가리키거든요. (비어 있는 URL로 있기 보다는 말이죠). 굵게 되어 있는 부분을 template 내의 태그에 넣어주세요. + <pre class="brush: html"><p><strong>Author:</strong> <a href="<strong>{% url 'author-detail' book.author.pk %}</strong>">\{{ book.author }}</a></p> +</pre> + </li> +</ul> +</div> + +<p>모두 마치면, 당신의 페이즈들은 아마 밑의 스크린샷과 비슷하게 보일 겁니다.</p> + +<p><img alt="Author List Page" src="https://mdn.mozillademos.org/files/14053/author_list_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<ul> +</ul> + +<p><img alt="Author Detail Page" src="https://mdn.mozillademos.org/files/14055/author_detail_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; height: 358px; margin: 0px auto; width: 825px;"></p> + +<ul> +</ul> + +<h2 id="요약">요약</h2> + +<p>축하합니다, 도서관의 가장 기본적인 기능들은 모두 끝났어요! </p> + +<p>이번 글에서, 우리는 제네릭 클래스 기반의 list view와 detailv view에 대해서 배웠고, 이를 이용하여 책들과 작가들을 보여주기 위한 페이지를 만들었습니다. 이 과정중에 우리는 정규표현식을 이용하여 패턴 매칭을 하는 것도 배웠고, URL로 부터 데이터를 view에 보내는 것도 배웠습니다. 또한, template을 이용하는 몇 가지 트릭도 배웠죠. 마지막에는 list views에 어떻게 paginate를 할 수 있는지도 보았습니다. 이로 인해 우리는 레코드들이 많아져도 리스트를 관리할 수 있게 되었습니다.</p> + +<p>다음 글에서 우리는 유저 계정을 지원하도록 도서관을 확장하고, 이를 통해 유저 인증, 권한, 세션, forms을 볼 것입니다.</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/generic-display/">Built-in class-based generic views</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/class-based-views/generic-display/">Generic display views</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/intro/">Introduction to class-based views</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins">Built-in template tags and filters</a> (Django docs).</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/pagination/">Pagination</a> (Django docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Home_page", "Learn/Server-side/Django/Sessions", "Learn/Server-side/Django")}}</p> + +<p> </p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> + +<p> </p> diff --git a/files/ko/learn/server-side/django/home_page/index.html b/files/ko/learn/server-side/django/home_page/index.html new file mode 100644 index 0000000000..25c67b7949 --- /dev/null +++ b/files/ko/learn/server-side/django/home_page/index.html @@ -0,0 +1,414 @@ +--- +title: 'Django Tutorial Part 5: Creating our home page' +slug: Learn/Server-side/Django/Home_page +translation_of: Learn/Server-side/Django/Home_page +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Admin_site", "Learn/Server-side/Django/Generic_views", "Learn/Server-side/Django")}}</div> + +<p class="summary">우리는 이제 첫 전체 페이지를 보여주는 코드를 추가할 때가 되었습니다 — <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> website를 위한 홈페이지를요. 이 홈페이지는 각각의 모델 타입마다 갖고 있는 레코드들의 숫자를 보여주고, 우리의 다른 페이지들로 이동할 수 있는 사이드바 내비게이션 링크들을 제공합니다. 이 섹션에서 우리는 기본 URL 맵과 뷰들을 작성하고, 데이터베이스에서 레코드들을 가져오고 그리고 탬플릿을 사용하는 것에 대한 연습 경험을 가질 수 있습니다.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">사전 준비:</th> + <td><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django Introduction</a>을 읽어보세요. 이전 튜토리얼들을 완료하세요 (<a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a> 포함).</td> + </tr> + <tr> + <th scope="row">목표:</th> + <td>간단한 url 맵과 뷰를 생성하고(URL 안에 아무런 데이터도 인코드되지 않은), 모델로부터 데이터를 가져오고, 탬플릿을 생성하는 방법을 배우기.</td> + </tr> + </tbody> +</table> + +<h2 id="개요">개요</h2> + +<p>지금까지는 우리의 모델을 정의하고 약간의 초기 도서관 레코드들을 만들어 왔고, 이제는 사용자에게 정보를 제공하기 위한 코드를 작성할 때입니다. 첫 번째 우리가 할 일은 우리의 페이지에서 어떤 정보를 보여줄 것인지를 결정하고, 그 요소들을 반환하는 데 사용되는 URL들을 정의하는 것입니다. 다음으로 우리는 페이지를 나타내기 위한 URL 매퍼, views, 그리고 탬플릿들을 생성할 것입니다. </p> + +<p> 아래 다이어그램은 주요 데이터 흐름 그리고 HTTP 요청과 응답을 처리하는 데 필요한 요소들을 보여줍니다. 모델은 이미 구현되었기 떄문에, 우리가 생성할 주요 요소들은 다음과 같습니다:</p> + +<ul> + <li>URL 매퍼들 : 적절한 view 함수들을 위해 지원되는 URL들(그리고 URL들 안에 인코딩된 어떤 정보라도)을 포워딩하기 위해.</li> + <li>View 함수들: 요청된 데이터를 모델들에게서 가져오고, 데이터를 표시하는 HTML 페이지를 생성하고 그리고 브라우저 안의 view로 페이지들을 사용자에게 반환하기 위해.</li> + <li>탬플릿들: 데이터를 뷰들 안에 렌더링할 때 사용하기 위해.</li> +</ul> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13931/basic-django.png" style="display: block; margin: 0px auto;"></p> + +<p> 우리가 표시해야 할 페이지는 총 다섯 페이지입니다. 하나의 글에 담기에는 너무 많은 정보죠. 따라서, 이 글의 대부분은 홈 페이지를 어떻게 구현하는 지에 대해 집중하고, 다음 글에서 다른 페이지들에 대해 다루겠습니다. 이렇게 하면 URL 매퍼들, view들, 그리고 모델이 실제로 작동하는 방식에 대해 완벽하고 철저하게 이해할 수 있을 것입니다.</p> + +<h2 id="resource_URLs_정의하기">resource URLs 정의하기</h2> + +<p>이 버전의 LocalLibrary는 근본적으로 최종 사용자들에게는 읽기 전용이기 때문에, 우리는 사이트의 방문 페이지(홈 페이지) 그리고 책들과 저자들에 대한 목록 및 세부 view들을 보여주는 페이지만 제공하면 됩니다. </p> + +<p>우리의 페이지들을 위해 필요한 URL들은:</p> + +<ul> + <li><code>catalog/</code> — 홈/색인(index) 페이지.</li> + <li><code>catalog/books/</code> — 모든 책들의 목록.</li> + <li><code>catalog/authors/</code> — 모든 저자들의 목록.</li> + <li><code>catalog/book/<em><id></em></code> — <code><em><id></em></code> 라는 이름의(기본값) 프라이머리 키(primary key) 필드를 가지는 특정한 책을 위한 세부 사항 뷰(detail view). 예를 들어, 목록에 추가된 세 번째 책은 <code>/catalog/book/3</code>이 될 것입니다. </li> + <li><code>catalog/author/<em><id></em></code><em> </em>— <code><em><id></em></code> 라는 이름의 프라이머리 키(primary key) 필드를 가지는 특정한 저자를 위한 세부 사항 뷰(detail view). 예를 들어, 목록에 추가된 11번째 저자는 <code>/catalog/author/11</code>이 될 것입니다.</li> +</ul> + +<p>처음 세 개의 URL들은 인덱스 페이지, 책 목록, 그리고 저자 목록을 반환합니다. 이것들은 아무런 추가적인 정보도 인코드하지 않고, 데이터베이스에서 데이터를 가져오는 쿼리들도 항상 똑같습니다. 그러나, 쿼리들이 반환할 결과들은 데이터베이스의 내용물에 따라 다를 것입니다.</p> + +<p>그에 반해서 마지막 두 개의 URL들은 특정한 책 또는 저자에 대한 세부 정보를 나타낼 것입니다. 이 URL들은 표시할 항목의 ID를 인코딩합니다(위에서 <code><em><id></em></code> 로 표시). URL 매퍼는 인코딩된 정보를 추출하여 view로 전달합니다. 그리고 view는 데이터베이스에서 무슨 정보를 가져올지 동적으로 결정합니다. URL의 정보를 인코딩하여 우리는 모든 책들(또는 저자들)을 처리하기 위해 단일 모임의 url 매핑, 뷰, 탬플릿을 사용할 것입니다. </p> + +<div class="note"> +<p><strong>주의:</strong> 장고를 이용해서 당신이 필요로 하는 대로 URL들을 만들 수 있습니다 — 위와 같이 URL의 본문(body)에 정보를 인코딩할 수도 있고, 또는 URL 안에 GET 매개 변수들을 포함시킬 수 있습니다(예: /book/?id=6). 어떤 방식이건 URL들은 깨끗하고, 논리적이고, 읽기 쉬워야 합니다. (<a href="https://www.w3.org/Provider/Style/URI">check out the W3C advice here</a>).<br> + 장고 문서는 더 나은 URL 설계(design)를 위해 URL의 본문(body)에 정보를 인코딩하는 것을 권장합니다.</p> +</div> + +<p>개요에서 다룬 것 처럼, 이 글의 나머지는 색인(index) 페이지를 만드는 방법을 보여줍니다.</p> + +<h2 id="index_page_만들기">index page 만들기</h2> + +<p>우리가 만들 첫 번째 페이지는 index page입니다 (<code>catalog/</code>). index 페이지는 데이터베이스 안의 서로 다른 레코드들의 생성된 "개수(count)" 와 함께 몇 가지 정적 HTML을 포함합니다. 이것이 작동하도록 하기 위해서 우리는 URL 매핑, 뷰 그리고 탬플릿을 생성하겠습니다. </p> + +<div class="note"> +<p><strong>주의</strong>:이 섹션에 조금 더 집중해 봅시다. 대부분의 정보들이 우리가 생성할 다른 페이지들에도 적용되기 때문입니다.</p> +</div> + +<h3 id="URL_매핑">URL 매핑</h3> + +<p><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">skeleton website</a>를 만들었을 때, 우리는 <strong>locallibrary/urls.py</strong> 파일을 업데이트했습니다. <code>catalog/</code>로 시작하는 URL을 받았을 때, URLConf 모듈인<em> </em><code>catalog.urls</code> 가 나머지 문자열을 처리하도록 하기 위해서죠.</p> + +<p><strong>locallibrary/urls.py</strong> 의 아래 코드 조각은 <code>catalog.urls</code> 모듈을 포함합니다:</p> + +<pre>urlpatterns += [ + path('catalog/', include('catalog.urls')), +] +</pre> + +<div class="note"> +<p><strong> 주의</strong>: 장고는 import 함수 django.urls.include()를 만날 때 마다 지정된 마지막 문자에서 문자열을 나누고, 나머지 부분 문자열을 추가 작업을 위해 포함된 URLconf 모듈로 보냅니다.</p> +</div> + +<p>우리는 또한 <strong>/catalog/urls.py</strong>로 이름지어진 URLConf 모듈을 위한 자리 표시자(placeholder) 파일도 생성했습니다. 그 파일에 아래 줄을 추가하세요</p> + +<pre class="brush: python">urlpatterns = [ +<strong> path('', views.index, name='index'),</strong> +]</pre> + +<p>이 <code>path()</code> 함수는 아래를 정의합니다 : </p> + +<ul> + <li>빈 문자열인 URL 패턴 :<code>''</code>. 다른 뷰들을 작업할 때 URL 패턴들에 관해 자세히 다룰겁니다.</li> + <li>URL 패턴이 감지되었을 때 호출될 view 함수:<code>views.index</code>. 이 함수는 <strong>views.py</strong> 파일 안에서 <code>index()</code>로 이름지어져 있습니다.</li> +</ul> + +<p>이 <code>path()</code> 함수는 또한 <code>name</code> 매개 변수를 지정합니다. 그것은 이 특정한 URL 매핑을 위한 고유 ID 입니다. 당신은 이 이름을 매퍼를 "반전" 시킬 수 있습니다. 즉, 매퍼가 처리하도록 설계된 리소스를 향하는 URL을 동적으로 생성하기 위해서죠. 예를 들자면, 우리는 아래 링크를 탬플릿에 추가해서 이름 매개 변수를 사용하여 다른 모든 페이지에서 홈 페이지로 링크를 걸 수 있습니다:</p> + +<pre class="brush: html"><a href="<strong>{% url 'index' %}</strong>">Home</a>.</pre> + +<div class="note"> +<p><strong>주의</strong>: 우리는 위 링크를 하드코딩할 수 있지만(예: <code><a href="<strong>/catalog/</strong>">Home</a></code>), 그렇게 하면 만약에 우리가 홈페이지를 바꿨을 때 (예: <code>/catalog/index</code>로 바꿨을 때) 탬플릿들은 더이상 알맞게 링크되지 않습니다. 반전된 url 매핑을 사용하는 것이 훨씬 유연하고 강력합니다.</p> +</div> + +<h3 id="View_(함수-기반의)">View (함수-기반의)</h3> + +<p>뷰는 HTTP 요청을 처리하고, 데이터베이스에서 요청된 데이터를 가져오고, HTML 탬플릿을 이용해서 데이터를 HTML 페이지에 렌더링하고 그리고 생성된 HTML을 HTTP 응답으로 반환하여 사용자들에게 페이지를 보여주는 함수입니다. 색인(index) 뷰는 이 구조(model)를 따라갑니다 — 이것은 데이터베이스 안에 있는<code>Book</code>, <code>BookInstance</code>, 사용 가능한 <code>BookInstance</code> 그리고 <code>Author</code> 레코드들의 개수에 대한 정보를 가져오고, 그 정보를 디스플레이(display)를 위해 탬플릿으로 전달합니다.</p> + +<p><strong>catalog/views.py</strong> 를 열어서, 파일이 이미 탬플릿과 데이터를 이용해 HTML 파일을 생성하는 <a href="https://docs.djangoproject.com/en/2.0/topics/http/shortcuts/#django.shortcuts.render">render()</a> 바로가기 함수를 포함(import)하고 있음을 확인하세요.</p> + +<pre class="brush: python">from django.shortcuts import render + +# Create your views here. +</pre> + +<p> 파일의 하단에 아래 코드를 복사 붙여넣기 하세요.</p> + +<pre class="brush: python">from catalog.models import Book, Author, BookInstance, Genre + +def index(request): + """View function for home page of site.""" + + # Generate counts of some of the main objects + num_books = Book.objects.all().count() + num_instances = BookInstance.objects.all().count() + + # Available books (status = 'a') + num_instances_available = BookInstance.objects.filter(status__exact='a').count() + + # The 'all()' is implied by default. + num_authors = Author.objects.count() + + context = { + 'num_books': num_books, + 'num_instances': num_instances, + 'num_instances_available': num_instances_available, + 'num_authors': num_authors, + } + + # Render the HTML template index.html with the data in the context variable + return render(request, 'index.html', context=context)</pre> + +<p>첫번째 줄은 우리의 모든 뷰들 안에서 데이터에 접근하는 데 사용할 모델 클래스들을 포함(import)합니다.</p> + +<p>view 함수의 첫번째 부분은 모델 클래스들에서 <code>objects.all()</code> 속성을 사용하는 레코드들의 개수를 가져옵니다. 이 함수는 또한 상태 필드에서 'a'(Available) 값을 가지고 있는 <code>BookInstance</code> 객체들의 목록도 가져옵니다. 이전 튜토리얼에서 모델 데이터에 접근하는 방법에 대한 더 많은 정보를 찾을 수 있습니다 : <a href="/en-US/docs/Learn/Server-side/Django/Models#Searching_for_records">Django Tutorial Part 3: Using models > Searching for records</a>.</p> + +<p>view 함수의 마지막에선 HTML 페이지를 생성하고 이 페이지를 응답으로서 반환하기 위해 <code>render()</code> 함수를 호출합니다. 이 바로가기(shortcut) 함수는 아주 일반적으로 사용되는 경우들을 간단히 하기 위해 여러 다른 함수들을 포함합니다. <code>render()</code> 함수는 아래 매개 변수들을 허용합니다:</p> + +<ul> + <li><code>HttpRequest</code>인 원본 <code>request</code> 객체.</li> + <li>데이터에 대한 플레이스홀더(placeholder)들을 갖고 있는 HTML 탬플릿.</li> + <li>파이썬 딕셔너리(dictionary)인, 플레이스홀더에 삽입할 데이터를 갖고 있는 <code>context</code>변수.</li> +</ul> + +<p>다음 섹션에서 탬플릿과 context 변수에 대해 더 다루도록 하겠습니다. 이제 탬플릿을 생성하여 사용자들에게 실제로 무언가를 표시해 봅시다!</p> + +<h3 id="탬플릿(Template)">탬플릿(Template)</h3> + +<p>탬플릿은 파일(HTML 페이지 같은)의 구조(structure)나 배치(layout)을 정의하는 텍스트 파일입니다. 탬플릿은 실제 내용물(content)를 나타내기 위해 플레이스홀더(placeholder)들을 사용합니다. 장고는 당신의 어플리케이션 안에서 'templates' 라고 이름지어진 경로 안에서 자동적으로 탬플릿들을 찾을 것입니다. 예를 들어서, 우리가 방금 추가한 색인(index) 뷰 안에서, <code>render()</code> 함수는 <strong>/locallibrary/catalog/templates/ </strong>경로 안에서<strong> <em>index.html </em></strong><em>파일을 찾으려 할 것이고, 파일이 없다면 에러를 표시할 것입니다. 이것은 이전의 변경점들을 저장하고 브라우저에서 <code>127.0.0.1:8000</code>으로 접근해서 확인할 수 있습니다 - 이것은 다른 세부 사항들과 함께 상당히 직관적인 오류 메세지를 표시할 것입니다 : "<code>TemplateDoesNotExist at /catalog/</code>".</em></p> + +<div class="note"> +<p><strong>주의:</strong> 프로젝트의 settings 파일에 기초해서, 장고는 여러 장소에서 탬플릿들을 찾아볼 것입니다. 기본적으로는 설치된 어플리케이션에서 검색합니다. 장고가 어떻게 탬플릿들을 찾는지, 그리고 어떤 탬플릿 양식(format)들을 지원하는지에 관해 여기(<a href="https://docs.djangoproject.com/en/2.0/topics/templates/">Templates</a> (Django docs))에서 찾아볼 수 있습니다.</p> +</div> + +<h4 id="탬플릿_확장(extend)하기">탬플릿 확장(extend)하기</h4> + +<p> 색인(index) 팀플릿은 head 및 body를 위해 표준 HTML 마크업이 필요할 것입니다. 우리가 아직 생성하지 않은 사이트들의 다른 페이지들을 향한 링크를 걸기 위한 탐색(navigation) 섹션도 필요하고요. 그리고 소개 텍스트 및 책 데이터를 표시하는 섹션 또한 필요합니다. 대부분의 HTML과 탐색 구조는 사이트의 모든 페이지에서 동일할 것입니다. 모든 페이지마다 똑같은 코드를 복사하는 대신, 기본 템플릿을 선언하기 위해 장고 탬플릿 언어(Django templating language)를 사용하고, 탬플릿을 확장하여 각각의 페이지 마다 다른 부분들만을 대체(replace)할 수 있습니다.</p> + +<p>아래 코드 조각은 <strong>base_generic.html </strong>파일의 기본 탬플릿 샘플입니다. 이 샘플은 제목, 사이드바를 위한 섹션과 이름이 지정된 <code>block</code> 및 <code>endblock</code> 탬플릿 태그(굵게 표시)가 마크된 주요 내용(main contents)들이 포함된 일반적인 HTML을 포함합니다. 블럭(block)들을 비워두거나, 또는 탬플릿에서 파생된 페이지들을 렌더링할 때 사용할 기본 내용을 포함할 수 있습니다.</p> + +<div class="note"> +<p><strong>주의:</strong> 탬플릿 태그들은 목록을 반복하거나, 변수 값을 기반으로 조건부 연산을 수행하거나, 여타 다른 일들을 할 수 있는 함수입니다. 탬플릿 태그 외에도 탬플릿 구문(syntax)을 사용하면 view에서 탬플릿으로 전달된 변수들을 참조할 수 있고, 탬플릿 필터(filters)를 사용해서 변수의 형식을 지정할 수 있습니다(예를 들어, 문자열을 소문자로 변환).</p> +</div> + +<pre class="brush: html"><!DOCTYPE html> +<html lang="en"> +<head> + <strong>{% block title %}</strong><title>Local Library</title><strong>{% endblock %}</strong> +</head> +<body> + <strong>{% block sidebar %}</strong><!-- insert default navigation text for every page --><strong>{% endblock %}</strong> + <strong>{% block content %}</strong><!-- default content text (typically empty) --><strong>{% endblock %}</strong> +</body> +</html> +</pre> + +<p> 특정한 view를 위해 탬플릿을 정의할 땐, 먼저 <code>extends</code> 탬플릿 태그를 이용하여 기본 탬플릿을 지정합니다 — 아래 코드 샘플을 참조하세요. 그리고 나서 기본 탬플릿에서와 같이 <code>block</code>/<code>endblock</code> 섹션들을 이용해서 대체할 탬플릿의 섹션들을 선언합니다(있을 경우). </p> + +<p>예를 들어, 아래 코드 조각은 extends 탬플릿 태그의 사용 및 content 블럭(block)을 재정의하는 방법을 보여줍니다. 생성된 HTML은 기본 탬플릿에서 정의된 코드와 구조를 포함할 것입니다(<code>title</code> 블럭에서 정의한 기본 내용은 포함하지만, 기본 <code>contents</code> 블럭 대신 새로운 <code>contents</code> 블럭 포함).</p> + +<pre class="brush: html">{% extends "base_generic.html" %} + +{% block content %} + <h1>Local Library Home</h1> + <p>Welcome to LocalLibrary, a website developed by <em>Mozilla Developer Network</em>!</p> +{% endblock %}</pre> + +<h4 id="LocalLibrary_기본_탬플릿">LocalLibrary 기본 탬플릿</h4> + +<p>우리는 아래 코드 조각을 LocalLibrary 웹사이트의 베이스 탬플릿으로 사용할 것입니다. 보시는 바와 같이, 이것은 HTML 코드를 조금 포함하고 <code>title</code>, <code>sidebar</code> 그리고 <code>content</code> 블럭을 정의합니다. 우리는 기본 제목과 모든 책들 및 저자들에 대한 링크를 갖고 있는 기본 사이드바를 갖고 있습니다. 둘 다 미래에 쉽게 변경하기 위해 블럭들 안에 묶여 있습니다.</p> + +<div class="note"> +<p><strong>주의</strong>: 우리는 또한 두 개의 추가적인 탬플릿 태그를 소개합니다: <code>url</code> 과 <code>load static</code>. 이 태그들은 아래 섹션들에서 설명될 것입니다.</p> +</div> + +<p>새로운 파일 <strong>base_generic.html</strong> 을 <strong>/locallibrary/catalog/templates/<em>base_generic.html</em></strong> 경로 안에 생성해서 아래 코드를 파일에 복사 붙여넣기 하세요:</p> + +<pre class="brush: html"><!DOCTYPE html> +<html lang="en"> +<head> + {% block title %}<title>Local Library</title>{% endblock %} + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous"> + <!-- Add additional CSS in static file --> + {% load static %} + <link rel="stylesheet" href="{% static 'css/styles.css' %}"> +</head> +<body> + <div class="container-fluid"> + <div class="row"> + <div class="col-sm-2"> + {% block sidebar %} + <ul class="sidebar-nav"> + <li><a href="{% url 'index' %}">Home</a></li> + <li><a href="">All books</a></li> + <li><a href="">All authors</a></li> + </ul> + {% endblock %} + </div> + <div class="col-sm-10 ">{% block content %}{% endblock %}</div> + </div> + </div> +</body> +</html></pre> + +<p>탬플릿에는 HTML 페이지의 레이아웃과 프리젠테이션을 개선하기 위한 <a href="http://getbootstrap.com/">Bootstrap</a> 의 CSS가 포함되어 있습니다. 부스트스트랩(또는 다른 클라이언트-사이드 웹 프레임워크)를 사용해서 서로 다른 크기의 화면에서도 잘 표시되는 매력적인 페이지를 빠르게 만들 수 있습니다.</p> + +<p>또한 기본 탬플릿은 추가적인 꾸미기(styling)를 제공하는 로컬 css 파일(styles.css)을 참조합니다. <strong>styles.css</strong> 파일을 <strong>/locallibrary/catalog/static/css/</strong> 경로 안에 생성하고 아래 코드를 파일 안에 복사 붙여넣기 하세요:</p> + +<pre class="brush: css">.sidebar-nav { + margin-top: 20px; + padding: 0; + list-style: none; +}</pre> + +<h4 id="색인(index)_탬플릿">색인(index) 탬플릿</h4> + +<p> 새로운 HTML 파일 <strong>index.html</strong> 을 <strong>/locallibrary/catalog/templates/</strong> 경로 안에 생성해서 아래 코드를 파일 안에 복사 붙여넣기 하세요. 보시는 바와 같이 첫째 행에서 우리의 기본 탬플릿을 확장하고, 탬플릿의 기본 <code>content</code> 블럭을 새로운 블럭으로 대체합니다. </p> + +<pre class="brush: html">{% extends "base_generic.html" %} + +{% block content %} + <h1>Local Library Home</h1> + <p>Welcome to LocalLibrary, a website developed by <em>Mozilla Developer Network</em>!</p> + <h2>Dynamic content</h2> + <p>The library has the following record counts:</p> + <ul> + <li><strong>Books:</strong> <strong>\{{ num_books }}</strong></li> + <li><strong>Copies:</strong> <strong>\{{ num_instances }}</strong></li> + <li><strong>Copies available:</strong> <strong>\{{ num_instances_available }}</strong></li> + <li><strong>Authors:</strong> <strong>\{{ num_authors }}</strong></li> + </ul> +{% endblock %}</pre> + +<p>동적 콘텐츠 섹션에서 우리는 우리가 포함하고 싶은 view의 정보를 위한 플레이스홀더(탬플릿 변수)를 선언합니다. 이 변수들은 코드 샘플에서 굵게 표시된 것과 같이 이중 중괄호로(핸들 바)로 묶입니다.</p> + +<div class="note"> +<p><strong>주의:</strong> 탬플릿 변수와 탬플릿 태그(함수)들을 쉽게 알 수 있습니다 - 변수들은 이중 중괄호로 감싸여져 있고(<code>\{{ num_books }}</code>) , 태그들은 퍼센트 기호와 단일 중괄호로 감싸여 있습니다(<code>{% extends "base_generic.html" %}</code>).</p> +</div> + +<p>여기서 주의해야 할 중요한 것은 변수들의 이름은 열쇠(key)들로 정해진다는 것입니다. 이 열쇠(key)들은 우리의 view의 <code>render()</code>함수 안의 <code>context</code> 사전(dictionary)로 전달하는 열쇠입니다(아래를 확인하세요). 변수들은 탬플릿이 렌더링 될 때 그것들과 연관된 값들로 대체될 것입니다. </p> + +<pre class="brush: python">context = { + '<strong>num_books</strong>': num_books, + '<strong>num_instances</strong>': num_instances, + '<strong>num_instances_available</strong>': num_instances_available, + '<strong>num_authors</strong>': num_authors, +} + +return render(request, 'index.html', context=context)</pre> + +<h4 id="Templates_에_정적_파일_참조하기(referencing)">Templates 에 정적 파일 참조하기(referencing)</h4> + +<p>당신의 프로젝트는 자바스크립트, CSS 그리고 이미지를 포함하는 정적 리소스들을 사용할 가능성이 높습니다. 이 파일들의 위치가 알 수 없기 때문에(또는 바뀔 수 있기 때문에), 장고는 <code>STATIC_URL</code> 전역 설정을 기준으로 탬플릿에서의 위치를 특정할 수 있도록 합니다. 기본 뼈대 웹사이트(skeleton website)는 <code>STATIC_URL</code>의 값을 '<code>/static/</code>'으로 설정하지만, 당신은 이것들을 콘텐츠 전달 네트워크(content delivery network)나 다른 곳에서 호스트할 수도 있습니다.</p> + +<p>아래 코드 샘플처럼, 탬플릿 안에서 당신은 먼저 탬플릿 라이브러리를 추가하기 위해 "static"을 지정하는 <code>load</code> 탬플릿 태그를 호출합니다. 그러고 나서 <code>static</code> 탬플릿 태그를 사용할 수 있고 관련 URL을 요구되는 파일에 지정할 수 있습니다.</p> + +<pre class="brush: html"><!-- Add additional CSS in static file --> +{% load static %} +<link rel="stylesheet" href="{% static 'css/styles.css' %}"></pre> + +<p>비슷한 방법으로 이미지를 페이지에 추가할 수 있습니다. 예를 들어:</p> + +<pre class="brush: html">{% load static %} +<img src="{% static 'catalog/images/local_library_model_uml.png' %}" alt="UML diagram" style="width:555px;height:540px;"> +</pre> + +<div class="note"> +<p><strong> 주의</strong>: 위의 샘플은 파일들의 위치를 특정하지만, 장고는 기본적으로 파일을 제공하지 않습니다. 우리는 우리가 웹사이트 뼈대를 생성했을 때(<a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">created the website skeleton</a>) 전역 URL 매퍼(/locallibrary/locallibrary/urls.py)를 수정하여 개발 웹 서버가 파일을 제공하도록 설정했습니다만, 제품화되었을(in production)때도 파일을 제공할 수 있어야 합니다. 이것에 관해 차후에 다루겠습니다.</p> +</div> + +<p> 정적 파일들로 작업하는 것에 대한 더 많은 정보는 장고 문서 안의 <a href="https://docs.djangoproject.com/en/2.0/howto/static-files/">Managing static files</a> 를 참고하세요.</p> + +<h4 id="URL_링크하기(Linking_to_URLs)">URL 링크하기(Linking to URLs)</h4> + +<p>위의 기본 탬플릿은 <code>url</code> 탬플릿 태그를 소개했습니다.</p> + +<pre class="brush: python"><li><a href="{% url 'index' %}">Home</a></li> +</pre> + +<p> 이 태그는 <strong>urls.py</strong>에서 호출된 <code>path()</code> 함수의 이름 및 연관된 view가 그 함수에서 수신받을 모든 인자들을 위한 값들을 허용하고, 리소스에 링크하는 데 사용할 수 있는 URL을 반환합니다 .</p> + +<h4 id="탬플릿을_찾을_수_있는_곳_설정하기">탬플릿을 찾을 수 있는 곳 설정하기</h4> + +<p>탬플릿 폴더 안에서 탬플릿을 찾아볼 수 있도록 장고에게 위치를 가르쳐 주어야 합니다. 그것을 하기 위해서, 아래 코드 샘플에 굵게 표시된 것 처럼 <strong>settings.py</strong> 파일을 수정하여 TEMPLATES 객체에 templates 경로(dir)를 추가하세요.</p> + +<p> </p> + +<pre><code>TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [ +<strong> os.path.join(BASE_DIR, 'templates'), +</strong> ], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +]</code></pre> + +<p> </p> + +<h2 id="어떻게_보일까요">어떻게 보일까요?</h2> + +<p>이 시점에서 우리는 색인(index) 페이지를 나타내기 위해 필요한 모든 요소들을 생성했습니다. 서버를 실행하고 (<code>python3 manage.py runserver</code>) 브라우저에서 <a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>으로 이동하세요. 모든 것이 알맞게 설정되었다면, 당신의 사이트는 아래 스크린샷과 같이 보여야 합니다.</p> + +<p><img alt="Index page for LocalLibrary website" src="https://mdn.mozillademos.org/files/14045/index_page_ok.png" style="border-style: solid; border-width: 1px; display: block; height: 356px; margin: 0px auto; width: 874px;"></p> + +<div class="note"> +<p><strong>주의:</strong> All books와 All authors 링크들에 대한 경로, 뷰 그리고 탬플릿들이 정의되지 않았기 때문에 그 링크들은 작동하지 않을 것입니다. 우리는 단지 <code>base_generic.html</code> 탬플릿 안에 그 링크들을 위한 플레이스홀더(placeholder)들을 삽입했을 뿐입니다.</p> +</div> + +<h2 id="도전_과제">도전 과제</h2> + +<p>모델 쿼리, 뷰 그리고 탬플릿들과의 친밀함을 시험할 수 있는 두 가지 임무가 있습니다. </p> + +<ol> + <li>LocalLibrary 기본 탬플릿(<a href="#The_LocalLibrary_base_template">base template</a>)에는 <code>title</code> 블록이 정의되어 있습니다. 색인 탬플릿(<a href="#The_index_template">index template</a>) 안에 이 블록을 덮어쓰기하고 페이지를 위한 새로운 제목을 만들어 보세요. + + <div class="note"> + <p><strong>힌트:</strong> <a href="#Extending_templates">Extending templates</a> 섹션은 블럭(block)을 생성하고 다른 탬플릿에서 블럭을 확장(extend)하는 방법을 설명합니다.</p> + </div> + </li> + <li>대소문자 구분 없이 특정한 단어를 포함하는 장르와 책들의 개수(count)를 생성하도록 <a href="#View_(function-based)">view</a> 를 수정하고, 결과를 <code>context</code>에 전달해 보세요. 이것은 <code>num_books</code>와 <code>num_instances_available</code>을 생성하고 사용하는 것과 비슷한 방법으로 달성할 수 있습니다. 그리고 나서 이 변수들을 포함시키기 위해 <a href="#The_index_template">index template</a> 를 업데이트 하세요.<br> + </li> +</ol> + +<ul> +</ul> + +<h2 id="요약">요약</h2> + +<p>이제 우리의 사이트를 위한 홈 페이지를 생성했습니다 — 데이터베이스의 여러 레코드들을 표시하고 아직 생성되지 않은 페이지로 링크하는 HTML 페이지 입니다. 그 과정에서 우리는 url 매퍼, view, 모델을 이용한 데이터베이스 쿼리, view에서 탬플릿으로의 정보 전달 그리고 탬플릿의 생성과 확장에 관한 기본적인 정보를 배웠습니다.</p> + +<p>다음 글에서는 이 지식들을 토대로 우리 웹사이트의 나머지 네 개의 페이지들을 생성할 것입니다.</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/intro/tutorial03/">Writing your first Django app, part 3: Views and Templates</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/http/urls/">URL dispatcher</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/http/views/">View functions</a> (DJango docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/templates/">Templates</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/howto/static-files/">Managing static files</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/http/shortcuts/#django.shortcuts.render">Django shortcut functions</a> (Django docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Admin_site", "Learn/Server-side/Django/Generic_views", "Learn/Server-side/Django")}}</p> + +<p> </p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> + +<p> </p> diff --git a/files/ko/learn/server-side/django/index.html b/files/ko/learn/server-side/django/index.html new file mode 100644 index 0000000000..2545082955 --- /dev/null +++ b/files/ko/learn/server-side/django/index.html @@ -0,0 +1,65 @@ +--- +title: Django 웹 프레임워크 (파이썬) +slug: Learn/Server-side/Django +tags: + - 서버 사이드 프로그래밍 + - 장고 + - 초보자 + - 파이썬 +translation_of: Learn/Server-side/Django +--- +<div>{{LearnSidebar}}</div> + +<p>Django는 파이썬으로 구성된, 인기 많고 완벽한 기능을 갖춘 서버-사이드 웹 프레임워크입니다. 이 모듈은 Django가 웹 서버 프레임워크 중 가장 유명한 이유, 개발환경을 설정하는 방법, 그리고 이를 통해 자신만의 웹 애플리케이션을 만드는 방법을 알려줍니다. </p> + +<h2 id="전제조건">전제조건</h2> + +<p>이 모듈을 시작하기에 앞서, 당신이 Django에 대해 미리 알 필요는 전혀 없습니다. 하지만 <a href="/ko/docs/Learn/Server-side/First_steps">Server-side website programming first steps</a> 모듈을 보면서 과연 서버-사이드 웹 프로그래밍과 웹 프레임워크가 무엇인가에 대해서는 이해 할 필요가 있습니다.</p> + +<p>프로그래밍 개념 그리고 <a href="/ko/docs/Glossary/Python">Python</a>에 대한 일반적인 지식을 공부하는 것은 권장합니다. 하지만 이것이 Django의 핵심 개념을 이해하는데 반드시 필요한 것은 아닙니다.</p> + +<div class="note"> +<p><strong>노트</strong>: Python은 초보자가 읽고 이해할 수 있는 가장 쉬운 프로그래밍 언어 중 하나 입니다. 즉, 만약 당신이 이 모듈을 더 깊이 이해하고 싶다면, 인터넷에서 많은 무료 서적과 자습서들을 이용할 수 있습니다. ( 초보 프로그래머들은 python.org wiki 에서 <a href="https://wiki.python.org/moin/BeginnersGuide/NonProgrammers">Python for Non Programmers</a> 페이지를 확인해도 좋습니다.).</p> +</div> + +<h2 id="가이드">가이드</h2> + +<dl> + <dt><a href="/ko/docs/Learn/Server-side/Django/Introduction">Django 소개</a></dt> + <dd>이 문서에서는 "Django란 무엇인가?" 라는 물음에 답을 합니다. 그리고 이 웹 프레임워크를 특별하게 만드는 요소에 대해 대략적으로 살펴볼 것입니다. 우리는 여기서 Django의 주요 특징들 (자세히 설명할 시간은 없는 심화된 기능들 포함) 을 훑어볼 것입니다. 또한 Django 애플리케이션의 주요 구성 요소들 중 일부를 보여줍니다. 따라서 당신이 Django 애플리케이션을 설정하고 시작하기 전에, 우리는 Django가 무슨 일을 해 줄 수 있는지에 대해 알려줄 것입니다. </dd> + <dt><a href="/ko/docs/Learn/Server-side/Django/development_environment">Django 개발 환경 설정</a></dt> + <dd>이제 Django가 무엇인지 알았으므로 Windows, Linux (Ubuntu) 및 Mac OS X에서 Django 개발 환경을 설정하고 테스트하는 방법을 살펴봅시다. 이 문서에서는 당신이 어떠한 운영 체제를 사용하든지 상관없습니다. 우리는 당신이 Django로 앱 개발을 시작하기 전에 필요한 것을 마땅히 알려줘야 합니다. </dd> + <dt><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></dt> + <dd>이 (실용적) 튜토리얼의 첫번째 문서에서는 앞으로 무엇을 배울지 알아봅니다. - 우리가 후속 문서에서 계속 작업하고 개발해 나갈 "로컬 저장소" 의 예제 웹사이트를 살펴봅니다.</dd> + <dt><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></dt> + <dd>이 수업에서는 웹사이트의 기본인 "뼈대"를 만드는 방법을 살펴봅니다. 그런 다음 사이트별로 settings, urls, models, views, templates 을 사용하여 채워 넣을 것입니다.</dd> + <dt><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></dt> + <dd>이 수업에서는 로컬 저장소 웹사이트에서 모델을 저장하는 방법에 대해 알아봅니다. 모델이란 웹 앱의 자료구조를 나타내며, Django의 데이터 베이스에 데이터를 저장할 수 있도록 해줍니다. 여기서는 모델이 무엇인지, 어떻게 선언하는지, 그리고 몇몇 중요한 필드 타입도 살펴볼 것입니다. 그리고 모델 데이터에 접근하는 몇가지 주요 방법도 간단하게 알아봅니다. </dd> + <dt><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></dt> + <dd>앞에서 로컬라이브러리 웹사이트의 모델을 생성했다면, 이제는 Django 관리자 사이트를 사용해서 "실제" 책 데이터를 추가할 차례입니다. 먼저 관리자 사이트에 모델에 등록하는 방법을 보여줄 것입니다. 그 다음에 로그인 하고 데이터를 생성하는 방법도 배울 것입니다. 마지막 순서에서는 관리자 사이트의 PT를 향상시키는 더 많은 방법도 알아볼 것입니다. </dd> + <dt><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></dt> + <dd>이제 우리는 처음으로 완성된 페이지(홈페이지 개념으로 모델 종류를 기록하고 사이드바나 다른 페이지의 링크들이 있음)를 표시하기 위한 코드를 입력할 준비가 되었습니다. 이 방법을 통해 기본적인 URL 맵이나 뷰를 작성하고, 데이터베이스에 기록하고, 템플릿을 사용하는 실용적인 경험을 얻을 것입니다. </dd> + <dt><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></dt> + <dd>이 수업에서는 우리가 만든 local library의 웹사이트를 확장해 볼 것입니다. 목록, 책이나 저자 정보를 담은 자세한 페이지들을 추가할 것입니다. 여기서는 일반적인 클래스 기반의 view를 배우고 보통 상황에서 어떻게 코드의 양을 줄일 수 있는지 살펴볼 것입니다. 우리는 또한 URL 핸들링에 대해 정말 자세히 들어가서, 기본적인 패턴 매칭을 어떻게 해야 하는지도 볼 것입니다. </dd> + <dt><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></dt> + <dd>여기서는 우리가 만든 로컬 라이브러리의 웹사이트에 '세션 기반 방문자 수 계산기' 를 홈페이지에 추가할 것입니다. 이것은 비교적 간단한 예제입니다. 하지만 귀하는 이 예제를 통해, 세션 프레임워크를 사용해서 어떻게 사이트에 방문하는 이름없는 사용자들의 반복적인 행동을 볼 수 있는지 (그 방법을) 알 수 있습니다. </dd> + <dt><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></dt> + <dd>이 수업에서는 유저들에게 그들의 계정으로 웹사이트에 로그인 하게 하는 방법에 대해 배웁니다. 그리고 로그인 상태에 따라 그들이 보고 작성할 수 있는 범위를 통제하는 방법, 그들에게 허가를 내주는 방법을 배웁니다. 우리는 연습을 위해서 로컬 라이브러리 웹사이트를 확장해 볼 것입니다. 로그인 및 로그아웃 페이지를 추가하고, 대출 도서를 보여주는 페이지를 사용자용, 관리자용 각각 따로 만들어 볼 것입니다. </dd> + <dt><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></dt> + <dd>여기서는 Django에서 <a href="/en-US/docs/Web/Guide/HTML/Forms">HTML Forms</a>을 어떻게 사용하는 살펴볼 것입니다. HTML은 특히 모델을 생성하고, 갱신하고, 지우는 등의 폼을 작성하는 가장 쉬운 방법입니다. 이번 연습에서는 로컬 라이브러리의 웹사이트를 확장하는 것도 포함되어 있습니다. 여기서 우리는 도서관 사서들이 (관리자 어플리케이션 보다는) 우리가 작성한 폼을 이용해서 책을 고치고, 생성하고, 업데이트하고 정보를 삭제할 수 있도록 웹사이트를 확장해 볼 것입니다. </dd> + <dt><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></dt> + <dd>웹사이트가 확장되어감에 따라 일일이 확인하기가 점점 어려워질 것입니다. 테스트해야할 요소 자체가 많아질 뿐만 아니라 요소간의 상호관계도 복잡해지면서 작은 요소의 변화가 다른 큰 요소들에까지 영향을 미치게됩니다. 이런 문제에 대한 걱정을 덜어줄 수 있는 방법은 자동 테스트 코드를 작성하는 것입니다. 자동 테스트 코드는 소스에 변화가 생길때마다 작동하는 코드입니다. 이번 강좌에서는 성능이 우수하면서도 작성이 간단한 Django의 테스트 프레임워크로 어떻게 당신의 웹사이트를 단위 테스트할수 있는지 알아봅니다.</dd> + <dt><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></dt> + <dd>이제 당신은 훌륭한 로컬저장소 웹사이트를 만들었으니, 로컬저장소가 아닌 공개 서버에 업로드 함으로써 인터넷을 통해 관리자와 사용자들이 접근할 수 있도록 하고싶을 겁니다. 이 수업에서는 호스트 업체를 찾고 웹사이트를 등록하는 방법과 사이트에 상품가치를 부여하는 방법을 알아봅니다.</dd> + <dt><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></dt> + <dd>사용자의 데이터를 보호하는 것은 웹사이트 디자인에서 중요한 부분입니다. 이전에 <a href="https://developer.mozilla.org/en-US/docs/Web/Security">Web security</a> 수업에서 일반적인 보안 위협들에 대해 알아보았습니다. 이번 항목에서는 Django에 내장된 보호 시스템이 이런 위협을 어떻게 처리하는지 실질적인 예시에 대해 살펴봅니다. </dd> +</dl> + +<h2 id="평가">평가</h2> + +<p>밑에 제시되어있는 평가는 위에 설명 된 대로, 장고(Django)를 사용하여 웹 사이트를 만드는 방법에 대한 이해도를 테스트합니다.</p> + +<dl> + <dt><a href="/ko/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django 미니 블로그</a></dt> + <dd>이 평가에서, 귀하는 여기서 배운 지식을 활용해서 자신만의 블로그를 만들 수 있습니다. </dd> +</dl> diff --git a/files/ko/learn/server-side/django/introduction/index.html b/files/ko/learn/server-side/django/introduction/index.html new file mode 100644 index 0000000000..f8034af90e --- /dev/null +++ b/files/ko/learn/server-side/django/introduction/index.html @@ -0,0 +1,256 @@ +--- +title: Django 소개 +slug: Learn/Server-side/Django/Introduction +tags: + - 장고 +translation_of: Learn/Server-side/Django/Introduction +--- +<p>{{LearnSidebar}}</p> + +<div>{{NextMenu("Learn/Server-side/Django/development_environment", "Learn/Server-side/Django")}}</div> + +<p class="summary">Django의 첫번째 문서에서는 "Django가 뭐지?"라는 질문에 답해보고, Django 웹 프레임워크의 특별한 부분에 대해 전반적으로 살펴봅니다. 우리가 이 수업에서 자세히 다루지는 않을 고급 기능들까지 포함하여 간단하게 전반적인 부분을 살펴 볼겁니다. 또한, Django 애플리케이션을 구성하는 중요한 요소도 살펴보겠습니다. (물론 지금 시점에서는 테스트를 할 개발환경을 가지고 있지 않겠지만요.)</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">요구 사항</th> + <td>기본적인 컴퓨터 지식. <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps">server-side website 프로그래밍</a>에 대한 전반적인 이해, 그리고 웹사이트의 <a href="/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview">client-server interactions</a> 의 매커니즘에 대한 특정한 지식.</td> + </tr> + <tr> + <th scope="row">목표</th> + <td> + <p>Django란 무엇인지, 어떤 기능이 있는지, Django 어플리케이션의 주요 구성요소는 어떤것들인지에 대해 익숙해지기</p> + </td> + </tr> + </tbody> +</table> + +<h2 id="Django란">Django란?</h2> + +<p>Djano란 보안이 우수하고 유지보수가 편리한 웹사이트를 신속하게 개발하는 하도록 도움을 주는 파이썬 웹 프레임워크입니다. 훌륭한 개발자에 의해 만들어진 이 프레임워크는, 웹 개발을 하는데 많은 도움을 주기 때문에 새롭게 웹 개발을 시작할 필요없이 그저 프레임워크를 활용하여 앱 개발에만 집중할 수 있게되죠. 무료 오픈소스인데다가, 활발한 커뮤니티들이 있고, 좋은 참고자료와 무료 및 유료 지원을 하는 옵션들이 제공됩니다.</p> + +<p>Django는 다음과 같은 소프트웨어를 개발하는데 도움을 줍니다.</p> + +<dl> + <dt>Complete(완결성 있는)</dt> + <dd>Django는 "Batteries included" 의 철학을 기반으로 개발자들이 개발하고 싶은 거의 모든것을 개발하는데 도움을 줍니다. 개발자들이 원하는 것은 모두 하나의 "결과물"의 일부일 것이기 때문에 도달하고자 하는 목표지점은 같으며 이 덕분에 일관된 디자인 룰을 적용하여 <a href="https://docs.djangoproject.com/en/2.2/">광범위한 최신 문서</a>를 제공합니다.</dd> + <dt>Versatile(다용도의)</dt> + <dd>Django는 문서관리시스템과 Wiki부터 SNS, 뉴스에 이르기까지 다양한 종류의 웹 사이트를 빌드하는데 사용할 수 있고 사용되어 왔습니다. 또한 어떠한 클라이언트측 프레임워크와도 협업할 수 있고, 대부분의 형식(HTML, RSS 피드, JSON, XML 등)으로 컨텐츠를 전송할 수 있습니다. 당신이 보고 있는 이 사이트도 Django 기반입니다!<br> + <br> + 내부적으로 Django는 당신이 원하는 대부분의 기능들(몇몇 유명한 데이터베이스들, 템플릿 엔진 등)을 제공하지만, 필요하다면 다른 컴포넌트들을 사용하기 위해 확장될 수 있습니다. </dd> + <dt>Secure(안전한)</dt> + <dd>Django 는 개발자들이 웹사이트를 개발할 때 실수하기 쉽지만 고려해야하는 보안 문제에 대해서 많은 도움을 줍니다. 예를 들면, 장고는 유저의 계정과 비밀번호를 관리하는 안전한 방법을 제공합니다. 이 예에서 발생할 수 있는 개발자들의 실수로 세션의 정보를 보안에 취약한 위치에 있는 쿠키(해결책은 쿠키는 그저 key값을 가지도록 하는 반면 실제 데이터는 데이터 베이스에 저장하도록 하는 것입니다)에 넣는 실수를 하는 것입니다. 또 달리 쉽게할 수 있는 실수는 비밀번호를 hash를 통하지 않고 그대로 변형없이 저장하는 것이 있습니다.<br> + <br> + <em>비밀번호에 사용되는 hash 는 <a href="https://en.wikipedia.org/wiki/Cryptographic_hash_function">cryptographic hash function</a>에 의해 생성된 고정된 길이의 값을 가집니다. Django는 이렇게 변형되어 입력된 비밀번호가 유효한지 hash 함수를 통해 확인할 수 있습니다. 하지만 "단방향" 적인 함수의 특성상, 저장된 hash 값을 웹을 공격하는 사람들이 알아낸다고 하더라도 원본 비밀번호는 알아낼 수 없습니다.</em><br> + <br> + Django 는 SQL 인젝션, 크로스사이트 스크립팅, 크로스사이트 요청 위조 그리고 클릭 하이젝킹 (이러한 공격 방법에 대한 상세 정보는 <a href="/en-US/docs/Learn/Server-side/First_steps/Website_security">Website security</a>에서 볼 수 있습니다)과 같은 보안 취약점을 보완할 방법 기본적으로 제공합니다.</dd> + <dt>Scalable(확장성 있는)</dt> + <dd>Django는 컴포넌트 기반의 “<a href="https://en.wikipedia.org/wiki/Shared_nothing_architecture">shared-nothing</a>” 아키텍쳐(각각의 아키텍쳐가 독립적이어서 필요하다면 교체 및 변경할 수 있는)를 사용합니다. 각 부분이 분명하게 분리되면 어떤 레벨에서든(예를 들면 캐싱 서버, 데이터베이스 서버, 혹은 어플리케이션 서버) 하드웨어를 추가해서 발생하는 늘어난 트래픽에 대응해 크기를 변경할 수 있게 됩니다. 사용자가 가장 많은 몇몇 사이트는 요구사항에 맞춰서 Django의 크기를 성공적으로 변경했습니다. (예를들면 Instagram, Disqus 등)</dd> + <dt>Maintainable(유지보수가 쉬운)</dt> + <dd>Django 코드는 유지보수가 쉽고 재사용하기 좋게끔 하는 디자인 원칙들과 패턴들을 이용하여 작성됩니다. 특히 Don't Repeat Yourself (DRY) 원칙을 적용해서 불필요한 중복이 없고 많은 양의 코드를 줄였습니다. 또한 Django는 관련된 기능들을 재사용 가능한 "applications"로 그룹화했고, 더 낮은 레벨에서 관련된 코드들을 모듈로 만들었습니다. (<a href="/en-US/Apps/Fundamentals/Modern_web_app_architecture/MVC_architecture">Model View Controller (MVC)</a> 패턴과 유사합니다).</dd> + <dt>Portable(포터블한)</dt> + <dd>장고는 파이썬으로 작성되어 있으며, 파이썬은 많은 플랫폼에서 작동합니다. 그것은 특정한 서버 플랫폼에 얽매이지 않는다는 것을 의미하며, 리눅스, 윈도우 그리고 맥 OS X 등등 다양한 운영체제에서 작동할 수 있다는 뜻입니다. 나아가, 장고는 많은 웹 호스팅 공급자들에 의해서 지원되고 있습니다. 그들은 장고 사이트의 호스팅과 관련해서 특정한 인프라와 문서를 제공합니다. </dd> +</dl> + +<h2 id="탄생_배경은_어떻게_되나요">탄생 배경은 어떻게 되나요?</h2> + +<p>장고는 신문 웹사이트를 제작 및 관리하던 어떤 웹 팀에 의해 2003년에서 2005년 사이에 처음으로 개발이 시작되었습니다. 여러 사이트들을 만들면서 웹 팀은 많은 공통 코드와 설계 패턴을 뽑아내어 재사용하였습니다. 이 공통 코드는 일반 웹 개발 프레임워크로 발전했습니다. 그리고 2005년 7월 "장고" 프로젝트로서 오픈소스화 되었죠.. </p> + +<p>장고는 2008년 9월 첫 번째 주요 릴리즈(1.0)에서부터 2017년의 최근 버전(2.0)까지 성장하고 발전했습니다. 장고는 각각의 버전에서 기능을 추가하고 버그를 수정했습니다. 새로운 유형의 데이터베이스, 탬플릿 엔진들 그리고 캐싱에 대한 지원에서부터 일반 보기 함수와 클래스들의 추가까지요(이를 통해 여러 프로그래밍 작업을 위해 개발자들이 작성해야 할 코드를 줄여줍니다). </p> + +<div class="note"> +<p><strong>Note</strong>: 장고를 더 좋게 만들기 위해 어떤 작업이 이루어지고 있는지, 최근 버전에서 어떤 변경이 있었는지 확인하려면 장고 웹사이트의 <span style="line-height: 1.5;"><a href="https://docs.djangoproject.com/en/1.10/releases/">release notes</a> 를 살펴보세요.</span></p> +</div> + +<p>장고는 수많은 사용자와 기여자가 있는 협력적이고 번성하는 프로젝트입니다. 여전히 몇 가지 장고만의 특징이 있지만, 장고는 모든 유형의 웹사이트를 개발할 수 있는 다용도적인 웹 프레임워크로 발전했습니다.</p> + +<h2 id="Django의_인기는_어떤가요">Django의 인기는 어떤가요?</h2> + +<p>사실 서버 측 프레임워크의 인기에 대해 쉽고 확정적인 측정값은 없습니다(다만 <a href="http://hotframeworks.com/">Hot Frameworks</a> 와 같은 사이트는 각 플랫폼에 대해 GitHub 프로젝트와 StackOverflow 질문의 숫자를 세는 방법으로 인기에 대해 접근하려고 합니다). 장고가 인기없는 플랫폼의 문제를 피할 수 있을 만큼 "충분히 인기있는지"가 더 좋은 질문입니다. 장고가 계속 발전하나요? 도움이 필요할 때 받을 수 있나요? 장고를 배우면 돈을 받고 일할 기회가 생기나요?</p> + +<p>장고를 사용하는 상위 사이트의 숫자, 장고 코드베이스에 기여하는 사람들의 숫자, 그리고 급여가 지불되거나 지불되지 않거나에 상관없이 지원을 제공하는 사람들의 숫자에 근거해서, 맞습니다. 장고는 인기있는 프레임워크 입니다!</p> + +<p>장고를 사용하는 상위 사이트는 다음을 포함합니다 : Disqus, Instagram, Knight Foundation, MacArthur Foundation, Mozilla, National Geographic, Open Knowledge Foundation, Pinterest, and Open Stack (출처: <a href="https://www.djangoproject.com/">Django home page</a>).</p> + +<h2 id="Django는_독선적인가요">Django는 독선적인가요?</h2> + +<p>많은 웹 프레임웍들이 흔히 스스로를 "독선적(opinionated)"이라거나 "관용적(unopinionated)"이라고 표현합니다.</p> + +<p>독선적인 프레임웍들은 어떤 특정 작업을 다루는 "올바른 방법"에 대한 분명한 의견을 가지고 있습니다. 그것들은 대체로 특정 도메인(특정 타입의 문제를 해결하는)내에서 빠른 개발방법을 제시합니다. 어떤 작업에 대한 올바른 방법이란 보통 잘 알려져있고 문서화가 잘되어있기 때문입니다. 하지만 그것들은 주요 도메인을 벗어난 문제에 대해서는 그리 유연하지 못한 해결책을 제시할 수 있습니다. 또한 이용할수 있는 접근법이나 선택가능한 구성요소가 그리 많지 않을것입니다. </p> + +<p>반면에, 관용적인 프레임웍들은, 구성요소를 한데 붙여서 해결해야 한다거나 심지어 어떤 컴퍼넌트를 써야한다는 '올바른 방법'에 대한 제약이 거의 없다시피 합니다. 그것들은 개발자들이 특정 작업을 완수하는데에 가장 적절한 도구들을 이용하기 쉽게 만들어줍니다. 비록 당신 스스로가 그 컴퍼넌트들을 찾아야 한다는 수고는 해야하긴 하지만 말이죠.</p> + +<p>Django는 "다소 독선적" 입니다. 그럼으로써 "양쪽 세계의 최선"의 결과를 전달합니다. Django는 대부분의 웹 개발 작업을 다루는 컴퍼넌트 세트와 그 세트를 이용하는 한, 두가지의 인기있는 방법을 제공합니다. 하지만 Django의 비결합 구조 (decoupled architecture) 덕분에 당신은 꽤 많은 옵션들중에서 다른 방법을 선택하거나 원한다면 완전히 새로운 방법을 만들어 낼 수도 있습니다.</p> + +<h2 id="Django_코드는_어떻게_생겼나요">Django 코드는 어떻게 생겼나요?</h2> + +<p>전형적인 데이터 기반 웹 사이트에서 웹 어플리케이션은 웹 브라우저(또는 다른 클라이언트)로부터 HTTP 요청(Request)을 기다립니다. 요청을 받으면, 웹 어플리케이션은 URL과 <code>POST</code> 데이터 또는 <code>GET</code> 데이터의 정보에 기반하여 요구사항을 알아냅니다. 그 다음 무엇이 필요한 지에 따라, 데이터베이스로부터 정보를 읽거나 쓰고, 또는 필요한 다른 작업들을 수행할 것입니다. 그 다음 웹 어플리케이션은 웹 브라우저에 응답(Response)을 반환하는데, 주로 동적인 HTML 페이지를 생성하면서 응답합니다.</p> + +<p>Django 웹 어플리케이션은 전형적으로 아래와 같이 분류된 파일들에 대해 일련의 단계를 수행하는 코드로 구성되어 있습니다:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13931/basic-django.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<ul> + <li><strong>URLs: </strong>단일 함수를 통해 모든 URL 요청을 처리하는 것이 가능하지만, 분리된 뷰 함수를 작성하는 것이 각각의 리소스를 유지보수하기 훨씬 쉽습니다. URL mapper는 요청 URL을 기준으로 HTTP 요청을 적절한 뷰(view)로 보내주기 위해 사용됩니다. 또한 URL mapper는 URL에 나타나는 특정한 문자열이나 숫자의 패턴을 일치시켜 데이터로서 뷰 함수에 전달할 수 있습니다.</li> + <li><strong>View:</strong> 뷰는 HTTP 요청을 수신하고 HTTP 응답을 반환하는 요청 처리 함수입니다. 뷰는 Model을 통해 요청을 충족시키는데 필요한 데이터에 접근합니다. 그리고 탬플릿에게 응답의 서식 설정을 맡깁니다.</li> + <li><strong>Models:</strong> 모델은 응용프로그램의 데이터 구조를 정의하고 데이터베이스의 기록을 관리(추가, 수정, 삭제)하고 쿼리하는 방법을 제공하는 파이썬 객체입니다.</li> + <li><strong>Templates: </strong>탬플릿은 파일의 구조나 레이아웃을 정의하고(예: HTML 페이지), 실제 내용을 보여주는 데 사용되는 플레이스홀더를 가진 텍스트 파일입니다. 뷰는 HTML 탬플릿을 이용하여 동적으로 HTML 페이지를 만들고 모델에서 가져온 데이터로 채웁니다. 탬플릿으로 모든 파일의 구조를 정의할 수 있습니다.탬플릿이 꼭 HTML 타입일 필요는 없습니다!</li> +</ul> + +<div class="note"> +<p><strong>Note</strong>: 장고는 이 구조를 "모델 뷰 템플릿(Model View Template)(MVT)" 아키텍처라고 부릅니다. 이것은 더 익숙한 <a href="/en-US/docs/Web/Apps/Fundamentals/Modern_web_app_architecture/MVC_architecture">Model View Controller</a> 아키텍처와 많은 유사점을 가지고 있습니다.</p> +</div> + +<ul> +</ul> + +<p>아래 부문들은 장고 앱의 주요 부분들이 어떻게 보일지에 대한 단서를 보여줄 것입니다 (우리는 개발 환경을 설치한 이후에 세부적인 디테일에 대해 다룰겁니다).</p> + +<h3 id="요청을_알맞은_뷰로_전달_urls.py">요청을 알맞은 뷰로 전달 (urls.py)</h3> + +<p>URL mapper는 보통 <strong>urls.py</strong>라는 이름의 파일에 저장되어 있습니다. 아래 예시에서 <code>urlpatterns</code> 맵퍼는 경로들(특정 URL 패턴들)과 해당하는 뷰 함수에 대한 맵핑 목록들을 정의합니다. 만약 지정된 URL 패턴과 일치하는 HTTP 요청이 수신된다면 관련된 view 함수가 요청을 전달합니다.</p> + +<pre class="notranslate"><code>urlpatterns = [ + path('admin/', admin.site.urls), + path('book/<int:id>/', views.book_detail, name='book_detail'), + path('catalog/', include('catalog.urls')), + re_path(r'^([0-9]+)/$', views.best), +]</code></pre> + +<p><code>urlpatterns</code> 객체는 <code>path()</code>함수와 <code>re_path()</code><span> 함수를 항목으로 가지는 리스트입니다 (파이썬 리스트는 대괄호를 사용하여 구분되며, 항목은 쉼표로 분리되고 선택적으로 후행 쉼표가 있을 수 있습니다. 예시: [item1, item2, item3, ]).</span></p> + +<p><span>두 메소드의 첫 번째 인수는 일치시킬 경로(패턴)입니다. </span><code>path()</code><span> 메소드는 꺾쇠 괄호(<, >)를 사용해서 인수를 정의합니다. 이 인수는 URL의 한 부분으로, 명명된 인수로 수집되어 뷰 함수로 보내집니다. </span><code>re_path()</code><span> 함수는 정규식이라는 유연한 패턴 매칭 접근을 사용합니다. 이것에 대해서는 나중에 다루도록 하겠습니다!</span></p> + +<p>두 번째 인수는 패턴이 일치할 때 호출되는 다른 함수입니다. <code>views.book_detail</code>은 이 함수가 <code>book_detail()</code>이며 <code>views</code> 모듈 안에서 찾을 수 있다는 것을 나타냅니다 (즉, <code>views.py</code>라는 파일 안에서요).</p> + +<h3 id="요청_처리하기_views.py">요청 처리하기 (views.py)</h3> + +<p>뷰들은 웹 클라이언트로부터 HTTP 요청을 수신하고 HTTP 응답을 되돌려주는 웹 어플리케이션의 심장입니다. 그 사이에 그들은 데이터베이스에 접근하고 템플릿을 렌더링하기 위해 프레임워크읟 다른 자원들을 정리합니다.</p> + +<p>아래 예시는 이전 예시의 URL mapper가 불러올 수 있는 최소 뷰 함수 <code>index()</code>를 보여줍니다. 다른 모든 뷰 함수처럼 이 함수도 <code>HttpRequest</code> 객체를 인자(<code>request</code>)로 받고 <code>HttpResponse</code> 객체를 반환합니다. 이 예시에서는 요청에 관해서는 아무것도 하지 않고, 단순히 하드코딩된 문자열을 반환합니다. 요청에 관련해서는 이후 글에서 더 자세하게 다루겠습니다.</p> + +<pre class="brush: python notranslate">## filename: views.py (Django view functions) + +from django.http import HttpResponse + +def index(request): + # Get an HttpRequest - the request parameter + # perform operations using information from the request. + # Return HttpResponse + return HttpResponse('Hello from Django!') +</pre> + +<div class="note"> +<p><strong>Note</strong>: 파이썬에 관하여:</p> + +<ul> + <li><a href="https://docs.python.org/3/tutorial/modules.html">Python modules</a>은 우리가 코드에 쓰고 싶을지도 모르며 분리된 파일로 저장되어 있는 함수의 "라이브러리" 입니다.</li> + <li><code>from django.http import HttpResponse</code>와 같은 방법으로 <code>django.http</code> 모듈에서 <code>HttpResponse</code> 객체만 가져와서 뷰에서 사용할 수 있습니다. 모듈에서 여럿, 아니면 전체 모듈을 임포트할 수 있는 몇 가지 방법이 있습니다.</li> + <li>함수들은 위에 보여진 것과 같이<code>def</code> 키워드로 정의됩니다. 함수의 이름 뒤 괄호 안에 는 명명된 인자들이 나열되어 있습니다. 전체 줄은 콜론으로 끝납니다. 그 아랫 줄이 모두 <strong>들여쓰기 되어있다는 것</strong>에 유의하세요. 들여쓰기는 코드 행이 특정한 블록 안에 있다는 것을 나타내기 때문에 중요합니다(필수적인 들여쓰기는 파이썬의 주요 기능이며, 파이썬 코드가 읽기 쉬운 이유 중 하나이기도 합니다).</li> +</ul> +</div> + +<ul> +</ul> + +<p>뷰들은 보통 <strong>views.py</strong>.라는 파일 안에 저장되어 있습니다.</p> + +<h3 id="데이터_모델_정의하기_models.py">데이터 모델 정의하기 (models.py)</h3> + +<p>장고 웹 어플리케이션은 모델(models)이라는 파이썬 객체를 통해 데이터를 관리하고 쿼리합니다. 모델은 필드 타입과 그들의 최대 크기, 기본 값들, 선택 목록 옵션, 문서의 도움말 텍스트, 폼(form)을 위한 labe text등을 포함하여 저장된 데이터의 구조를 정의합니다. 모델의 정의는 기본 데이터베이스와 별개입니다. 본인의 프로젝트 설정의 일부로써 여러 모델 중 하나를 선택할 수 있습니다. 본인이 사용할 데이터베이스를 정했다면, 그것에 직접적으로 접근할 필요가 없습니다. 그저 모델 구조와 다른 코드들을 작성하면, 장고가 당신과 데이터베이스가 소통하는 데 필요한 모든 더러운 작업들을 처리합니다.</p> + +<p>아래 코드는 <code>Team</code> 객체를 위한 아주 간단한 장고 모델을 보여줍니다. <code>Team</code> 객체는 장고 클래스<code>models.Model</code>에서 파생되었습니다. 이 객체는 팀 이름과 팀 레벨을 캐릭터 필드로 정의하고 각각의 기록에 저장될 최대 캐릭터 숫자를 정합니다. <code>team_level</code>은 랜덤으로 값이 선정되기 때문에, 우리는 이를 choice 필드로 정의하며, choices들 간에 선택된 값이 보여지고 디폴트 값에 따른 데이터가 저장되도록 합니다. </p> + +<pre class="brush: python notranslate"># filename: models.py + +from django.db import models + +class Team(models.Model): + team_name = models.CharField(max_length=40) + + TEAM_LEVELS = ( + ('U09', 'Under 09s'), + ('U10', 'Under 10s'), + ('U11', 'Under 11s'), + ... #list other team levels + ) + team_level = models.CharField(max_length=3, choices=TEAM_LEVELS, default='U11') +</pre> + +<div class="note"> +<p><strong>참고 : 파이썬에 대해</strong></p> + +<p>파이썬은 코드를 객체로 구성하는 프로그래밍 스타일인 "객체 지향 프로그래밍"을 지원합니다. 여기에는 관련 데이터 및 해당 데이터를 조작하기위한 함수가 포함됩니다. 객체는 다른 객체로부터 상속, 확장, 파생할 수 있어 관련 객체 간의 공통 동작을 공유 할 수 있습니다. 파이썬에서는 키워드 클래스를 사용하여 객체의 "청사진"을 정의합니다. 클래스의 모델을 기반으로 객체 유형의 여러 특정 인스턴스를 만들 수 있습니다.</p> + +<p>예를 들어 여기 Model 클래스에서 파생된 Team 클래스가 있습니다. 이는 모델이며 모델의 모든 방법을 포함할 것이지만 고유한 기능도 제공 할 수 있습니다. 이 모델에서는 데이터베이스가 데이터를 저장하는데 필요한 필드를 정의하여 특정 이름을 지정합니다. 장고는 필드 이름을 포함한 이러한 정의를 사용하여 기본 데이터베이스를 만듭니다.</p> +</div> + +<h3 id="데이터_쿼리하기_views.py">데이터 쿼리하기 (views.py)</h3> + +<p>장고 모델은 데이터베이스를 간단히 탐색하기 위한 쿼리 API를 제공합니다. 이 API는 다양한 조건을 통해 수 많은 필드를 빠르게 매칭시킵니다. (예를 들어, 정확하게 일치(exact), 대소문자 구분없이(case-insensitive), 해당 숫자보다 큰(greater than) 등이 있습니다.) 그리고 복잡한 쿼리문을 지원합니다. 예를 들어, 당신은 팀의 이름이 "Fr"로 시작하거나 "al"로 끝나는 U11 레벨의 팀만을 지정할 수 있습니다.</p> + +<p>굵게 표시된 줄은 모델 쿼리 API를 사용하여 <code>team_level</code> 필드의 텍트스가 정확히 'U09'인 모든 레코드를 필터링하는 방법을 보여줍니다 (이 기준이 필드 이름의 인수로 <code>filter()</code> 함수에 전달되는 방법에 유의하십시오. 일치 유형은 <strong><code>team_level__exact</code></strong>와 같이 이중 밑줄로 구분됩니다).</p> + +<pre class="brush: python notranslate">## filename: views.py + +from django.shortcuts import render +from .models import Team + +def index(request): + <strong>list_teams = Team.objects.filter(team_level__exact="U09")</strong> + context = {'youngest_teams': list_teams} + return render(request, '/best/index.html', context) +</pre> + +<dl> +</dl> + +<p>이 함수는 <code>render()</code> 함수를 사용하여 브라우저로 다시 전송되는 <code>HttpResponse</code>를 만듭니다. 지정된 HTML 템플릿과 템플릿에 삽입할 일부 데이터( "컨텍스트"라는 변수에 제공)를 결합하여 HTML 파일을 생성합니다. 다음 섹션에서는 템플릿을 생성하기 위해 템플릿에 데이터를 삽입하는 방법을 보여줍니다.</p> + +<h3 id="데이터_렌더링_HTML_템플릿">데이터 렌더링 (HTML 템플릿)</h3> + +<p>템플릿 시스템을 사용하면 페이지가 생성될 때 채워질 데이터에 자리 표시자를 사용하여 출력 문서의 구조를 지정할 수 있습니다. 템플릿은 종종 HTML을 만드는 데 사용되지만 다른 유형의 문서를 만들 수도 있습니다. 장고는 기본 템플릿 시스템과 Jinja2라는 인기있는 파이썬 라이브러리를 모두 지원합니다 (필요한 경우 다른 시스템을 지원하도록 만들 수도 있음).</p> + +<p>아래 코드는 이전 섹션의 <code>render()</code> 함수가 호출한 HTML 템플릿의 모양을 보여줍니다. 이 템플릿은 렌더링될 때 (위의 <code>render()</code> 함수 내의 컨텍스트 변수에 포함 된) "youngest_teams"라는 목록 변수에 액세스할 수 있다는 가정하에 작성되었습니다. HTML 스켈레톤에는 먼저 youngest_teams 변수가 있는지 확인한 후 <code>for</code> 루프에서 반복하는 표현식이 있습니다. 각 반복에서 템플리트는 각 팀의 <code>team_name</code> 값을 <code><li></code> 태그의 값으로 표시합니다.</p> + +<pre class="notranslate">## filename: best/templates/best/index.html + +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>Home page</title> +</head> +<body> + {% if youngest_teams %} + <ul> + {% for team in youngest_teams %} + <li>\{\{ team.team_name \}\}</li> + {% endfor %} + </ul> + {% else %} + <p>No teams are available.</p> + {% endif %} +</body> +</html></pre> + +<h2 id="또_무엇을_할수_있나요">또 무엇을 할수 있나요?</h2> + +<p>이전 섹션에서는 거의 모든 웹 응용 프로그램에서 사용할 주요 기능인 URL 매핑, 뷰, 모델 및 템플릿을 보여줍니다. 추가로 장고가 제공하는 기능들은 다음과 같습니다.</p> + +<ul> + <li><strong>양식</strong> : HTML 양식은 서버에서 처리할 사용자 데이터를 수집하는 데 사용됩니다. 장고는 양식 작성, 유효성 검사 및 처리를 단순화합니다.</li> + <li><strong>사용자 인증 및 권한 </strong>: 장고에는 보안을 염두에 두고 구축된 강력한 사용자 인증 및 권한 시스템이 포함되어 있습니다.</li> + <li><strong>캐싱</strong> : 컨텐츠를 동적으로 작성하는 것은 정적 컨텐츠를 제공하는 것 보다 많은 연산을 필요로 하기 때문에 느립니다. 장고는 유연한 캐싱을 제공하여 렌더링된 페이지 전체 또는 일부를 저장하여 필요할 때를 제외하고 다시 렌더링하지 않도록 할 수 있습니다.</li> + <li><strong>관리 사이트 </strong>: 기본 스켈레톤을 사용하여 앱을 만들 때 장고 관리 사이트가 기본적으로 포함됩니다. 사이트 관리자가 사이트의 모든 데이터 모델을 작성, 편집 및 볼 수있는 관리 페이지를 쉽게 제공할 수 있습니다.</li> + <li><strong>데이터 직렬화 </strong>: 장고를 사용하면 데이터를 XML 또는 JSON으로 직렬화하고 제공할 수 있습니다. 이 기능은 웹 서비스 (다른 응용 프로그램이나 사이트에서 사용하기 위해 순수하게 데이터를 제공하고 자체를 표시하지 않는 웹 사이트)를 만들거나 클라이언트 쪽 코드가 모든 데이터 렝더링을 처리하는 웹 사이트를 만들 때 유용할 수 있습니다. </li> +</ul> + +<h2 id="요약하기">요약하기</h2> + +<p>축하합니다. 이제 장고 여행의 첫발을 떼셨군요! 이제 우리는 장고의 주요 이점과 역사를 조금 알게됐고 장고 응용프로그램의 주요한 부분을 대략 이해했습니다. 또한 목록, 함수 및 클래스 구문을 포함하여 파이썬 프로그래밍 언어에 대해 몇 가지 사실을 배워야합니다.</p> + +<p>위의 실제 장고 코드를 이미 보았지만 클라이언트 측 코드와 달리 실행하기 위해서는 개발 환경을 설정해야합니다. 그것이 우리의 다음 단계입니다.</p> + +<div>{{NextMenu("Learn/Server-side/Django/development_environment", "Learn/Server-side/Django")}}</div> diff --git a/files/ko/learn/server-side/django/models/index.html b/files/ko/learn/server-side/django/models/index.html new file mode 100644 index 0000000000..03204b9df7 --- /dev/null +++ b/files/ko/learn/server-side/django/models/index.html @@ -0,0 +1,454 @@ +--- +title: 'Django Tutorial Part 3: Using models' +slug: Learn/Server-side/Django/Models +translation_of: Learn/Server-side/Django/Models +--- +<div>803{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/skeleton_website", "Learn/Server-side/Django/Admin_site", "Learn/Server-side/Django")}}</div> + +<p class="summary">이 문서에서는 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 웹사이트의 모델을 어떻게 정의할지 보여줄 것입니다. 모델이라는 것이 무엇인지, 어떻게 선언하는지, 그리고 주된 필드 타입들에 대해서 설명합니다. 그리고 모델의 데이터에 접근할 수 있는 몇몇 방법에 대해서 간단히 보여줄 것입니다.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">사전 준비:</th> + <td><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a>.</td> + </tr> + <tr> + <th scope="row">목표:</th> + <td>적절한 필드를 사용하여 모델을 설계 및 생성할 줄 알기.</td> + </tr> + </tbody> +</table> + +<h2 id="개요">개요</h2> + +<p>장고 웹 어플리케이션들은 모델이라는 파이썬 객체를 통해 데이터에 접속하고 관리합니다. 모델은 저장된 데이터의 구조를 정의합니다. 그것엔 필드 타입, 그리고 데이터의 최대 크기, 기본값, 선택 리스트 옵션, 문서를 위한 도움 텍스트, 폼을 위한 라벨 텍스트 등등이 있습니다. 모델의 정의는 기초 데이터베이스에 대해 독립적입니다 — 프로젝트 설정의 일부로 여러 옵션 중 하나를 선택할 수 있습니다. 사용할 데이터베이스를 정했다면 데이터베이스에 직접적으로 말할 필요가 없습니다 — 그저 모델 구조와 기타 코드를 작성하면, 장고가 데이터베이스와 소통하는 모든 더러운 작업을 대신해줍니다.</p> + +<p>이 튜토리얼은 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary website</a> 예제에서 어떻게 모델을 정의하고 그것에 접근하는지에 대해 보여줍니다.</p> + +<h2 id="LocalLibrary_models_디자인하기">LocalLibrary models 디자인하기</h2> + +<p>모델을 코딩하기 전에, 우리가 어떤 데이터를 저장할 것인지, 그리고 다른 객체(object)들에 대한 관계를 어떻게 지정할 것인지 생각해 봅시다.</p> + +<p>우리는 책에 관한 정보들을 저장할 필요가 있고 (제목, 요약, 저자, 작성된 언어, 분류, ISBN) 여러 개의 사본을 사용할 수 있어야 합니다(전 세계적으로 고유한 ID, 가용성 상태 등). 저자에 대해서 그들의 이름 뿐만 아니라 더 많은 정보를 저장해야 할 수도 있습니다. 여러 명의 같거나 비슷한 이름의 저자가 있을 수도 있기 때문이죠. 우리는 정보를 책 제목, 저자, 언어, 그리고 분류에 따라 정렬할 수 있기를 원합니다.</p> + +<p>모델을 디자인할 때는 각각의 "객체(object: 관련된 정보의 모임)" 마다 분리된 모델을 가지는 것이 타당합니다. 이 예시에서 명백히 확인할 수 있는 객체(object)들은 책, 책 인스턴스, 저자입니다.</p> + +<p>선택을 웹사이트 자체에 하드코딩하는 것 보다는 모델을 사용해서 선택-리스트 옵션을 나타내도록(예시: 드롭 다운 목록)할 수 있습니다 — 이것은 모든 옵션들을 미리 알 수 없거나 옵션들이 변할 수 있을 때에 추천됩니다. 이 경우에 명백한 모델의 후보자로 책의 장르(예시: 공상 과학, 프랑스 시, 등등)와 언어(영어, 프랑스어, 한국어)가 있습니다.</p> + +<p>우리가 우리의 모델과 필드를 결정하고 나면, 우리는 그 관계에 대해서 생각해야 합니다. 장고는 당신이 그 관계를 다음과 같이 세 가지로 설정할 수 있게 하는데, 일대일(<code>OneToOneField</code>), 일대다(<code>ForeignKey</code>), 다대다(<code>ManyToManyField</code>) 관계가 그것입니다.</p> + +<p>그것들을 염두에 두고 아래의 UML 관계 다이어그램을 살펴봅시다. 이 다이어그램은 우리가 이 예시에서 정의할 모델들을 상자로 보여줍니다. 위에서 살펴본 바와 같이, 우리는 책(Book, 책의 일반적인 세부 사항들), 책 인스턴스(BookInstance, 시스템에서 사용 가능한 책의 특정한 물리적 복사본의 상태), 그리고 저자(Author)를 모델로 생성했습니다. 우리는 또한 장르(Genre)에 대한 모델을 만들어서 값들이 관리자 인터페이스에서 생성/선택이 가능하도록 만들었습니다. 우리는 <code>BookInstance:status</code>에 대한 모델을 생성하지 않았습니다 — 값들을(<code>LOAN_STATUS</code>) 하드코딩 했죠. 왜냐하면 그것은 변하지 않는 값들이기 때문입니다. 각각의 상자 안에서 모델 이름, 필드 이름과 타입, 그리고 또한 함수(method)와 그들의 반환 타입(return type)을 확인할 수 있습니다.</p> + +<p>이 다이어그램은 또한 다중도(multiplicities)을 포함한 모델 간의 관계를 보여줍니다. 다중도(multiplicities)는 관계 안에 존재하는 각각의 모델의 숫자(최대 그리고 최소)를 보여주는 다이어그램 위의 숫자입니다. 예를 들어, 상자 사이를 연결하는 선은 책과 장르가 연관되어 있다는 것을 보여줍니다. 장르(Genre) 모델에 가까이 있는 숫자들은 책이 하나 또는 그 이상의 장르(원하는 만큼 많이)를 가지고 있어야 함을 보여주는 반면, 선의 반대편 끝에 있는 책(Book) 모델 옆의 숫자들은 장르 모델이 0 또는 여러 개의 관련된 책 모델을 가질 수 있음을 보여줍니다 .</p> + +<p><img alt="LocalLibrary Model UML" src="https://mdn.mozillademos.org/files/15646/local_library_model_uml.png" style="height: 660px; width: 977px;"></p> + +<div class="note"> +<p><strong>주의</strong>: 다음 섹션은 모델이 어떻게 정의되고 사용되는지에 대한 초보적인 설명입니다. 읽으면서 위 다이어그램의 각각의 모델들을 어떻게 구성할 지 생각해 보십시오.</p> +</div> + +<h2 id="모델_입문서">모델 입문서</h2> + +<p>이 부분에서는 어떻게 모델을 정의하는지, 그리고 더 중요할 지도 모르는 필드와 필드의 인자에 대해서 간단한 개요를 제공합니다.</p> + +<h3 id="모델의_정의">모델의 정의</h3> + +<p>모델들은 보통 어플리케이션의 <strong>models.py</strong> 파일에서 정의됩니다. 이들은 <code>django.db.models.Model</code>의 서브 클래스로 구현되며 필드, 메소드 그리고 메타데이터를 포함할 수 있습니다. 아래의 코드 조각은 <code>MyModelName</code>라고 이름지어진 "전형적인" 모델을 보여줍니다:</p> + +<pre class="brush: python notranslate">from django.db import models + +class MyModelName(models.Model): + """A typical class defining a model, derived from the Model class.""" + + # Fields + my_field_name = models.CharField(max_length=20, help_text='Enter field documentation') + ... + + # Metadata + class Meta: + ordering = ['-my_field_name'] + + # Methods + def get_absolute_url(self): + """Returns the url to access a particular instance of MyModelName.""" + return reverse('model-detail-view', args=[str(self.id)]) + + def __str__(self): + """String for representing the MyModelName object (in Admin site etc.).""" + return self.field_name</pre> + +<p>아래 섹션에서 모델 안에 있는 각각의 요소들을 세부적으로 다뤄봅시다:</p> + +<h4 id="필드Fields">필드(Fields)</h4> + +<p>모델은 모든 타입의, 임의의 숫자의 필드를 가질 수 있습니다 — 각각의 필드는 우리의 데이터베이스 목록(table)에 저장하길 원하는 데이터 열(column)을 나타냅니다. 각각의 데이터베이스 레코드(행, row)는 각 필드 값들 중 하나로 구성되어 있습니다. 위의 예제를 살펴봅시다:</p> + +<pre class="brush: python notranslate">my_field_name = models.CharField(max_length=20, help_text='Enter field documentation')</pre> + +<p>위 예제는 <code>my_field_name</code>이라는 하나의 필드를 가지고 있고, <code>models.CharField</code> 타입입니다 — 즉, 이 필드가 영숫자(alphanumeric) 문자열을 포함한다는 뜻이죠. 필드 타입들은 특정한 클래스들을 사용하여 등록되며, HTML 양식(form)에서 값을 수신할 때 사용할 유효성 검증 기준과 함께 데이터베이스에 데이터를 저장하는데 사용되는 레코드의 타입을 결정합니다. 또한 필드 타입은 필드가 어떻게 저장되고 사용될지 지정하는 인수를 사용할 수 있습니다. 이 예제에서는 필드에 두 가지 인수를 줍니다:</p> + +<ul> + <li><code>max_length=20</code> — 이 필드 값의 최대 길이는 20자임을 알립니다.</li> + <li><code>help_text='Enter field documentation'</code> — 이 값이 HTML 양식(form)에서 사용자들에게 입력될 때 어떤 값을 입력해야 하는지 사용자들에게 알려주기 위해 보여주는 텍스트 라벨을 제공합니다.</li> +</ul> + +<p>필드 이름은 쿼리 및 탬플릿에서 이를 참조하는데 쓰입니다. 필드는 또한 인수로 지정된 라벨(<code>verbose_name</code>)을 가지고 있거나, 또는 필드 변수 이름의 첫자를 대문자로 바꾸고 밑줄을 공백으로 바꿔서 기본 라벨을 추정할 수 있습니다(예를 들어 <code>my_field_name</code> 은 My field name을 기본 라벨로 가지고 있습니다) .</p> + +<p>필드가 선언된 순서는 모델이 폼에서 렌더링 된다면(예시 : 관리자 사이트) 기본 순서에 영향을 미치지만, 이것은 재정렬될 수 있습니다.</p> + +<h5 id="일반적common_필드_인수">일반적(common) 필드 인수</h5> + +<p>아래의 일반적인 인수들은 많은/거의 대부분의 서로 다른 필드 타입들을 선언할 때 사용할 수 있습니다:</p> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/models/fields/#help-text">help_text</a>: 위에서 다뤘던 것 처럼, HTML 양식(form)에 대해 텍스트 라벨을 제공합니다 (예시 : 관리자 사이트).</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/models/fields/#verbose-name">verbose_name</a>: 필드 라벨 안에서 사용되는 인간이 읽을 수 있는 필드 이름입니다. 지정되지 않았다면, 장고가 기본 verbose_name을 필드 이름으로부터 유추합니다.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/models/fields/#default">default</a>: 필드를 위한 기본값입니다. 이것은 값 또는 호출 가능한 객체일 수 있습니다. 이때 객체는 새로운 레코드가 생성될 때 마다 호출됩니다.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/models/fields/#null">null</a>: 만약 <code>True</code>라면, 장고는 빈 <code>NULL</code> 값을 필드를 위한 데이터베이스에 저장할 것입니다(<code>CharField</code>는 대신 빈 문자열을 저장할 것입니다). 기본값은 <code>False</code>입니다.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/models/fields/#blank">blank</a>: 만약 <code>True</code>라면, 필드는 양식(form) 안에서 비워두는 것이 허락됩니다. 기본값은 <code>False</code>이며, 이것은 장고의 양식(form) 검증이 값을 입력하도록 강제한다는 뜻입니다. 이것은 종종 <code>null=True</code>와 함께 사용됩니다. blank 값을 허락할 때, 데이터베이스에서도 공백값을 적절하게 표시할 수 있어야 하기 때문입니다.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/models/fields/#choices">choices</a>: 필드를 위한 선택들의 모임입니다. 이 인수가 제공된다면, 대응하는 기본 양식(form) 위젯은 표준 텍스트 필드가 아닌 이 선택 항목을 가진 선택 상자입니다.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/models/fields/#primary-key">primary_key</a>: 만약 <code>True</code>라면, 현재 필드를 모델의 primary key로 설정합니다(primary key는 모든 다른 테이블 레코드들을 고유하게 확인하도록 지정된 특별한 데이터베이스 열입니다). primary key로 지정된 필드가 없다면 장고가 자동적으로 이 목적의 필드를 추가합니다.</li> +</ul> + +<p>다른 많은 옵션들이 있습니다 — 여기(<a href="https://docs.djangoproject.com/en/2.0/ref/models/fields/#field-options">full list of field options here</a>)에서 모든 필드 옵션 목록을 볼 수 있습니다.</p> + +<h5 id="일반적인common_필드_타입">일반적인(common) 필드 타입</h5> + +<p>아래의 목록은 일반적으로 사용되는 필드 타입들을 보여줍니다. </p> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/models/fields/#django.db.models.CharField">CharField</a>는 작거나 중간 크기의 고정된 길이의 문자열을 정의할 때 사용합니다. 저장되기 위해서는 데이터의 최대 길이(<code>max_length</code>)를 정해주어야 합니다.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/models/fields/#django.db.models.TextField">TextField</a>는 임의의 긴 문자열에 사용됩니다. 필드의 최대 길이(<code>max_length</code>)를 지정해야 할 수도 있지만, 그것은 필드가 양식(form) 안에 표시될 때만 지정하면 됩니다(데이터베이스 레벨에서 강제되지 않습니다).</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/models/fields/#django.db.models.IntegerField" title="django.db.models.IntegerField">IntegerField</a>은 정수값(모든 숫자)을 저장하는 필드입니다. 그리고 양식(form)에 입력된 값이 정수임을 검증하기도 합니다.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/models/fields/#datefield">DateField</a>와 <a href="https://docs.djangoproject.com/en/2.0/ref/models/fields/#datetimefield">DateTimeField</a>는 날짜와 날짜시간 정보를 저장, 표현하는데 사용됩니다 (각각 파이썬 <code>datetime.date</code>와 <code>datetime.datetime</code> 객체로). 이 필드들은 추가적으로 (서로 독점적인) <code>auto_now=True</code> (모델이 저장될 때 마다 필드를 현재 날짜로 설정하기 위해), <code>auto_now_add</code> (모델이 처음 생성되었을 때만 날짜를 설정하기 위해) , 그리고 <code>default</code> (사용자에 의해 변경될 수 있는 기본 날짜를 설정하기 위해) 매개 변수를 선언할 수 있습니다.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/models/fields/#emailfield">EmailField</a>는 이메일 주소를 저장하고 검증하기 위해 사용합니다.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/models/fields/#filefield">FileField</a>와 <a href="https://docs.djangoproject.com/en/2.0/ref/models/fields/#imagefield">ImageField</a>는 각각 파일과 이미지를 업로드하기 위해 사용됩니다 (<code>ImageField</code>는 단지 업로드된 파일이 이미지임을 확인하는 추가 검증을 더할 뿐입니다). 이것들은 업로드된 파일들이 어디에 어떻게 저장되는지 정의하는 매개 변수를 가집니다.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/models/fields/#autofield">AutoField</a>는 자동적으로 증가하는 <code>IntegerField</code>의 특별한 타입입니다. 이 타입의 primary key는 명시적으로 지정하지 않는 이상 모델에 자동적으로 추가됩니다.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/models/fields/#foreignkey">ForeignKey</a>는 다른 데이터베이스 모델과 일대다 관계를 지정하기 위해 사용됩니다 (예시: 차는 하나의 제조사를 갖고 있지만 제조사는 많은 차들을 만들 수 있습니다). 일대다에서 "일"쪽이 key를 포함하는 모델입니다.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/models/fields/#manytomanyfield">ManyToManyField</a>는 다대다 관계를 지정하기 위해 사용됩니다 (예시: 책은 여러 장르를 가질 수 있고, 각각의 장르에도 많은 책들이 있습니다). 우리 예제인 도서관 어플리케이션에서는 이 필드를 <code>ForeignKeys</code>와 매우 유사하게 사용할 겁니다. 하지만 그룹 사이의 관계를 보여주기 위해서는 더욱 복잡한 방식으로 사용될 수 있습니다. 이것은 레코드가 삭제됐을 때 어떤 일이 일어나는지 정의하기 위해 <code>on_delete</code> 매개변수를 가집니다 (예시: <code>models.SET_NULL</code>의 값은 단순히 <code>NULL</code>값으로 설정될 겁니다).</li> +</ul> + +<p>다른 많은 타입의 필드들이 많이 있습니다. 서로 다른 타입의 숫자를 위한 필드 (큰 정수, 작은 정수, 부동소수(float)), 불리언(booleans), URL, slug, unique id, 그리고 다른 "시간-관련된" 정보들(duration, time, 등등)들을 포함해서요. 모든 목록을 여기(<a href="https://docs.djangoproject.com/en/2.0/ref/models/fields/#field-types">full list here</a>)에서 확인할 수 있습니다.</p> + +<h4 id="메타데이터">메타데이터</h4> + +<p>아래와 같이 <code>class Meta</code>를 선언하여 모델에 대한 모델-레벨의 메타데이타를 선언할 수 있습니다.</p> + +<pre class="brush: python notranslate">class Meta: + ordering = ['-my_field_name'] +</pre> + +<p>이 메타데이터의 가장 유용한 기능들 중 하나는 모델 타입을 쿼리(query)할 때 반환되는 기본 레코드 순서를 제어하는 것입니다. 이렇게 하려면 위와 같이 필드 이름 목록의 일치 순서를 <code>ordering</code> 속성에 지정해야 합니다. 순서는 필드의 타입에 따라 달라질 것입니다(문자 필드는 알파벳 순서에 따라 정렬될 것이고, 반면 날짜 필드는 날짜순으로 정렬될 것입니다). 위와 같이, 반대로 정렬하고 싶다면 마이너스 기호(-)를 필드 이름 앞에 접두사로 붙이면 됩니다.</p> + +<p>예를 들어, 우리가 기본적으로 아래와 같이 책들을 정렬하려고 한다면:</p> + +<pre class="brush: python notranslate">ordering = ['title', '-pubdate']</pre> + +<p>책들은 A-Z까지 알파벳 순으로 정렬되고, 그 후에는 제목(title) 안에 있는 발행일 별로 가장 최근 것부터 가장 오래된 것 순으로 정렬될 것입니다.</p> + +<p>다른 일반적인(common) 속성은 <code>verbose_name</code>이며, 단일 및 복수 형식(form)의 클래스를 위한 자세한(verbose) 이름입니다:</p> + +<pre class="brush: python notranslate">verbose_name = 'BetterName'</pre> + +<p>다른 유용한 속성들은 모델을 위한 새로운 "접근 권한"을 생성 및 적용 가능하게 하며(기본 권한은 자동적으로 적용됨), 다른 필드에 기반한 순서 정렬을 허용하거나, 또는 클래스가 "추상(abstract: 레코드를 생성할 수 없고, 대신 다른 모델들을 만들기 위해 파생되는 기본 클래스)"임을 선언할 수 있습니다.</p> + +<p>여러가지 메타데이터 옵션들은 모델에 무슨 데이터베이스를 사용해야만 하는가 그리고 데이터가 어떻게 저장되는가를 제어한다 (이것들은 모델을 기존 데이터베이스에 매핑할 때만 유용하다).</p> + +<p>메타데이터 옵션의 모든 목록은 여기에서 볼 수 있습니다: <a href="https://docs.djangoproject.com/en/2.0/ref/models/options/">Model metadata options</a> (장고 문서).</p> + +<h4 id="메소드Methods">메소드(Methods)</h4> + +<p>모델은 또한 메소드를 가질 수 있습니다.</p> + +<p><strong><code><font face="Open Sans, arial, x-locale-body, sans-serif"><span style="background-color: #ffffff;">최소한, 모든 모델마다 표준 파이썬 클래스의 메소드인 </span></font>__str__()</code> 을 정의하여 각각의 object가 사람이 읽을 수 있는 문자열을 반환(return)하도록 합니다.</strong> 이 문자열은 관리자 사이트에 있는 개별적인 레코드들을 보여주는 데 사용됩니다(그리고 모델 인스턴스를 참조해야 하는 다른 모든 곳에도요). 종종 이것은 모델에서 제목(title)이나 이름 필드(name field)를 반환할 것입니다 .</p> + +<pre class="brush: python notranslate">def __str__(self): + return self.field_name</pre> + +<p>장고 모델에 포함할 다른 일반적인 메소드는 <code>get_absolute_url()</code>입니다. 웹사이트의 개별적인 모델 레코드들을 보여주기 위한 URL을 반환하는 메소드입니다(만약 이 메소드를 정의했다면 장고는 관리자 사이트 안의 모델 레코드 수정 화면에 "View on Site" 버튼을 자동적으로 추가할 것입니다). 아래에서 <code>get_absolute_url()</code>의 표준적인 사용을 볼 수 있습니다.</p> + +<pre class="brush: python notranslate">def get_absolute_url(self): + """Returns the url to access a particular instance of the model.""" + return reverse('model-detail-view', args=[str(self.id)]) +</pre> + +<div class="note"> +<p><strong> 주의</strong>: 모델의 개별적인 레코드들을 보여주기 위해서 <code>/myapplication/mymodelname/2</code> 와 같은 URL을 사용한다고 가정한다면("2"는 특정한 레코드를 위한 <code>id</code> 입니다), 응답과 id를 "모델 디테일 뷰(model detail view)"에 전달하기 위해 (레코드를 표시하기 위한 작업을 할) URL 매퍼를 만들 필요가 있습니다 . 위의 <code>reverse()</code> 함수는 알맞은 포맷의 URL을 생성하기 위해서 URL 매퍼를(위 경우에선 'model-detail-view'라고 명명됨) "반전" 시킬 수 있습니다.</p> + +<p> 물론 이것이 작동하기 위해선 URL 매핑, 뷰, 그리고 탬플릿을 작성해야 합니다!</p> +</div> + +<p>또한 원하는 메소드를 정의해서 (그들이 어떤 변수도 가지고 있지 않다면) 코드나 탬플릿에서 불러올 수 있습니다.</p> + +<h3 id="모델_관리management">모델 관리(management)</h3> + +<p>모델 클래스들을 정의한 이후엔 클래스들을 사용해서 레코드들을 생성, 업데이트, 또는 삭제할 수 있고, 모든 레코드 또는 레코드의 특정 하위 집합을 가져오기 위해 쿼리를 실행할 수 있습니다. 튜토리얼에서 뷰를 정의할 때 그 방법을 보여줄 것이지만, 아래에 간략한 요약이 있습니다.</p> + +<h4 id="레코드의_생성과_수정">레코드의 생성과 수정</h4> + +<p>레코드를 생성하려먼 모델의 인스턴스를 정의하고 <code>save()</code>를 호출할 수 있습니다.</p> + +<pre class="brush: python notranslate"># Create a new record using the model's constructor. +record = MyModelName(my_field_name="Instance #1") + +# Save the object into the database. +record.save() +</pre> + +<div class="note"> +<p><strong>Note</strong>: 만약 당신이 어떤 필드도 <code>primary_key</code>를 선언하지 않았다면, 새로운 레코드는 자동적으로 <code>id</code>라는 필드 이름을 가진 <code>primary_key</code>가 주어지게 됩니다. 위의 레코드를 저장한 후 이 <code>id</code> 필드를 쿼리할 수 있는데, 1의 값을 가질 겁니다.</p> +</div> + +<p>이 새로운 레코드 안의 필드에 점 구문(.)을 사용해서 접근하여 값을 변경할 수 있습니다. 수정된 값들을 데이터베이스에 저장하기 위해 <code>save()</code>를 호출해야 합니다.</p> + +<pre class="brush: python notranslate"># Access model field values using Python attributes. +print(record.id) # should return 1 for the first record. +print(record.my_field_name) # should print 'Instance #1' + +# Change record by modifying the fields, then calling save(). +record.my_field_name = "New Instance Name" +record.save()</pre> + +<h4 id="레코드_검색하기">레코드 검색하기</h4> + +<p>모델의 객체(<code>objects</code>) 속성(기본 클래스에서 제공됨)을 사용하여 특정 기준과 일치하는 레코드를 검색할 수 있습니다.</p> + +<div class="note"> +<p><strong> 주의:</strong> "추상(abstract)" 모델과 필드 이름을 사용하여 레코드들을 검색하는 방법을 설명하는 것은 조금 혼란스러울 수 있습니다. 아래에서는 <code>title</code>과 <code>genre</code> 필드가 있는 <code>Book</code> 모델을 참조하겠습니다. 이 때 <code>genre</code>는 또한 <code>name</code>이라는 단일 필드를 가지고 있는 모델입니다.</p> +</div> + +<p>우리는 <code>objects.all()</code>을 사용하여 모델의 모든 레코드들을 <code>QuerySet</code>으로 가져올 수 있습니다. <code>QuerySet</code>은 반복가능한(iterable) 객체이며, 이것은 반복/루프할 수 있는 많은 객체들을 포함하고 있다는 의미입니다.</p> + +<pre class="brush: python notranslate">all_books = Book.objects.all() +</pre> + +<p> 장고의 <code>filter()</code>는 반환된 <code>QuerySet</code>이 지정된 문자(<strong>text</strong>) 또는 숫자(<strong>numeric</strong>)<strong> </strong>필드를 특정한 기준에 맞추어 필터링할 수 있게 합니다. 예를 들어서, "wild"를 제목 안에 포함하는 책들을 필터링하여 그 숫자를 세어 보려면, 아래와 같이 하면 됩니다.</p> + +<pre class="brush: python notranslate">wild_books = Book.objects.filter(title__contains='wild') +number_wild_books = Book.objects.filter(title__contains='wild').count() +</pre> + +<p>기준이 될 필드와 타입은 필터 매개 변수 이름에서 정의됩니다. 다음 포맷을 이용해서요: <code>field_name__match_type</code> (주의: 위의 <code>title</code>과 <code>contains</code> 사이 밑줄은 두 개입니다). 위에서 우리는 대소문자를 구분하여 <code>title</code>을 필터링합니다. 다른 많은 유형의 일치 방법이 있습니다: <code>icontains</code>(대소문자를 구분하지 않음), <code>iexact</code>(대소문자를 구분하지 않는 정확히 일치), <code>exact</code>(대소문자를 구분하는 정확한 일치) 그리고 <code>in</code>, <code>gt</code>(보다 더 큰(greater than)), <code>startswith</code> 등등이 있습니다. 모든 일치방법 목록은 여기(<a href="https://docs.djangoproject.com/en/2.0/ref/models/querysets/#field-lookups">full list is here</a>) 있습니다.</p> + +<p>어떤 경우엔 일대다 관계를 다른 모델에 정의하는 필드를 필터링해야 할 때도 있습니다(예:<code>ForeignKey</code>). 이 경우에 추가적인 이중 밑줄을 사용하여 관련 모델 안의 필드에 "색인(index)"할 수 있습니다. 예를 들어 특정한 장르 패턴을 가진 책들을 필터링하려면, 아래와 같이 <code>genre</code> 필드를 통해서 <code>name</code>에 색인(index)해야 할 겁니다.</p> + +<pre class="brush: python notranslate"># Will match on: Fiction, Science fiction, non-fiction etc. +books_containing_genre = Book.objects.filter(genre<strong>__</strong>name<strong>__</strong>icontains='fiction') +</pre> + +<div class="note"> +<p><strong> 주의</strong>: 밑줄(__)을 사용하여 원하는 만큼 다양한 레벨의 관계(<code>ForeignKey</code>/<code>ManyToManyField</code>)를 탐색할 수 있습니다. 예를 들어서 추가적인 "cover" 관계를 사용하여 정의된 다른 타입의 <code>Book</code>은 다음과 같은 매개 변수 이름을 가질겁니다 :<code>type__cover__name__exact='hard'.</code></p> +</div> + +<p>관련된 모델의 역방향 검색, 필터 변경, 값의 더 작은 집합 반환하기 등 쿼리로 할 수 있는 일들은 더욱 많이 있습니다. 더 많은 정보를 보려면 <a href="https://docs.djangoproject.com/en/2.0/topics/db/queries/">Making queries</a> (장고 문서)를 참고하세요.</p> + +<h2 id="LocalLibrary_모델_정의하기">LocalLibrary 모델 정의하기</h2> + +<p>이 섹션에서는 도서관을 위한 모델을 정의하기 시작할 겁니다. models.py ( /locallibrary/catalog/에 있음)파일을 여세요. 페이지 상단의 표준 코드(boilerplate)는 우리들의 모델이 상속받을 모델 기본 클래스 <code>models.Model</code>을 포함하는 models 모듈을 가져옵니다.</p> + +<pre class="brush: python notranslate">from django.db import models + +# Create your models here.</pre> + +<h3 id="장르Genre_모델">장르(Genre) 모델</h3> + +<p>아래의 장르(<code>Genre</code>) 모델 코드를 복사해서 <code>models.py</code> 파일에 붙여넣기 하세요. 이 모델은 책 카테고리에 관한 정보를 저장하는데 사용됩니다 — 예를 들어 소설인지, 논픽션인지, 로맨스인지 군사 역사물인지 등등. 위에서 말했던 것 처럼, 우리는 장르를 자유 텍스트나 선택 목록으로 만들지 않고 모델을 이용해 만들었습니다. 가능한 값들이 하드코딩되기 보다는 데이터베이스를 통해 관리되도록 하기 위해서입니다.</p> + +<pre class="brush: python notranslate">class Genre(models.Model): + """Model representing a book genre.""" + name = models.CharField(max_length=200, help_text='Enter a book genre (e.g. Science Fiction)') + + def __str__(self): + """String for representing the Model object.""" + return self.name</pre> + +<p><code><font face="Arial, x-locale-body, sans-serif"><span style="background-color: #ffffff;">모델은 하나의 </span></font>CharField</code> 필드(<code>name</code>)을 가지고 있습니다. 이것은 장르의 이름을 나타냅니다 . 이것은 200자로 제한되어 있고 <code>help_text</code>를 갖고 있습니다. 모델의 마지막에서 우리는 <code>__str__()</code> 메소드를 선언합니다. 이 메소드는 특정한 레코드에 의해 정의된 장르의 이름을 단지 반환합니다. 아무런 자세한 이름(verbose name)도 정의되지 않았기 때문에, 필드는 폼(form)에서 <code>Name</code>으로 호출(call)될 겁니다.</p> + +<h3 id="책Book_모델">책(Book) 모델</h3> + +<p>아래의 책(Book) 모델을 복사해서 파일의 아래에 붙여넣기 하세요. 책 모델은 일반적으로 사용 가능한 책에 대한 모든 정보들을 보여주지만, 대여 가능한 특정한 물리적 "인스턴스(instance)"나 "복사본(copy)"은 보여주지 않습니다. 모델은 <code>CharField</code>를 사용하여 책의 <code>title</code>과 <code>isbn</code>을 나타냅니다(<code>isbn</code>이 이름을 지정하지 않은 첫 번째 매개변수를 사용하여 라벨을 "ISBN"으로 지정한 것에 주목하세요. 만약 그러지 않았다면 기본 라벨은 "Isbn"이었을 것입니다). 텍스트가 상당히 길 것이기 때문에 <code>summery</code>에는 <code>TextField</code>를 사용합니다.</p> + +<pre class="brush: python notranslate">from django.urls import reverse # Used to generate URLs by reversing the URL patterns + +class Book(models.Model): + """Model representing a book (but not a specific copy of a book).""" + title = models.CharField(max_length=200) + author = models.ForeignKey('Author', on_delete=models.SET_NULL, null=True) + + # Foreign Key used because book can only have one author, but authors can have multiple books + # Author as a string rather than object because it hasn't been declared yet in the file. + summary = models.TextField(max_length=1000, help_text='Enter a brief description of the book') + isbn = models.CharField('ISBN', max_length=13, help_text='13 Character <a href="https://www.isbn-international.org/content/what-isbn">ISBN number</a>') + + # ManyToManyField used because genre can contain many books. Books can cover many genres. + # Genre class has already been defined so we can specify the object above. + genre = models.ManyToManyField(Genre, help_text='Select a genre for this book') + + def __str__(self): + """String for representing the Model object.""" + return self.title + + def get_absolute_url(self): + """Returns the url to access a detail record for this book.""" + return reverse('book-detail', args=[str(self.id)]) +</pre> + +<p>장르는 책이 여러 개의 장르를 가지고, 장르도 여러 개의 책을 가질 수 있는 다대다 필드(<code>ManyToManyField</code>)입니다. 저자는 <code>ForeignKey</code> 로 선언됩니다. 따라서 각각의 책은 하나의 저자만 가질 수 있지만, 저자는 여러 개의 책들을 가질 수 있습니다(실제로는 책이 여러 명의 작가를 가질 수 있지만, 이 구현에서는 아닙니다!).</p> + +<p> 두 필드 타입들 안에서 관련된 모델 클래스는 모델 클래스나 관련된 모델의 이름을 포함하는 문자열을 사용하여 이름없는 첫 번째 매개 변수로 선언됩니다. 연관된 클래스가 참조되기 전에 파일 안에서 아직 정의되지 않았다면 모델의 이름을 문자열로 사용해야 합니다! 저자 ( <code>author</code> )필드에서 관심을 가져야 할 다른 변수는 <code>null=True</code>와 <code>on_delete=models.SET_NULL</code>입니다. <code>null=True</code>는 어떤 저자도 선택되지 않았다면 데이터베이스에 <code>Null</code> 값을 저장하도록 하고, <code>on_delete=models.SET_NULL</code>은 관련된 저자(author) 레코드가 삭제되었을 때 저자(author)의 값을 <code>Null</code>로 설정할 겁니다.</p> + +<p>모델은 또한 <code>Book</code> 레코드를 나타내는 책의 <code>title</code> 필드를 사용하여 <code>__str__()</code> 를 정의합니다. 마지막 메소드 <code>get_absolute_url()</code> 은 이 모델의 세부 레코드에 접근하는 데에 사용될 수 있는 URL을 반환합니다 (이것이 작동하도록 하기 위해선 <code>book-detail</code>이라는 이름의 URL 매핑을 정의하고, 관련 뷰와 탬플릿을 정의해야 합니다).</p> + +<h3 id="책_인스턴스BookInstance_모델">책 인스턴스(BookInstance) 모델</h3> + +<p> 다음으로, <code>BookInstance</code> 모델(아래에 있는)을 다른 모델들 아래에 복사하세요. <code>BookInstance</code> 은 누군가 빌릴지도 모를 특정한 책의 복사본을 나타냅니다. 그리고 복사본이 사용 가능한지 여부, 언제 되돌려받을 수 있을지, "출판사(imprint)" 또는 버전 세부 사항, 그리고 도서관 안에 있는 책의 고유 id에 대한 정보를 포함합니다.</p> + +<p> 이제 몇몇 필드와 메소드는 친숙할 겁니다. BookInstance 모델은 아래를 사용합니다</p> + +<ul> + <li><code>ForeignKey</code> : 연관된 <code>Book</code> 을 식별하기 위해(각각의 책은 많은 복사본을 가질 수 있지만, 복사본은 하나의 <code>Book</code>만을 가질 수 있음).</li> + <li><code>CharField</code> : 책의 출판사(imprint)(특정한 발간일)을 나타내기 위해.</li> +</ul> + +<pre class="brush: python notranslate">import uuid # Required for unique book instances + +class BookInstance(models.Model): + """Model representing a specific copy of a book (i.e. that can be borrowed from the library).""" + id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text='Unique ID for this particular book across whole library') + book = models.ForeignKey('Book', on_delete=models.SET_NULL, null=True) + imprint = models.CharField(max_length=200) + due_back = models.DateField(null=True, blank=True) + + LOAN_STATUS = ( + ('m', 'Maintenance'), + ('o', 'On loan'), + ('a', 'Available'), + ('r', 'Reserved'), + ) + + status = models.CharField( + max_length=1, + choices=LOAN_STATUS, + blank=True, + default='m', + help_text='Book availability', + ) + + class Meta: + ordering = ['due_back'] + + def __str__(self): + """String for representing the Model object.""" + return f'{self.id} ({self.book.title})'</pre> + +<p>추가적으로 몇 가지 새로운 타입의 필드를 선언합니다:</p> + +<ul> + <li> <code>UUIDField</code> 는 <code>id</code> 필드가 이 모델의 <code>primary_key</code> 로 설정되는 데 사용됩니다. 이 타입의 필드는 각 인스턴스에 전역적으로 고유한 값을 할당합니다 (도서관에서 찾을 수 있는 모든 책 마다 하나씩).</li> + <li><code>DateField</code> 는 <code>due_back</code> (만기일) 날짜에 사용됩니다 (책이 빌려지거나 유지 보수된 이후 사용할 수 있을 것으로 예상되는 날짜). 이 값은 <code>blank</code> 나 <code>null</code> 이 될 수 있습니다(책을 사용하 할 수 있는 경우 필요). 메타데이터 모델 (<code>Class Meta</code>) 은 레코드들이 쿼리에서 반환되었을 때 레코드들을 정렬하기 위해서 이 필드를 사용합니다 .</li> + <li><code>status</code> 는 선택/선택 목록(choice/selection list)을 정의하는 <code>CharField</code> 입니다. 보시다시피, 우리는 열쇠-값(key-value) 쌍의 튜플(tuple)을 포함하는 튜플을 정의해서 choices 인자에 전달합니다. 열쇠/값(key/value) 쌍에서 값(value)은 사용자가 선택할 수 있는 표시값인 반면, 열쇠(key)는 그 옵션이 선택되었을 때 실제로 저장되는 값입니다. 또한 책이 선반에 저장되기 전에는 사용할 수 없으므로 기본값인 'm'(유지 관리, maintenanace)을 설정했습니다.</li> +</ul> + +<p> 모델 <code> __str__()</code> 은 그것의 고유한 id 그리고 연관된 <code>Book</code>의 제목을 조합하여 <code>BookInstance</code> 객체를 나타냅니다.</p> + +<div class="note"> +<p><strong>주의</strong>: 조금의 파이썬:</p> + +<ul> + <li>파이썬 3.6에서부터 문자열 보간 구문을 쓸 수 있습니다 (f-strings로 잘 알려진): <code>f'{self.id} ({self.book.title})'</code>.</li> + <li>이 튜토리얼의 옛 버전에서는 <a href="https://www.python.org/dev/peps/pep-3101/">formatted string</a> 구문을 사용했고, 이것 또한 파이썬 안에 문자열을 형식화(formatting)하는 유효한 방법입니다 (예 : <code>'{0} ({1})'.format(self.id,self.book.title)</code>).</li> +</ul> +</div> + +<h3 id="저자Author_모델">저자(Author) 모델</h3> + +<p> <strong>models.py </strong>안에 작성된 코드 아래에 <code>Author</code> 모델을 복사 붙여넣기 하세요 (아래에 있습니다).</p> + +<p> 이제 모든 필드/메소드들이 익숙할 겁니다. 모델은 저자를 이름(first name), 성(last name), 생일, 그리고 (선택적으로) 사망일을 가진다고 정의합니다. 기본적으로 <code>__str__()</code> 는 name을 첫째로 성(last name), 그 다음 이름(first name)이 오는 순서로 반환합니다. <code>get_absolute_url()</code> 메소드는 개별 저자를 나타내기 위한 URL을 가져오기 위해 <code>author-detail</code> URL 매핑을 반대로 합니다.</p> + +<pre class="brush: python notranslate">class Author(models.Model): + """Model representing an author.""" + first_name = models.CharField(max_length=100) + last_name = models.CharField(max_length=100) + date_of_birth = models.DateField(null=True, blank=True) + date_of_death = models.DateField('Died', null=True, blank=True) + + class Meta: + ordering = ['last_name', 'first_name'] + + def get_absolute_url(self): + """Returns the url to access a particular author instance.""" + return reverse('author-detail', args=[str(self.id)]) + + def __str__(self): + """String for representing the Model object.""" + return f'{self.last_name}, {self.first_name}' + +</pre> + +<h2 id="데이터베이스_마이그레이션_재실행하기"> 데이터베이스 마이그레이션 재실행하기</h2> + +<p> 모든 모델이 생성되었습니다. 이제 데이터베이스 migration을 재실행하여 모델들을 데이터베이스에 추가하세요.</p> + +<pre class="notranslate"><code>python3 manage.py makemigrations +python3 manage.py migrate</code></pre> + +<h2 id="도전과제_-_언어Language_모델">도전과제 - 언어(Language) 모델</h2> + +<p> 지역 후원자가 다른 언어로 저술된 새로운 책들을 후원한다고 생각해 보세요(아마도, 프랑스어?). 도전과제는 이것이 도서관 웹사이트에서 이것을 가장 잘 나타낼 수 있는 방법을 찾아내고, 모델에 추가하는 것입니다.</p> + +<p>고려해야 할 사항들:</p> + +<ul> + <li>"언어"(language)는 <code>Book</code>, <code>BookInstance</code>, 아니면 어떤 다른 객체와 연관되어야 할까요?</li> + <li> 서로 다른 언어들은 모델로 나타내어야 할까요? 아니면 자유 텍스트 필드?그것도 아니라면 하드-코딩된 선택 리스트로 나타내어야 할까요?</li> +</ul> + +<p>결정을 내린 후에, 필드를 추가하세요. 우리가 선택한 것은 여기(<a href="https://github.com/mdn/django-locallibrary-tutorial/blob/master/catalog/models.py">here</a>) 깃허브에서 볼 수 있습니다.</p> + +<ul> +</ul> + +<ul> +</ul> + +<h2 id="요약">요약</h2> + +<p> 이 글에서 우리는 모델이 어떻게 정의되는지 배웠고, 이 정보를 지역 도서관 웹사이트를 위한 적절한 모델을 설계하고 구현하기 위해 사용했습니다.</p> + +<p>At this point we'll divert briefly from creating the site, and check out the <em>Django Administration site</em>. This site will allow us to add some data to the library, which we can then display using our (yet to be created) views and templates.</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/intro/tutorial02/">Writing your first Django app, part 2</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/db/queries/">Making queries</a> (Django Docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/models/querysets/">QuerySet API Reference</a> (Django Docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/skeleton_website", "Learn/Server-side/Django/Admin_site", "Learn/Server-side/Django")}}</p> + + + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> diff --git a/files/ko/learn/server-side/django/sessions/index.html b/files/ko/learn/server-side/django/sessions/index.html new file mode 100644 index 0000000000..59634c6c36 --- /dev/null +++ b/files/ko/learn/server-side/django/sessions/index.html @@ -0,0 +1,190 @@ +--- +title: 'Django Tutorial Part 7: Sessions framework' +slug: Learn/Server-side/Django/Sessions +translation_of: Learn/Server-side/Django/Sessions +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Generic_views", "Learn/Server-side/Django/authentication_and_sessions", "Learn/Server-side/Django")}}</div> + +<p class="summary">이 튜토리얼에서는 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> website을 확장시킬 것입니다. 방문 수를 셀 수 있는 session-based 기능을 더한 홈페이지입니다. 이것은 상대적으로 간단한 예제인데, 이는 당신의 홈페이지에서 익명의 유저들에게 지속적으로 서비스를 제공하는 session framework의 사용법입니다.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td>Complete all previous tutorial topics, including <a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>To understand how sessions are used.</td> + </tr> + </tbody> +</table> + +<h2 id="개요">개요</h2> + +<p>이전 튜토리얼에서 우리가 만든 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> website는 카탈로그에서 유저가 책과 저자를 검색할 수 있도록 합니다. 컨텐츠가 Database로부터 다이나믹하게 생성되기 때문에, 모든 유저는 사용시에 필수적으로 같은 페이지와 정보 타입에 필수적으로 접근할 것입니다.</p> + +<p>실제 도서관에서는 각각의 유저들에게 그들의 이전 사이트 사용과 선호 등에 기반한 커스텀된 경험을 제공하고 싶을지도 모릅니다 . 예를 들어, 유저가 이미 알고 있는 경고 메세지들을 숨길 수도 있습니다. 그 유저들이 다음에 사이트를 방문하거나 그들의 선호사항(e.g. 그들이 각 페이지에서 보여지길 원하는 검색의 결과 수)에 대해서 저장하는 것을 말합니다. </p> + +<p>session framework는 당신이 이 행동의 분류하도록 하며, 각 사이트 방문자에 기반한 임의의 데이터를 검색하거나 저장하도록 합니다. </p> + +<h2 id="세션이란">세션이란?</h2> + +<p>웹 브라우저와 서버가 HTTP 프로토콜을 통해서 하는 모든 커뮤니케이션은 무상태(stateless)라고 합니다. 프로토콜이 무상태라는 뜻은 클라이언트와 서버 사이의 메시지가 완벽하게 각각 독립적이라는 뜻입니다. — 여기엔 이전 메시지에 근거한 행동이나 순서(sequence)라는 것이 존재하지 않습니다. 결국, 만약 사이트가 클라이언트와 계속적인 관계를 유지하는 것을 당신이 원한다면, 당신이 직접 그 작업을 해야합니다.</p> + +<p>세션이라는 것은 Django 그리고 대부분의 인터넷에서 사용되는 메카니즘으로, 사이트와 특정 브라우저 사이의 "state"를 유지시키는 것입니다. 세션은 당신이 매 브라우저마다 임의의 데이터를 저장하게 하고, 이 데이터가 브라우저에 접속할 때 마다 사이트에서 활용될 수 있도록 합니다. 세션에 연결된 각각의 데이터 아이템들은 "key"에 의해 인용되고, 이는 또다시 데이터를 찾거나 저장하는 데에 이용됩니다.</p> + +<p>장고는 특정 <em>session id</em> 를 포함하는 쿠키를 사용해서 각각의 브라우저와 사이트가 연결된 세션을 알아냅니다. 실질적인 세션의 <em>data</em> 사이트의 Database에 기본 설정값으로 저장됩니다 (이는 쿠키안에 데이터를 저장하는 것보다 더 보안에 유리하고, 쿠키는 악의적인 사용자에게 취약합니다). 당신은 Django를 다른 장소 (캐시, 파일, "보안이 된" 쿠키)에 저장하도록 설정할 수 있지만, 그러나 기본 위치가 상대적으로 더 안전합니다.</p> + +<h2 id="세션_사용설정하기">세션 사용설정하기</h2> + +<p>세션설정은 처음에 skeleton website를 생성했을 때 (in tutorial 2) 자동으로 사용할 수 있도록 설정되었습니다. </p> + +<p>세션사용설정은 프로젝트 settings.py에서 아래와 같이 <code>INSTALLED_APPS</code> 와 <code>MIDDLEWARE</code> 부분에 있습니다.</p> + +<pre class="brush: python">INSTALLED_APPS = [ + ... +<strong> 'django.contrib.sessions',</strong> + .... + +MIDDLEWARE = [ + ... +<strong> 'django.contrib.sessions.middleware.SessionMiddleware',</strong> + ....</pre> + +<h2 id="세션_사용하기">세션 사용하기</h2> + +<p><code>session</code> 속성은 <code>request</code> parameter 에서 파생된 view 안에 있습니다. ( view 로 전달되는 H<code>ttpRequest </code> 의 첫번째 함수 ). 이 세션의 속성은 현재의 사용자(정확히는 브라우저) 의 site 에 대한 connection 을 의미합니다. 브라우저의 cookie 안에 정의 되어 있습니다.</p> + +<p>이 <code>session</code> 속성은 사전 같은 객체인데 여러번 읽고 쓰고 심지어 수정까지 가능합니다.여러분은 다양한 dictionary 연산 - 즉, 모든 데이타 삭제, Key 가 존재하는지 데이타를 통한 looping 기타등등. 무엇보다 표준적인 "dictionary" API 를 통해 값을 가져오거나 저장 할 수 있습니다.</p> + +<p>아래 코드는 key 값이 "my_car" 인 데이타를 어떻게 읽고 쓰고 삭제하는지 보여줍니다. 물론 그 key 값은 현재의 session (브자우져와 싸이트의 연결정보) 과 연관되어진 key 입니다.</p> + +<div class="note"> +<p><strong>Note</strong>: 장고가 대단한점 한가지는 여러분이 이런 session 의 매카니즘에 생각하지 않게끔 한다는 점입니다. 만일 view 안에 있는 아래의 code 를 사용하면 <strong>오직 현재의 브라우저만</strong>이 현재의 request 에 관한 <code>my_car</code> 정보를 알 수 있다는 겁니다. </p> +</div> + +<pre class="brush: python"># Get a session value by its key (e.g. 'my_car'), raising a KeyError if the key is not present +my_car = request.session['my_car'] + +# Get a session value, setting a default if it is not present ('mini') +my_car = request.session.get('my_car', 'mini') + +# Set a session value +request.session['my_car'] = 'mini' + +# Delete a session value +del request.session['my_car'] +</pre> + +<p>이 API 는 또한 여러가지 다른 방법을 제공하는데 그들은 대부분 관련된 session cookie 를 다루는데 사용되는 것들 입니다. 예를 들어, 그 cookies 가 사용자의 브라우져에서 지원이 되는지 태스트하거나, 만기일을 알아본다던지, 만기된 session 을 삭제 한다던지. 좀더 알아보고 싶다면 <a href="https://docs.djangoproject.com/en/2.0/topics/http/sessions/">How to use sessions</a> (Django docs) 에서 그 API 를 배울수 있습니다.</p> + +<h2 id="세션_데이터_저장하기">세션 데이터 저장하기</h2> + +<p>기본적으로 장고는 세션 데이타를 세션 database 에 저장합니다. 그리고 그 session cookie 를 필요할때 client 에게로 내려보내는 거지요. 즉, session 정보가 변경되었거나 삭제 되었을떄. 앞장에서 기술한것처럼, 만일 session key 값을 이용해서 어떤 정보가 변경이 되었다면 우리는 이에 대해 염려 할 필요가 없습니다.예를 들자면 : </p> + +<pre class="brush: python"># This is detected as an update to the session, so session data is saved. +request.session['my_car'] = 'mini'</pre> + +<p>만일 당신이 session data 안에 있는 어떤 정보를 수정 했다면 장고는 여러분이 수정한걸 알아채지 못합니다. (예를 들어, 만일 "my_car" 안의 "wheels" 정보를 수정 했다면 ). 이경우 명시적으로 그 session 이 수정 되었다고 아래의 코드처럼 표현해 주어야 합니다.</p> + +<pre class="brush: python"># Session object not directly modified, only data within the session. Session changes not saved! +request.session['my_car']['wheels'] = 'alloy' + +# Set session as modified to force data updates/cookie to be saved. +<code>request.session.modified = True</code> +</pre> + +<div class="note"> +<p><strong>Note</strong>: You can change the behavior so the site will update the database/send cookie on every request by adding <code>SESSION_SAVE_EVERY_REQUEST = True</code> into your project settings (<strong>locallibrary/locallibrary/settings.py</strong>).</p> +</div> + +<h2 id="간단한_예제_—_방문자수_받아오기">간단한 예제 — 방문자수 받아오기</h2> + +<p>간단한 실제 예제로서, 우리는 현재 유저가 LocalLibrary 홈페이지에 몇 번이나 방문했는지 알려주도록 업데이트할 것입니다. <strong>/locallibrary/catalog/views.py</strong> 을 열고 밑에 굵게 표시된 부분을 추가해서 바꿔봅시다. </p> + +<pre class="brush: python">def index(request): + ... + + num_authors = Author.objects.count() # The 'all()' is implied by default. + +<strong> # Number of visits to this view, as counted in the session variable. + num_visits = request.session.get('num_visits', 0) + request.session['num_visits'] = num_visits + 1</strong> + +<strong> context = { + 'num_books': num_books, + 'num_instances': num_instances, + 'num_instances_available': num_instances_available, + 'num_authors': num_authors, + 'num_visits': num_visits, + }</strong> + + # Render the HTML template index.html with the data in the context variable. + return render(request, 'index.html', context=context)</pre> + +<p>먼저 <code>'num_visits'</code> 세션 키 값을 가져오도록 합니다, 그리고 만약 전에 방문한적이 없다면 0이 되도록 합니다. 매번 요청받을 때 마다, 값을 증가시키고 세션에 값을 저장합니다 (유저의 다음 방문을 위해서요). context 변수를 통해 template에 <code>num_visits</code> 변수가 전달됩니다.</p> + +<div class="note"> +<p><strong>Note</strong>: 이 지점에서 우리는 브라우저가 쿠키를 지원하는 지 그렇지 않은지 테스트할 수도 있습니다(예제로서 <a href="https://docs.djangoproject.com/en/2.0/topics/http/sessions/">How to use sessions</a> 를 보도록하십시오). 또한 쿠키를 지원하는 지와는 별개로 UI를 디자인할 것입니다. </p> +</div> + +<p>메인 HTML template(<strong>/locallibrary/catalog/templates/index.html</strong>) "Dynamic content" 섹션 밑 부분에 context 변수가 보일 수 있도록 밑에 보이는 굵은 선으로 표시된 코드를 추가해주세요:</p> + +<pre class="brush: html"><h2>Dynamic content</h2> + +<p>The library has the following record counts:</p> +<ul> + <li><strong>Books:</strong> \{{ num_books }}</li> + <li><strong>Copies:</strong> \{{ num_instances }}</li> + <li><strong>Copies available:</strong> \{{ num_instances_available }}</li> + <li><strong>Authors:</strong> \{{ num_authors }}</li> +</ul> + +<strong><p>You have visited this page \{{ num_visits }}{% if num_visits == 1 %} time{% else %} times{% endif %}.</p></strong> +</pre> + +<p>바뀐 것을 저장해주시고 테스트 서버를 재시작해주세요. 페이지를 새로고침할 때 마다, 숫자가 업데이트되는 것을 보실 수 있을겁니다.</p> + +<ul> +</ul> + +<h2 id="요약">요약</h2> + +<p>익명 유저와의 상호작용을 증대하기 위해 세션을 이용하는 것이 얼마나 쉬운일인지 알 수 있었습니다. </p> + +<p>다음 장에서는, 인증과 권한부여(허가)에 관한 프레임워크를 설명할 것입니다. 그리고 유저 계정을 어떻게 지원할 수 있는지를 보도록 하죠. </p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/http/sessions/">How to use sessions</a> (Django docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Generic_views", "Learn/Server-side/Django/Authentication", "Learn/Server-side/Django")}}</p> + +<p> </p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> + +<p> </p> diff --git a/files/ko/learn/server-side/django/skeleton_website/index.html b/files/ko/learn/server-side/django/skeleton_website/index.html new file mode 100644 index 0000000000..b514a6ee61 --- /dev/null +++ b/files/ko/learn/server-side/django/skeleton_website/index.html @@ -0,0 +1,398 @@ +--- +title: '장고 튜토리얼 강좌 2 : 뼈대 사이트 만들기' +slug: Learn/Server-side/Django/skeleton_website +tags: + - 가이드 + - 입문서 + - 장고 + - 초심자 + - 튜토리얼 +translation_of: Learn/Server-side/Django/skeleton_website +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Tutorial_local_library_website", "Learn/Server-side/Django/Models", "Learn/Server-side/Django")}}</div> + +<p class="summary"><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">장고 튜트리얼</a>의 두 번째 기사에서는 웹 사이트 프로젝트의 기본 뼈대(skeleton)를 만들고, 사이트의 특성에 맞춰 설정, 경로, 모델, 뷰 및 템플릿을 다루는 방법을 보여줍니다. </p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">사전 준비:</th> + <td><a href="/en-US/docs/Learn/Server-side/Django/development_environment">장고 개발 환경을 설치하세요. (Set up a Django development environment.)</a> <a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">장고 튜트리얼</a>을 복습하세요.</td> + </tr> + <tr> + <th scope="row">목표:</th> + <td>당신만의 새로운 웹사이트 프로젝트를 시작하기 위해 장고의 도구들을 사용할 수 있는 능력 기르기.</td> + </tr> + </tbody> +</table> + +<h2 id="개요">개요</h2> + +<p>이 글은 웹사이트의 "뼈대"를 생성하는 법을 보여줍니다. 그리고 이 사이트는 사이트에 특화된 설정, 경로, 모델, 뷰, 템플릿 등을 작성할 수 있습니다. (이후 글에서 이것들에 관해 다루겠습니다)</p> + +<p>과정은 직관적입니다:</p> + +<ol> + <li><span style="line-height: 1.5;">프로젝트 폴더, 기본적인 파일 템플릿과 프로젝트 관리 스크립트(</span><strong style="line-height: 1.5;">manage.py</strong><span style="line-height: 1.5;">)를 만들기 위해서 </span><code style="font-style: normal; font-weight: normal; line-height: 1.5;">django-admin</code><span style="line-height: 1.5;">을 사용합니다.</span></li> + <li><span style="line-height: 1.5;">하나 또는 그 이상의 애플리케이션을 만들기 위해서 </span><strong style="line-height: 1.5;">manage.py</strong><span style="line-height: 1.5;">를 사용합니다.</span> + <div class="note"> + <p><strong>Note</strong>: 하나의 웹사이트는 하나 또는 그 이상의 섹션으로 구성될 수 있습니다. (예를 들어 main site, blog, wiki, downloads area 등). 장고는 필요할 때에 다른 프로젝트에서 재사용이 가능할 수 있게 , 이 요소들을 분리된 어플리케이션으로 개발하는 것을 추천합니다.</p> + </div> + </li> + <li><span style="line-height: 1.5;">프로젝트에 포함시키기 위해 새 어플리케이션들을 등록(register)합니다. </span></li> + <li><span style="line-height: 1.5;">각 어플리케이션에 대해 url/mapper를 연결(hook up)합니다.</span></li> +</ol> + +<p><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Local Library website</a> 용으로 웹사이트 폴더와 프로젝트 폴더는 locallibrary라고 이름지어질 것입니다. 그리고 catalog라는 단 하나의 어플리케이션을 가질 겁니다. 그러므로 최상위 폴더 구조는 아래와 같습니다:</p> + +<pre class="brush: bash notranslate"><em>locallibrary/ # Website folder</em> + <strong>manage.py </strong># Script to run Django tools for this project (created using django-admin) + <em>locallibrary/ # Website/project folder </em>(created using django-admin) + <em>catalog/ # Application folder </em>(created using manage.py) +</pre> + +<p><span style="line-height: 1.5;">다음 섹션에서는 프로세스 단계를 자세히 설명하고 변화를 테스트할 수 있는 방법을 설명합니다. 글의 마지막에서 당신이 이 단계에서 수행해야 하는 몇 가지 다른 사이트 전체 설정에 관해 논의합니다.</span></p> + +<h2 id="프로젝트_만들기">프로젝트 만들기</h2> + +<p>먼저 명령 프롬프트 또는 터미널을 열어서, 당신이 <a href="/en-US/docs/Learn/Server-side/Django/development_environment#Using_a_virtual_environment">virtual environment</a> 안에 있는지 확인하고, 어디에 당신의 <span style="line-height: 1.5;">장고 앱을 (당신의 '문서'와 같이 찾기 쉬운 어딘가로 하세요) 저장하기 원하는지 탐색합니다. 그리고 당신의 새로운 웹사이트 폴더를 만드세요 (이 예제에서는: </span><em>locallibrary</em>). 그리고 cd 명령어를 사용하여 해당 폴더로 들어가세요:</p> + +<pre class="brush: bash notranslate">mkdir locallibrary +cd locallibrary +</pre> + +<p>다음과 같이 <code>django-admin startproject</code> 명령을 사용하여 새로운 프로젝트를 만들고, 그 폴더 안으로 들어가세요. (변역자주: 실제 해보니 위의 문장은 하지 말아야 합니다. 하나의 parent folder 가 더 만들어집니다. 즉, locallibrary-locallibrary-locallibrary )</p> + +<pre class="brush: bash notranslate">django-admin startproject locallibrary +cd locallibrary</pre> + +<p><code>django-admin</code> 도구가 아래와 같이 폴더/파일 구조를 생성합니다. (번역자주: 윈도우 환경에서는 <code>tree /f locallibrary_path</code> 명령으로 구조를 확인할 수 있습니다.)</p> + +<pre class="brush: bash notranslate"><em>locallibrary/</em> + manage.py + <em>locallibrary/</em> + __init__.py + settings.py + urls.py + wsgi.py</pre> + +<p>locallibrary 프로젝트의 하위 폴더는 웹사이트의 시작점입니다:</p> + +<ul> + <li><strong>__init__.py</strong>는 빈 파일입니다. 이 파일은 파이썬에게 이 디렉토리를 하나의 파이썬 패키지로 다루도록 지시합니다.</li> + <li><strong>settings.py</strong>는 웹사이트의 모든 설정을 포함하고 있습니다. 이 파일에는 우리가 만드는 모든 어플리케이션, 정적 파일의 위치, 데이터베이스 세부 설정 등을 등록합니다. </li> + <li><strong>urls.py</strong>는 사이트의 URL과 뷰의 연결을 지정해줍니다. 여기에는 모든 URL 매핑 코드가 포함될 수 있지만, 특정한 어플리케이션에 매핑의 일부를 할당해주는 것이 일반적입니다<span style="line-height: 1.5;">. </span></li> + <li><strong style="line-height: 1.5;">wsgi.py</strong><span style="line-height: 1.5;">는 당신의 장고 어플리케이션이 웹서버와 연결 및 소통하는 것을 돕습니다. 당신은 이것을 표준 형식(boilerplate)으로 다뤄도 무방합니다.</span></li> + <li><strong>asgi.py</strong> is a standard for Python asynchronous web apps and servers to communicate with each other and positioned as an asynchronous successor to WSGI, where WSGI provided a standard for synchronous Python apps, ASGI provides one for both asynchronous and synchronous apps, with a WSGI backward-compatibility implementation and multiple servers and application frameworks.</li> +</ul> + +<p><strong>manage.py</strong>는 어플리케이션을 생성하고, 데이터베이스와 작업하고, 그리고 개발 웹 서버를 시작하기 위해 사용됩니다. </p> + +<h2 id="catalog_application_만들기">catalog application 만들기</h2> + +<p>다음으로, locallibrary 프로젝트 안에 생성될 catalog 어플리케이션을 만들기 위해 아래 명령어를 실행하세요. 이것은 프로젝트의 <strong>manage.py</strong>와 같은 폴더 안에서 실행되어야 합니다.</p> + +<pre class="brush: bash notranslate">python3 manage.py startapp catalog</pre> + +<div class="note"> +<p><strong>주의 </strong>: 위 명령어는 리눅스/macOS X를 위한 명령어입니다. 윈도우에서의 명령어는 다음과 같습니다. <code>py -3 manage.py startapp catalog</code></p> + +<p>만약 윈도우에서 작업한다면, 이 튜트리얼 전체에서 <code>python3</code>를 <code>py -3</code>로 바꾸십시오.</p> + +<p>만약 파이썬 3.7.0 이상을 사용한다면 <code>py manage.py startapp catalog</code>를 사용하면 됩니다.</p> +</div> + +<p>이 도구는 새로운 폴더를 생성하고 폴더를 어플리케이션의 파일들로 채웁니다(아래에 굵게 표시). 대부분의 파일들은 그것의 목적에서 따온 이름을 갖고 있습니다. 예를들어, 뷰는 <strong>views.py</strong>에, 모델은 <strong>models.py</strong>에, 테스트는 <strong>tests.py</strong>에, 관리자 사이트 설정은 <strong>admin.py</strong>에, 어플리케이션 등록(registration)은 <strong>apps.py</strong>에 있습니다. 그리고 관련 객체(object)에 대한 작업을 위한 최소한의 표준 코드를 포함합니다.</p> + +<p>이제 업데이트된 프로젝트 디렉토리는 다음과 같아야 합니다. </p> + +<pre class="brush: bash notranslate"><em>locallibrary/</em> + manage.py + <em>locallibrary/ +</em><strong> <em>catalog/</em> + admin.py + apps.py + models.py + tests.py + views.py + __init__.py + <em>migrations/</em></strong> +</pre> + +<p> 추가로 다음을 갖게 됐습니다:</p> + +<ul> + <li><strong>migrations 폴더</strong> — 모델을 수정할 때 마다 자동으로 데이터베이스를 업데이트하는 것을 가능하게 해 줄 마이그레이션 파일들을 저장할 폴더 </li> + <li><strong>__init__.py</strong> — 장고/파이썬이 폴더를 <a href="https://docs.python.org/3/tutorial/modules.html#packages">파이썬 패키지</a>로 인식하게 할 빈 파일입니다. 또한 프로젝트의 다른 부분에서 객체(object)를 사용할 수 있게 합니다.</li> +</ul> + +<div class="note"> +<p><strong>주의 </strong>: 위의 파일 리스트에서 뭔가 부족한게 있다는 것을 알아챘나요? 뷰와 모델 관련 파일은 있는 반면 URL 맵핑, 템플릿, 정적 파일(static file)과 연관된 파일이 없습니다. 그들을 어떻게 생성하는지에 관해서는 추후에 보여드리겠습니다 (이것들은 모든 웹사이트에서 필요하진 않지만 우리 프로젝트에서는 필요합니다).</p> +</div> + +<h2 id="catalog_application_등록하기">catalog application 등록하기</h2> + +<p>이제 어플리케이션이 생성되었으니 프로젝트에 등록(register)해야합니다. 도구가 실행될 때 프로젝트에 포함시키기 위해서 말이죠(예를 들어 모델을 데이터베이스에 추가할 때 처럼요). 어플리케이션들은 프로젝트 설정 안의 <code>INSTALLED_APPS</code> 리스트에 추가함으로써 등록할 수 있습니다. </p> + +<p>프로젝트의 설정 파일(<strong>locallibrary/locallibrary/settings.py</strong>)을 열고 <code>INSTALLED_APPS</code> 리스트의 정의 부분을 찾으세요. 그리고 그 리스트 제일 아래에 다음과 같이 기입해 주세요.</p> + +<pre class="brush: bash notranslate">INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +<strong> 'catalog.apps.CatalogConfig', </strong> +]</pre> + +<p>새로운 행은 어플리케이션 구성 객체(application configuration object)(<code>CatalogConfig</code>)를 지정하게 됩니다. 이것은 어플리케이션을 생성할 때 <strong>/locallibrary/catalog/apps.py</strong> 안에 생성됩니다.</p> + +<div class="note"> +<p><strong>주의</strong>: 설정 파일에 이미 많은 <code>INSTALLED_APPS</code> 항목과 <code>MIDDLEWARE</code> 항목이 있음을 알 수 있습니다. 이를 통해 <a href="/en-US/docs/Learn/Server-side/Django/Admin_site">장고 관리 사이트</a>를 지원할 수 있으며 결과적으로 세션, 인증 등을 포함한 많은 기능이 사용됩니다.</p> +</div> + +<h2 id="데이터베이스_설정">데이터베이스 설정</h2> + +<p>이제 보통 프로젝트에 사용할 데이터베이스를 지정하는 시점입니다— 가능한 한 개발과 결과물에 동일한 데이터베이스를 사용하여 사소한 동작 차이를 방지해야 합니다. <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#databases">Databases에 대한 장고 문서</a>에서 가능한 다른 옵션을 확인할 수 있습니다. </p> + +<p>이 예제에서는 SQLite 데이터베이스를 사용합니다. 데모 데이터베이스에서 많은 동시 접속을 예상하지 않고, 설정에 추가적인 작업이 필요없기 때문입니다. 이 데이터베이스가 어떻게 설정되어 있는지 <strong>settings.py</strong>에서 확인할 수 있습니다.</p> + +<pre class="brush: python notranslate">DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} +</pre> + +<p>우리는 SQLite를 사용하기 때문에 여기서 다른 어떤 작업도 할 필요가 없습니다. 다음으로 가죠!</p> + +<h2 id="프로젝트의_다른_설정">프로젝트의 다른 설정</h2> + +<p><strong>settings.py</strong> 파일은 다른 많은 설정을 조정하는 데에 사용됩니다. 그러나 지금은 <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-TIME_ZONE">TIME_ZONE</a> 만 바꿔 봅시다— 이 부분은 표준화된 <a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">List of tz database time zones</a>과 일치되는 문자열을 사용해야 합니다 (테이블의 TZ 열의 값들을 참고하세요). <code>TIME_ZONE</code> 값을 당신의 타임존에 알맞은 문자열로 바꾸세요. (역자주: 한국은 <code>'Asia/Seoul'</code>로 설정)</p> + +<pre class="brush: python notranslate">TIME_ZONE = 'Europe/London'</pre> + +<p>지금은 바꾸지 않지만 알아둬야 할 두 가지 설정이 있습니다:</p> + +<ul> + <li><code>SECRET_KEY</code> : 이것은 장고의 웹사이트 보안 전략의 일부로 사용되는 비밀키입니다. 만약 이 코드를 개발 과정에서 보호하지 않는다면 제품화(production) 과정에서 다른 코드(아마 환경변수나 파일에서 읽어오는)를 사용해야 할 것입니다. </li> + <li><code>DEBUG</code> : 이것은 에러가 발생했을 때 HTTP 상태코드 응답 대신 디버깅 로그가 표시되게 합니다. 디버깅 정보는 공격자에게 유용하기 때문에 제품화된(production) 환경에서는 <code>False</code> 로 설정해야 합니다. 하지만, 지금은 <code>True</code>로 설정합니다.</li> +</ul> + +<h2 id="URL_맵퍼_연결">URL 맵퍼 연결</h2> + +<p>웹사이트는 프로젝트 폴더 안의 URL 맵퍼 파일(<strong>urls.py</strong>)과 같이 생성됩니다. <strong>urls.py</strong>를 통해 모든 URL 맵핑을 관리할 수 있지만, 연관된 어플리케이션에 따라 매핑을 다르게 하는 것이 일반적입니다.</p> + +<p><strong> locallibrary/locallibrary/urls.py</strong> 파일을 열어서 URL 맵퍼를 사용하는 몇 가지 방법을 설명하는 문서를 살펴보세요. </p> + +<pre class="brush: python notranslate">"""locallibrary URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/2.0/topics/http/urls/ +예제: +Function views 일 경우 + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views 일 경우 + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +다른 참조할 URL FILE 들을 포함시켜야 하는경우 + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path + +urlpatterns = [ + path('admin/', admin.site.urls), +] +</pre> + +<p>URL 맵핑은 <code>urlpatterns</code> 변수를 통해 관리되는데, 이 변수는 <code>path()</code> 함수의 파이썬 <code>list</code> 타입입니다. 각각의 <code>path()</code> 함수는 패턴이 일치할 때 표시될 뷰에 URL 패턴을 연결하거나, 다른 URL 패턴 테스트 코드 목록에 연결합니다(이 두 번째 경우에서 패턴은 대상 모듈에서 정의된 패턴의 "기본 URL"이 됩니다). <code>urlpatterns</code> 리스트는 맨 처음에 관리자 어플리케이션의 고유한 URL 맵핑 정의를 갖고 있는 <code>admin.site.urls</code> 모듈에 <code>admin/</code> 패턴을 가지고 있는 모든 URL을 매핑하는 단일 함수를 정의합니다.</p> + +<div class="note"> +<p><strong>주의</strong>: <code>path()</code> 속의 경로는 일치시킬 URL 패턴을 정의하는 문자열입니다. 이 문자열은 명명된(named) 변수를 꺽쇠 괄호(<code>< ></code>) 안에 포함할 수 있습니다. (예시: <code>'catalog/<id>/'</code>) 이 패턴은 URL을 <strong>/catalog/</strong><em>any_chars</em><strong>/</strong> 처럼 일치시키고 <em>any_chars</em>를 뷰에 매개 변수 이름이 <code>id</code> 인 문자열로 전달합니다. 경로(path) 함수(method)와 경로(route) 패턴에 대해서는 추후에 더 논의하겠습니다.</p> +</div> + +<p><code>urlpatterns</code> 리스트에 새로운 리스트 항목을 추가하기 위해서 아래 코드를 파일의 마지막에 추가하세요. 이 새로운 항목은 요청(request)을 모듈 <code>catalog.urls</code>(관련 URL <strong>/catalog/urls.py</strong>가 있는 파일)에 <code>catalog/</code> 패턴과 함께 전달하는 <code>path()</code>를 포함합니다. (번역자주: 만일 <code>www.xxxx.com/catalog</code>로 시작되는 요청이 들어 오면 <code>catalog/urls.py</code>를 참조해서 맵핑하겠다는 의미)</p> + +<pre class="brush: python notranslate"># Use include() to add paths from the catalog application +from django.conf.urls import include +from django.urls import path + +urlpatterns += [ + path('catalog/', include('catalog.urls')), +] +</pre> + +<p>이제 사이트의 루트 URL(즉, <code>127.0.0.1:8000</code>)을 <code>127.0.0.1:8000/catalog/</code>로 리다이렉트 하도록 합시다. 이것이 우리가 이 프로젝트에서 사용하는 유일한 어플리케이션입니다. 이것을 하기 위해서는 특별한 뷰 함수(<code>RedirectView</code>)를 사용할 겁니다. 이 함수는 <code>path()</code>에서 지정된 URL 패턴이 일치할 때(위의 경우에선 루트 URL이죠) 첫 번째 인자를 (<code>/catalog/</code>)로 리다이렉트할 새로운 상대 URL로 간주합니다.</p> + +<p>파일의 하단에 아래 코드를 다시 추가하세요:</p> + +<pre class="brush: python notranslate">#Add URL maps to redirect the base URL to our application +from django.views.generic import RedirectView +</pre> + +<pre class="notranslate"><code>urlpatterns += [ + path('', RedirectView.as_view(url='/catalog/', permanent=True)), +]</code></pre> + +<p><code>path()</code> 함수의 첫 번째 매개변수(parameter)를 비워 놓으면 <code>'/'</code>를 의미합니다. 만약 첫 번째 매개변수(parameter)를 <code>'/'</code>라고 작성한다면 개발 서버를 시작할 때 장고는 아래의 경고를 보여줄 겁니다.</p> + +<pre class="brush: python notranslate">System check identified some issues: + +WARNINGS: +?: (urls.W002) Your URL pattern '/' has a route beginning with a '/'. +Remove this slash as it is unnecessary. +If this pattern is targeted in an include(), ensure the include() pattern has a trailing '/'. +</pre> + +<p>장고는 기본적으로 CSS, JavaScript, 그리고 이미지와 같은 정적 파일을 제공하지 않지만, 이들은 사이트에 매우 유용할 수 있습니다. 최종적으로 이 URL 매퍼에 추가할 것은 개발 중에 정적 파일들을 제공하는 것을 가능하게 하는 아래 코드입니다. </p> + +<p>아래 코드를 파일의 하단에 추가하세요:</p> + +<pre class="notranslate"><code># Use static() to add url mapping to serve static files during development (only) +from django.conf import settings +from django.conf.urls.static import static + +urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)</code> +</pre> + +<div class="note"> +<p><strong> 주의</strong>: <code>urlpatterns</code> 리스트를 확장하는 여러 가지 방법들이 있습니다(위에서는 코드들을 명백하게 구분하기 위해서 간단히 <code>+=</code> 연산자를 사용해서 새로운 리스트 항목을 추가했습니다). 대신 원래의 리스트 정의 안에 새로운 패턴-맵을 추가하는 방법이 있습니다:</p> + +<pre class="brush: python notranslate">urlpatterns = [ + path('admin/', admin.site.urls), + path('catalog/', include('catalog.urls')), + path('', RedirectView.as_view(url='/catalog/', permanent=True)), +] + <code>static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)</code> +</pre> + +<p>추가적으로 임포트(import) 코드(<code>from django.urls import include</code>)를 그것을 사용하는 코드 바로 위에 선언했지만(무엇을 추가했는지 보기 쉽도록) 대개 import 문장은 파이썬 파일의 상단에 포함하는 것이 일반적입니다.</p> +</div> + +<p>마지막으로 <strong>urls.py</strong>라는 파일을 catalog 폴더 안에 생성하세요. 그리고 임포트된 (텅 빈(emptyt))<code>urlpatterns</code>를 정의하기 위해 아래 코드를 추가하세요. 어플리케이션을 만들면서 패턴들을 이곳에 추가할 것입니다. </p> + +<pre class="brush: python notranslate">from django.urls import path +from catalog import views + + +urlpatterns = [ + +] +</pre> + +<h2 id="Website_framework_테스트_하기">Website framework 테스트 하기</h2> + +<p>우리는 이 프로젝트의 뼈대(skeleton)를 만들었습니다. 웹사이트는 아직 아무것도 하지 않지만, 우리들이 여기까지 완성한 프로젝트가 오류 없이 돌아가는지 한번 실행해 볼 필요가 있습니다. </p> + +<p>그 전에 먼저 데이터베이스로의 마이그레이션(migration) 작업을 해야 합니다. 이것은 데이터베이스에 우리의 어플리케이션에 속한 모든 모델을 포함하도록 업데이트합니다(그리고 몇몇 빌드 경고의 원인을 제거합니다).</p> + +<h3 id="데이터베이스_마이그레이션migration_실행하기">데이터베이스 마이그레이션(migration) 실행하기</h3> + +<p>장고는 ORM(Object-Relational-Mapper : 객체-관계-매퍼)를 사용하여 장고 코드 안에 있는 모델 정의(객체)를 기본 데이터베이스에서 사용하는 데이터 구조(관계형 DB)에 매핑합니다. 모델의 정의를 바꿀 때 마다, 장고는 변화를 추적해서, 데이터베이스 안의 기본 데이터 구조가 모델과 일치하도록 자동적으로 이전(migrate)하는 스크립트를(<strong>/locallibrary/catalog/migrations/</strong>안에)생성할 수 있습니다.</p> + +<p>웹사이트를 생성할 때 장고는 사이트의 관리자 섹션(나중에 살펴볼)에서 사용할 여러 모델들을 자동으로 추가했습니다. 데이터베이스 안의 그 모델들을 위한 테이블들을 정의하기 위해 아래 명령어를 실행하세요(<strong>manage.py</strong>파일이 포함되어 있는 디렉토리에서 실행합니다). </p> + +<pre class="brush: bash notranslate">python3 manage.py makemigrations +python3 manage.py migrate +</pre> + +<div class="warning"> +<p><strong> 중요</strong>: 저장되어야 할 데이터의 구조에 영향을 미치는 방식으로 모델이 변경될 때마다 위의 명령어를 실행해야 합니다(모든 모델과 개별적인 필드의 추가와 제거를 포함하여).</p> +</div> + +<p><code>makemigrations</code> 명령어는 프로젝트에 설치된 모든 어플리케이션에 대한 migration을 생성합니다(하지만 적용하진 않습니다)(또한 그저 단일 프로젝트를 위한 migration을 실행하기 위해 어플리케이션 이름을 지정할 수 있습니다). 이것으로 migration이 적용되기 전에 코드를 점검할 기회를 가질 수 있습니다 — 당신이 장고 전문가가 되었을 땐 그것들을 조금 조정할 수도 있습니다!</p> + +<p><code>migrate</code> 명령어는 migration을 실제로 데이터베이스에 적용합니다(장고는 현재 데이터베이스에 어떤 것들이 추가되었는지 추적합니다).</p> + +<div class="note"> +<p><strong>주의</strong>: 덜 사용되는 migration 명령어에 대한 추가적인 정보는 <a href="https://docs.djangoproject.com/en/2.0/topics/migrations/">Migrations</a> (장고 문서)를 참고하세요.</p> +</div> + +<h3 id="웹사이트_실행하기">웹사이트 실행하기</h3> + +<p>개발 중에 먼저 개발 웹 서버를 사용해서 웹사이트를 서비스한 후 로컬 웹 브라우저에서 볼 수 있습니다. </p> + +<div class="note"> +<p><strong>주의</strong>: 개발용 웹 서버는 견고하거나 제품에 쓰일 만큼 충분하진 않지만, 개발 중에 편하고 빠른 테스트를 위해 장고 웹사이트를 실행할 수 있는 아주 쉬운 방법입니다. 기본적으로 사이트를 당신의 로컬 컴퓨터에(<code>http://127.0.0.1:8000/)</code>서비스 하지만, 네트워크에 있는 다른 컴퓨터를 지정해서 서비스하도록 할 수 있습니다. <a href="https://docs.djangoproject.com/en/2.0/ref/django-admin/#runserver">django-admin and manage.py: runserver</a> (장고 문서)에서 더 많은 정보를 확인하세요.</p> +</div> + +<p><strong>manage.py</strong>와 같은 디렉터리 안에 있는 <code>runserver</code> 명령어를 실행해 개발 웹 서버를 실행해 보세요.</p> + +<pre class="brush: bash notranslate">python3 manage.py runserver + + Performing system checks... + + System check identified no issues (0 silenced). + August 15, 2018 - 16:11:26 + Django version 2.1, using settings 'locallibrary.settings' + Starting development server at http://127.0.0.1:8000/ + Quit the server with CTRL-BREAK. +</pre> + +<p> 서버가 실행된다면 로컬 웹 브라우저에서 <code>http://127.0.0.1:8000/</code> 으로 이동하여 사이트를 볼 수 있습니다. 아래와 같은 사이트 에러 페이지가 뜰 겁니다:</p> + +<p><img alt="Django Debug page for Django 2.0" src="https://mdn.mozillademos.org/files/15729/django_404_debug_page.png"></p> + +<p>걱정 마세요! 예상된 에러 페이지입니다. 그 이유는 아직(사이트의 루트에 대한 URL을 가져올 때 리다이렉트되는) <code>catalogs.urls</code> 모듈 안에 정의된 page/url들이 없기 때문입니다. </p> + +<div class="note"> +<p><strong>주의</strong>: 위 페이지는 중요한 장고 기능을 보여줍니다— 자동화된 디버그 기록(logging)이죠. 에러 화면은 페이지를 찾을 수 없거나, 코드에서 에러가 발생했을 어떤 때라도 유용한 정보가 표시될 겁니다. 이 경우엔 (목록에 있는 대로) 우리가 제공한 URL과 일치하는 어떤 URL 패턴도 없다는 것을 볼 수 있습니다. 디버그 기록(logging)은 제품화되었을 (웹에 라이브로 사이트를 올려놓으면) 때는 꺼져 있을 겁니다. 정보는 더 적지만, 사용자 친화적인 페이지가 서비스되는 것이죠.</p> +</div> + +<p>이 지점에서 장고가 작동한다는 것을 알 수 있습니다! </p> + +<div class="note"> +<p><strong>주의</strong>: 어떤 때라도 중요한 변경이 있은 후에는 migration들을 재실행하고 사이트를 다시 테스트해야 합니다. 그렇게 오래 걸리진 않으니까 꼭 하세요!</p> +</div> + +<h2 id="도전_과제">도전 과제</h2> + +<p><strong>catalog/</strong> 디렉토리는 뷰, 모델, 그리고 어플리케이션의 다른 부분들을 위한 파일들을 포함하고 있습니다. 이 파일들을 열어 표준 코드(boilerplate)들을 점검해 보세요. </p> + +<p>위에서 본 것처럼, 관리자 사이트를 위한 URL 매핑은 이미 프로젝트의 <strong>urls.py</strong> 안에 추가되어 있습니다. 브라우저에서 관리자 영역으로 이동하여 어떤 일이 일어나는지 살펴보세요(위서 살펴본 매핑에서 올바른 URL을 추론할 수 있습니다).</p> + +<ul> +</ul> + +<h2 id="요약">요약</h2> + +<p>이제 urls, models, views, 그리고 templates으로 채울 수 있는 완벽한 뼈대 웹사이트 프로젝트를 만들었습니다.</p> + +<p><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Local Library website</a>를 위한 뼈대는 완성되어서 실행되고 있고, 이제는 이 웹사이트가 해야 할 일을 하게 만드는 코드를 작성할 시간입니다. </p> + +<h2 id="참고_항목">참고 항목</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/intro/tutorial01/">Writing your first Django app - part 1</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/applications/#configuring-applications">Applications</a> (Django Docs). 어플리케이션의 구성(configuring)에 관한 정보를 갖고 있습니다.</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Tutorial_local_library_website", "Learn/Server-side/Django/Models", "Learn/Server-side/Django")}}</p> + + + +<h2 id="이_모듈_안에서">이 모듈 안에서</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> diff --git a/files/ko/learn/server-side/django/testing/index.html b/files/ko/learn/server-side/django/testing/index.html new file mode 100644 index 0000000000..d15550bd43 --- /dev/null +++ b/files/ko/learn/server-side/django/testing/index.html @@ -0,0 +1,933 @@ +--- +title: 'Django 튜토리얼 파트 10: Django 웹 어플리케이션 테스트하기' +slug: Learn/Server-side/Django/Testing +translation_of: Learn/Server-side/Django/Testing +--- +<h2 id="LearnSidebar">{{LearnSidebar}}</h2> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Forms", "Learn/Server-side/Django/Deployment", "Learn/Server-side/Django")}}</div> + +<p class="summary">웹사이트가 성장함에 따라 손으로 일일히 테스트하는 것은 점점 더 어려워진다. 테스트 할 내용이 늘어날 뿐만 아니라, 컴포넌트간의 상호작용도 복잡해지고, 한 쪽의 작은 수정이 다른쪽에 큰 영향을 줄수 있기 때문에, 모든것이 잘 동작할 수 있도록 더 많은 수정이 필요해지며, 그렇게 추가된 수정이 새로운 에러를 유발하지 않도록 확인되어야 한다. 이러한 문제들의 해결책중 하나는, 쉽고 안정적으로 수정사항이 발생할 때마다 실행되는 자동화된 테스트를 작성하는 것이다. 이 튜토리얼은 Django의 테스트 프레임워크를 사용하여 당신의 웹 사이트에 대한 Unit Testing을 자동화하는 방법을 보여줄것이다. </p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">사전학습:</th> + <td>아래 파트를 포함하여 앞선 모든 튜토리얼 파트의 학습을 완료할 것. <a href="/en-US/docs/Learn/Server-side/Django/Forms">Django 튜토리얼 파트 9: 폼으로 작업하기</a>.</td> + </tr> + <tr> + <th scope="row">학습목표:</th> + <td>Django 기반 웹사이트의 Unit Test 작성방법 이해하기.</td> + </tr> + </tbody> +</table> + +<h2 id="개요">개요</h2> + +<p>이제까지 개발된 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 의 현재 상태는 다음과 같다. 모든 book과 author의 목록, <code>Book</code> 과 <code>Author</code> 항목별 상세 뷰, <code>BookInstance</code> 갱신용 페이지, <code>Author</code> 항목의 생성,갱신,삭제를 위한 페이지( <a href="/en-US/docs/Learn/Server-side/Django/Forms">forms tutorial</a> 편의 도전과제도 완성 했다면 <code>Book</code> 편집 페이지도 포함)가 완성 되었다. 상대적으로 작은 이 사이트조차, 각 페이지가 기대한 대로 동작하는지 손으로 대강 체크하는 것만 해도 몇 분 정도는 걸린다. 사이트가 성장하면서 수정사항이 늘어날수록 적절하게 동작하는지 우리가 수동으로 체크해야 하는 양도 늘어날 수 밖에 없다. 손으로 직접 테스트 하는 방법을 계속 유지한다면, 결국은 대부분의 시간을 테스트에 사용하고 코드를 개선할 수 있는 시간은 거의 없어질 것이다. </p> + +<p>자동화된 테스트는 이러한 문제를 진짜로 해결할 수 있다! 명백한 이점은 수동 테스트보다는 훨신 빠르고, 훨씬 세부적인 내용까지도 테스트 할수 있으며, 매번 정확히 같은 기능을 테스트할 수 있다는 점(사람이 테스트한다면 결코 신뢰성있게 할 수 없는 부분!) 이다. 자동화 테스트는 빠르기 때문에 좀 더 정기적으로 실행할 수 있고, 테스트 실패시 코드가 기대대로 동작하지 않았던 부분을 정확히 지목할 수 있다.</p> + +<p>게다가, 자동화된 테스트는 당신의 코드의 첫번째 실전 고객으로 역할을 수행하여, 당신의 웹사이트가 어떻게 동작해야하는지 엄격하게 정의하고 문서화하도록 압력을 준다. 종종 이 내용이 당신이 작성하게될 코드 예제나 관련문서의 기초 자료가 된다. 이러한 이유 때문에, 어떤 소프트웨어 개발 프로세스는 테스트 정의와 구현으로 시작되어, 테스트가 요구하는 동작을 충족하도록 코드가 작성되기도 한다. ( 예를 들면, <a href="https://en.wikipedia.org/wiki/Test-driven_development">test-driven</a> 과 <a href="https://en.wikipedia.org/wiki/Behavior-driven_development">behaviour-driven</a> 개발론).</p> + +<p>이 튜토리얼은 , LocalLibrary 웹사이트에 몇가지 테스트를 추가하면서, Django에 대한 자동화된 테스트를 작성하는 방법을 보여준다.</p> + +<h3 id="Testing의_종류">Testing의 종류</h3> + +<p>테스트의 성격, 수준, 테스트의 종류및 테스트에 대한 접근방법은 수없이 많다. 가장 중요한 방법들은 다음과 같다. :</p> + +<dl> + <dt>Unit tests (유닛 테스트)</dt> + <dd>독립적인 콤포넌트의 (성능이 아닌) 기능적인 동작을 검증한다. 흔히 class나 function 레벨로 수행한다.</dd> + <dt>Regression tests ( 버그수정 확인 테스트 )</dt> + <dd>기존에 보고된 버그들이 재발하는지 테스트한다. 각 테스트는, 먼저 이전에 발생했던 버그가 수정되었는지 체크한 이후에, 버그 수정으로 인해 새롭게 발생되는 버그가 없는지 확인차 재수행하게 된다.</dd> + <dt>Integration tests ( 통합 테스트 )</dt> + <dd>유닛 테스트를 완료한 각각의 독립적인 콤포넌트들이 함께 결합되어 수행하는 동작을 검증한다. 통합 테스트는 콤포넌트간에 요구되는 상호작용을 검사하며, 각 콤포넌트의 내부적인 동작까지 검증할 필요는 없다. 이 테스트는 단지 전체 웹사이트에 걸쳐 각 콤포넌트가 결합하여 수행하는 동작을 대상으로 한다.</dd> +</dl> + +<div class="note"> +<p><strong>참고사항 : </strong>이외의 일반적인 테스트의 종류는 다음과 같다. 블랙박스, 화이트 박스, 수동, 자동, 카나리아, 스모크, 적합성 평가(conformance), 인수(acceptance), 기능성, 시스템, 성능, 로드, 스트레스 테스트등. 더 자세한 정보는 위의 키워드로 검색 바람.</p> +</div> + +<h3 id="Django가_testing을_위해_제공하는_것은">Django가 testing을 위해 제공하는 것은?</h3> + +<p>웹사이트를 테스트하는 것은 복잡한 작업입니다. 왜냐하면 이것이 HTTP 레벨의 리퀘스트 핸들링, 쿼리모델들, 폼 인증과 처리 그리고 템플릿 렌더링과 같은 여러 로직 레이어로 만들어졌기 때문입니다.</p> + +<p>Django는 파이선 표준 라이브러리 unittest로 만들어진 작은 클라스계층의 테스트 프레임워크를 제공합니다. 그 이름과 다르게 이 테스트 프레임워크는 유닛테스트와 통합테스트 모두에게 적당합니다. 이 Django 프레임워크는 웹과 Django의 독특한 특징을 테스트하는 것을 돕기 위한 API메소드와 도구들을 추가합니다. 이것들은 당신이 리퀘스트를 시험하고, 시험 데이터를 삽입하고 그리고 당신의 애플리케이션의 결과물을 검사할 수 있게 합니다. Django는 또한 다른 테스트 프레임워크들을 사용하기 위한 API (LiveServerTestCase)와 도구들을 제공합니다. 예를 들면 당신은 사용자가 생중계 브라우저와 소통하는 것을 시뮬레이션하는 유명한 Selenium 프레임워크와 통합할 수 있습니다.</p> + +<p>테스트를 작성하기 위해서는 Django(또는 unittest) 테스트기반 클라스들(<a href="https://docs.djangoproject.com/en/2.1/topics/testing/tools/#simpletestcase">SimpleTestCase</a>, <a href="https://docs.djangoproject.com/en/2.1/topics/testing/tools/#transactiontestcase">TransactionTestCase</a>, <a href="https://docs.djangoproject.com/en/2.1/topics/testing/tools/#testcase">TestCase</a>, <a href="https://docs.djangoproject.com/en/2.1/topics/testing/tools/#liveservertestcase">LiveServerTestCase</a>)로부터 어떤 것을 가져오고 그리고 그 다음에 특별한 기능이 기대했던대로 작동하는지 체크하기 위한 분리된 메소드들을 작성합니다. (테스트들은 그 표현식의 결과가 True 또는 False 값인지 또는 그 두 값들이 동등한지 등을 테스트하기 위해 "assert" 메소드를 사용합니다) 당신이 테스트를 시작하면, 그 프레임워크는 당신의 가져온 클라스들안에서 선택된 테스트 메소드들을 실행합니다. 아래에 보이는 것과 같이, 그 테스트 메소드들은 클라스에서 정의된 보통의 셋업 그리고/또는 tear-down 방식을 가지고 독립적으로 실행됩니다.</p> + +<pre class="brush: python notranslate">class YourTestClass(TestCase): + def setUp(self): + # Setup run before every test method. + pass + + def tearDown(self): + # Clean up run after every test method. + pass + + def test_something_that_will_pass(self): + self.assertFalse(False) + + def test_something_that_will_fail(self): + self.assertTrue(False) +</pre> + +<p>대부분의 테스트들을 위한 최선의 기본 클라스는 <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#testcase">django.test.TestCase</a> 입니다. 이 테스트 클라스는 이것이 실행되기 전에 하나의 깨끗한 데이터베이스를 생성하고, 그리고 이 자체의 트랜젝션에서 모든 테스트를 실행합니다. 이 클라스는 자체의 테스트 클라이언트를 갖고있는데, 이것은 당신이 view 레벨에서 그 코드가 사용자와 상호작용하는 것을 시뮬레이션할 수 있게 합니다. 아래 섹션에서는 이 <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#testcase">TestCase</a> 기본 클라스를 이용하여 유닛테스트들에 집중할 것입니다. </p> + +<div class="note"> +<p><strong>Note:</strong> 이<a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#testcase">django.test.TestCase</a> 클라스는 매우 편리합니다. 그러나 어떤 테스트들은 그들이 필요로하는 것보다 느려지는 결과가 올 수 있습니다(모든 테스트들이 그들 자체의 데이터베이스나 또는 view 상호작용의 셋업이 필요한 것은 아닙니다) 한번 당신이 이 클라스를 통해서 무엇을 할 수 있는지 익숙해진다면, 당신은 더 심플한 테스트 클라스들을 가지고 당신의 몇몇 테스트들을 대체하게 될 것입니다.</p> +</div> + +<h3 id="무엇을_테스트해야_하는가">무엇을 테스트해야 하는가?</h3> + +<p>Python 또는 Django의 일부분으로서 제공되는 라이브러리들 또는 기능들을 제외한 당신 코드의 모든 면을 테스트해야합니다. </p> + +<p>예를 들면, 아래에 정의된 Author 모델을 가정합니다. 당신은 first_name과 last_name이 데이터베이스 CharField로서 적당하게 저장됐는지에 대해 명시적으로 테스트할 필요가 없습니다. 왜냐하면, 그것은 Django에 의해 정의된 것이기 때문입니다. (물론 사실은 당신이 개발하는 중에 이 기능들을 필연적으로 테스트했음에도 불구하고요) 또한 date_of_birth가 날짜 필드에 적합한 값을 갖었는지 테스트하는 것도 필요없습니다. 왜냐하면 그렇게 하는 것은 Django에서 다시한번 더 실행하는 것이 되니까요. </p> + +<p>그러나 당신은 그 레이블들(<em>First name, Last name, Date of birth, Died</em>)로 사용된 텍스트 그리고 그 텍스트(100 chars)을 위해 할당한 그 필드의 크기를 확인해야 합니다. 왜냐하면 이것들은 당신이 디자인한 것이고 추후에 깨지거나 변경될 수 있는 것이기 때문입니다.</p> + +<pre class="brush: python notranslate">class Author(models.Model): + first_name = models.CharField(max_length=100) + last_name = models.CharField(max_length=100) + date_of_birth = models.DateField(null=True, blank=True) + date_of_death = models.DateField('Died', null=True, blank=True) + + def get_absolute_url(self): + return reverse('author-detail', args=[str(self.id)]) + + def __str__(self): + return f'{self.last_name}, {self.first_name}'</pre> + +<p>단순히, 당신은 당신의 요구에 따라 만들어지 메소드들 <code style="font-style: normal; font-weight: normal;">get_absolute_url()</code> 과 <code style="font-style: normal; font-weight: normal;">__str__()</code> 이 원래 요구된 대로 작동하는지 체크해야합니다. 왜냐하면 이것들은 당신이 만들어낸 코드/비즈니스 로직이기 때문입니다. <code style="font-style: normal; font-weight: normal;">get_absolute_url()</code>의 경우에 있어서는 Django의 reverse() 메소드는 적당하게 작동되었다고 신뢰할 수 있습니다, 그래서 당신이 테스트해야 하는 것은 실제로 정의되어온 관련된 view 입니다. </p> + +<div class="note"> +<p><strong>Note:</strong> 영특한 독자들은 date of birth와 date of death를 합리적인 값으로 제한해야 한다고 적어야 하고, 그리고 death는 birth보다 뒤에 왔는지를 체크해야 한다고 할 것입니다. Django에 있어서 이 제약은 당신의 폼클라스에 추가될 것입니다(당신이 그 필드들의 이러한 모습을 모델 레벨이 아니라 오직 폼 레벨에서 사용될 수 있도록 인증을 정의할 수 있다고 할지라도 말입니다)</p> +</div> + +<p>이런 것들을 마음에 두고서 테스트를 정의하고 실행해봅시다.</p> + +<h2 id="테스트_구조_개요">테스트 구조 개요</h2> + +<p>무엇을 테스트 할 지 자세히 보기 전에, 간단히 어디서 그리고 어떻게 테스트가 정의되는지 대략 살펴 봅시다.</p> + +<p>장고는 유닛테스트의 모듈인 <a href="https://docs.python.org/3/library/unittest.html#unittest-test-discovery" title="(in Python v3.5)">built-in test discovery</a>을 사용하는데, 이는 현재 작업중인 디렉토리의 <strong>test*.py</strong>라는 패턴을 가진 모든 파일들을 체크합니다. 그 파일들의 이름을 적당하게 붙이는 한, 당신은 당신이 원하는 어떤 구조라도 이용할 수 있습니다. 우리는 당신의 테스트코드를 위한 한 모듈을 만들 것을 추천합니다. 그리고 모델들, 뷰들, 폼들 그리고 테스트가 필요한 어떤 다른 타입의 코드라도 각각을 분리하기를 바랍니다. 예를 들면:</p> + +<pre class="notranslate">catalog/ + /tests/ + __init__.py + test_models.py + test_forms.py + test_views.py +</pre> + +<p>당신의 LocalLibrary 프로젝트에서 위와 같은 구조의 파일을 만드십시오. __init__.py 파일은 비어있는 파일입니다.(이것은 Python에게 이 디렉토리가 패키지임을 알려줍니다) skeleton 테스트파일인 /catalog/tests.py를 복사하여 이름을 바꿔서 위의 세개의 테스트파일을 만드십시오.</p> + +<div class="note"> +<p><strong>Note:</strong> 이 skeleton 테스트파일 <strong>/catalog/tests.py 은 우리가 </strong><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django skeleton website</a> 를 만들었을 때 자동으로 생성됩니다. 당신의 테스트들을 여기에 모두 넣는 것도 괜찮습니다만, 당신이 적절하게 테스트를 해나가면 당신은 금방 매우 크고, 관리할 필요 없는 테스트파일로 끝나게 될 것입니다. </p> + +<p>이 skeleton 파일은 우리에게 필요하지 않으니 지우십시오.</p> +</div> + +<p><strong>/catalog/tests/test_models.py </strong>파일을 오픈하십시오. 이 파일은 아래와 같이 <code>django.test.TestCase </code>가 import 되었을 것입니다.</p> + +<pre class="brush: python notranslate">from django.test import TestCase + +# Create your tests here. +</pre> + +<p> 당신은 종종 특정한 기능을 테스트하기 위한 개별적인 메소드들을 가지고 당신이 테스트하기를 원하는 각각의 모델/뷰/폼을 위한 테스트 클라스를 추가할 것입니다. 또 다른 경우에는 당신은 그 사용사례의 다방면을 테스트하기 위한 개별적인 테스트기능을 가지고, 특별한 사용사례를 테스트하기 위한 별도의 클라스를 갖기를 원할 것입니다. (예를 들면, 실패한 사례들을 테스트하는 기능을 가지고 모델 필드가 적정하게 인증되었는지 테스트하는 한 클라스) 다시한번, 그 구조는 오직 당신 자신에게 달렸습니다. 그러나 당신이 일관되게 하는 것이 최선의 방법입니다. </p> + +<p>아래의 테스트 클라스를 파일 맨아랫부분에 추가하십시오. 이 클라스는 <code>TestCase</code>으로부터 이끌어내어서 어떻게 테스트케이스 클라스를 구축하는지 예시를 보여줍니다.</p> + +<pre class="brush: python notranslate">class YourTestClass(TestCase): + @classmethod + def setUpTestData(cls): + print("setUpTestData: Run once to set up non-modified data for all class methods.") + pass + + def setUp(self): + print("setUp: Run once for every test method to setup clean data.") + pass + + def test_false_is_false(self): + print("Method: test_false_is_false.") + self.assertFalse(False) + + def test_false_is_true(self): + print("Method: test_false_is_true.") + self.assertTrue(False) + + def test_one_plus_one_equals_two(self): + print("Method: test_one_plus_one_equals_two.") + self.assertEqual(1 + 1, 2)</pre> + +<p> 이 새로운 클래스는 테스트 전 준비를 위해 사용할 수 있는 메쏘드 두 개를 정의합니다. (예를 들면 테스트에 필요한 모델이나 객체를 생성해 놓을 수 있습니다):</p> + +<ul> + <li><code>setUpTestData()</code> 는 클래스 전체에서 사용되는 설정을 위해서 테스트 시작 때 딱 한 번만 실행됩니다. 테스트 메쏘드가 실행되면서 수정되거나 변경되지 않을 객체들을 이곳에서 생성할 수 있습니다.</li> + <li><code>setUp()</code> 은 각각의 테스트 메쏘드가 실행될 때마다 실행됩니다. 테스트 중 내용이 변경될 수 있는 객체를 이곳에서 생성할 수 있습니다 (모든 테스트 메쏘드는 방금 막 생성된 ("fresh") 오브젝트를 입력받게 됩니다).</li> +</ul> + +<div class="note"> +<p>테스트 클래스는 위 예제에서 사용하지 않은 <code>tearDown(</code>h 메소드를 가지고 있습니다. 이 메소드는 특히 데이터베이스 테스트에는 유용하지가 않은데 바로 베이스 클래스인 <code>TestCase</code> 가 데이터베이스 삭제(teardown) 을 처리해주기 때문입니다.</p> +</div> + +<p>아래에는 <code>Assert</code> 함수를 사용하여 조건이 참, 거짓 또는 동일한지 테스트하는 여러 가지 테스트 메서드가 있습니다 (<code>AssertTrue</code>, <code>AssertFalse</code>, <code>AssertEqual</code>). 조건이 예상대로 실행되지 않으면 테스트가 실패하고 콘솔에 오류를 보고합니다.</p> + +<div class="note"> +<p>일반적으로 위와 같이 테스트에 <strong>print()</strong> 함수를 포함하면 <strong>안</strong>됩니다. 여기서는 setup 함수들이 콘솔에서 호출되는 순서를 (다음 절에서) 볼 수 있도록 하기 위해 사용되었습니다.</p> +</div> + +<h2 id="어떻게_테스트를_작동시키는가">어떻게 테스트를 작동시키는가</h2> + +<p>모든 테스트를 실행하는 가장 쉬운 방법은 다음 명령을 사용하는 것입니다.</p> + +<pre class="brush: bash notranslate">python3 manage.py test</pre> + +<p>이 명령은 현재 경로에서 <strong>test*.py </strong>패턴을 만족하는 모든 파일을 찾은 후 이들 테스트를 적합한 베이스 클래스를 이용해서 실행합니다 (우리는 현재 여러 개의 테스트 파일을 가지고 있지만 <strong>/catalog/tests/test_models.py </strong>만이 실제 테스트를 포함하고 있습니다. 기본적으로 각각의 테스트는 실패 결과만을 보고하며, 마지막으로 테스트 결과 요약을 출력합니다.</p> + +<div class="note"> +<p>만약 <code>ValueError: Missing staticfiles manifest entry ...</code> 과 같은 에러를 마주칠 수 있습니다. 이런 에러는 보통 테스팅 도구가 기본적으로 collectstatic을 실행하지 않고, 당신의 앱이 이를 요구하는 storage 클래스를 사용하기 때문일 수 있습니다 (<a href="https://docs.djangoproject.com/en/2.0/ref/contrib/staticfiles/#django.contrib.staticfiles.storage.ManifestStaticFilesStorage.manifest_strict">manifest_strict</a> 에 더 자세한 정보가 적혀 있습니다). 이 문제를 해결할 수 있는 다양한 방법이 있지만 가장 간단한 방법은 테스트 실행 전에 collectstatic을 실행하는 것입니다.</p> + +<pre class="brush: bash notranslate">python3 manage.py collectstatic +</pre> +</div> + +<p><em>LocalLibrary</em>의 루트 디렉토리에서 테스트를 실행하세요. 당신은 아래와 같은 출력output을 볼 것입니다.</p> + +<pre class="brush: bash notranslate">> python3 manage.py test + +Creating test database for alias 'default'... +<strong>setUpTestData: Run once to set up non-modified data for all class methods. +setUp: Run once for every test method to setup clean data. +Method: test_false_is_false. +setUp: Run once for every test method to setup clean data. +Method: test_false_is_true. +setUp: Run once for every test method to setup clean data. +Method: test_one_plus_one_equals_two.</strong> +. +====================================================================== +FAIL: test_false_is_true (catalog.tests.tests_models.YourTestClass) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "D:\Github\django_tmp\library_w_t_2\locallibrary\catalog\tests\tests_models.py", line 22, in test_false_is_true + self.assertTrue(False) +AssertionError: False is not true + +---------------------------------------------------------------------- +Ran 3 tests in 0.075s + +FAILED (failures=1) +Destroying test database for alias 'default'...</pre> + +<p>여기서 우리는 테스트 하나가 실패했음을 알 수 있으며, 실패한 함수와 실패한 이유를 정확히 볼 수 있습니다 (False가 True가 아니기 때문에 실패하는 것이 당연합니다).</p> + +<div class="note"> +<p><strong>팁</strong>: 우리는 위의 출력 결과를 통해서 객체와 메소드에 설명적인, 정보가 많은 이름을 사용하는 것이 테스트 결과를 이해하는데 도움이 된다는 것을 배울 수 있습니다.</p> +</div> + +<p><strong>굵게 </strong>표시된 텍스트는 보통은 테스트 결과에 출력되지 않습니다 (우리가 테스트에서 사용한 <code>print()</code> 함수가 출력한 것입니다). 이는 <code>setUpTestData()</code> 가 클래스 당 한 번, <code>setUp()</code> 이 메소드 당 한 번 실행되는 것을 보여줍니다.</p> + +<p>다음 섹션은 특정 테스트만 실행하는 방법과 테스트가 출력하는 정보를 조절하는 방법을 알려줍니다.</p> + +<h3 id="테스트에_대해_더_많은_정보를_출력하기">테스트에 대해 더 많은 정보를 출력하기</h3> + +<p>테스트 실행에 대한 자세한 정보를 얻으려면 verbosity를 조절할 수 있습니다. 예를 들어 테스트 실패와 더불어 성공 (그리고 테스트 데이터베이스 생성 과정에 대한 정보)를 나열하려면 다음과 같이 verbosity를 "2"로 설정할 수 있습니다.</p> + +<pre class="brush: bash notranslate">python3 manage.py test --verbosity 2</pre> + +<p>허용되는 verbosity levels 은 0, 1, 2, and 3, 이며 기본값은 "1" 입니다.</p> + +<h3 id="테스트의_일부만_실행하기">테스트의 일부만 실행하기</h3> + +<p>테스트 중 일부만 실행하려면 패키지, 모듈, <code>TestCase</code> 서브클래스, 메서드의 전체 경로를 지정해주면 됩니다.</p> + +<pre class="brush: bash notranslate"># Run the specified module +python3 manage.py test catalog.tests + +# Run the specified module +python3 manage.py test catalog.tests.test_models + +# Run the specified class +python3 manage.py test catalog.tests.test_models.YourTestClass + +# Run the specified method +python3 manage.py test catalog.tests.test_models.YourTestClass.test_one_plus_one_equals_two +</pre> + +<h2 id="LocalLibrary_테스트">LocalLibrary 테스트</h2> + +<p>테스트를 어떻게 돌릴지와 어떤 내용을 테스트할지를 알았으니 이제 실제 예제들을 봅시다.</p> + +<div class="note"> +<p><strong>Note: </strong>우리는 가능한 모든 테스트를 작성하지는 않을 것입니다. 하지만 아래의 예제만으로도 테스트가 어떻게 동작하는지와 어떤 테스트를 작성할 수 있을지 아이디어를 얻을 수 있을 것입니다.</p> +</div> + +<h3 id="모델">모델</h3> + +<p>위에서 논의했듯이 우리는 우리가 디자인 했거나, 우리가 작성한 코드의 동작만을 테스트해야하지 Django 또는 Python 개발팀에서 이미 테스트 한 라이브러리 / 코드는 테스트하지 않아야 합니다.</p> + +<p>예를 들어, 아래 <code>Author</code> 모델을 봅시다. 여기서 우리는 모든 필드의 라벨을 테스트해야 합니다. 우리가 필드의 라벨을 지정하지는 않았지만 우리의 디자인은 라벨의 값이 어때야하는지를 이미 정해놓고 있기 때문입니다. 우리가 이 값들을 테스트하지 않는다면 필드 라벨에 의도된 값을 가지는지 알 수 없습니다. 마찬가지로 비록 우리는 Django가 필드들을 지정된 길이대로 만들 것이라고 믿지만, 그래도 필드의 길이를 테스트해보는 것이 헛되지는 않습니다.</p> + +<pre class="brush: python notranslate">class Author(models.Model): + first_name = models.CharField(max_length=100) + last_name = models.CharField(max_length=100) + date_of_birth = models.DateField(null=True, blank=True) + date_of_death = models.DateField('Died', null=True, blank=True) + + def get_absolute_url(self): + return reverse('author-detail', args=[str(self.id)]) + + def __str__(self): + return f'{self.last_name}, {self.first_name}'</pre> + +<p><strong>/catalog/tests/test_models.py </strong>을 열어서 기존 코드를 다음의 <code>Author</code> 모델 테스트 코드로 바꿔주세요.</p> + +<p>먼저 우리는 <code>TestCase</code> 를 불러온 후 이를 상속해서 우리의 테스트 케이스인 <code>AuthorModelTest</code> 를 작성합니다. 테스트가 실패할 경우 출력에서 테스트 명을 쉽게 파악할 수 있도록 이름을 작성한 것에 주목해 주세요. 또 우리는 <code>setUpTestData()</code>를 불러서 테스트 시에 이용되지만 수정은 가하지 않는 저자 객체를 생성합니다. </p> + +<pre class="brush: python notranslate">from django.test import TestCase + +from catalog.models import Author + +class AuthorModelTest(TestCase): + @classmethod + def setUpTestData(cls): + # Set up non-modified objects used by all test methods + Author.objects.create(first_name='Big', last_name='Bob') + + def test_first_name_label(self): + author = Author.objects.get(id=1) + field_label = author._meta.get_field('first_name').verbose_name + self.assertEquals(field_label, 'first name') + + def test_date_of_death_label(self): + author=Author.objects.get(id=1) + field_label = author._meta.get_field('date_of_death').verbose_name + self.assertEquals(field_label, 'died') + + def test_first_name_max_length(self): + author = Author.objects.get(id=1) + max_length = author._meta.get_field('first_name').max_length + self.assertEquals(max_length, 100) + + def test_object_name_is_last_name_comma_first_name(self): + author = Author.objects.get(id=1) + expected_object_name = f'{author.last_name}, {author.first_name}' + self.assertEquals(expected_object_name, str(author)) + + def test_get_absolute_url(self): + author = Author.objects.get(id=1) + # This will also fail if the urlconf is not defined. + self.assertEquals(author.get_absolute_url(), '/catalog/author/1')</pre> + +<p>필드 테스트들은 필드 라벨의 값(<code>verbose_name</code>)을 확인하고 character 필드의 크기가 예상대로인지를 확인합니다. 이들 테스트 메소드의 변수명은 모두 메소드의 기능을 묘사하고 있으며 같은 규칙을 따릅니다.</p> + +<pre class="brush: python notranslate"># Get an author object to test +author = Author.objects.get(id=1) + +# Get the metadata for the required field and use it to query the required field data +field_label = author._meta.get_field('first_name').verbose_name + +# Compare the value to the expected result +self.assertEquals(field_label, 'first name')</pre> + +<p>몇 가지 흥미로운 점들이 있습니다:</p> + +<ul> + <li>우리는 <code>author.first_name.verbose_name</code> 을 이용해서 <code>verbose_name</code>을 얻지 않습니다. 왜냐면 <code>author.first_name</code>은 문자열이지 우리가 속성에 접근할 수 있는 <code>first_name</code>객체가 아니기 때문입니다. 대신 우리는 author 객체의 <code>_meta</code> 속성을 이용해서 필드에 접근하여 필요한 정보를 얻습니다. </li> + <li>우리는 <code>assertTrue(field_label == 'first name'</code> 대신 <code>assertEquals(field_label,'first name')</code> 을 이용했습니다. 왜냐면 만약 테스트가 실패할 경우 후자는 실제 라벨의 값을 알려주어 디버깅을 조금 더 용이하게 만들어주기 때문입니다.</li> +</ul> + +<div class="note"> +<p><strong>Note:</strong> <code>last_name</code> 과 <code>date_of_birth</code> 필드의 라벨에 대한 테스트와 <code>last_name</code> 필드의 길이에 대한 테스트는 생략되었습니다. 기존 테스트의 이름 작성 규칙 (naming convention)과 테스트 작성법을 이용해서 직접 테스트를 작성해보세요. </p> +</div> + +<p>우리는 우리가 작성한 메서드도 테스트를 해야 합니다. 객체의 이름이 우리가 예상한 대로 "Last Name", "First Name" 규칙에 맞게 생성되었는지와 <code>Author</code> 객체의 URL이 우리가 예상한 대로 생성되는지를 보면 됩니다. </p> + +<pre class="brush: python notranslate">def test_object_name_is_last_name_comma_first_name(self): + author = Author.objects.get(id=1) + expected_object_name = f'{author.last_name}, {author.first_name}' + self.assertEquals(expected_object_name, str(author)) + +def test_get_absolute_url(self): + author = Author.objects.get(id=1) + # This will also fail if the urlconf is not defined. + self.assertEquals(author.get_absolute_url(), '/catalog/author/1')</pre> + +<p>테스트들을 돌려봅시다. Author 모델을 앞선 튜토리얼에 따라 만들었다면 아마 다음과 같이 <code>date_of_death</code> 라벨에 대한 에러가 나올 것입니다. 이 테스트는 라벨명이 라벨의 첫글자를 대문자로 처리하지 않는 장고의 컨벤션에 따라 생성된 것을 가정했기 때문에 실패합니다.</p> + +<pre class="brush: bash notranslate">====================================================================== +FAIL: test_date_of_death_label (catalog.tests.test_models.AuthorModelTest) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "D:\...\locallibrary\catalog\tests\test_models.py", line 32, in test_date_of_death_label + self.assertEquals(field_label,'died') +AssertionError: 'Died' != 'died' +- Died +? ^ ++ died +? ^</pre> + +<p>이는 아주 사소한 버그이지만, 테스트를 작성하는 것이 코드를 작성할 때 하는 가정들을 얼마나 꼼꼼히 체크하는지를 보여줍니다. </p> + +<div class="note"> +<p><strong>Note: </strong> date_of_death 필드의 라벨을 (/catalog/models.py) "died" 로 바꾸고 테스트를 다시 돌려보세요.</p> +</div> + +<p>다른 모델을 테스트하는 패턴도 이와 비슷하므로 여기서는 더 다루지 않겠습니다. 다른 모델에 대한 테스트 코드도 직접 작성해 보세요.</p> + +<h3 id="Forms">Forms</h3> + +<p>Forms를 테스트 하는 것은 모델을 테스트 하는 것과 비슷합니다; 당신이 만들고 디자인한 세세한 모든 것들은 테스트가 필요하며, 프레임워크나 써드 파티 라이브러리 등에 대해서는 테스트를 작성하지 않아도 좋습니다.</p> + +<p>따라서 폼에 대한 테스트 코드를 작성할 때는 보통 폼이 우리가 원하는 필드를 가지고 있는지, 그리고 이들 필드들이 적절한 라벨과 도움말과 함께 나타나는지를 테스트하면 됩니다. 직접 별도의 필드와 검증 로직을 작성하지 않은 이상 장고가 필드 타입을 제대로 검증하는지는 테스트 하지 않아도 됩니다 - 예를 들어 이메일 필드가 정말로 이메일 주소 값만을 받아들이는지 직접 테스트 할 필요가 없습니다. 하지만 필드에 대한 다른 추가적인 유효성 검증 로직과 다른 에러 메시지에 대해서는 테스트가 필요합니다.</p> + +<p>책 정보를 갱신하기 위한 우리의 Form을 생각해봅시다. 이 Form은 갱신 날짜를 위한 하나의 필드를 가지고 있으며 해당 필드는 우리가 테스트해야 할 라벨과 도움말을 가지고 있습니다.</p> + +<pre class="brush: python notranslate">class RenewBookForm(forms.Form): + """Form for a librarian to renew books.""" + renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).") + + def clean_renewal_date(self): + data = self.cleaned_data['renewal_date'] + + # Check if a date is not in the past. + if data < datetime.date.today(): + raise ValidationError(_('Invalid date - renewal in past')) + + # Check if date is in the allowed range (+4 weeks from today). + if data > datetime.date.today() + datetime.timedelta(weeks=4): + raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead')) + + # Remember to always return the cleaned data. + return data</pre> + +<p><strong>/catalog/tests/test_forms.py</strong> 을 열어서 기존 코드를 아래의 <code>RenewBookForm</code> 테스트 코드로 바꿔주세요. 아래 코드는 우선 우리의 Form과 시간에 관련한 로직 테스트를 도와줄 파이썬 및 장고 라이브러리를 불러옵니다. 그리고 우리는 모델을 테스트 했을 때와 마찬가지로 Form 테스트 클래스를 선언하고, 마찬가지로 테스트의 목적과 기능을 알려주는 이름을 지어줬습니다.</p> + +<pre class="brush: python notranslate">import datetime + +from django.test import TestCase +from django.utils import timezone + +from catalog.forms import RenewBookForm + +class RenewBookFormTest(TestCase): + def test_renew_form_date_field_label(self): + form = RenewBookForm() + self.assertTrue(form.fields['renewal_date'].label == None or form.fields['renewal_date'].label == 'renewal date') + + def test_renew_form_date_field_help_text(self): + form = RenewBookForm() + self.assertEqual(form.fields['renewal_date'].help_text, 'Enter a date between now and 4 weeks (default 3).') + + def test_renew_form_date_in_past(self): + date = datetime.date.today() - datetime.timedelta(days=1) + form = RenewBookForm(data={'renewal_date': date}) + self.assertFalse(form.is_valid()) + + def test_renew_form_date_too_far_in_future(self): + date = datetime.date.today() + datetime.timedelta(weeks=4) + datetime.timedelta(days=1) + form = RenewBookForm(data={'renewal_date': date}) + self.assertFalse(form.is_valid()) + + def test_renew_form_date_today(self): + date = datetime.date.today() + form = RenewBookForm(data={'renewal_date': date}) + self.assertTrue(form.is_valid()) + + def test_renew_form_date_max(self): + date = timezone.now() + datetime.timedelta(weeks=4) + form = RenewBookForm(data={'renewal_date': date}) + self.assertTrue(form.is_valid())</pre> + +<p>앞의 두 함수는 필드의 라벨과 도움말이 예상대로 생성되었는지를 확인합니다. 우리는 필드를 fields 딕셔너리를 통해서 접근했습니다 (e.g. <code>form.fields['renewal_date']</code>). 라벨의 값이 <code>None</code> 인지도 확인해야하는 것을 기억해 두세요. 장고가 올바른 라벨을 렌더하더라도 만약 라벨의 값이 명시적으로 정해지지 않았다면 <code>None</code> 이 반환됩니다.</p> + +<p>나머지 함수들은 폼이 적절한 구간 내에 있는 갱신 일자를 수락하는지와 더불어 부적합한 구간에 있는 일자를 거절하는지를 테스트 합니다. 우리가 테스트용 날짜들을 <code>datetime.timedelta()</code> (몇 일이나 몇 주를 나타냅니다)을 이용해서 현재 날짜 근처로(<code>datetime.date.today()</code> ) 생성하는 것을 기억해두세요. 그리고나서 우리는 폼을 만들고, 데이터를 집어넣고, 데이터가 유효한지를 테스트합니다.</p> + +<div class="note"> +<p><strong>Note:</strong> 여기서 우리는 데이터베이스나 테스트 클라이언트를 사용하지 않습니다. <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#django.test.SimpleTestCase">SimpleTestCase</a>를 이용해서 테스트 클래스를 만드는 것을 고려해보세요.</p> + +<p>또한 우리는 만약 폼이 유효하지 않을 경우 적절한 에러가 발생하는지 역시 검증해야 합니다. 하지만 보통 이 부분은 view processing의 일부로 처리되기 때문에 다음 섹션에서 다루도록 하겠습니다.</p> +</div> + +<p>이것이 form에 대한 전부입니다. 비록 다른 폼들이 있지만, 이들은 우리의 클래스 기반 편집 뷰에 의해 생성된 것들이기 때문에 그쪽에서 테스트 되어야 합니다. 테스트를 실행하고 우리의 코드가 여전히 테스트를 통과하는지 확인해 보세요.</p> + +<h3 id="Views">Views</h3> + +<p>우리의 뷰 동작을 유효하게 하기 위해서 우리는 Django test <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#django.test.Client">Client</a>를 사용합니다. 이 클래스는 더미 웹 브라우저와 같이 동작하는데, 우리는 URL의 <code>GET</code> 과 <code>POST</code> 요청을 동시에 하여 그 반응을 살핍니다. 우리는 저수준의 HTTP (응답의 헤더와 상태 코드) 부터 HTML을 렌더하기 위한 템플릿, 그리고 우리가 템플릿에 입력하는 컨텍스트 데이터까지 응답에 대한 거의 모든 것을 확인할 수 있습니다. 또한 우리는 (만약 이뤄진다면) redirect가 진행되는 단계와 각 단계에 대한 URL 및 상태 코드 역시 확인할 수 있습니다. 이를 통해서 우리는 각 뷰가 예상된 대로 동작하는지를 확인할 수 있습니다.</p> + +<p>우리의 가장 간단한 뷰 중 하나인 모든 저자들의 목록을 반환하는 뷰부터 시작해봅시다. 이 뷰는 <strong>/catalog/authors/</strong> URL(URL 설정 상에서 'authors'로 명명되었습니다) 에서 출력됩니다.</p> + +<pre class="brush: python notranslate">class AuthorListView(generic.ListView): + model = Author + paginate_by = 10</pre> + +<p>이 뷰가 generic list 뷰이다 보니 거의 모든 것이 장고에 의해서 처리됩니다. 만약 장고를 믿는다면 우리는 오로지 뷰가 올바른 URL과 뷰 이름으로 접근 가능한지만 테스트하면 됩니다. 하지만 만약 테스트 주도 방법론 (TDD)를 따른다면 우리는 (코드를 작성하기 전에) 뷰가 모든 저자들을 10명 씩 paginate해서 보여주는지를 확인하는 테스트부터 작성해야 합니다.</p> + +<p><strong>/catalog/tests/test_views.py</strong>를 열어서 기존 코드를 다음의 <code>AuthorListView</code> 테스트 코드로 바꿔주세요. 앞서와 마찬가지로 우리 모델과 유용한 클래스들을 불러옵니다.<code>setUpTestData()</code> 에서는 pagination을 테스트하기 위해 <code>Author</code> 객체 여러 개를 생성합니다.</p> + +<pre class="brush: python notranslate">from django.test import TestCase +from django.urls import reverse + +from catalog.models import Author + +class AuthorListViewTest(TestCase): + @classmethod + def setUpTestData(cls): + # Create 13 authors for pagination tests + number_of_authors = 13 + + for author_id in range(number_of_authors): + Author.objects.create( + first_name=f'Christian {author_id}', + last_name=f'Surname {author_id}', + ) + + def test_view_url_exists_at_desired_location(self): + response = self.client.get('/catalog/authors/') + self.assertEqual(response.status_code, 200) + + def test_view_url_accessible_by_name(self): + response = self.client.get(reverse('authors')) + self.assertEqual(response.status_code, 200) + + def test_view_uses_correct_template(self): + response = self.client.get(reverse('authors')) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'catalog/author_list.html') + + def test_pagination_is_ten(self): + response = self.client.get(reverse('authors')) + self.assertEqual(response.status_code, 200) + self.assertTrue('is_paginated' in response.context) + self.assertTrue(response.context['is_paginated'] == True) + self.assertTrue(len(response.context['author_list']) == 10) + + def test_lists_all_authors(self): + # Get second page and confirm it has (exactly) remaining 3 items + response = self.client.get(reverse('authors')+'?page=2') + self.assertEqual(response.status_code, 200) + self.assertTrue('is_paginated' in response.context) + self.assertTrue(response.context['is_paginated'] == True) + self.assertTrue(len(response.context['author_list']) == 3)</pre> + +<p>모든 테스트는 <code>TestCase</code>를 상속한 우리 테스트 클래스에 있는 클라이언트를 이용해서 <code>GET</code> 요청을 하고 그에 따른 응답을 받습니다. 첫번째 테스트는 특정 URL (도메인이 아닌 상대 경로임을 기억하세요) 을 확인하고 두번째 테스트는 URL 설정에서 설정해준 뷰의 이름에서 얻은 URL을 확인합니다.</p> + +<pre class="brush: python notranslate">response = self.client.get('/catalog/authors/') +response = self.client.get(reverse('authors'))</pre> + +<p>응답을 받으면 우리는 응답에서 상태 코드, 사용된 템플릿, pagination이 되었는지 여부, 반환된 객체의 갯수, 그리고 전체 아이템의 갯수를 확인합니다.</p> + +<p>우리가 검증하는 변수 중 가장 흥미로운 변수는 바로 <code>response.context</code>로 뷰에 의해서 템플릿에 전달되는 context 변수입니다. 이는 템플릿이 필요한 모든 데이터를 받는지를 검증할 수 있게 해주기 때문에 테스팅에 정말 유용합니다. 즉 우리는 어떤 템플릿이 사용되고 또 어떤 데이터가 템플릿에 전달되는지를 확인할 수 있기 때문에 자신있게 렌더링에 관한 나머지 문제들은 오로지 템플릿의 문제라고 생각할 수 있습니다.</p> + +<h4 id="로그인한_사용자에게만_보이는_뷰">로그인한 사용자에게만 보이는 뷰</h4> + +<p>종종 우리는 로그인 한 사용자에게만 보이는 뷰를 테스트하고 싶습니다. 예를 들어서 <code>LoanedBooksByUserListView</code>는 방금 테스트한 뷰와 비슷하지만 로그인한 유저만 접근할 수 있으며, 현재 유저가 대출한, '대출 중' 상태의 <code>BookInstance</code> 만 보여주며 가장 오래된 것 부터 먼저 보여줍니다.</p> + +<pre class="brush: python notranslate">from django.contrib.auth.mixins import LoginRequiredMixin + +class LoanedBooksByUserListView(LoginRequiredMixin, generic.ListView): + """Generic class-based view listing books on loan to current user.""" + model = BookInstance + template_name ='catalog/bookinstance_list_borrowed_user.html' + paginate_by = 10 + + def get_queryset(self): + return BookInstance.objects.filter(borrower=self.request.user).filter(status__exact='o').order_by('due_back')</pre> + +<p>아래 코드를 <strong>/catalog/tests/test_views.py </strong> 에 추가해주세요. 여기서 우리는 <code>SetUp()</code> 을 이용해서 계정을 생성한 다음 테스트에 사용할 <code>BookInstance</code> 객체와 관련된 책 및 기타 정보를 생성합니다. 각각의 테스트 계정에 의해서 책이 반반씩 대출되었지만 일단 우리는 모든 책의 상태를 "maintenance"로 설정합니다. 우리는 테스트 하면서 이들 객체들을 수정할 것이기 때문에 <code>setUpTestData()</code> 대신 <code>SetUp()</code>을 사용했습니다.</p> + +<div class="note"> +<p><strong>Note:</strong> 아래의 <code>setUp()</code> 코드는 특정 <code>Language</code>의 책을 생성하지만, <code>Language</code> 모델이 이전 튜토리얼에서 도전 과제로 생성되었기 때문에 여러분의 코드에는 존재하지 않을 수 있습니다. 이때는 간단히 <code>Language</code> 객체를 불러오거나 생성하는 코드를 주석처리 해주세요. 같은 작업을 곧 나올 <code>RenewBookInstancesViewTest</code> 에도 해줘야 합니다.</p> +</div> + +<pre class="brush: python notranslate">import datetime + +from django.utils import timezone +from django.contrib.auth.models import User # Required to assign User as a borrower + +from catalog.models import BookInstance, Book, Genre, Language + +class LoanedBookInstancesByUserListViewTest(TestCase): + def setUp(self): + # Create two users + test_user1 = User.objects.create_user(username='testuser1', password='1X<ISRUkw+tuK') + test_user2 = User.objects.create_user(username='testuser2', password='2HJ1vRV0Z&3iD') + + test_user1.save() + test_user2.save() + + # Create a book + test_author = Author.objects.create(first_name='John', last_name='Smith') + test_genre = Genre.objects.create(name='Fantasy') + test_language = Language.objects.create(name='English') + test_book = Book.objects.create( + title='Book Title', + summary='My book summary', + isbn='ABCDEFG', + author=test_author, + language=test_language, + ) + + # Create genre as a post-step + genre_objects_for_book = Genre.objects.all() + test_book.genre.set(genre_objects_for_book) # Direct assignment of many-to-many types not allowed. + test_book.save() + + # Create 30 BookInstance objects + number_of_book_copies = 30 + for book_copy in range(number_of_book_copies): + return_date = timezone.now() + datetime.timedelta(days=book_copy%5) + the_borrower = test_user1 if book_copy % 2 else test_user2 + status = 'm' + BookInstance.objects.create( + book=test_book, + imprint='Unlikely Imprint, 2016', + due_back=return_date, + borrower=the_borrower, + status=status, + ) + + def test_redirect_if_not_logged_in(self): + response = self.client.get(reverse('my-borrowed')) + self.assertRedirects(response, '/accounts/login/?next=/catalog/mybooks/') + + def test_logged_in_uses_correct_template(self): + login = self.client.login(username='testuser1', password='1X<ISRUkw+tuK') + response = self.client.get(reverse('my-borrowed')) + + # Check our user is logged in + self.assertEqual(str(response.context['user']), 'testuser1') + # Check that we got a response "success" + self.assertEqual(response.status_code, 200) + + # Check we used correct template + self.assertTemplateUsed(response, 'catalog/bookinstance_list_borrowed_user.html')</pre> + +<p>뷰가 로그인하지 않은 사용자를 로그인 화면으로 redirect 하는 것을 확인하기 위해 우리는 <code>assertRedirects</code>를 사용함을 <code>test_redirect_if_not_logged_in()</code>에서 확인할 수 있습니다. 페이지가 로그인 한 사용자에게 보임을 확인하기 위해서 우리는 테스트 유저로 로그인을 한 후에 페이지에 접근해서 응답의 상태코드가 200번임을 확인합니다 (성공을 의미).</p> + +<p>나머지 테스트 코드는 뷰가 현재 로그인 한 사용자가 대출한 책만을 반환하는지를 검증합니다. 아래의 코드를 위의 테스트 클래스 아래에 붙여 넣어 주세요.</p> + +<pre class="brush: python notranslate"> def test_only_borrowed_books_in_list(self): + login = self.client.login(username='testuser1', password='1X<ISRUkw+tuK') + response = self.client.get(reverse('my-borrowed')) + + # Check our user is logged in + self.assertEqual(str(response.context['user']), 'testuser1') + # Check that we got a response "success" + self.assertEqual(response.status_code, 200) + + # Check that initially we don't have any books in list (none on loan) + self.assertTrue('bookinstance_list' in response.context) + self.assertEqual(len(response.context['bookinstance_list']), 0) + + # Now change all books to be on loan + books = BookInstance.objects.all()[:10] + + for book in books: + book.status = 'o' + book.save() + + # Check that now we have borrowed books in the list + response = self.client.get(reverse('my-borrowed')) + # Check our user is logged in + self.assertEqual(str(response.context['user']), 'testuser1') + # Check that we got a response "success" + self.assertEqual(response.status_code, 200) + + self.assertTrue('bookinstance_list' in response.context) + + # Confirm all books belong to testuser1 and are on loan + for bookitem in response.context['bookinstance_list']: + self.assertEqual(response.context['user'], bookitem.borrower) + self.assertEqual('o', bookitem.status) + + def test_pages_ordered_by_due_date(self): + # Change all books to be on loan + for book in BookInstance.objects.all(): + book.status='o' + book.save() + + login = self.client.login(username='testuser1', password='1X<ISRUkw+tuK') + response = self.client.get(reverse('my-borrowed')) + + # Check our user is logged in + self.assertEqual(str(response.context['user']), 'testuser1') + # Check that we got a response "success" + self.assertEqual(response.status_code, 200) + + # Confirm that of the items, only 10 are displayed due to pagination. + self.assertEqual(len(response.context['bookinstance_list']), 10) + + last_date = 0 + for book in response.context['bookinstance_list']: + if last_date == 0: + last_date = book.due_back + else: + self.assertTrue(last_date <= book.due_back) + last_date = book.due_back</pre> + +<p>당신은 pagination 역시 테스트 할 수 있습니다. 한 번 해보셨으면 좋겠습니다 :)</p> + +<h4 id="Form을_이용하는_view를_테스트하기">Form을 이용하는 view를 테스트하기</h4> + +<p>Form을 이용하는 뷰를 테스트하는 것은 앞선 경우에 비해 살짝 더 복잡합니다. 왜냐면 우리는 코드의 더 다양한 부분을 - 첫 화면, 데이터 유효성 검증이 실패한 화면, 데이터 유효성 검증이 성공한 화면 - 모두를 테스트해야 하기 때문입니다. 다행히 우리는 데이터를 보여주기만 하는 뷰를 테스트 할 때 사용했던 클라이언트를 거의 그대로 사용할 수 있습니다.</p> + +<p>대출을 갱신하기 위한 뷰를 테스트하기 위한 코드를 짜봅시다 (<code>renew_book_librarian()</code>):</p> + +<pre class="brush: python notranslate">from catalog.forms import RenewBookForm + +@permission_required('catalog.can_mark_returned') +def renew_book_librarian(request, pk): + """View function for renewing a specific BookInstance by librarian.""" + book_instance = get_object_or_404(BookInstance, pk=pk) + + # If this is a POST request then process the Form data + if request.method == 'POST': + + # Create a form instance and populate it with data from the request (binding): + book_renewal_form = RenewBookForm(request.POST) + + # Check if the form is valid: + if form.is_valid(): + # process the data in form.cleaned_data as required (here we just write it to the model due_back field) + book_instance.due_back = form.cleaned_data['renewal_date'] + book_instance.save() + + # redirect to a new URL: + return HttpResponseRedirect(reverse('all-borrowed')) + + # If this is a GET (or any other method) create the default form + else: + proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3) + book_renewal_form = RenewBookForm(initial={'renewal_date': proposed_renewal_date}) + + context = { + 'book_renewal_form': book_renewal_form, + 'book_instance': book_instance, + } + + return render(request, 'catalog/book_renew_librarian.html', context)</pre> + +<p>이제 우리는 <code>can_mark_returned</code> permission을 가진 사용자만이 view를 사용할 수 있는지, 그리고 그 사용자들이 가지고 있지 않은 <code>BookInstance</code> 을 수정하려고 시도하면 HTTP 404 에러 페이지로 리다이렉트 되는지 테스트해봐야 합니다. We should check that the initial value of the form is seeded with a date three weeks in the future, and that if validation succeeds we're redirected to the "all-borrowed books" view. As part of checking the validation-fail tests we'll also check that our form is sending the appropriate error messages.</p> + +<p>Add the first part of the test class (shown below) to the bottom of <strong>/catalog/tests/test_views.py</strong>. This creates two users and two book instances, but only gives one user the permission required to access the view. The code to grant permissions during tests is shown in bold:</p> + +<pre class="brush: python notranslate">import uuid + +from django.contrib.auth.models import Permission # Required to grant the permission needed to set a book as returned. + +class RenewBookInstancesViewTest(TestCase): + def setUp(self): + # Create a user + test_user1 = User.objects.create_user(username='testuser1', password='1X<ISRUkw+tuK') + test_user2 = User.objects.create_user(username='testuser2', password='2HJ1vRV0Z&3iD') + + test_user1.save() + test_user2.save() + +<strong> permission = Permission.objects.get(name='Set book as returned') + test_user2.user_permissions.add(permission) + test_user2.save()</strong> + + # Create a book + test_author = Author.objects.create(first_name='John', last_name='Smith') + test_genre = Genre.objects.create(name='Fantasy') + test_language = Language.objects.create(name='English') + test_book = Book.objects.create( + title='Book Title', + summary='My book summary', + isbn='ABCDEFG', + author=test_author, + language=test_language, + ) + + # Create genre as a post-step + genre_objects_for_book = Genre.objects.all() + test_book.genre.set(genre_objects_for_book) # Direct assignment of many-to-many types not allowed. + test_book.save() + + # Create a BookInstance object for test_user1 + return_date = datetime.date.today() + datetime.timedelta(days=5) + self.test_bookinstance1 = BookInstance.objects.create( + book=test_book, + imprint='Unlikely Imprint, 2016', + due_back=return_date, + borrower=test_user1, + status='o', + ) + + # Create a BookInstance object for test_user2 + return_date = datetime.date.today() + datetime.timedelta(days=5) + self.test_bookinstance2 = BookInstance.objects.create( + book=test_book, + imprint='Unlikely Imprint, 2016', + due_back=return_date, + borrower=test_user2, + status='o', + )</pre> + +<p>Add the following tests to the bottom of the test class. These check that only users with the correct permissions (<em>testuser2</em>) can access the view. We check all the cases: when the user is not logged in, when a user is logged in but does not have the correct permissions, when the user has permissions but is not the borrower (should succeed), and what happens when they try to access a <code>BookInstance</code> that doesn't exist. We also check that the correct template is used.</p> + +<pre class="brush: python notranslate"> def test_redirect_if_not_logged_in(self): + response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk})) + # Manually check redirect (Can't use assertRedirect, because the redirect URL is unpredictable) + self.assertEqual(response.status_code, 302) + self.assertTrue(response.url.startswith('/accounts/login/')) + + def test_redirect_if_logged_in_but_not_correct_permission(self): + login = self.client.login(username='testuser1', password='1X<ISRUkw+tuK') + response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk})) + + # Manually check redirect (Can't use assertRedirect, because the redirect URL is unpredictable) + self.assertEqual(response.status_code, 302) + self.assertTrue(response.url.startswith('/accounts/login/')) + + def test_logged_in_with_permission_borrowed_book(self): + login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD') + response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance2.pk})) + + # Check that it lets us login - this is our book and we have the right permissions. + self.assertEqual(response.status_code, 200) + + def test_logged_in_with_permission_another_users_borrowed_book(self): + login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD') + response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk})) + + # Check that it lets us login. We're a librarian, so we can view any users book + self.assertEqual(response.status_code, 200) + + def test_HTTP404_for_invalid_book_if_logged_in(self): + # unlikely UID to match our bookinstance! + test_uid = uuid.uuid4() + login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD') + response = self.client.get(reverse('renew-book-librarian', kwargs={'pk':test_uid})) + self.assertEqual(response.status_code, 404) + + def test_uses_correct_template(self): + login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD') + response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk})) + self.assertEqual(response.status_code, 200) + + # Check we used correct template + self.assertTemplateUsed(response, 'catalog/book_renew_librarian.html') +</pre> + +<p>아래에 보이는 것처럼 새로운 테스트 방법을 추가해봅시다. 이것은 form의 초기 날짜가 3주 후인지를 확인합니다. 어떻게 우리가 form 필드의 첫 번째 값에 접근하는 것이 가능한지 참고해보세요. (굵게 표시된 부분에서 확인할 수 있습니다.)</p> + +<pre class="brush: python notranslate"> def test_form_renewal_date_initially_has_date_three_weeks_in_future(self): + login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD') + response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk})) + self.assertEqual(response.status_code, 200) + + date_3_weeks_in_future = datetime.date.today() + datetime.timedelta(weeks=3) + self.assertEqual(response<strong>.context['form'].initial['renewal_date']</strong>, date_3_weeks_in_future)</pre> + +<div class="warning"> +<p>만약 당신이 class <code>RenewBookForm(forms.Form)</code> 대신에 class <code>RenewBookModelForm(forms.ModelForm)</code> 을 사용한다면, form의 필드명은 <strong>'renewal_date' </strong>대신 <strong>'due_back'</strong>으로 나타날 것입니다.</p> +</div> + +<p>The next test (add this to the class too) checks that the view redirects to a list of all borrowed books if renewal succeeds. What differs here is that for the first time we show how you can <code>POST</code> data using the client. The post <em>data</em> is the second argument to the post function, and is specified as a dictionary of key/values.</p> + +<pre class="brush: python notranslate"> def test_redirects_to_all_borrowed_book_list_on_success(self): + login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD') + valid_date_in_future = datetime.date.today() + datetime.timedelta(weeks=2) + <strong>response = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':valid_date_in_future})</strong> + self.assertRedirects(response, reverse('all-borrowed')) +</pre> + +<div class="warning"> +<p>The <em>all-borrowed</em> view was added as a <em>challenge</em>, and your code may instead redirect to the home page '/'. If so, modify the last two lines of the test code to be like the code below. The <code>follow=True</code> in the request ensures that the request returns the final destination URL (hence checking <code>/catalog/</code> rather than <code>/</code>).</p> + +<pre class="brush: python notranslate"> response = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':valid_date_in_future}, <strong>follow=True</strong> ) + <strong>self.assertRedirects(</strong><strong>response, '/catalog/')</strong></pre> +</div> + +<p>Copy the last two functions into the class, as seen below. These again test <code>POST</code> requests, but in this case with invalid renewal dates. We use <code>assertFormError() </code>to verify that the error messages are as expected.</p> + +<pre class="brush: python notranslate"> def test_form_invalid_renewal_date_past(self): + login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD') + date_in_past = datetime.date.today() - datetime.timedelta(weeks=1) + response = self.client.post(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}), {'renewal_date': date_in_past}) + self.assertEqual(response.status_code, 200) + <strong>self.assertFormError(</strong><strong>response, 'form', 'renewal_date', 'Invalid date - renewal in past')</strong> + + def test_form_invalid_renewal_date_future(self): + login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD') + invalid_date_in_future = datetime.date.today() + datetime.timedelta(weeks=5) + response = self.client.post(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}), {'renewal_date': invalid_date_in_future}) + self.assertEqual(response.status_code, 200) + <strong>self.assertFormError(</strong><strong>response, 'form', 'renewal_date', 'Invalid date - renewal more than 4 weeks ahead')</strong> +</pre> + +<p>The same sorts of techniques can be used to test the other view.</p> + +<h3 id="Templates">Templates</h3> + +<p>Django provides test APIs to check that the correct template is being called by your views, and to allow you to verify that the correct information is being sent. There is however no specific API support for testing in Django that your HTML output is rendered as expected.</p> + +<h2 id="Other_recommended_test_tools">Other recommended test tools</h2> + +<p>Django's test framework can help you write effective unit and integration tests — we've only scratched the surface of what the underlying <strong>unittest</strong> framework can do, let alone Django's additions (for example, check out how you can use <a href="https://docs.python.org/3.5/library/unittest.mock-examples.html">unittest.mock</a> to patch third party libraries so you can more thoroughly test your own code).</p> + +<p>While there are numerous other test tools that you can use, we'll just highlight two:</p> + +<ul> + <li><a href="http://coverage.readthedocs.io/en/latest/">Coverage</a>: This Python tool reports on how much of your code is actually executed by your tests. It is particularly useful when you're getting started, and you are trying to work out exactly what you should test.</li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">Selenium</a> is a framework to automate testing in a real browser. It allows you to simulate a real user interacting with the site, and provides a great framework for system testing your site (the next step up from integration testing).</li> +</ul> + +<h2 id="Challenge_yourself">Challenge yourself</h2> + +<p>There are a lot more models and views we can test. As a simple task, try to create a test case for the <code>AuthorCreate</code> view.</p> + +<pre class="brush: python notranslate">class AuthorCreate(PermissionRequiredMixin, CreateView): + model = Author + fields = '__all__' + initial = {'date_of_death':'12/10/2016'} + permission_required = 'catalog.can_mark_returned'</pre> + +<p>Remember that you need to check anything that you specify or that is part of the design. This will include who has access, the initial date, the template used, and where the view redirects on success.</p> + +<h2 id="Summary">Summary</h2> + +<p>Writing test code is neither fun nor glamorous, and is consequently often left to last (or not at all) when creating a website. It is however an essential part of making sure that your code is safe to release after making changes, and cost-effective to maintain.</p> + +<p>In this tutorial we've shown you how to write and run tests for your models, forms, and views. Most importantly we've provided a brief summary of what you should test, which is often the hardest thing to work out when you're getting started. There is a lot more to know, but even with what you've learned already you should be able to create effective unit tests for your websites.</p> + +<p>The next and final tutorial shows how you can deploy your wonderful (and fully tested!) Django website.</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/testing/overview/">Writing and running tests</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/intro/tutorial05/">Writing your first Django app, part 5 > Introducing automated testing</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/">Testing tools reference</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/testing/advanced/">Advanced testing topics</a> (Django docs)</li> + <li><a href="http://toastdriven.com/blog/2011/apr/10/guide-to-testing-in-django/">A Guide to Testing in Django</a> (Toast Driven Blog, 2011)</li> + <li><a href="http://test-driven-django-development.readthedocs.io/en/latest/index.html">Workshop: Test-Driven Web Development with Django</a> (San Diego Python, 2014)</li> + <li><a href="https://realpython.com/blog/python/testing-in-django-part-1-best-practices-and-examples/">Testing in Django (Part 1) - Best Practices and Examples</a> (RealPython, 2013)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Forms", "Learn/Server-side/Django/Deployment", "Learn/Server-side/Django")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> diff --git a/files/ko/learn/server-side/django/tutorial_local_library_website/index.html b/files/ko/learn/server-side/django/tutorial_local_library_website/index.html new file mode 100644 index 0000000000..6cad27324e --- /dev/null +++ b/files/ko/learn/server-side/django/tutorial_local_library_website/index.html @@ -0,0 +1,92 @@ +--- +title: 'Django 튜토리얼: 지역 도서관 웹사이트' +slug: Learn/Server-side/Django/Tutorial_local_library_website +translation_of: Learn/Server-side/Django/Tutorial_local_library_website +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/development_environment", "Learn/Server-side/Django/skeleton_website", "Learn/Server-side/Django")}}</div> + +<p class="summary">이번은 실전 튜토리얼 시리즈의 첫번째 파트로서 당신이 배울 내용을 설명하고, 이어지는 튜토리얼 시리즈에서 개발하고 개선시켜볼 "지역 도서관" 예제 웹사이트에 대한 개요를 제공한다.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">사전학습:</th> + <td><a href="/ko/docs/Learn/Server-side/Django/Introduction">Django 소개</a> 파트를 읽으세요. 이어지는 파트를 위해서는 <a href="/ko/docs/Learn/Server-side/Django/development_environment">Django개발 환경 설치하기</a> 파트도 필요합니다.</td> + </tr> + <tr> + <th scope="row">학습목표:</th> + <td>이 튜토리얼에서 사용될 예제 어플리케이션을 소개하고, 여기서 논의될 토픽들의 범위에 대해 이해하기</td> + </tr> + </tbody> +</table> + +<h2 id="개요">개요</h2> + +<p>MDN "로컬 라이브러리" 장고 튜토리얼에서 오신 것을 환영합니다. 여기에서는 , "로컬 라이브러리" 카탈로그를 운영하는데 사용될 수 있는 웹사이트를 개발할 것이다. </p> + +<p>이 튜토리얼 시리즈는 아래 내용을 다룬다:</p> + +<ul> + <li>웹 애플리케이션의 골격을 만들기 위해 장고 도구 사용하기</li> + <li>개발 서버 시작하기와 끝내기</li> + <li>애플리케이션 데이터의 틀이 되는 모델 생성하기</li> + <li>데이터를 입력하기 위해서 장고 관리자(admin) 사이트 사용하기</li> + <li>여러가지 요청에 따른 특정 데이터를 가져오는 뷰(view)와 브라우저상에서 이 데이타를 볼수 있도록 HTML로 렌더링하는 템플릿을 생성하기</li> + <li>여러가지 URL 패턴과 특정한 뷰를 연결하는 맵퍼(mappers) 만들기</li> + <li>사이트 동작과 접속을 통제하기 위한 유저 인증(authorization) & 세션 추가하기</li> + <li>폼으로 작업하기</li> + <li>앱을 테스트할 코드 작성하기</li> + <li>장고의 보안도구를 효과적으로 사용하기</li> + <li>애플리케이션을 운영환경에 배포하기</li> +</ul> + +<p>여러분은 이 토픽들 중 일부는 이미 배웠고, 나머지는 가볍게 경험했다. 이 튜토리얼 시리즈를 완료하면, 여러분은 간단한 장고 앱을 혼자서 충분히 개발할 수 있다. </p> + +<h2 id="LocalLibrary_웹사이트">LocalLibrary 웹사이트</h2> + +<p><em>LocalLibrary는 이 튜토리얼 시리즈에서 우리가 만들고 개선시켜나갈 웹사이트의 이름이다.</em> 이름에서 예상되듯이, 이용자들이 대여 가능한 책을 찾아보고 사용자 계정을 관리할 수 있는, 작은 지역 도서관을 위한 온라인 도서목록을 제공하는 것이 목적이다.</p> + +<p>이 예제는, 우리가 필요에 따라 크게 혹은 작게 확장할 수 있고, 대부분은 장고의 특성을 보여줄 수 있도록 아주 신중하게 선택된 예제이다. 더욱 중요한 것은 이 예제는 장고 웹 프레임워크의 가장 중요한 기능들을 경험해 보도록 안내된 경로를 제공한다:</p> + +<ul> + <li>처음 몇몇의 튜토리얼에서, 사용자가 어떤 책을 이용할 수 있는지 찾아볼 수 있도록, 간단한 둘러보기 전용 도서관 기능을 정의할 것이다. 이 내용은 거의 모든 웹사이트에서 일반적으로 제공되는 동작(데이타베이스에서 내용을 읽고 보여주는 것)을 탐색해볼 수 있도록 해줄 것이다.</li> + <li>튜토리얼을 좀 더 진행해 가면서, 도서관 예제는 좀 더 고급의 장고 기능을 보여줄 수 있도록 자연스럽게 확장된다. 예를 들면, 사용자가 책을 예약하도록 기능을 확장할 수 있고 이것을 이용해 폼을 사용하는 방법과 사용자 인증을 지원하는 방법을 보여줄 수 있다.</li> +</ul> + +<p>이것은 매우 확장성있는 예제이지만, 다음과 같은 이유로 <em><strong>Local</strong>Library(Local에 강조)로 이름을 지었다. 그 이유는, 당신이 장고 개발을 빠르게 착수할 수 있도록, 필요한 최소한의 정보만 보여주고자 의도한 것이다. 결과적으로 책, 책의 판본, 저자및 다른 Key 정보를 저장할 것이다. 하지만 그외의 일반적인 도서관이 추가로 저장할만한 정보는 저장하지 않을 것이며, 여러개의 도서관 사이트를 지원하거나, "커다란 도서관"을 위한 기능은 제공하지 않을것이다.</em></p> + +<h2 id="개발중에_막혔어요_소스코드는_어딨죠">개발중에 막혔어요, 소스코드는 어딨죠?</h2> + +<p>튜토리얼을 진행하면서, 각 포인트마다 복사해서 붙여넣기할 수 있는 적절한 토막 코드가 제공될 것이다. 또한 당신이 스스로 (약간의 안내문과 함께) 도전해볼 수 있는 부분도 있을 것이다.</p> + +<p>개발중에 진행이 어렵다면, 여기<a href="https://github.com/mdn/django-locallibrary-tutorial"> Github</a>에 완전히 개발된 버전의 웹사이트 소스코드를 참고할 수도 있다.</p> + +<h2 id="요약">요약</h2> + +<p>LocalLibrary 웹사이트와 당신이 앞으로 배울 내용에 대해 좀 더 알게되었다. 이제 우리 예제를 담을 <a href="/ko/docs/Learn/Server-side/Django/skeleton_website">뼈대 프로젝트(skeleton project)</a>를 생성해볼 차례이다.</p> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/development_environment", "Learn/Server-side/Django/skeleton_website", "Learn/Server-side/Django")}}</p> + + + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> diff --git a/files/ko/learn/server-side/django/web_application_security/index.html b/files/ko/learn/server-side/django/web_application_security/index.html new file mode 100644 index 0000000000..849f5bff3d --- /dev/null +++ b/files/ko/learn/server-side/django/web_application_security/index.html @@ -0,0 +1,176 @@ +--- +title: Django web application security +slug: Learn/Server-side/Django/web_application_security +translation_of: Learn/Server-side/Django/web_application_security +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Deployment", "Learn/Server-side/Django/django_assessment_blog", "Learn/Server-side/Django")}}</div> + +<p class="summary">사용자의 데이터를 보호하는 것은 모든 웹사이트 개발에서 중요한 부분입니다. 우리는 이전의 강의 <a href="https://developer.mozilla.org/en-US/docs/Web/Security">Web security</a> 에서 자주 나타나는 보안 위협에 대해 알아봤습니다. — 이번 강의에서는 장고의 내장 보안 기능이 이런 위험에 어떻게 대처하는지 실제 예시들을 통해 살펴보겠습니다. </p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">이 문서를 보기 전에: </th> + <td>서버사이드 개발에 대한 "<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Website_security">Website security</a>" 부분을 읽고 오세요. MDN 장고 튜토리얼에서 적어도 <a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a> 까지는 모두 알고있어야 합니다. </td> + </tr> + <tr> + <th scope="row">목적: </th> + <td>장고 웹사이트의 보안을 위해 해야 하는 (하지 말아야 하는) 것들에 대해 이해해 봅시다. </td> + </tr> + </tbody> +</table> + +<h2 id="개요">개요</h2> + +<p><a href="https://developer.mozilla.org/en-US/docs/Web/Security">Website security</a> 문서는 서버사이드 설계에서 웹사이트 보안이란 무엇을 이야기하는건지, 또한 개발자가 막아내야 하는 몇가지 대표적인 위협에 대해 소개합니다. 이 문서에서 중요한 내용중의 하나는 바로 웹 애플리케이션이 브라우저에서 전송된 데이터를 신뢰하는 경우에는 거의 모든 종류의 공격이 가능하다는 것입니다. </p> + +<div class="warning"> +<p><strong>Important:</strong> 웹사이트 보안에 대해 당신이 배울 수 있는 가장 중요한 점 한가지는 <strong>브라우저의 데이터를 절대로 믿지 말라는 것</strong> 입니다. 이건 URL 파라미터의 <code>GET</code> request 데이터, <code>POST </code>데이터, HTTP 헤더와 쿠키, 사용자가 업로드한 파일들...기타등등을 포함합니다. 언제나 전송받는 모든 데이터를 의심하고 체크하십시오. 언제나 최악을 가정하십시오.</p> +</div> + +<p>Django 사용자들에게 좋은 소식은 대부분의 일반적인 위협들은 프레임워크에 의해 차단된다는 것입니다! <a href="https://docs.djangoproject.com/en/2.0/topics/security/">Security in Django</a> 문서는 Django의 보안 개요와 Django 기반의 웹사이트를 어떻게 지킬 수 있는지에 대해 설명하고 있습니다.</p> + +<h2 id="Common_threatsprotections">Common threats/protections</h2> + +<p>Django 문서를 여기로 복사해오기보다, 이 문서에서 우리는 Django <a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 튜토리얼에 있는 Django의 몇가지 보안 형태를 묘사해보도록 하겠습니다.</p> + +<h3 id="Cross_site_scripting_XSS">Cross site scripting (XSS)</h3> + +<p>XSS is a term used to describe a class of attacks that allow an attacker to inject client-side scripts <em>through</em> the website into the browsers of other users. This is usually achieved by storing malicious scripts in the database where they can be retrieved and displayed to other users, or by getting users to click a link that will cause the attacker’s JavaScript to be executed by the user’s browser.</p> + +<p>Django's template system protects you against the majority of XSS attacks by <a href="https://docs.djangoproject.com/en/2.0/ref/templates/language/#automatic-html-escaping">escaping specific characters</a> that are "dangerous" in HTML. We can demonstrate this by attempting to inject some JavaScript into our LocalLibrary website using the Create-author form we set up in <a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a>.</p> + +<ol> + <li>Start the website using the development server (<code>python3 manage.py runserver</code>).</li> + <li>Open the site in your local browser and login to your superuser account.</li> + <li>Navigate to the author-creation page (which should be at URL: <code><a href="http://127.0.0.1:8000/catalog/author/create/">http://127.0.0.1:8000/catalog/author/create/</a></code>).</li> + <li>Enter names and date details for a new user, and then append the following text to the Last Name field:<br> + <code><script>alert('Test alert');</script></code>.<br> + <img alt="Author Form XSS test" src="https://mdn.mozillademos.org/files/14305/author_create_form_alert_xss.png" style="border-style: solid; border-width: 1px; height: 245px; width: 594px;"> + <div class="note"> + <p><strong>Note:</strong> This is a harmless script that, if executed, will display an alert box in your browser. If the alert is displayed when you submit the record then the site is vulnerable to XSS threats.</p> + </div> + </li> + <li>Press <strong>Submit</strong> to save the record.</li> + <li>When you save the author it will be displayed as shown below. Because of the XSS protections the <code>alert()</code> should not be run. Instead the script is displayed as plain text.<img alt="Author detail view XSS test" src="https://mdn.mozillademos.org/files/14307/author_detail_alert_xss.png" style="border-style: solid; border-width: 1px; height: 248px; width: 986px;"></li> +</ol> + +<p>If you view the page HTML source code, you can see that the dangerous characters for the script tags have been turned into their harmless escape code equivalents (e.g. <code>></code> is now <code>&gt;</code>)</p> + +<pre class="brush: html notranslate"><h1>Author: Boon&lt;script&gt;alert(&#39;Test alert&#39;);&lt;/script&gt;, David (Boonie) </h1> +</pre> + +<p>Using Django templates protects you against the majority of XSS attacks. However it is possible to turn off this protection, and the protection isn't automatically applied to all tags that wouldn't normally be populated by user input (for example, the <code>help_text</code> in a form field is usually not user-supplied, so Django doesn't escape those values).</p> + +<p>It is also possible for XSS attacks to originate from other untrusted source of data, such as cookies, Web services or uploaded files (whenever the data is not sufficiently sanitized before including in a page). If you're displaying data from these sources, then you may need to add your own sanitisation code.</p> + +<h3 id="Cross_site_request_forgery_CSRF_protection">Cross site request forgery (CSRF) protection</h3> + +<p>CSRF attacks allow a malicious user to execute actions using the credentials of another user without that user’s knowledge or consent. For example consider the case where we have a hacker who wants to create additional authors for our LocalLibrary.</p> + +<div class="note"> +<p><strong>Note:</strong> Obviously our hacker isn't in this for the money! A more ambitious hacker could use the same approach on other sites to perform much more harmful tasks (e.g. transfer money to their own accounts, etc.)</p> +</div> + +<p>In order to do this, they might create an HTML file like the one below, which contains an author-creation form (like the one we used in the previous section) that is submitted as soon as the file is loaded. They would then send the file to all the Librarians and suggest that they open the file (it contains some harmless information, honest!). If the file is opened by any logged in librarian, then the form would be submitted with their credentials and a new author would be created.</p> + +<pre class="brush: html notranslate"><html> +<body onload='document.EvilForm.submit()'> + +<form action="http://127.0.0.1:8000/catalog/author/create/" method="post" name='EvilForm'> + <table> + <tr><th><label for="id_first_name">First name:</label></th><td><input id="id_first_name" maxlength="100" name="first_name" type="text" value="Mad" required /></td></tr> + <tr><th><label for="id_last_name">Last name:</label></th><td><input id="id_last_name" maxlength="100" name="last_name" type="text" value="Man" required /></td></tr> + <tr><th><label for="id_date_of_birth">Date of birth:</label></th><td><input id="id_date_of_birth" name="date_of_birth" type="text" /></td></tr> + <tr><th><label for="id_date_of_death">Died:</label></th><td><input id="id_date_of_death" name="date_of_death" type="text" value="12/10/2016" /></td></tr> + </table> + <input type="submit" value="Submit" /> +</form> + +</body> +</html> +</pre> + +<p>Run the development web server, and log in with your superuser account. Copy the text above into a file and then open it in the browser. You should get a CSRF error, because Django has protection against this kind of thing!</p> + +<p>The way the protection is enabled is that you include the <code>{% csrf_token %}</code> template tag in your form definition. This token is then rendered in your HTML as shown below, with a value that is specific to the user on the current browser.</p> + +<pre class="brush: html notranslate"><input type='hidden' name='csrfmiddlewaretoken' value='0QRWHnYVg776y2l66mcvZqp8alrv4lb8S8lZ4ZJUWGZFA5VHrVfL2mpH29YZ39PW' /> +</pre> + +<p>Django generates a user/browser specific key and will reject forms that do not contain the field, or that contain an incorrect field value for the user/browser.</p> + +<p>To use this type of attack the hacker now has to discover and include the CSRF key for the specific target user. They also can't use the "scattergun" approach of sending a malicious file to all librarians and hoping that one of them will open it, since the CSRF key is browser specific.</p> + +<p>Django's CSRF protection is turned on by default. You should always use the <code>{% csrf_token %}</code> template tag in your forms and use <code>POST</code> for requests that might change or add data to the database.</p> + +<h3 id="Other_protections">Other protections</h3> + +<p>Django also provides other forms of protection (most of which would be hard or not particularly useful to demonstrate):</p> + +<dl> + <dt>SQL injection protection</dt> + <dd>SQL injection vulnerabilities enable malicious users to execute arbitrary SQL code on a database, allowing data to be accessed, modified, or deleted irrespective of the user's permissions. In almost every case you'll be accessing the database using Django’s querysets/models, so the resulting SQL will be properly escaped by the underlying database driver. If you do need to write raw queries or custom SQL then you'll need to explicitly think about preventing SQL injection.</dd> + <dt>Clickjacking protection</dt> + <dd>In this attack a malicious user hijacks clicks meant for a visible top level site and routes them to a hidden page beneath. This technique might be used, for example, to display a legitimate bank site but capture the login credentials in an invisible <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe" title="The HTML Inline Frame Element (<iframe>) represents a nested browsing context, effectively embedding another HTML page into the current page. In HTML 4.01, a document may contain a head and a body or a head and a frameset, but not both a body and a frameset. However, an <iframe> can be used within a normal document body. Each browsing context has its own session history and active document. The browsing context that contains the embedded content is called the parent browsing context. The top-level browsing context (which has no parent) is typically the browser window."><code><iframe></code></a> controlled by the attacker. Django contains <a href="https://docs.djangoproject.com/en/2.0/ref/clickjacking/#clickjacking-prevention">clickjacking protection</a> in the form of the <a href="https://docs.djangoproject.com/en/2.0/ref/middleware/#django.middleware.clickjacking.XFrameOptionsMiddleware" title="django.middleware.clickjacking.XFrameOptionsMiddleware"><code>X-Frame-Options middleware</code></a> which, in a supporting browser, can prevent a site from being rendered inside a frame.</dd> + <dt>Enforcing SSL/HTTPS</dt> + <dd>SSL/HTTPS can be enabled on the web server in order to encrypt all traffic between the site and browser, including authentication credentials that would otherwise be sent in plain text (enabling HTTPS is highly recommended). If HTTPS is enabled then Django provides a number of other protections you can use:</dd> +</dl> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SECURE_PROXY_SSL_HEADER"><code>SECURE_PROXY_SSL_HEADER</code></a> can be used to check whether content is secure, even if it is incoming from a non-HTTP proxy.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SECURE_SSL_REDIRECT"><code>SECURE_SSL_REDIRECT</code></a> is used to redirect all HTTP requests to HTTPS.</li> + <li>Use <a href="https://docs.djangoproject.com/en/2.0/ref/middleware/#http-strict-transport-security">HTTP Strict Transport Security</a> (HSTS). This is an HTTP header that informs a browser that all future connections to a particular site should always use HTTPS. Combined with redirecting HTTP requests to HTTPS, this setting ensures that HTTPS is always used after a successful connection has occurred. HSTS may either be configured with <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SECURE_HSTS_SECONDS"><code>SECURE_HSTS_SECONDS</code></a> and <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SECURE_HSTS_INCLUDE_SUBDOMAINS"><code>SECURE_HSTS_INCLUDE_SUBDOMAINS</code></a> or on the Web server.</li> + <li>Use ‘secure’ cookies by setting <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SESSION_COOKIE_SECURE"><code>SESSION_COOKIE_SECURE</code></a> and <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-CSRF_COOKIE_SECURE"><code>CSRF_COOKIE_SECURE</code></a> to <code>True</code>. This will ensure that cookies are only ever sent over HTTPS.</li> +</ul> + +<dl> + <dt>Host header validation</dt> + <dd>Use <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-ALLOWED_HOSTS"><code>ALLOWED_HOSTS</code></a> to only accept requests from trusted hosts.</dd> +</dl> + +<p>There are many other protections, and caveats to the usage of the above mechanisms. While we hope that this has given you an overview of what Django offers, you should still read the Django security documentation.</p> + +<ul> +</ul> + +<h2 id="Summary">Summary</h2> + +<p>Django has effective protections against a number of common threats, including XSS and CSRF attacks. In this article we've demonstrated how those particular threats are handled by Django in our <em>LocalLibrary</em> website. We've also provided a brief overview of some of the other protections.</p> + +<p>This has been a very brief foray into web security. We strongly recommend that you read <a href="https://docs.djangoproject.com/en/2.0/topics/security/">Security in Django</a> to gain a deeper understanding.</p> + +<p>The next and final step in this module about Django is to complete the <a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">assessment task</a>.</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/security/">Security in Django</a> (Django docs)</li> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/Security">Server side website security</a> (MDN)</li> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/Security">Web security</a> (MDN)</li> + <li><a href="/en-US/docs/Web/Security/Securing_your_site">Securing your site</a> (MDN)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Deployment", "Learn/Server-side/Django/django_assessment_blog", "Learn/Server-side/Django")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> diff --git a/files/ko/learn/server-side/express_nodejs/index.html b/files/ko/learn/server-side/express_nodejs/index.html new file mode 100644 index 0000000000..453d7490f0 --- /dev/null +++ b/files/ko/learn/server-side/express_nodejs/index.html @@ -0,0 +1,63 @@ +--- +title: Express 웹 프레임워크 (Node.js/JavaScript의 활용) +slug: Learn/Server-side/Express_Nodejs +tags: + - Express + - node.js + - 서버 + - 시작 + - 웹프레임워크 + - 자바스크립트 + - 초보개발자 +translation_of: Learn/Server-side/Express_Nodejs +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">Express는 JavaScript로 작성되고 Node.js 런타임 환경에서 구동되는 인기 있는 웹 프레임워크입니다. 이 장에서는 Express 프레임워크의 몇 가지 장점과 개발환경 설치 방법, 웹 개발과 배포작업의 방법을 다룹니다.</p> + +<h2 id="알아야할_것들">알아야할 것들</h2> + +<p>이 장의 내용은 Server-side 웹 프로그래밍과 웹 프레임워크에 대한 이해가 필요합니다. 잘 모르겠다면 <a href="/ko/docs/Learn/Server-side/First_steps">Server-side website programming first steps</a> 을 먼저 확인해보세요. 일반적인 프로그래밍 컨셉과 <a href="/ko/docs/Web/JavaScript">JavaScript</a> 의 지식이 요구되지만, 핵심까지 세세하게 알 필요는 없습니다.</p> + +<div class="note"> +<p><strong>Note</strong>: 여기서는 클라이언트 측에서의 자바스크립에 관한 많은 유용한 자료들을 이용할 수 있다. <a href="/ko/docs/Web/JavaScript">JavaScript</a>, <a href="/en-US/docs/Web/JavaScript/Guide">JavaScript Guide</a>, <a href="/en-US/docs/Learn/Getting_started_with_the_web/JavaScript_basics">JavaScript Basics</a>, <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> (한 번 배워보자). 자바스크립트의 핵심과 컨셉은 Node.js를 이용한 서버측 개발과 같으므로, 여기의 자료들을 이용하는 게 좋을 것이다. Node.js 는HTTP서버를 구축하고 파일 시스템에 접근하는 등의 브라우저가 필요없는 환경에서에서 유용한 기능을 제공하는 <a href="https://nodejs.org/dist/latest-v6.x/docs/api/documentation.htm">additional APIs</a>를 제공하지만, 브라우저나 DOM에서 작동되는 자바스크립트 API는 지원하지 않는다.</p> + +<p>다음 목차에서는 Node.js와 Express 그리고 인터넷과 책에서의 방대한 좋은 자료들에 있는 정보들을 알려준다. 이들은 <a href="http://stackoverflow.com/a/5511507/894359">How do I get started with Node.js</a> (StackOverflow) 나 <a href="https://www.quora.com/What-are-the-best-resources-for-learning-Node-js?">What are the best resources for learning Node.js?</a> (Quora)에 참고해보자</p> +</div> + +<h2 id="목차">목차</h2> + +<dl> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></dt> + <dd>처음으로 Express에 배우는 이 곳에서는 "Node가 뭐지?", "Express는 뭐지?"의 물음에 답하고, Express 웹 프레임워크의 전반적인 사항에 대해 알아볼 것이다. 주된 내용의 뼈대를 완성하고, Express 어플리케이션을 하나하나씩 배워볼 것이다. (하지만, 이 곳에서는 아직 어디서 테스팅이 이루어질 지 개발 환경등에서는 다루지 않을 것이다.).</dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></dt> + <dd>이제 Express가 어디에 이용되는지 알아볼 것이다. Windows, Linux(Ubuntu), Mac OS X에서 Node/Express의 개발환경을 구축하기 위한 방법도 살펴볼 것이다. 운영체제에 관계없이, 여기서는 Express 개발을 시작하기위해서 어떤 것이 필요한지도 알려준다.</dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></dt> + <dd>실질적인 튜토리얼에 해당하는 이번 수업에서는 어떤 것을 배우고 차후 수업에 필요한 "로컬 라이브러리"에서의 웹사이트의 전반적인 개요도 알아본다.</dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></dt> + <dd>여기서는 웹사이트의 뼈대를 구성해 나갈 것이다. 웹사이트의 뼈대란 사이트의 사이트맵, 템플릿 및 데이터베이스등을 말하므로 이를 만들어볼 것이다.</dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></dt> + <dd>여기서는 간단하게나마 Node/Express에 필요한 데이터베이스의 개요에 대해 소개할 것이다. 그리고 로컬의 웹사이트의 DB에 접근하기 위해 <a href="http://mongoosejs.com/">Mongoose</a>를 사용하는 법도 알아본다. DB에서의 스키마와 모델이 어떻게 정의되는지, 필드의 타입과 기본적인 유효성에 대해서도 알아본다. 또한, 짧게나마 모델 데이터를 접근하는 주된 방법도 알아본다.</dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></dt> + <dd>이 수업에서는 <em>LocalLibray </em>웹사이트에 사용하기 위해 "더미" 핸들러 함수를 통한 라우터(URL 핸들링 코드)에 대해 배운다. 여러분의 라우팅 핸들링 코드를 사용할 수 있는 모듈 구조를 가지고 있으며, 다음 장에서 실제로 핸들러 기능을 확장할 수 있게 된다. 또한, Express에서 사용가능한 모듈 형식의 라우팅에 대해 쉽게 이해할 수 있을 것이다.</dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></dt> + <dd>자, 이제 웹사이트에 책이나 데이터들을 표시할 페이지를 추가할 수 있다. 페이지에는 사이트에 관련된 자세한 부분과 리스트 및 모델 타입들이 얼마나 많이 기록되는지에 관한 홈 페이지가 포함되어 있다. 따라서 우리들은 데이터베이스에서 기록을 얻고 템플릿을 사용하는 데 실질적인 경험을 가질 수 있다.</dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></dt> + <dd>이 수업에서는 Pug를 사용해서 어떻게 Express에서 <a href="/en-US/docs/Web/Guide/HTML/Forms">HTML Forms</a> 이 사용되는지 보여주고, 특히 데이터베이스에서 폼을 작성하고 업데이트하고 지우기 위해 사용하는 방법에 대해 배울 것이다.</dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></dt> + <dd>이제 꽤 훌륭한 로컬라이브러리 웹사이트 만들 수 있으며 , 웹 서버에 업로드 함으로서 여러 사람들이 인터넷을 통해 접근할 수 있게 만들 수 있다. 이 수업은 전반적으로 웹 사이트를 배포하기 위해 호스트와 연결하는 등을 배우고, 실제 서비스를 하기위해 준비해야할 것들을 알려 준다.</dd> +</dl> + +<h2 id="튜토리얼_추가하기">튜토리얼 추가하기</h2> + +<p><strong>자습서의 끝입니다. </strong>(지금은 말이죠). 만약 이 자습서의 내용을 보충하고 싶으시다면 아래와 같은 주제를 해 주시면 좋을 것 같네요:</p> + +<ul> + <li>세션 이용하기</li> + <li>사용자 인증</li> + <li>사용자 권한 부여 및 허가</li> + <li>Express 웹 어플리케이션 테스트</li> + <li>Express 웹 어플리케이션 보안</li> +</ul> + +<p>그리고 평가 작업도 있으면 정말 좋을 것 같아요!</p> diff --git a/files/ko/learn/server-side/express_nodejs/introduction/index.html b/files/ko/learn/server-side/express_nodejs/introduction/index.html new file mode 100644 index 0000000000..caa4eb8eaa --- /dev/null +++ b/files/ko/learn/server-side/express_nodejs/introduction/index.html @@ -0,0 +1,488 @@ +--- +title: Express/Node 소개 +slug: Learn/Server-side/Express_Nodejs/Introduction +translation_of: Learn/Server-side/Express_Nodejs/Introduction +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">첫번째 Express 수업에서는 Node, Express를 알아보고, Express 웹 프레임워크 제작의 전반에 대해 배우게 됩니다.<br> + 우선 주요 특징들에 대한 틀을 정리한 후 Express 어플리케이션을 구성하는 주요 구성요소들을 살펴볼 것입니다. (테스트할 개발환경은 아직이겠지만요)</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">알아야 할 것들</th> + <td>기본적인 컴퓨터의 이해. 서버 사이드 웹사이트 프로그래밍(<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps">server-side website programming</a>), 그리고 특별히 웹사이트에서 클라이언트와 서버간의 상호작용(<a href="/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview">client-server interactions in websites</a>)의 메커니즘에 대한 이해</td> + </tr> + <tr> + <th scope="row">목표</th> + <td>Express에 익숙해지고, Node와 어떻게 함께 사용되는지, 기능은 어떠한지, 그리고 Express 어플리케이션의 주요한 구성요소들에 대해 배운다.</td> + </tr> + </tbody> +</table> + +<h2 id="Express와_Node란">Express와 Node란?</h2> + +<p><a href="https://nodejs.org/">Node</a> (또는 더 공식적으로는 Node.js) 는 오픈소스, 크로스 플랫폼이며, 개발자가 모든 종류의 서버 사이드 도구들과 어플리케이션을 <a href="/en-US/docs/Glossary/JavaScript">JavaScript</a>로 만들수 있도록 해주는 런타임 환경이다.런타임은 브라우져 영역 밖에서도 사용할수 있도록 의도했다.(예를들면 서버 OS 또는 컴퓨터에서 직접적으로 실행되는). 이와 같이, 이 환경에서 특정 브라우져에서의 자바스트립트 API들을 제외시키고 , HTTP 와 파일 시스템 라이브러리들을 포함하여 더 많은 전형적인 OS API들을 추가했다.</p> + +<p>웹 서버 관점에서 노드는 많은 장점들을 가진다:</p> + +<ul> + <li>훌륭한 퍼포먼스! Node는 단위시간당 처리량과 어플리케이션에서 확장성을 최적화 시키고, 많은 공통적인 웹 개발 문제들을 맞먹는다.(예를들면 실시간 웹 어플리케이션들)</li> + <li>코드는 순수한 자바스크립트로 작성된다, 이는 당신이 각각 브라우져와 웹 서버 코드를 작성할때 언어들 사이에 "context shift" 를 다루는 시간을 적게 할수 있을을 의미한다.</li> + <li>자바스크립트는 비교적 새로운 프로그래밍 언어이고 또다른 전통적인 웹서버 언어들과 비교할때 언어적 설계에서 향상의 이득을 가진다. 많은 다른 새롭거나 인기있는 언어들은 자바스크립트로 컴파일하거나 변환한다 그래서 또한 당신은 커피스크립트, 클로져스크립트, 스칼라, 라이브 스크립트 등등을 사용할 수 있다.</li> + <li>노드 패키지 매니저(NPM)는 수천만개의 재사용가능한 패키지에 접근할 수 있도록 한다. 이것은 최고의 의존성 해결과 또한 수많은 빌드 툴체인이 자동화되도록 한다.</li> + <li>이것은 마이크로소프트 윈도우, OS X, 룩스, 솔라리스, FreeBSD, OpenBSD, WebOS, 그리고 NonStop OS 등에서 돌아가는 버전과 같이 포터플하다. 더군다나, 이것은 특정한 인프라구조를 지원하고 Node 사이트 호스팅을 위한 문서를 제공하는 많은 웹 호스팅 공급자들에 의해서 잘 지원되고 있다.</li> + <li>도움을 주고자 하는 수많은 사람들이 존재하는 아주 활발한 개발 생태계와 커뮤니티를 지니고 있다.</li> +</ul> + +<h3 id="Hello_Node.js">Hello Node.js</h3> + +<p>아래 코드처럼 Node에 HTTP 모듈을 사용하여 모든 요청에 응답이 가능한 간단한 웹 서버를 쉽게 생성할 수 있습니다.</p> + +<p>이렇게하면 웹 서버가 만들어지고 URL <code>http://127.0.0.1:8000/</code> 에 있는 모든 종류의 HTTP 요청에 수신하게 됩니다. 요청이 하나 들어왔을 때, "Hello World" 텍스트 응답을 보내도록 하겠습니다.</p> + +<ol> + <li>터미널을 연다. (윈도우에서는, 커맨드라인 유틸리티)<br> + ※ 윈도우 키 + R => 'CMD'</li> + <li>프로그램을 저장할 폴더를 생성하고(여기서는 <code>test-node</code>), 아래 명령을 입력하여 해당 폴더로 이동한다.</li> +</ol> + +<pre class="notranslate">cd test-node</pre> + +<ol start="3"> + <li>즐겨쓰는 텍스트에디터를 열어 아래 코드를 입력한 후, 파일명 <code>hello.js</code> 로 저장한다.</li> +</ol> + +<pre class="brush: js notranslate">//Load HTTP module +var http = require("http"); + +//Create HTTP server and listen on port 8000 for requests +http.createServer(function (request, response) { + + // Set the response HTTP header with HTTP status and Content type + response.writeHead(200, {'Content-Type': 'text/plain'}); + + // Send the response body "Hello World" + response.end('Hello World\n'); +}).listen(8000); + +// Print URL for accessing server +console.log('Server running at http://127.0.0.1:8000/')</pre> + +<p>4. 터미널로 돌아가 아래 명령을 입력한다.</p> + +<pre class="brush: bash notranslate">node hello.js</pre> + +<p>이제, 웹브라우저를 열어 다음 주소로 이동한다. <code>http://localhost:8000</code> 곧바로 좌상단에 "<strong>Hello World</strong>" 문구가 있고, 나머지 영역은 비어있는 웹페이지를 볼 수 있다.</p> + +<p>Node 자체가 다른 일반적인 웹 개발 기능을 지원하지 않습니다. 만약 다른 HTTP 패턴 (예 : <code>GET</code>, <code>POST</code>, <code>DELETE</code> 등)에 대한 특정 처리를 추가하려면 서로 다른 URL 경로("routes")를 사용하여 요청을 개별적으로 처리, 정적 파일을 제공, 템플릿을 사용하여 동적으로 응답을 생성할 수 있으며, 코드를 직접 작성할 필요가가 생기게 됩니다. 또는 기본적인 것들을 직접 구현하는 작업을 피하고 웹 프레임 워크를 사용할 수 있습니다! </p> + +<p><a href="https://expressjs.com/">Express</a> 는 가장 인기있는 Node 웹 프레임 워크 이며, 다른 많은 인기있는 <a href="https://expressjs.com/en/resources/frameworks.html">Node web frameworks</a>의 기본 라이브러리 입니다. Express는 다음과 같은 메커니즘을 제공합니다:</p> + +<ul> + <li>HTTP 통신 요청(Request; GET, POST, DELETE 등)에 대한 핸들러를 만든다.</li> + <li>템플릿에 데이터를 넣어 응답(response)을 만들기 위해 view의 렌더링 엔진과 결합(integrate)한다. </li> + <li>접속을 위한 포트나 응답 렌더링을 위한 템플릿 위치같은 공통 웹 어플리케이션 세팅을 한다. </li> + <li>핸들링 파이프라인(reqest handling pipeline) 중 필요한 곳에 추가적인 미들웨어 처리 요청을 추가한다.</li> +</ul> + +<p><em>Express</em> 자체는 꽤나 최소한의 기능만 탑재하지만 개발자들이 거의 모든 웹 개발의 문제를 다루는 호환성있는 미들웨어 패키지를 만들어왔습니다. 쿠키, 세션, 사용자 로그인, URL 파라미터, <code>POST</code> 데이터, 보안 헤더와 그외 많은 것들에 대한 라이브러리들이 있습니다. 여러분은 <a href="http://expressjs.com/en/resources/middleware.html">Express Middleware</a>에서 Express 팀이 유지보수하는 미들웨어 패키지 리스트를 확인할 수 있습니다. (유명한 서드파티 패키지들을 포함).</p> + +<div class="note"> +<p><strong>Note:</strong> This flexibility is a double edged sword. There are middleware packages to address almost any problem or requirement, but working out the right packages to use can sometimes be a challenge. There is also no "right way" to structure an application, and many examples you might find on the Internet are not optimal, or only show a small part of what you need to do in order to develop a web application.</p> +</div> + +<h2 id="유래">유래</h2> + +<p>노드(Node)는 2009년 리눅스 한정으로 배포 되었다. NPM은 2010년에 배포되었고, 네이티브 윈도우즈(Windows)는 2012년부터 지원하기 시작하였다. 현재 배포중인 최신 LTS 버전은 Node v8.9.3이 있고, 가장 최신 버전은 Node 9 이다. 이것은 유구한 역사를 짧게 적어본 것으로 더 알고 싶다면 <a href="https://ko.wikipedia.org/wiki/Node.js">위키페디아</a>를 참조하면 된다.</p> + +<p>Express는 2010년 11월 처음 배포되었고 현재는 4.16 버전이 되었다. 현재 배포 버전과의 변경사항을 알고 싶다면 <a href="https://expressjs.com/en/changelog/4x.html">changelog</a> 를 확인하면 된다. 그리고 더 자세한 역사와 릴리즈 노트는 <a href="https://github.com/expressjs/express/blob/master/History.md">GitHub</a> 를 참조하면 된다.</p> + +<h2 id="어떻게_NodeExpress가_유명해졌을까">어떻게 Node/Express가 유명해졌을까?</h2> + +<p>유명한 웹 프레임워크 쓴다는 것은 아주 중요합니다. 바로 해당 기술에 대한 지속가능성, 문서화, 추가 라이브러리, 그리고 기술 지원 자원에 대한 지표가 되기 때문입니다. </p> + +<p>There isn't any readily-available and definitive measurement of the popularity of server-side frameworks (although sites like <a href="http://hotframeworks.com/">Hot Frameworks</a> attempt to assess popularity using mechanisms like counting the number of GitHub projects and StackOverflow questions for each platform). A better question is whether Node and Express are "popular enough" to avoid the problems of unpopular platforms. Are they continuing to evolve? Can you get help if you need it? Is there an opportunity for you to get paid work if you learn Express?</p> + +<p>Based on the number of <a href="https://expressjs.com/en/resources/companies-using-express.html">high profile companies</a> that use Express, the number of people contributing to the codebase, and the number of people providing both free and paid for support, then yes, <em>Express</em> is a popular framework!</p> + +<h2 id="Express는_개발이_자유로울까">Express는 개발이 자유로울까?</h2> + +<p>Web frameworks often refer to themselves as "opinionated" or "unopinionated".</p> + +<p>Opinionated frameworks are those with opinions about the "right way" to handle any particular task. They often support rapid development <em>in a particular domain </em>(solving problems of a particular type) because the right way to do anything is usually well-understood and well-documented. However they can be less flexible at solving problems outside their main domain, and tend to offer fewer choices for what components and approaches they can use.</p> + +<p>Unopinionated frameworks, by contrast, have far fewer restrictions on the best way to glue components together to achieve a goal, or even what components should be used. They make it easier for developers to use the most suitable tools to complete a particular task, albeit at the cost that you need to find those components yourself.<br> + <br> + Express is unopinionated. You can insert almost any compatible middleware you like into the request handling chain, in almost any order you like. You can structure the app in one file or multiple files, and using any directory structure. You may sometimes feel that you have too many choices!</p> + +<h2 id="Express의_코드는_어떻게_생겼을까">Express의 코드는 어떻게 생겼을까?</h2> + +<p>In a traditional data-driven website, a web application waits for HTTP requests from the web browser (or other client). When a request is received the application works out what action is needed based on the URL pattern and possibly associated information contained in <code>POST</code> data or <code>GET</code> data. Depending on what is required it may then read or write information from a database or perform other tasks required to satisify the request. The application will then return a response to the web browser, often dynamically creating an HTML page for the browser to display by inserting the retrieved data into placeholders in an HTML template.</p> + +<p>Express provides methods to specify what function is called for a particular HTTP verb (<code>GET</code>, <code>POST</code>, <code>SET</code>, etc.) and URL pattern ("Route"), and methods to specify what template ("view") engine is used, where template files are located, and what template to use to render a response. You can use Express middleware to add support for cookies, sessions, and users, getting <code>POST</code>/<code>GET</code> parameters, etc. You can use any database mechanism supported by Node (Express does not define any database-related behaviour).</p> + +<p>The following sections explain some of the common things you'll see when working with <em>Express</em> and <em>Node</em> code.</p> + +<h3 id="Helloworld_Express">Helloworld Express</h3> + +<p>First lets consider the standard Express <a href="https://expressjs.com/en/starter/hello-world.html">Hello World</a> example (we discuss each part of this below, and in the following sections).</p> + +<div class="note"> +<p><strong>Tip:</strong> If you have Node and Express already installed (or if you install them as shown in the <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">next article</a>), you can save this code in a file called <strong>app.js</strong> and run it in a command prompt by calling <code>node app.js</code>.</p> +</div> + +<pre class="brush: js notranslate">var express = require('express'); +var app = express(); + +<strong>app.get('/', function (req, res) { + res.send('Hello World!'); +});</strong> + +app.listen(3000, function () { + console.log('Example app listening on port 3000!'); +}); +</pre> + +<p>The first two lines <code>require()</code> (import) the express module and create an <a href="https://expressjs.com/en/4x/api.html#app">Express application</a>. This object, which is traditionally named <code>app</code>, has methods for routing HTTP requests, configuring middleware, rendering HTML views, registering a template engine, and modifying <a href="https://expressjs.com/en/4x/api.html#app.settings.table">application settings</a> that control how the application behaves (e.g. the environment mode, whether route definitions are case sensitive, etc.)</p> + +<p>The middle part of the code (the three lines starting with <code>app.get</code>) shows a <em>route definition</em>. The <code>app.get()</code> method specifies a callback function that will be invoked whenever there is an HTTP <code>GET</code> request with a path (<code>'/'</code>) relative to the site root. The callback function takes a request and a response object as arguments, and simply calls <code><a href="https://expressjs.com/en/4x/api.html#res.send">send()</a></code> on the response to return the string "Hello World!"</p> + +<p>The final block starts up the server on port '3000' and prints a log comment to the console. With the server running, you could go to <code>localhost:3000</code> in your browser to see the example response returned.</p> + +<h3 id="모듈의_생성과_불러옴Importing_and_creating_modules">모듈의 생성과 불러옴(Importing and creating modules)</h3> + +<p>A module is a JavaScript library/file that you can import into other code using Node's <code>require()</code> function. <em>Express</em> itself is a module, as are the middleware and database libraries that we use in our <em>Express</em> applications.</p> + +<p>The code below shows how we import a module by name, using the <em>Express</em> framework as an example. First we invoke the <code style="font-style: normal; font-weight: normal;">require()</code> function, specifying the name of the module as a string (<code>'express'</code>), and calling the returned object to create an <a href="https://expressjs.com/en/4x/api.html#app">Express application</a>. We can then access the properties and functions of the application object.</p> + +<pre class="brush: js notranslate">var express = require('express'); +var app = express(); +</pre> + +<p>You can also create your own modules that can be imported in the same way.</p> + +<div class="note"> +<p><strong>Tip:</strong> You will <em>want </em>to create your own modules, because this allows you to organise your code into managable parts — a monolithic single-file application is hard to understand and maintain. Using modules also helps you manage your namespace, because only the variables you explicitly export are imported when you use a module.</p> +</div> + +<p>To make objects available outside of a module you just need to assign them to the <code>exports</code> object. For example, the <strong>square.js</strong> module below is a file that exports <code>area()</code> and <code>perimeter()</code> methods:</p> + +<pre class="brush: js notranslate">exports.area = function (width) { return width * width; }; +exports.perimeter = function (width) { return 4 * width; }; +</pre> + +<p>We can import this module using <code>require()</code>, and then call the exported method(s) as shown:</p> + +<pre class="brush: js notranslate">var square = require('./square'); // Here we require() the name of the file without the (optional) .js file extension +console.log('The area of a square with a width of 4 is ' + square.area(4));</pre> + +<div class="note"> +<p><strong>Note:</strong> You can also specify an absolute path to the module (or a name, as we did initially).</p> +</div> + +<p>If you want to export a complete object in one assignment instead of building it one property at a time, assign it to <code>module.exports</code> as shown below (you can also do this to make the root of the exports object a constructor or other function):</p> + +<pre class="brush: js notranslate">module.exports = { + area: function(width) { + return width * width; + }, + + perimeter: function(width) { + return 4 * width; + } +}; +</pre> + +<p>For a lot more information about modules see <a href="https://nodejs.org/api/modules.html#modules_modules">Modules</a> (Node API docs).</p> + +<h3 id="비동기식_API의_사용">비동기식 API의 사용</h3> + +<p>JavaScript 코드는 완료까지 시간이 다소 소요될 수 있는 작업에 대해 동기보다 비동기 API를 자주 사용합니다. 동기 API는 다음 작업이 시작하기 전에 각 작업이 완료되어야만 합니다. 예를 들어, 다음의 로그함수들은 동기식이며 텍스트를 순서대로 콘솔에 나타낼 것입니다.(First, Second)</p> + +<pre class="brush: js notranslate">console.log('First'); +console.log('Second');</pre> + +<p>반면 비동기 API는 API가 작업을 시작하고, 즉시 반환(작업이 완료되기 전에)합니다. 작업이 완료되면 API는 추가적인 작업 수행을 위한 일부 매커니즘을 사용합니다. 예를 들어 아래의 코드는 "Second, First"를 출력합니다. 그 이유는 <code>setTimeout()</code> 메서드가 먼저 호출되고 즉시 반환되더라도, 작업이 몇 초 동안 완료되지 않기 때문입니다.</p> + +<pre class="brush: js notranslate">setTimeout(function() { + console.log('First'); + }, 3000); +console.log('Second'); +</pre> + +<p>Node는 싱글 스레드 이벤트 기반 환경이기 때문에 non-blocking 비동기 API는 브라우저보다 Node에서 훨씬 더 중요합니다.</p> + +<p>'싱글 스레드'는 서버에 모든 요청이 별도의 프로세스로 생성되지 않고 동일한 스레드에서 실행되는 것을 의미합니다. 이 모델은 속도와 서버 리소스 측면에서 아주 효율적이지만, 만약 특정 함수가 완료되는데에 오랜 시간이 걸리는 동기 메서드를 호출하는 함수가 있다면 현재 요청 뿐만 아니라 웹 어플리케이션에서 다른 요청이 처리되는 것도 차단합니다.</p> + +<p>비동기 API가 완료되었음을 어플리케이션에 알리는 방법은 여러가지가 있습니다. 가장 일반적인 방법은 비동기 API를 호출시 작업이 완료되면 다시 호출되는 콜백함수를 이용하는 것이며, 위의 예제에서 사용된 방식입니다.</p> + +<div class="note"> +<p><strong>Tip:</strong> 순서대로 수행해야하는 종속적인 비동기 작업들이 있을 경우, 콜백을 사용하는 것은 꽤 복잡할 수 있습니다. 중첩된 여러 단계의 콜백이 생성되기 때문입니다. 이 문제는 흔히 'callback hell'이라고 일컬어집니다. 이 문제는 good coding practices(<a href="http://callbackhell.com/">http://callbackhell.com/</a> 참고), <a href="https://www.npmjs.com/package/async">async</a>와 같은 모듈의 사용, <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promises</a>와 같은 ES6 기능을 사용함으로써 개선될 수 있습니다.</p> +</div> + +<div class="note"> +<p><strong>Note:</strong> Node와 Express의 일반적인 규칙은 error-first callbacks을 사용하는 것입니다. 이 규칙에서 콜백 함수의 첫번 째 값은 에러값이고, 다음 인자에는 성공 데이터가 포함됩니다. 이 방법에 대한 좋은 설명은 이 블로그에서 확인할 수 있습니다.:: <a href="http://fredkschott.com/post/2014/03/understanding-error-first-callbacks-in-node-js/ ">The Node.js Way - Understanding Error-First Callbacks</a> (fredkschott.com).</p> +</div> + +<h3 id="라우트_핸들러의_사용">라우트 핸들러의 사용</h3> + +<p>In our <em>Hello World</em> Express example see above we defined a (callback) route handler function for HTTP <code>GET</code> requests to the site root (<code>'/'</code>).</p> + +<pre class="brush: js notranslate">app.<strong>get</strong>('/', function (req, res) { + res.send('Hello World!'); +}); +</pre> + +<p>The callback function takes a request and a response object as arguments. In this case the method simply calls <code><a href="https://expressjs.com/en/4x/api.html#res.send">send()</a></code> on the response to return the string "Hello World!" There are a <a href="https://expressjs.com/en/guide/routing.html#response-methods">number of other response methods</a> for ending the request/response cycle, for example you could call <code><a href="https://expressjs.com/en/4x/api.html#res.json">res.json()</a></code> to send a JSON response or <code><a href="https://expressjs.com/en/4x/api.html#res.sendFile">res.sendFile()</a></code> to send a file.</p> + +<div class="note"> +<p><strong>JavaScript tip:</strong> You can use any argument names you like in the callback functions; when the callback is invoked the first argument will always be the request and the second will always be the response. It makes sense to name them such that you can identify the object you're working with in the body of the callback.</p> +</div> + +<p>The <em>Express application</em> object also provides methods to define route handlers for all the other HTTP verbs, which are mostly used in exactly the same way: <code>post()</code>, <code>put()</code>, <code>delete()</code>, <code>options()</code>, <code>trace()</code>, <code>copy()</code>, <code>lock()</code>, <code>mkcol()</code>, <code>move()</code>, <code>purge()</code>, <code>propfind()</code>, <code>proppatch()</code>, <code>unlock()</code>, <code>report()</code>, <code>mkactivity()</code>, <code>checkout()</code>, <code>merge()</code>, <code>m-</code><code>search()</code>, <code>notify()</code>, <code>subscribe()</code>, <code>unsubscribe()</code>, <code>patch()</code>, <code>search()</code>, and <code>connect()</code>.</p> + +<p>There is a special routing method, <code>app.all()</code>, which will be called in response to any HTTP method. This is used for loading middleware functions at a particular path for all request methods. The following example (from the Express documentation) shows a handler that will be executed for requests to <code>/secret</code> irrespective of the HTTP verb used (provided it is supported by the <a href="https://nodejs.org/api/http.html#http_http_methods">http module</a>).</p> + +<pre class="brush: js notranslate">app.all('/secret', function (req, res, next) { + console.log('Accessing the secret section ...') + next() // pass control to the next handler +})</pre> + +<p>Routes allow you to match particular patterns of characters in a URL, and extract some values from the URL and pass them as parameters to the route handler (as attributes of the request object passed as a parameter).</p> + +<p>Often it is useful to group route handlers for a particular part of a site together and access them using a common route-prefix (e.g. a site with a Wiki might have all wiki-related routes in one file and have them accessed with a route prefix of <em>/wiki/</em>). In <em>Express</em> this is achieved by using the <code><a href="http://expressjs.com/en/guide/routing.html#express-router">express.Router</a></code> object. For example, we can create our wiki route in a module named <strong>wiki.js</strong>, and then export the <code>Router</code> object, as shown below:</p> + +<pre class="brush: js notranslate">// wiki.js - Wiki route module + +var express = require('express') +var router = express.Router() + +// Home page route +router.get('/', function (req, res) { + res.send('Wiki home page') +}) + +// About page route +router.get('/about', function (req, res) { + res.send('About this wiki') +}) + +module.exports = router +</pre> + +<div class="note"> +<p><strong>Note:</strong> Adding routes to the <code>Router</code> object is just like adding routes to the <code>app</code> object (as shown previously).</p> +</div> + +<p>To use the router in our main app file we would then <code>require()</code> the route module (<strong>wiki.js</strong>), then call <code>use()</code> on the <em>Express</em> application to add the Router to the middleware handling path. The two routes will then be accessible from <code style="font-style: normal; font-weight: normal;">/wiki/</code> and <code style="font-style: normal; font-weight: normal;">/wiki/about/</code>.</p> + +<pre class="brush: js notranslate">var wiki = require('./wiki.js') +// ... +app.use('/wiki', wiki)</pre> + +<p>We'll show you a lot more about working with routes, and in particular about using the <code>Router</code>, later on in the linked section<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes"> Routes and controllers .</a></p> + +<h3 id="미들웨어의_사용">미들웨어의 사용</h3> + +<p>미들웨어는 정적 파일 제공에서 오류 처리, HTTP 응답 압축에 이르기까지 Express 앱에서 광범위하게 사용됩니다. 라우트 함수는 <font><font>HTTP 클라이언트에 일부 응답을 반환하여 HTTP 요청-응답주기를 종료하는 반면, 미들웨어 함수는 </font></font><em><font><font>일반적으로</font></font></em><font><font> 요청 또는 응답에 대해 일부 작업을 수행 한 다음 "스택"(이는 미들웨어 혹은 라우트 핸들러일 수 있습니다.)에서 다음 함수를 호출합니다. 미들웨어가 호출되는 순서는 앱 개발자에게 달려있습니다.</font></font></p> + +<div class="note"> +<p><strong>Note:</strong> <font><font>미들웨어는 모든 작업을 수행하고, 코드를 실행하고, 요청 및 응답 객체를 변경할 수 </font></font><em><font><font>있으며, 요청-응답주기를 종료</font></font></em><font><font> 할 </font><em><font>수도</font></em><font> 있습니다. 만약 주기가 종료되지 않으면, 다음 미들웨어 함수의 제어를 위해 <code>next()</code>를 호출해야합니다.( 혹은 요청이 중단된 상태로 유지될 것입니다.)</font></font></p> +</div> + +<p><font><font>대부분의 앱은 </font><font>쿠키, 세션, 사용자 인증,<code> POST</code>요청 </font><font>및 JSON 데이터 접근</font><font> </font><font>, logging 등과 </font><font>같은 일반적인 웹 개발 작업을 단순화하기 위해<em>서드파티</em></font></font><font><font> 미들웨어를 사용합니다 </font><font><a href="http://expressjs.com/en/resources/middleware.html" rel="noopener">. Express 팀에서 관리하는 미들웨어 패키지 목록을</a><font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);"> </span></font></font></font><font><font>이 곳에서 찾을 수 있습니다 </font><font>( 다른 인기있는 서드파티 패키지도 포함). </font><font>다른 Express 패키지는 NPM 패키지 관리자에서 사용할 수 있습니다.</font></font></p> + +<p><font><font>서드파티 미들웨어를 사용하려면 먼저 NPM을 사용하여 앱에 설치해야합니다. </font><font>예를 들어 </font></font><a href="http://expressjs.com/en/resources/middleware/morgan.html" rel="noopener"><font><font>morgan</font></font></a><font><font> HTTP 요청 logger 미들웨어 </font><font>를 설치하려면 다음과 </font><font>같이 진행합니다.</font></font></p> + +<pre class="brush: bash notranslate"><code>$ npm install morgan +</code></pre> + +<p>그런 다음 <em>Express application object에 </em>use()를 호출해서 스택에 이 미들웨어를 추가합니다.</p> + +<pre class="brush: js notranslate">var express = require('express'); +<strong>var logger = require('morgan');</strong> +var app = express(); +<strong>app.use(logger('dev'));</strong> +...</pre> + +<div class="note"> +<p><strong>Note:</strong> <font>미들웨어 및 라우팅 함수는 선언된 순서대로 호출됩니다. </font><font>일부 미들웨어의 경우 순서가 중요합니다 (예를 들어 세션 미들웨어가 쿠키 미들웨어에 의존하는 경우, 쿠키 핸들러를 먼저 추가해야합니다). 거의 항상 라우트를 설정하기 전에 미들웨어가 호출되거나, 미들웨어로 인해 추가된 기능에 라우트 핸들러가 접근할 수 없습니다.</font></p> +</div> + +<p><font><font>당신은 자신만의 미들웨어 함수를 작성할 수 있으며, 그렇게 해야 할 가능성이 높습니다 (에러 처리 코드를 생성하는 경우에만 해당). 미들웨어 함수와 라우트 핸들러 콜백 </font><font>의 </font></font><strong><font><font>유일한</font></font></strong><font><font> 차이점은 미들웨어 함수에 세 번째 인자로 미들웨어 함수가 </font></font><font><font>요청 주기를 완료하지 않을 때 호출되는 <code>next</code>가 있다는 것입니다. (미들웨어 함수가 호출 될 때 여기에는 반드시 호출되는 <em>next</em></font></font><font><font> 함수가 포함됩니다.).</font></font></p> + +<p><font><font>당신이 모든 응답 혹은 특정 HTTP 동사(</font></font><code>GET</code><font><font>, </font></font><code>POST</code> 등)에 미들웨어를 적용할지 여부에 따라<code>app.use()</code><font><font>또는 </font></font><code>app.add()</code>와 함께<font><font> 프로세싱 체인에 미들웨어 기능을 추가 할 수 있습니다. 두 경우 모두 라우트를 동일하게 지정하지만 </font></font><strong><font><font>app.use ()</font></font></strong><font><font> 호출시 라우트는 선택 사항 </font><font>입니다.</font></font></p> + +<p><font><font>아래의 예제는 두 가지 방법을 사용하고, 경로를 사용하거나 사용하지 않고 미들웨어 기능을 추가하는 방법을 보여줍니다.</font></font></p> + +<pre class="brush: js notranslate">var express = require('express') +var app = express() + +// An example middleware function +var a_middleware_function = function(req, res, <em>next</em>) { + //... perform some operations + next(); //Call next() so Express will call the next middleware function in the chain. +} + +// Function added with use() for all routes and verbs +app.use(a_middleware_function) + +//Function added with use() for a specific route +app.use('/someroute', a_middleware_function) + +// A middleware function added for a specific HTTP verb and route +app.get('/', a_middleware_function ) + +app.listen(3000)</pre> + +<div class="note"> +<p><strong>JavaScript Tip:</strong> <font>위에서 우리는 미들웨어 함수를 별도로 선언 한 다음 그것을 콜백으로 설정합니다. </font><font>이전 라우트 핸들러 함수에서는 우리는 콜백 함수가 사용될 때 선언했습니다. </font><font>JavaScript에서는 두 방법 모두 유효합니다.</font></p> +</div> + +<p><font><font>Express 공식 문서에는 </font><font>Express 미들웨어 </font></font><a href="https://expressjs.com/en/guide/using-middleware.html" rel="noopener"><font><font>사용</font></font></a><font><font> 및 </font></font><a href="http://expressjs.com/en/guide/writing-middleware.html" rel="noopener"><font><font>작성</font></font></a><font><font> 에 대한 훨씬 더 우수한 자료들이 </font><font>있습니다.</font></font></p> + +<h3 id="저장된_파일을_서버화하기">저장된 파일을 서버화하기</h3> + +<p>You can use the <a href="http://expressjs.com/en/4x/api.html#express.static">express.static</a> middleware to serve static files, including your images, CSS and JavaScript (<code>static()</code> is the only middleware function that is actually <strong>part</strong> of <em>Express</em>). For example, you would use the line below to serve images, CSS files, and JavaScript files from a directory named '<strong>public'</strong> at the same level as where you call node:</p> + +<pre class="brush: js notranslate">app.use(express.static('public')) +</pre> + +<p>Any files in the public directory are served by adding their filename (<em>relative</em> to the base "public" directory) to the base URL. So for example:</p> + +<pre class="notranslate"><code>http://localhost:3000/images/dog.jpg +http://localhost:3000/css/style.css +http://localhost:3000/js/app.js +http://localhost:3000/about.html +</code></pre> + +<p>You can call <code>static()</code> multiple times to serve multiple directories. If a file cannot be found by one middleware function then it will simply be passed on to the subsequent middleware (the order that middleware is called is based on your declaration order).</p> + +<pre class="brush: js notranslate">app.use(express.static('public')) +app.use(express.static('media')) +</pre> + +<p>You can also create a virtual prefix for your static URLs, rather than having the files added to the base URL. For example, here we <a href="http://expressjs.com/en/4x/api.html#app.use">specify a mount path</a> so that the files are loaded with the prefix "/media":</p> + +<pre class="brush: js notranslate">app.use('/media', express.static('public')) +</pre> + +<p>Now, you can load the files that are in the <code>public</code> directory from the <code>/media</code> path prefix.</p> + +<pre class="notranslate"><code>http://localhost:3000/media/images/dog.jpg +http://localhost:3000/media/video/cat.mp4 +http://localhost:3000/media/cry.mp3</code> +</pre> + +<p>For more information, see <a href="Serving static files in Express">Serving static files in Express</a>.</p> + +<h3 id="핸들링_에러">핸들링 에러</h3> + +<p>Errors are handled by one or more special middleware functions that have four arguments, instead of the usual three: <code>(err, req, res, next)</code>. For example:</p> + +<pre class="brush: js notranslate">app.use(function (err, req, res, next) { + console.error(err.stack) + res.status(500).send('Something broke!') +}) +</pre> + +<p>These can return any content required, but must be called after all other <code>app.use()</code> and routes calls so that they are the last middleware in the request handling process!</p> + +<p>Express comes with a built-in error handler, which takes care of any remaining errors that might be encountered in the app. This default error-handling middleware function is added at the end of the middleware function stack. If you pass an error to <code>next()</code> and you do not handle it in an error handler, it will be handled by the built-in error handler; the error will be written to the client with the stack trace.</p> + +<div class="note"> +<p><strong>Note:</strong> The stack trace is not included in the production environment. To run it in production mode you need to set the the environment variable <code>NODE_ENV</code> to '<code>production'</code>.</p> +</div> + +<div class="note"> +<p><strong>Note:</strong> HTTP404 and other "error" status codes are not treated as errors. If you want to handle these, you can add a middleware function to do so. For more information see the <a href="http://expressjs.com/en/starter/faq.html#how-do-i-handle-404-responses">FAQ</a>.</p> +</div> + +<p>For more information see <a href="http://expressjs.com/en/guide/error-handling.html">Error handling</a> (Express docs).</p> + +<h3 id="데이터베이스의_사용">데이터베이스의 사용</h3> + +<p><em>Express</em> apps can use any database mechanism supported by <em>Node</em> (<em>Express</em> itself doesn't define any specific additional behaviour/requirements for database management). There are many options, including PostgreSQL, MySQL, Redis, SQLite, MongoDB, etc.</p> + +<p>In order to use these you have to first install the database driver using NPM. For example, to install the driver for the popular NoSQL MongoDB you would use the command:</p> + +<pre class="brush: bash notranslate"><code>$ npm install mongodb +</code></pre> + +<p>The database itself can be installed locally or on a cloud server. In your Express code you require the driver, connect to the database, and then perform create, read, update, and delete (CRUD) operations. The example below (from the Express documentation) shows how you can find "mammal" records using MongoDB.</p> + +<pre class="brush: js notranslate">var MongoClient = require('mongodb').MongoClient + +MongoClient.connect('mongodb://localhost:27017/animals', function (err, db) { + if (err) throw err + + db.collection('mammals').find().toArray(function (err, result) { + if (err) throw err + + console.log(result) + }) +})</pre> + +<p>Another popular approach is to access your database indirectly, via an Object Relational Mapper ("ORM"). In this approach you define your data as "objects" or "models" and the ORM maps these through to the underlying database format. This approach has the benefit that as a developer you can continue to think in terms of JavaScript objects rather than database semantics, and that there is an obvious place to perform validation and checking of incoming data. We'll talk more about databases in a later article.</p> + +<p>For more information see <a href="https://expressjs.com/en/guide/database-integration.html">Database integration</a> (Express docs).</p> + +<h3 id="데이터_랜더링시각화">데이터 랜더링(시각화)</h3> + +<p>Template engines (referred to as "view engines" by <em>Express</em>) allow you to specify the <em>structure</em> of an output document in a template, using placeholders for data that will be filled in when a page is generated. Templates are often used to create HTML, but can also create other types of document. Express has support for <a href="https://github.com/expressjs/express/wiki#template-engines">a number of template engines</a>, and there is a useful comparison of the more popular engines here: <a href="https://strongloop.com/strongblog/compare-javascript-templates-jade-mustache-dust/">Comparing JavaScript Templating Engines: Jade, Mustache, Dust and More</a>.</p> + +<p>In your application settings code you set the template engine to use and the location where Express should look for templates using the 'views' and 'view engines' settings, as shown below (you will also have to install the package containing your template library too!)</p> + +<pre class="brush: js notranslate">var express = require('express'); +var app = express(); + +// Set directory to contain the templates ('views') +app.set('views', path.join(__dirname, 'views')); + +// Set view engine to use, in this case 'some_template_engine_name' +app.set('view engine', 'some_template_engine_name'); +</pre> + +<p>The appearance of the template will depend on what engine you use. Assuming that you have a template file named "index.<template_extension>" that contains placeholders for data variables named 'title' and "message", you would call <code><a href="http://expressjs.com/en/4x/api.html#res.render">Response.render()</a></code> in a route handler function to create and send the HTML response:</p> + +<pre class="brush: js notranslate">app.get('/', function (req, res) { + res.render('index', { title: 'About dogs', message: 'Dogs rock!' }) +})</pre> + +<p>For more information see <a href="http://expressjs.com/en/guide/using-template-engines.html">Using template engines with Express</a> (Express docs).</p> + +<h3 id="파일구조">파일구조</h3> + +<p>Express makes no assumptions in terms of structure or what components you use. Routes, views, static files, and other application-specific logic can live in any number of files with any directory structure. While it is perfectly possible to have the whole <em>Express</em> application in one file, typically it makes sense to split your application into files based on function (e.g. account management, blogs, discussion boards) and architectural problem domain (e.g. model, view or controller if you happen to be using an <a href="/en-US/docs/Web/Apps/Fundamentals/Modern_web_app_architecture/MVC_architecture">MVC architecture</a>).</p> + +<p>In a later topic we'll use the <em>Express Application Generator</em>, which creates a modular app skeleton that we can easily extend for creating web applications.</p> + +<ul> +</ul> + +<h2 id="요약">요약</h2> + +<p id="Congratulations_you've_completed_the_first_step_in_your_ExpressNode_journey!_You_should_now_understand_Express_and_Node's_main_benefits_and_roughly_what_the_main_parts_of_an_Express_app_might_look_like_(routes_middleware_error_handling_and_template_code)._You_should_also_understand_that_with_Express_being_an_unopinionated_framework_the_way_you_pull_these_parts_together_and_the_libraries_that_you_use_are_largely_up_to_you!"><span style="color: #3b3c40; font-size: 14px; font-weight: normal; line-height: 1.5;">Congratulations, you've completed the first step in your Express/Node journey! You should now understand Express and Node's main benefits, and roughly what the main parts of an Express app might look like (routes, middleware, error handling, and template code). You should also understand that with Express being an unopinionated framework, the way you pull these parts together and the libraries that you use are largely up to you!</span></p> + +<p>Of course Express is deliberately a very lightweight web application framework, so much of its benefit and potential comes from third party libraries and features. We'll look at those in more detail in the following articles. In our next article we're going to look at setting up a Node development environment, so that you can start seeing some Express code in action.</p> + +<h2 id="더보기">더보기</h2> + +<ul> + <li><a href="https://nodejs.org/api/modules.html#modules_modules">Modules</a> (Node API docs)</li> + <li><a href="https://expressjs.com/">Express</a> (home page)</li> + <li><a href="http://expressjs.com/en/starter/basic-routing.html">Basic routing</a> (Express docs)</li> + <li><a href="http://expressjs.com/en/guide/routing.html">Routing guide</a> (Express docs)</li> + <li><a href="http://expressjs.com/en/guide/using-template-engines.html">Using template engines with Express</a> (Express docs)</li> + <li><a href="https://expressjs.com/en/guide/using-middleware.html">Using middleware</a> (Express docs)</li> + <li><a href="http://expressjs.com/en/guide/writing-middleware.html">Writing middleware for use in Express apps</a> (Express docs)</li> + <li><a href="https://expressjs.com/en/guide/database-integration.html">Database integration</a> (Express docs)</li> + <li><a href="Serving static files in Express">Serving static files in Express</a> (Express docs)</li> + <li><a href="http://expressjs.com/en/guide/error-handling.html">Error handling</a> (Express docs)</li> +</ul> + +<div>{{NextMenu("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs")}}</div> diff --git a/files/ko/learn/server-side/express_nodejs/mongoose/index.html b/files/ko/learn/server-side/express_nodejs/mongoose/index.html new file mode 100644 index 0000000000..07c0f1e422 --- /dev/null +++ b/files/ko/learn/server-side/express_nodejs/mongoose/index.html @@ -0,0 +1,792 @@ +--- +title: 'Express Tutorial Part 3: Using a Database (with Mongoose)' +slug: Learn/Server-side/Express_Nodejs/mongoose +translation_of: Learn/Server-side/Express_Nodejs/mongoose +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">이번에는 데이터베이스를 간단히 소개하고, Node/Express 어플리케이션으로 데이터베이스를 어떻게 다루는 지 알아보겠습니다. 그리고 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">LocalLibrary</a> 웹사이트를 위한 데이터베이스 접근을 제공하는 <a href="http://mongoosejs.com/">Mongoose</a>를 어떻게 사용할 수 있는지 보여줄 것입니다. 오브젝트 스키마와 모델을 선언하는 방법, 주요 필드 타입, 기본 유효성 검사를 설명합니다. 또한 당신이 모델 데이터에 접근할 수 있는 주요한 몇가지 방법들을 소개할 것입니다.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>To be able to design and create your own models using Mongoose.</td> + </tr> + </tbody> +</table> + +<h2 id="개요">개요</h2> + +<p>도서관 직원들은 책과 대여자의 정보를 저장하기 위해 Local Library 웹사이트를 사용할 것입니다. 그리고 도서관 회원들은 책을 빌리고, 검색하며, 어떤 책이 이용한지 알아내고, 책 대여를 예약하거나 책을 빌릴 것입니다. 정보를 효과적으로 저장하고 가져오기 위해서, 우리는 그 정보를 데이터베이스에 저장할 것입니다.</p> + +<p>Express 앱은 다양한 데이터베이스를 사용할 수 있고, 당신에게 CRUD(<strong>C</strong>reate, <strong>R</strong>ead, <strong>U</strong>pdate and <strong>D</strong>elete)를 수행할 수 있는 여러 방법을 제공합니다. 이번 튜토리얼은 이용가능한 몇가지 선택지에 대한 간략한 개요를 제공하며, 더 나아가 우리가 선택한 몇가지 메커니즘에 대해선 자세히 알아볼 것입니다.</p> + +<h3 id="사용할_수_있는_데이터베이스는_무엇이_있나요">사용할 수 있는 데이터베이스는 무엇이 있나요?</h3> + +<p>Express 앱은 노드에서 지원하는 어떤 데이터베이스라도 사용가능합니다. (Express 자체는 데이터베이스 관리에 대한 특정한 추가 동작/요구사항을 정의하지 않습니다.) PostgreSQL, MySQL, Redis, SQLite, and MongoDB를 포함한 많은 <a href="https://expressjs.com/en/guide/database-integration.html">인기있는 데이터베이스 옵션</a>을 선택가능합니다.</p> + +<p>데이터베이스를 고를때, 당신은 생산성/러닝커브, 성능, 쉬운 리플리케이션/백업, 비용, 커뮤니티 지원 등을 고려해야 합니다. 하나의 "최고" 데이터베이스를 정하지 못하는 동안, 우리의 Local Library 같이 작은 규모에서 중간규모의 사이트에 적합한 거의 모든 어떤 솔루션이라도 사용 가능해야 합니다.</p> + +<p>옵션에 대한 더 많은 정보는 여기를 보십시오: <a href="https://expressjs.com/en/guide/database-integration.html">데이터베이스 인테그레이션</a> (Express 문서)</p> + +<h3 id="데이터베이스와_상호작용하는_최소의_방법은_무엇인가요">데이터베이스와 상호작용하는 최소의 방법은 무엇인가요?</h3> + +<p>데이터베이스와 상호작용하는 두가지 접근법이 있습니다: </p> + +<ul> + <li>데이터베이스의 네이티브 쿼리 언어를 사용 (예를 들어 SQL)</li> + <li>오브젝트 데이터 모델 ("ODM") / 오브젝트 관계형 모델 ("ORM")을 사용. ODM/ORM은 웹사이트의 데이터를 Javascript 오브젝트로 나타내며, 그다음 기본 데이터베이스에 매핑됩니다. 어떤 ORM은 특정 데이터베이스에 연결된 반면, 또다른 ORM은 데이터베이스와 무관한 백엔드를 제공합니다.</li> +</ul> + +<p>최상의 퍼포먼스는 SQL이나 데이터베이스에서 지원하는 쿼리 언어를 사용할때 얻을 수 있습니다. ODM은 오브젝트와 데이터베이스 포맷을 매핑하는 변환코드를 사용하기 때문에 종종 느리며, 가장 효율적인 데이터베이스 쿼리를 사용하지 않을 수 있습니다.</p> + +<p>ORM을 사용하는 이점은 프로그래머가 데이터베이스의 의미보다 JavaScript 객체로 계속해서 생각할 수 있다는 것입니다. - 이는 다른 데이터베이스(같거나 다른 웹사이트 어느 쪽에서든)들에서 작업해야 하는 경우 특히 그렇습니다. 또한 데이터의 유효성 및 확인을 확실히 할 수 있습니다.</p> + +<div class="note"> +<p><strong>팁:</strong> ODM/ORM을 사용하면 개발 및 유지 보수 비용이 절감됩니다. 네이티브 쿼리 언어에 친숙하거나 퍼포먼스가 중요한 것이 아니라면, ODM 사용을 적극 고려해야 합니다.</p> +</div> + +<p>NPM 패키지 매니저 사이트에는 사용가능한 많은 ODM/ORM 솔루션이 있습니다.(<a href="https://www.npmjs.com/browse/keyword/odm">odm</a> 과 <a href="https://www.npmjs.com/browse/keyword/orm">orm</a> 태그 집합을 확인하십시오.)</p> + +<p>이 글 작성 시점에 인기있었던 솔루션은 다음과 같습니다:</p> + +<ul> + <li><a href="https://www.npmjs.com/package/mongoose">Mongoose</a>: Mongoose는 비동기적인 환경에서 작업할 수 있도록 디자인된 <a href="https://www.mongodb.org/">MongoDB</a> 객체 모델링 툴입니다.</li> + <li><a href="https://www.npmjs.com/package/waterline">Waterline</a>: An ORM extracted from the Express-based <a href="http://sailsjs.com/">Sails</a> web framework. It provides a uniform API for accessing numerous different databases, including Redis, mySQL, LDAP, MongoDB, and Postgres.</li> + <li><a href="https://www.npmjs.com/package/bookshelf">Bookshelf</a>: Features both promise-based and traditional callback interfaces, providing transaction support, eager/nested-eager relation loading, polymorphic associations, and support for one-to-one, one-to-many, and many-to-many relations. Works with PostgreSQL, MySQL, and SQLite3.</li> + <li><a href="https://www.npmjs.com/package/objection">Objection</a>: Makes it as easy as possible to use the full power of SQL and the underlying database engine (supports SQLite3, Postgres, and MySQL).</li> + <li> + <p><a href="https://www.npmjs.com/package/sequelize">Sequelize</a> is a promise-based ORM for Node.js and io.js. It supports the dialects PostgreSQL, MySQL, MariaDB, SQLite and MSSQL and features solid transaction support, relations, read replication and more.</p> + </li> +</ul> + +<p>일반적으로 솔루션을 선택할 때, 당신은 제공되는 기능과 "커뮤니티 활동" (다운로드, 공헌도, 버그 리포트, 문서 퀄리티 등) 모두를 고려해야 합니다. 이에 대한 글을 작성하고 있는 시점에, 몽구스는 가장 유명한 ORM이며, 당신이 MongoDB를 사용한다면 몽구스는 합리적인 선택입니다.</p> + +<h3 id="Using_Mongoose_and_MongoDb_for_the_LocalLibrary">Using Mongoose and MongoDb for the LocalLibrary</h3> + +<p>For the <em>Local Library</em> example (and the rest of this topic) we're going to use the <a href="https://www.npmjs.com/package/mongoose">Mongoose ODM</a> to access our library data. Mongoose acts as a front end to <a href="https://www.mongodb.com/what-is-mongodb">MongoDB</a>, an open source <a href="https://en.wikipedia.org/wiki/NoSQL">NoSQL</a> database that uses a document-oriented data model. A “collection” of “documents”, in a MongoDB database, <a href="https://docs.mongodb.com/manual/core/databases-and-collections/#collections">is analogous to</a> a “table” of “rows” in a relational database.</p> + +<p>This ODM and database combination is extremely popular in the Node community, partially because the document storage and query system looks very much like JSON, and is hence familiar to JavaScript developers.</p> + +<div class="note"> +<p><strong>Tip:</strong> You don't need to know MongoDB in order to use Mongoose, although parts of the <a href="http://mongoosejs.com/docs/guide.html">Mongoose documentation</a> <em>are</em> easier to use and understand if you are already familiar with MongoDB.</p> +</div> + +<p>The rest of this tutorial shows how to define and access the Mongoose schema and models for the <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">LocalLibrary website</a> example.</p> + +<h2 id="Designing_the_LocalLibrary_models">Designing the LocalLibrary models</h2> + +<p>Before you jump in and start coding the models, it's worth taking a few minutes to think about what data we need to store and the relationships between the different objects.</p> + +<p>We know that we need to store information about books (title, summary, author, genre, ISBN) and that we might have multiple copies available (with globally unique ids, availability statuses, etc.). We might need to store more information about the author than just their name, and there might be multiple authors with the same or similar names. We want to be able to sort information based on book title, author, genre, and category.</p> + +<p>When designing your models it makes sense to have separate models for every "object" (group of related information). In this case the obvious objects are books, book instances, and authors.</p> + +<p>You might also want to use models to represent selection-list options (e.g. like a drop down list of choices), rather than hard coding the choices into the website itself — this is recommended when all the options aren't known up front or may change. The obvious candidate for a model of this type is the book genre (e.g. Science Fiction, French Poetry, etc.)</p> + +<p>Once we've decided on our models and fields, we need to think about the relationships between them.</p> + +<p>With that in mind, the UML association diagram below shows the models we'll define in this case (as boxes). As discussed above, we've created models for book (the generic details of the book), book instance (status of specific physical copies of the book available in the system), and author. We have also decided to have a model for genre, so that values can be created dynamically. We've decided not to have a model for the <code>BookInstance:status</code> — we will hard code the acceptable values because we don't expect these to change. Within each of the boxes you can see the model name, the field names and types, and also the methods and their return types.</p> + +<p>The diagram also shows the relationships between the models, including their <em>multiplicities</em>. The multiplicities are the numbers on the diagram showing the numbers (maximum and minimum) of each model that may be present in the relationship. For example, the connecting line between the boxes shows that <code>Book</code> and a <code>Genre</code> are related. The numbers close to the <code>Book</code> model show that a book must have zero or more <code>Genre</code> (as many as you like), while the numbers on the other end of the line next to the <code>Genre</code> show that it can have zero or more associated books.</p> + +<div class="note"> +<p><strong>Note</strong>: As discussed in our <a href="#related_documents">Mongoose primer</a> below it is often better to have the field that defines the relationship between the documents/models in just <em>one</em> model (you can still find the reverse relationship by searching for the associated <code>_id</code> in the other model). Below we have chosen to define the relationship between Book/Genre and Book/Author in the Book schema, and the relationship between the Book/BookInstance in the BookInstance Schema. This choice was somewhat arbitrary — we could equally well have had the field in the other schema.</p> +</div> + +<p><img alt="Mongoose Library Model with correct cardinality" src="https://mdn.mozillademos.org/files/15645/Library%20Website%20-%20Mongoose_Express.png" style="height: 620px; width: 737px;"></p> + +<div class="note"> +<p><strong>Note</strong>: The next section provides a basic primer explaining how models are defined and used. As you read it, consider how we will construct each of the models in the diagram above.</p> +</div> + +<h2 id="Mongoose_primer">Mongoose primer</h2> + +<p>This section provides an overview of how to connect Mongoose to a MongoDB database, how to define a schema and a model, and how to make basic queries. </p> + +<div class="note"> +<p><strong>Note:</strong> This primer is "heavily influenced" by the <a href="https://www.npmjs.com/package/mongoose">Mongoose quick start</a> on <em>npm</em> and the <a href="http://mongoosejs.com/docs/guide.html">official documentation</a>.</p> +</div> + +<h3 id="Installing_Mongoose_and_MongoDB">Installing Mongoose and MongoDB</h3> + +<p>Mongoose is installed in your project (<strong>package.json</strong>) like any other dependency — using NPM. To install it, use the following command inside your project folder:</p> + +<pre class="brush: bash"><code>npm install mongoose</code> +</pre> + +<p>Installing <em>Mongoose</em> adds all its dependencies, including the MongoDB database driver, but it does not install MongoDB itself. If you want to install a MongoDB server then you can <a href="https://www.mongodb.com/download-center">download installers from here</a> for various operating systems and install it locally. You can also use cloud-based MongoDB instances.</p> + +<div class="note"> +<p><strong>Note:</strong> For this tutorial we'll be using the mLab cloud-based <em>database as a service</em> <a href="https://mlab.com/plans/pricing/">sandbox tier</a> to provide the database. This is suitable for development, and makes sense for the tutorial because it makes "installation" operating system independent (database-as-a-service is also one approach you might well use for your production database).</p> +</div> + +<h3 id="Connecting_to_MongoDB">Connecting to MongoDB</h3> + +<p><em>Mongoose</em> requires a connection to a MongoDB database. You can <code>require()</code> and connect to a locally hosted database with <code>mongoose.connect()</code>, as shown below.</p> + +<pre class="brush: js">//Import the mongoose module +var mongoose = require('mongoose'); + +//Set up default mongoose connection +var mongoDB = 'mongodb://127.0.0.1/my_database'; +mongoose.connect(mongoDB); +// Get Mongoose to use the global promise library +mongoose.Promise = global.Promise; +//Get the default connection +var db = mongoose.connection; + +//Bind connection to error event (to get notification of connection errors) +db.on('error', console.error.bind(console, 'MongoDB connection error:'));</pre> + +<p>You can get the default <code>Connection</code> object with <code>mongoose.connection</code>. Once connected, the open event is fired on the <code>Connection</code> instance.</p> + +<div class="note"> +<p><strong>Tip:</strong> If you need to create additional connections you can use <code>mongoose.createConnection()</code>. This takes the same form of database URI (with host, database, port, options etc.) as <code>connect()</code> and returns a <code>Connection</code> object).</p> +</div> + +<h3 id="Defining_and_creating_models">Defining and creating models</h3> + +<p>Models are <em>defined </em>using the <code>Schema</code> interface. The Schema allows you to define the fields stored in each document along with their validation requirements and default values. In addition, you can define static and instance helper methods to make it easier to work with your data types, and also virtual properties that you can use like any other field, but which aren't actually stored in the database (we'll discuss a bit further below).</p> + +<p>Schemas are then "compiled" into models using the <code>mongoose.model()</code> method. Once you have a model you can use it to find, create, update, and delete objects of the given type.</p> + +<div class="note"> +<p><strong>Note:</strong> Each model maps to a <em>collection</em> of <em>documents</em> in the MongoDB database. The documents will contain the fields/schema types defined in the model <code>Schema</code>.</p> +</div> + +<h4 id="Defining_schemas">Defining schemas</h4> + +<p>The code fragment below shows how you might define a simple schema. First you <code>require()</code> mongoose, then use the Schema constructor to create a new schema instance, defining the various fields inside it in the constructor's object parameter.</p> + +<pre class="brush: js">//Require Mongoose +var mongoose = require('mongoose'); + +//Define a schema +var Schema = mongoose.Schema; + +var SomeModelSchema = new Schema({ + a_string: String, + a_date: Date +}); +</pre> + +<p>In the case above we just have two fields, a string and a date. In the next sections we will show some of the other field types, validation, and other methods.</p> + +<h4 id="Creating_a_model">Creating a model</h4> + +<p>Models are created from schemas using the <code>mongoose.model()</code> method:</p> + +<pre class="brush: js">// Define schema +var Schema = mongoose.Schema; + +var SomeModelSchema = new Schema({ + a_string: String, + a_date: Date +}); + +<strong>// Compile model from schema +var SomeModel = mongoose.model('SomeModel', SomeModelSchema );</strong></pre> + +<p>The first argument is the singular name of the collection that will be created for your model (Mongoose will create the database collection for the above model <em>SomeModel</em> above), and the second argument is the schema you want to use in creating the model.</p> + +<div class="note"> +<p><strong>Note:</strong> Once you've defined your model classes you can use them to create, update, or delete records, and to run queries to get all records or particular subsets of records. We'll show you how to do this in the <a href="#Using_models">Using models</a> section, and when we create our views.</p> +</div> + +<h4 id="스키마_타입_(필드)">스키마 타입 (필드)</h4> + +<p>한 스키마는 임의의 숫자의 필드들을 가질 수 있습니다.(각각의 필드는 MongoDB에 저장된 문서의 필드를 대표합니다.) 아래의 예제 스키마는 일반적인 필드 타입들을 보여주고 있으며 그들이 어떻게 선언되는지 나타냅니다.</p> + +<pre class="brush: js">var schema = new Schema( +{ + name: <strong>String</strong>, + binary: <strong>Buffer</strong>, + living: <strong>Boolean</strong>, + updated: { type: <strong>Date</strong>, default: Date.now }, + age: { type: <strong>Number</strong>, min: 18, max: 65, required: true }, + mixed: <strong>Schema.Types.Mixed</strong>, + _someId: <strong>Schema.Types.ObjectId</strong>, + array: <strong>[]</strong>, + ofString: [<strong>String</strong>], // You can also have an array of each of the other types too. + nested: { stuff: { type: <strong>String</strong>, lowercase: true, trim: true } } +})</pre> + +<p>Most of the <a href="http://mongoosejs.com/docs/schematypes.html">SchemaTypes</a> (the descriptors after “type:” or after field names) are self explanatory. The exceptions are:</p> + +<ul> + <li><code>ObjectId</code>: Represents specific instances of a model in the database. For example, a book might use this to represent its author object. This will actually contain the unique ID (<code>_id</code>) for the specified object. We can use the <code>populate()</code> method to pull in the associated information when needed.</li> + <li><a href="http://mongoosejs.com/docs/schematypes.html#mixed">Mixed</a>: 임의의 스키마 타입입니다.(어떤 타입도 될 수 있음)</li> + <li><font face="Consolas, Liberation Mono, Courier, monospace">[]</font>: An array of items. You can perform JavaScript array operations on these models (push, pop, unshift, etc.). The examples above show an array of objects without a specified type and an array of <code>String</code> objects, but you can have an array of any type of object.</li> +</ul> + +<p>The code also shows both ways of declaring a field:</p> + +<ul> + <li>Field <em>name</em> and <em>type</em> as a key-value pair (i.e. as done with fields <code>name</code>, <code>binary </code>and <code>living</code>).</li> + <li>Field <em>name</em> followed by an object defining the <code>type</code>, and any other <em>options</em> for the field. Options include things like: + <ul> + <li>default values.</li> + <li>built-in validators (e.g. max/min values) and custom validation functions.</li> + <li>Whether the field is required</li> + <li>Whether <code>String</code> fields should automatically be set to lowercase, uppercase, or trimmed (e.g. <code>{ type: <strong>String</strong>, lowercase: true, trim: true }</code>)</li> + </ul> + </li> +</ul> + +<p>For more information about options see <a href="http://mongoosejs.com/docs/schematypes.html">SchemaTypes</a> (Mongoose docs).</p> + +<h4 id="Validation">Validation</h4> + +<p>Mongoose provides built-in and custom validators, and synchronous and asynchronous validators. It allows you to specify both the acceptable range or values and the error message for validation failure in all cases.</p> + +<p>The built-in validators include:</p> + +<ul> + <li>All <a href="http://mongoosejs.com/docs/schematypes.html">SchemaTypes</a> have the built-in <a href="http://mongoosejs.com/docs/api.html#schematype_SchemaType-required">required</a> validator. This is used to specify whether the field must be supplied in order to save a document.</li> + <li><a href="http://mongoosejs.com/docs/api.html#schema-number-js">Numbers</a> have <a href="http://mongoosejs.com/docs/api.html#schema_number_SchemaNumber-min">min</a> and <a href="http://mongoosejs.com/docs/api.html#schema_number_SchemaNumber-max">max</a> validators.</li> + <li><a href="http://mongoosejs.com/docs/api.html#schema-string-js">Strings</a> have: + <ul> + <li><a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-enum">enum</a>: specifies the set of allowed values for the field.</li> + <li><a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-match">match</a>: specifies a regular expression that the string must match.</li> + <li><a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-maxlength">maxlength</a> and <a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-minlength">minlength</a> for the string.</li> + </ul> + </li> +</ul> + +<p>The example below (slightly modified from the Mongoose documents) shows how you can specify some of the validator types and error messages:</p> + +<pre class="brush: js"><code> + var breakfastSchema = new Schema({ + eggs: { + type: Number, + min: [6, 'Too few eggs'], + max: 12 + required: [true, 'Why no eggs?'] + }, + drink: { + type: String, + enum: ['Coffee', 'Tea', 'Water',] + } + }); +</code></pre> + +<p>For complete information on field validation see <a href="http://mongoosejs.com/docs/validation.html">Validation</a> (Mongoose docs).</p> + +<h4 id="Virtual_properties">Virtual properties</h4> + +<p>Virtual properties are document properties that you can get and set but that do not get persisted to MongoDB. The getters are useful for formatting or combining fields, while setters are useful for de-composing a single value into multiple values for storage. The example in the documentation constructs (and deconstructs) a full name virtual property from a first and last name field, which is easier and cleaner than constructing a full name every time one is used in a template.</p> + +<div class="note"> +<p><strong>Note:</strong> We will use a virtual property in the library to define a unique URL for each model record using a path and the record's <code>_id</code> value.</p> +</div> + +<p>For more information see <a href="http://mongoosejs.com/docs/guide.html#virtuals">Virtuals</a> (Mongoose documentation).</p> + +<h4 id="Methods_and_query_helpers">Methods and query helpers</h4> + +<p>A schema can also have <a href="http://mongoosejs.com/docs/guide.html#methods">instance methods</a>, <a href="http://mongoosejs.com/docs/guide.html#statics">static methods</a>, and <a href="http://mongoosejs.com/docs/guide.html#query-helpers">query helpers</a>. The instance and static methods are similar, but with the obvious difference that an instance method is associated with a particular record and has access to the current object. Query helpers allow you to extend mongoose's <a href="http://mongoosejs.com/docs/queries.html">chainable query builder API</a> (for example, allowing you to add a query "byName" in addition to the <code>find()</code>, <code>findOne()</code> and <code>findById()</code> methods).</p> + +<h3 id="Using_models">Using models</h3> + +<p>Once you've created a schema you can use it to create models. The model represents a collection of documents in the database that you can search, while the model's instances represent individual documents that you can save and retrieve.</p> + +<p>We provide a brief overview below. For more information see: <a href="http://mongoosejs.com/docs/models.html">Models</a> (Mongoose docs).</p> + +<h4 id="Creating_and_modifying_documents">Creating and modifying documents</h4> + +<p>To create a record you can define an instance of the model and then call <code>save()</code>. The examples below assume SomeModel is a model (with a single field "name") that we have created from our schema.</p> + +<pre class="brush: js"><code>// Create an instance of model SomeModel +var awesome_instance = new </code>SomeModel<code>({ name: 'awesome' }); + +// Save the new model instance, passing a callback +awesome_instance.save(function (err) { + if (err) return handleError(err); + // saved! +}); +</code></pre> + +<p>Creation of records (along with updates, deletes, and queries) are asynchronous operations — you supply a callback that is called when the operation completes. The API uses the error-first argument convention, so the first argument for the callback will always be an error value (or null). If the API returns some result, this will be provided as the second argument.</p> + +<p>You can also use <code>create()</code> to define the model instance at the same time as you save it. The callback will return an error for the first argument and the newly-created model instance for the second argument.</p> + +<pre class="brush: js">SomeModel<code>.create({ name: 'also_awesome' }, function (err, awesome_instance) { + if (err) return handleError(err); + // saved! +});</code></pre> + +<p>Every model has an associated connection (this will be the default connection when you use <code>mongoose.model()</code>). You create a new connection and call <code>.model()</code> on it to create the documents on a different database.</p> + +<p>You can access the fields in this new record using the dot syntax, and change the values. You have to call <code>save()</code> or <code>update()</code> to store modified values back to the database.</p> + +<pre class="brush: js">// Access model field values using dot notation +console.log(<code>awesome_instance.name</code>); //should log '<code>also_awesome</code>' + +// Change record by modifying the fields, then calling save(). +<code>awesome_instance</code>.name="New cool name"; +<code>awesome_instance.save(function (err) { + if (err) return handleError(err); // saved! + });</code> +</pre> + +<h4 id="Searching_for_records">Searching for records</h4> + +<p>You can search for records using query methods, specifying the query conditions as a JSON document. The code fragment below shows how you might find all athletes in a database that play tennis, returning just the fields for athlete <em>name</em> and <em>age</em>. Here we just specify one matching field (sport) but you can add more criteria, specify regular expression criteria, or remove the conditions altogether to return all athletes.</p> + +<pre class="brush: js"><code>var Athlete = mongoose.model('Athlete', yourSchema); + +// find all athletes who play tennis, selecting the 'name' and 'age' fields +Athlete.find({ 'sport': 'Tennis' }, 'name age', function (err, athletes) { + if (err) return handleError(err); + // 'athletes' contains the list of athletes that match the criteria. +})</code></pre> + +<p>If you specify a callback, as shown above, the query will execute immediately. The callback will be invoked when the search completes.</p> + +<div class="note"> +<p><strong>Note:</strong> All callbacks in Mongoose use the pattern <code>callback(error, result)</code>. If an error occurs executing the query, the <code>error</code> parameter will contain an error document, and <code>result</code> will be null. If the query is successful, the <code>error</code> parameter will be null, and the <code>result</code> will be populated with the results of the query.</p> +</div> + +<p>If you don't specify a callback then the API will return a variable of type <a href="http://mongoosejs.com/docs/api.html#query-js">Query</a>. You can use this query object to build up your query and then execute it (with a callback) later using the <code>exec()</code> method.</p> + +<pre class="brush: js"><code>// find all athletes that play tennis +var query = Athlete.find({ 'sport': 'Tennis' }); + +// selecting the 'name' and 'age' fields +query.select('name age'); + +// limit our results to 5 items +query.limit(5); + +// sort by age +query.sort({ age: -1 }); + +// execute the query at a later time +query.exec(function (err, athletes) { + if (err) return handleError(err); + // athletes contains an ordered list of 5 athletes who play Tennis +})</code></pre> + +<p>Above we've defined the query conditions in the <code>find()</code> method. We can also do this using a <code>where()</code> function, and we can chain all the parts of our query together using the dot operator (.) rather than adding them separately. The code fragment below is the same as our query above, with an additional condition for the age.</p> + +<pre><code>Athlete. + find(). + where('sport').equals('Tennis'). + where('age').gt(17).lt(50). //Additional where query + limit(5). + sort({ age: -1 }). + select('name age'). + exec(callback); // where callback is the name of our callback function.</code></pre> + +<p>The <a href="http://mongoosejs.com/docs/api.html#query_Query-find">find()</a> method gets all matching records, but often you just want to get one match. The following methods query for a single record:</p> + +<ul> + <li><code><a href="http://mongoosejs.com/docs/api.html#model_Model.findById">findById()</a></code>: Finds the document with the specified <code>id</code> (every document has a unique <code>id</code>).</li> + <li><code><a href="http://mongoosejs.com/docs/api.html#query_Query-findOne">findOne()</a></code>: Finds a single document that matches the specified criteria.</li> + <li><code><a href="http://mongoosejs.com/docs/api.html#model_Model.findByIdAndRemove">findByIdAndRemove()</a></code>, <code><a href="http://mongoosejs.com/docs/api.html#model_Model.findByIdAndUpdate">findByIdAndUpdate()</a></code>, <code><a href="http://mongoosejs.com/docs/api.html#query_Query-findOneAndRemove">findOneAndRemove()</a></code>, <code><a href="http://mongoosejs.com/docs/api.html#query_Query-findOneAndUpdate">findOneAndUpdate()</a></code>: Finds a single document by <code>id</code> or criteria and either update or remove it. These are useful convenience functions for updating and removing records.</li> +</ul> + +<div class="note"> +<p><strong>Note:</strong> There is also a <code><a href="http://mongoosejs.com/docs/api.html#model_Model.count">count()</a></code> method that you can use to get the number of items that match conditions. This is useful if you want to perform a count without actually fetching the records.</p> +</div> + +<p>There is a lot more you can do with queries. For more information see: <a href="http://mongoosejs.com/docs/queries.html">Queries</a> (Mongoose docs).</p> + +<h4 id="Working_with_related_documents_—_population">Working with related documents — population</h4> + +<p>You can create references from one document/model instance to another using the <code>ObjectId</code> schema field, or from one document to many using an array of <code>ObjectIds</code>. The field stores the id of the related model. If you need the actual content of the associated document, you can use the <code><a href="http://mongoosejs.com/docs/api.html#query_Query-populate">populate()</a></code> method in a query to replace the id with the actual data.</p> + +<p>For example, the following schema defines authors and stories. Each author can have multiple stories, which we represent as an array of <code>ObjectId</code>. Each story can have a single author. The "ref" (highlighted in bold below) tells the schema which model can be assigned to this field.</p> + +<pre class="brush: js"><code>var mongoose = require('mongoose') + , Schema = mongoose.Schema + +var authorSchema = Schema({ + name : String, + stories : [{ type: Schema.Types.ObjectId, <strong>ref</strong>: 'Story' }] +}); + +var storySchema = Schema({ + author : { type: Schema.Types.ObjectId, <strong>ref</strong>: 'Author' }, + title : String +}); + +var Story = mongoose.model('Story', storySchema); +var Author = mongoose.model('Author', authorSchema);</code></pre> + +<p>We can save our references to the related document by assigning the <code>_id</code> value. Below we create an author, then a book, and assign the author id to our stories author field.</p> + +<pre class="brush: js"><code>var bob = new Author({ name: 'Bob Smith' }); + +bob.save(function (err) { + if (err) return handleError(err); + + //Bob now exists, so lets create a story + var story = new Story({ + title: "Bob goes sledding", + author: bob._id // assign the _id from the our author Bob. This ID is created by default! + }); + + story.save(function (err) { + if (err) return handleError(err); + // Bob now has his story + }); +});</code></pre> + +<p>Our story document now has an author referenced by the author document's ID. In order to get the author information in our story results we use <code>populate()</code>, as shown below.</p> + +<pre class="brush: js"><code>Story +.findOne({ title: 'Bob goes sledding' }) +.populate('author') //This populates the author id with actual author information! +.exec(function (err, story) { + if (err) return handleError(err); + console.log('The author is %s', story.author.name); + // prints "The author is Bob Smith" +});</code></pre> + +<div class="note"> +<p><strong>Note:</strong> Astute readers will have noted that we added an author to our story, but we didn't do anything to add our story to our author's <code>stories</code> array. How then can we get all stories by a particular author? One way would be to add our author to the stories array, but this would result in us having two places where the information relating authors and stories needs to be maintained.</p> + +<p>A better way is to get the <code>_id</code> of our <em>author</em>, then use <code>find()</code> to search for this in the author field across all stories.</p> + +<pre class="brush: js"><code>Story +.find({ author : bob._id }) +.exec(function (err, stories) { + if (err) return handleError(err); + // returns all stories that have Bob's id as their author. +});</code> +</pre> +</div> + +<p>This is almost everything you need to know about working with related items<em> for this tutorial</em>. For more detailed information see <a href="http://mongoosejs.com/docs/populate.html">Population</a> (Mongoose docs).</p> + +<h3 id="One_schemamodel_per_file">One schema/model per file</h3> + +<p>While you can create schemas and models using any file structure you like, we highly recommend defining each model schema in its own module (file), exporting the method to create the model. This is shown below:</p> + +<pre class="brush: js"><code>// File: ./models/somemodel.js + +//Require Mongoose +var mongoose = require('mongoose'); + +//Define a schema +var Schema = mongoose.Schema; + +var SomeModelSchema = new Schema({ + a_string : String, + a_date : Date, +}); + +<strong>//Export function to create "SomeModel" model class +module.exports = mongoose.model('SomeModel', SomeModelSchema );</strong></code></pre> + +<p>You can then require and use the model immediately in other files. Below we show how you might use it to get all instances of the model.</p> + +<pre class="brush: js"><code>//Create a SomeModel model just by requiring the module +var SomeModel = require('../models/somemodel') + +// Use the SomeModel object (model) to find all SomeModel records +SomeModel.find(callback_function);</code></pre> + +<h2 id="Setting_up_the_MongoDB_database">Setting up the MongoDB database</h2> + +<p>Now that we understand something of what Mongoose can do and how we want to design our models, it's time to start work on the <em>LocalLibrary</em> website. The very first thing we want to do is set up a MongoDb database that we can use to store our library data.</p> + +<p>For this tutorial we're going to use <a href="https://mlab.com/welcome/">mLab</a>'s free cloud-hosted "<a href="https://mlab.com/plans/pricing/">sandbox</a>" database. This database tier is not considered suitable for production websites because it has no redundancy, but it is great for development and prototyping. We're using it here because it is free and easy to set up, and because mLab is a popular <em>database as a service</em> vendor that you might reasonably choose for your production database (other popular choices at the time of writing include <a href="https://www.compose.com/">Compose</a>, <a href="https://scalegrid.io/pricing.html">ScaleGrid</a> and <a href="https://www.mongodb.com/cloud/atlas">MongoDB Atlas</a>).</p> + +<div class="note"> +<p><strong>Note:</strong> If you prefer you can set up a MongoDb database locally by downloading and installing the <a href="https://www.mongodb.com/download-center">appropriate binaries for your system</a>. The rest of the instructions in this article would be similar, except for the database URL you would specify when connecting.</p> +</div> + +<p>You will first need to <a href="https://mlab.com/signup/">create an account</a> with mLab (this is free, and just requires that you enter basic contact details and acknowledge their terms of service). </p> + +<p>After logging in, you'll be taken to the <a href="https://mlab.com/home">home</a> screen:</p> + +<ol> + <li>Click <strong>Create New</strong> in the <em>MongoDB Deployments</em> section.<img alt="" src="https://mdn.mozillademos.org/files/14446/mLabCreateNewDeployment.png" style="height: 415px; width: 1000px;"></li> + <li>This will open the <em>Cloud Provider Selection </em>screen.<br> + <img alt="MLab - screen for new deployment" src="https://mdn.mozillademos.org/files/15661/mLab_new_deployment_form_v2.png" style="height: 931px; width: 1297px;"><br> + + <ul> + <li>Select the SANDBOX (Free) plan from the Plan Type section. </li> + <li>Select any provider from the <em>Cloud Provider </em>section. Different providers offer different regions (displayed below the selected plan type).</li> + <li>Click the <strong>Continue</strong> button.</li> + </ul> + </li> + <li>This will open the <em>Select Region</em> screen. + <p><img alt="Select new region screen" src="https://mdn.mozillademos.org/files/15662/mLab_new_deployment_select_region_v2.png" style="height: 570px; width: 1293px;"></p> + + <ul> + <li> + <p>Select the region closest to you and then <strong>Continue</strong>.</p> + </li> + </ul> + </li> + <li> + <p>This will open the <em>Final Details</em> screen.<br> + <img alt="New deployment database name" src="https://mdn.mozillademos.org/files/15663/mLab_new_deployment_final_details.png" style="height: 569px; width: 1293px;"></p> + + <ul> + <li> + <p>Enter the name for the new database as <code>local_library</code> and then select <strong>Continue</strong>.</p> + </li> + </ul> + </li> + <li> + <p>This will open the <em>Order Confirmation</em> screen.<br> + <img alt="Order confirmation screen" src="https://mdn.mozillademos.org/files/15664/mLab_new_deployment_order_confirmation.png" style="height: 687px; width: 1290px;"></p> + + <ul> + <li> + <p>Click <strong>Submit Order</strong> to create the database.</p> + </li> + </ul> + </li> + <li> + <p>You will be returned to the home screen. Click on the new database you just created to open its details screen. As you can see the database has no collections (data).<br> + <img alt="mLab - Database details screen" src="https://mdn.mozillademos.org/files/15665/mLab_new_deployment_database_details.png" style="height: 700px; width: 1398px;"><br> + <br> + The URL that you need to use to access your database is displayed on the form above (shown for this database circled above). In order to use this you need to create a database user that you can specify in the URL.</p> + </li> + <li>Click the <strong>Users</strong> tab and select the <strong>Add database user</strong> button.</li> + <li>Enter a username and password (twice), and then press <strong>Create</strong>. Do not select <em>Make read only</em>.<br> + <img alt="" src="https://mdn.mozillademos.org/files/14454/mLab_database_users.png" style="height: 204px; width: 600px;"></li> +</ol> + +<p>You now have now created the database, and have an URL (with username and password) that can be used to access it. This will look something like: <code>mongodb://your_user_namer:your_password@ds119748.mlab.com:19748/local_library</code>.</p> + +<h2 id="Install_Mongoose">Install Mongoose</h2> + +<p>Open a command prompt and navigate to the directory where you created your <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">skeleton Local Library website</a>. Enter the following command to install Mongoose (and its dependencies) and add it to your <strong>package.json</strong> file, unless you have already done so when reading the <a href="#Installing_Mongoose_and_MongoDB">Mongoose Primer</a> above.</p> + +<pre class="brush: bash">npm install mongoose --save +</pre> + +<h2 id="Connect_to_MongoDB">Connect to MongoDB</h2> + +<p>Open <strong>/app.js</strong> (in the root of your project) and copy the following text below where you declare the <em>Express application object</em> (after the line <code>var app = express();</code>). Replace the database url string ('<em>insert_your_database_url_here</em>') with the location URL representing your own database (i.e. using the information from <a href="#Setting_up_the_MongoDB_database">from mLab</a>).</p> + +<pre class="brush: js">//Set up mongoose connection +var mongoose = require('mongoose'); +var mongoDB = '<em>insert_your_database_url_here</em>'; +mongoose.connect(mongoDB); +mongoose.Promise = global.Promise; +var db = mongoose.connection; +db.on('error', console.error.bind(console, 'MongoDB connection error:'));</pre> + +<p>As discussed <a href="#Connecting_to_MongoDB">in the Mongoose primer above</a>, this code creates the default connection to the database and binds to the error event (so that errors will be printed to the console). </p> + +<h2 id="Defining_the_LocalLibrary_Schema">Defining the LocalLibrary Schema</h2> + +<p>We will define a separate module for each model, as <a href="#One_schemamodel_per_file">discussed above</a>. Start by creating a folder for our models in the project root (<strong>/models</strong>) and then create separate files for each of the models:</p> + +<pre>/express-locallibrary-tutorial //the project root + <strong>/models</strong> + <strong>author.js</strong> + <strong>book.js</strong> + <strong>bookinstance.js</strong> + <strong>genre.js</strong> +</pre> + +<h3 id="Author_model">Author model</h3> + +<p>Copy the <code>Author</code> schema code shown below and paste it into your <strong>./models/author.js</strong> file. The scheme defines an author has having <code>String</code> SchemaTypes for the first and family names, that are required and have a maximum of 100 characters, and <code>Date</code> fields for the date of birth and death.</p> + +<pre class="brush: js">var mongoose = require('mongoose'); + +var Schema = mongoose.Schema; + +var AuthorSchema = new Schema( + { + first_name: {type: String, required: true, max: 100}, + family_name: {type: String, required: true, max: 100}, + date_of_birth: {type: Date}, + date_of_death: {type: Date}, + } +); + +<strong>// Virtual for author's full name +AuthorSchema +.virtual('name') +.get(function () { + return this.family_name + ', ' + this.first_name; +});</strong> + +// Virtual for author's URL +AuthorSchema +.virtual('url') +.get(function () { + return '/catalog/author/' + this._id; +}); + +//Export model +module.exports = mongoose.model('Author', AuthorSchema); + +</pre> + +<p>We've also declared a <a href="#Virtual_properties">virtual</a> for the AuthorSchema named "url" that returns the absolute URL required to get a particular instance of the model — we'll use the property in our templates whenever we need to get a link to a particular author.</p> + +<div class="note"> +<p><strong>Note:</strong> Declaring our URLs as a virtual in the schema is a good idea because then the URL for an item only ever needs to be changed in one place.<br> + At this point a link using this URL wouldn't work, because we haven't got any routes handling code for individual model instances. We'll set those up in a later article!</p> +</div> + +<p>At the end of the module we export the model.</p> + +<h3 id="Book_model">Book model</h3> + +<p>Copy the <code>Book</code> schema code shown below and paste it into your <strong>./models/book.js</strong> file. Most of this is similar to the author model — we've declared a schema with a number of string fields and a virtual for getting the URL of specific book records, and we've exported the model.</p> + +<pre class="brush: js">var mongoose = require('mongoose'); + +var Schema = mongoose.Schema; + +var BookSchema = new Schema( + { + title: {type: String, required: true}, + <strong> author: {type: Schema.ObjectId, ref: 'Author', required: true},</strong> + summary: {type: String, required: true}, + isbn: {type: String, required: true}, + <strong> genre: [{type: Schema.ObjectId, ref: 'Genre'}]</strong> + } +); + +// Virtual for book's URL +BookSchema +.virtual('url') +.get(function () { + return '/catalog/book/' + this._id; +}); + +//Export model +module.exports = mongoose.model('Book', BookSchema); +</pre> + +<p>The main difference here is that we've created two references to other models:</p> + +<ul> + <li>author is a reference to a single <code>Author</code> model object, and is required.</li> + <li>genre is a reference to an array of <code>Genre</code> model objects. We haven't declared this object yet!</li> +</ul> + +<h3 id="BookInstance_model">BookInstance model</h3> + +<p>Finally, copy the <code>BookInstance</code> schema code shown below and paste it into your <strong>./models/bookinstance.js</strong> file. The <code>BookInstance</code> represents a specific copy of a book that someone might borrow, and includes information about whether the copy is available or on what date it is expected back, "imprint" or version details.</p> + +<pre class="brush: js">var mongoose = require('mongoose'); + +var Schema = mongoose.Schema; + +var BookInstanceSchema = new Schema( + { + book: { type: Schema.ObjectId, ref: 'Book', required: true }, //reference to the associated book + imprint: {type: String, required: true}, + status: {type: String, required: true, <strong>enum: ['Available', 'Maintenance', 'Loaned', 'Reserved']</strong>, <strong>default: 'Maintenance'</strong>}, + due_back: {type: Date, <strong>default: Date.now</strong>} + } +); + +// Virtual for bookinstance's URL +BookInstanceSchema +.virtual('url') +.get(function () { + return '/catalog/bookinstance/' + this._id; +}); + +//Export model +module.exports = mongoose.model('BookInstance', BookInstanceSchema);</pre> + +<p>The new things we show here are the field options:</p> + +<ul> + <li><code>enum</code>: This allows us to set the allowed values of a string. In this case we use it to specify the availability status of our books (using an enum means that we can prevent mis-spellings and arbitrary values for our status)</li> + <li><code>default</code>: We use default to set the default status for newly created bookinstances to maintenance and the default <code>due_back</code> date to <code>now</code> (note how you can call the Date function when setting the date!)</li> +</ul> + +<p>Everything else should be familiar from our previous schema.</p> + +<h3 id="Genre_model_-_challenge!">Genre model - challenge!</h3> + +<p>Open your <strong>./models/genre.js</strong> file and create a schema for storing genres (the category of book, e.g. whether it is fiction or non-fiction, romance or military history, etc).</p> + +<p>The definition will be very similar to the other models:</p> + +<ul> + <li>The model should have a <code>String</code> SchemaType called <code>name</code> to describe the genre.</li> + <li>This name should be required and have between 3 and 100 characters.</li> + <li>Declare a <a href="#Virtual_properties">virtual</a> for the genre's URL, named <code>url</code>.</li> + <li>Export the model.</li> +</ul> + +<h2 id="Testing_—_create_some_items">Testing — create some items</h2> + +<p>That's it. We now have all models for the site set up!</p> + +<p>In order to test the models (and to create some example books and other items that we can use in our next articles) we'll now run an <em>independent</em> script to create items of each type:</p> + +<ol> + <li>Download (or otherwise create) the file <a href="https://raw.githubusercontent.com/hamishwillee/express-locallibrary-tutorial/master/populatedb.js">populatedb.js</a> inside your <em>express-locallibrary-tutorial</em> directory (in the same level as <code>package.json</code>). + + <div class="note"> + <p><strong>Note:</strong> You don't need to know how <a href="https://raw.githubusercontent.com/hamishwillee/express-locallibrary-tutorial/master/populatedb.js">populatedb.js</a> works; it just adds sample data into the database.</p> + </div> + </li> + <li>Enter the following commands in the project root to install the <em>async</em> module that is required by the script (we'll discuss this in later tutorials, ) + <pre class="brush: bash">npm install async --save</pre> + </li> + <li>Run the script using node in your command prompt, passing in the URL of your <em>MongoDB</em> database (the same one you replaced the <em>insert_your_database_url_here </em>placeholder with, inside <code>app.js</code> earlier): + <pre class="brush: bash">node populatedb <your mongodb url></pre> + </li> + <li>The script should run through to completion, displaying items as it creates them in the terminal.</li> +</ol> + +<div class="note"> +<p><strong>Tip:</strong> Go to your database on <a href="https://mlab.com/home">mLab</a>. You should now be able to drill down into individual collections of Books, Authors, Genres and BookInstances, and check out individual documents.</p> +</div> + +<h2 id="Summary">Summary</h2> + +<p>In this article we've learned a bit about databases and ORMs on Node/Express, and a lot about how Mongoose schema and models are defined. We then used this information to design and implement <code>Book</code>, <code>BookInstance</code>, <code>Author</code> and <code>Genre</code> models for the <em>LocalLibrary</em> website.</p> + +<p>Last of all we tested our models by creating a number of instances (using a standalone script). In the next article we'll look at creating some pages to display these objects.</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://expressjs.com/en/guide/database-integration.html">Database integration</a> (Express docs)</li> + <li><a href="http://mongoosejs.com/">Mongoose website</a> (Mongoose docs)</li> + <li><a href="http://mongoosejs.com/docs/guide.html">Mongoose Guide</a> (Mongoose docs)</li> + <li><a href="http://mongoosejs.com/docs/validation.html">Validation</a> (Mongoose docs)</li> + <li><a href="http://mongoosejs.com/docs/schematypes.html">Schema Types</a> (Mongoose docs)</li> + <li><a href="http://mongoosejs.com/docs/models.html">Models</a> (Mongoose docs)</li> + <li><a href="http://mongoosejs.com/docs/queries.html">Queries</a> (Mongoose docs)</li> + <li><a href="http://mongoosejs.com/docs/populate.html">Population</a> (Mongoose docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs")}}</p> + +<p> </p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li> +</ul> + +<p> </p> diff --git a/files/ko/learn/server-side/express_nodejs/routes/index.html b/files/ko/learn/server-side/express_nodejs/routes/index.html new file mode 100644 index 0000000000..8d8618ca98 --- /dev/null +++ b/files/ko/learn/server-side/express_nodejs/routes/index.html @@ -0,0 +1,639 @@ +--- +title: 'Express Tutorial Part 4: Routes and controllers' +slug: Learn/Server-side/Express_Nodejs/routes +translation_of: Learn/Server-side/Express_Nodejs/routes +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">이 튜토리얼에서 우리는 더미 핸들러 함수를 이용해 최종적으로 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">LocalLibrary</a> 웹사이트에 쓰이게 될 모든 리소스 종단점라우팅 모듈 (url 핸들링 코드)를 설정해 볼 것입니다.이 작업을 통해 우리는 향후 문서에 쓰일 함수들을 모듈화된 라우트 핸들링 코드 구조로 제작하는 법을 배울 수 있습니다.또한 Express를 이용해 모듈화된 라우팅방법을 잘 이해할 수 있게 될 것입니다.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">사전조건:</th> + <td><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a>를 먼저 구독해주세요.이전 강의 주제를 완료해 주세요.( <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part3 : Mongoose Database 와 연동하기</a>)</td> + </tr> + <tr> + <th scope="row">목표:</th> + <td> + <p> 간단한 라우팅 함수 구현.</p> + + <p>모든 URL 종단점 구성해보기.</p> + </td> + </tr> + </tbody> +</table> + +<h2 id="개요">개요</h2> + +<p>지난 튜토리얼에서 우리는 데이터베이스와의 상호작용을 위해서 Mongoose 모델을 정의 했으며 초기 도서관 기록들을 만들기 위해 단 하나의 스크립트 파일을 사용했습니다.이것으로 우리는 이제 사용자에게 정보를 제공을 위한 코드를 작성할 수 있게 되었습니다. 첫번째로 해야할 일은 어떠한 정보를 웹사이트 페이지에 노출시킬 지 정하는 것입니다.그 다음에 정보들을 반환하기 위한 적절한 URL을 정의하게 될 것입니다.</p> + +<p>하단의 다이어그램은 HTTP를 통해 정보를 요청/반환을 작업할 경우 실현시켜야 하는 정보와 객체들의 주요 흐름을 나타내고 있습니다.In addition to the views and routes the diagram shows "controllers" — functions that separate out the code to route requests from the code that actually processes requests.</p> + +<p> </p> + +<p>`이미 작성한 모델들을 제외한 우리가 앞으로 작성할 목록은 :</p> + +<ul> + <li>"Routes" to forward the supported requests (and any information encoded in request URLs) to the appropriate controller functions.</li> + <li>"라우트들"는 지원된 요청(요청 URL에 어떤 인코딩된 정보)들을 알맞은 Controller 함수들로 보낸다.</li> + <li>Controller functions to get the requested data from the models, create an HTML page displaying the data, and return it to the user to view in the browser.</li> + <li>Controller 함수는 모델로부터 요청된 데이터를 얻어내거나 , 데이터를 나타내는 HTML 페이지를 내고, 이것을 브라우져의 화면으로 사용자에게 전달한다.</li> + <li>Views (templates) used by the controllers to render the data.</li> + <li>View들(템플릿들)은 데이터를 렌더링하는 컨트롤러에 의해서 사용된다. </li> +</ul> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14456/MVC%20Express.png" style="height: 460px; width: 800px;"></p> + +<p>Ultimately we might have pages to show lists and detail information for books, genres, authors and bookinstances, along with pages to create, update, and delete records. That's a lot to document in one article. Therefore most of this article will concentrate on setting up our routes and controllers to return "dummy" content. We'll extend the controller methods in our subsequent articles to work with model data.</p> + +<p>The first section below provides a brief "primer" on how to use the Express "Router" middleware. We'll then use that knowledge in the following sections when we set up the LocalLibrary routes.</p> + +<h2 id="Routes_primer">Routes primer</h2> + +<p>A route is a section of Express code that associates an HTTP verb (<code>GET</code>, <code>POST</code>, <code>PUT</code>, <code>DELETE</code>, etc.), an URL path/pattern, and a function that is called to handle that pattern.</p> + +<p>There are several ways to create routes. For this tutorial we're going to use the <code><a href="http://expressjs.com/en/guide/routing.html#express-router">express.Router</a></code> middleware as it allows us to group the route handlers for a particular part of a site together and access them using a common route-prefix. We'll keep all our library-related routes in a "catalog" module, and, if we add routes for handling user accounts or other functions, we can keep them grouped separately.</p> + +<div class="note"> +<p><strong>Note:</strong> We discussed Express application routes briefly in our <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction#Creating_route_handlers">Express Introduction > Creating route handlers</a>. Other than providing better support for modularization (as discussed in the first subsection below), using <em>Router</em> is very similar to defining routes directly on the <em>Express application object</em>.</p> +</div> + +<p>The rest of this section provides an overview of how the <code>Router</code> can be used to define the routes.</p> + +<h3 id="분리된_라우트_모듈들의_사용과_정의(Defining_and_using_separate_route_modules)">분리된 라우트 모듈들의 사용과 정의(Defining and using separate route modules)</h3> + +<p>The code below provides a concrete example of how we can create a route module and then use it in an <em>Express</em> application.</p> + +<p>아래 코드는 우리가 어떻게 라우트 모듈을 생성하고 Express 어플리케이션에서 사용할 것인지에 대한 구체적인 예를 보여준다.</p> + +<p>First we create routes for a wiki in a module named <strong>wiki.js</strong>. The code first imports the Express application object, uses it to get a <code>Router</code> object and then adds a couple of routes to it using the <code>get()</code> method. Last of all the module exports the <code>Router</code> object.</p> + +<p>첫번재 우리는 wiki.js 를 이름을 가진 모듈에서 위키를 위한 라우트를 만든다. 첫번째 코드에서 Express 어플리케이션 객체가 중요하고, 이 객체를 라우트 오브젝트를 얻기 위해서 사용하고, get()메서드를 사용하여 라우트는 2개를 추가한다. 모듈에서 마지막에는 라우트 객체를 Export한다.</p> + +<pre class="brush: js"><code>// wiki.js - Wiki route module + +var express = require('express') +var router = express.Router() + +// Home page route +router.get('/', function (req, res) { + res.send('Wiki home page') +}) + +// About page route +router.get('/about', function (req, res) { + res.send('About this wiki') +}) + +module.exports = router</code> + +</pre> + +<div class="note"> +<p><strong>Note:</strong> Above we are defining our route handler callbacks directly in the router functions. In the LocalLibrary we'll define these callbacks in a separate controller module.</p> +</div> + +<p>To use the router module in our main app file we first <code>require()</code> the route module (<strong>wiki.js</strong>). We then call <code>use()</code> on the <em>Express</em> application to add the Router to the middleware handling path, specifying an URL path of 'wiki'.</p> + +<pre class="brush: js"><code>var wiki = require('./wiki.js') +// ... +app.use('/wiki', wiki)</code></pre> + +<p>The two routes defined in our wiki route module are then accessible from <code>/wiki/</code> and <code>/wiki/about/</code>.</p> + +<p>wiki 라우트 모듈에서 정희 두개의 라우트를 정의되면 /wiki그리고 /wiki/about/ 으로 접근가능해진다.</p> + +<h3 id="라우트_함수들(Route_functions)">라우트 함수들(Route functions)</h3> + +<p>Our module above defines a couple of typical route functions. The "about" route (reproduced below) is defined using the <code>Router.get()</code> method, which responds only to HTTP GET requests. The first argument to this method is the URL path while the second is a callback function that will be invoked if an HTTP GET request with the path is received.</p> + +<pre class="brush: js"><code>router.get('/about', function (req, res) { + res.send('About this wiki') +})</code> +</pre> + +<p>The callback takes three arguments (usually named as shown: <code>req</code>, <code>res</code>, <code>next</code>), that will contain the HTTP Request object, HTTP response, and the <em>next</em> function in the middleware chain.</p> + +<div class="note"> +<p><strong>Note:</strong> Router functions are <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction#Using_middleware">Express middleware</a>, which means that they must either complete (respond to) the request or call the next function in the chain. In the case above we complete the request, so the <code>next</code> argument is not actually used.</p> +</div> + +<p>The callback function here calls <code><a href="https://expressjs.com/en/4x/api.html#res.send">send()</a></code> on the response to return the string "About this wiki" when we receive a GET request with the path ('<code>/about'</code>). There are a <a href="https://expressjs.com/en/guide/routing.html#response-methods">number of other response methods</a> for ending the request/response cycle. For example, you could call <code><a href="https://expressjs.com/en/4x/api.html#res.json">res.json()</a></code> to send a JSON response or <code><a href="https://expressjs.com/en/4x/api.html#res.sendFile">res.sendFile()</a></code> to send a file. The response method that we'll be using most often as we build up the library is <a href="https://expressjs.com/en/4x/api.html#res.render">render()</a>, which creates and returns HTML files using templates and data—we'll talk a lot more about that in a later article!</p> + +<h3 id="HTTP_verbs">HTTP verbs</h3> + +<p>The example routes above use the <code>Router.get()</code> method to respond to HTTP GET requests with a certain path.</p> + +<p>The <code>Router</code> also provides route methods for all the other HTTP verbs, that are mostly used in exactly the same way: <code>post()</code>, <code>put()</code>, <code>delete()</code>, <code>options()</code>, <code>trace()</code>, <code>copy()</code>, <code>lock()</code>, <code>mkcol()</code>, <code>move()</code>, <code>purge()</code>, <code>propfind()</code>, <code>proppatch()</code>, <code>unlock()</code>, <code>report()</code>, <code>mkactivity()</code>, <code>checkout()</code>, <code>merge()</code>, <code>m-</code><code>search()</code>, <code>notify()</code>, <code>subscribe()</code>, <code>unsubscribe()</code>, <code>patch()</code>, <code>search()</code>, and <code>connect()</code>.</p> + +<p>For example, the code below behaves just like the previous <code>/about</code> route, but only responds to HTTP POST requests.</p> + +<pre class="brush: js"><code>router.post('/about', function (req, res) { + res.send('About this wiki') +})</code></pre> + +<h3 id="라우트_경로들(Route_paths)">라우트 경로들(Route paths)</h3> + +<p>The route paths define the endpoints at which requests can be made. The examples we've seen so far have just been strings, and are used exactly as written: '/', '/about', '/book', '/any-random.path'.</p> + +<p>Route paths can also be string patterns. String patterns use a subset of regular expression syntax to define <em>patterns</em> of endpoints that will be matched. The subset is listed below (note that the hyphen (<code>-</code>) and the dot (<code>.</code>) are interpreted literally by string-based paths):</p> + +<ul> + <li>? : The endpoint must have 0 or more of the preceding character. E.g. a route path of <code>'/ab?cd'</code> will match endpoints <code>acd</code> , <code>abcd</code>, <code>abbcd</code> etc.</li> + <li>+ : The endpoint must have 1 or more of the preceding character. E.g. a route path of <code>'/ab+cd'</code> will match endpoints <code>abcd</code>, <code>abbcd</code>, <code>abbbcd</code>, and so on.</li> + <li>* : The endpoint may have an arbitrary string where the * character is placed. E.g. a route path of <code>'ab*cd'</code> will match endpoints <code>abcd</code>, <code>abXcd</code>, <code>abSOMErandomTEXTcd</code>, and so on.</li> + <li>() : Grouping match on a set of characters to perform another operation on. E.g. <code>'/ab(cd)?e'</code> will peform a ? match on (cd) —it will match <code>abe</code>, <code>abcde</code>, <code>abcdcde</code>, and so on.</li> +</ul> + +<p>The route paths can also be JavaScript <a href="/en-US/docs/Web/JavaScript/Guide/Regular_Expressions">regular expressions</a>. For example, the route path below will match match <code>catfish </code>and <code>dogfish</code>, but not <code>catflap</code>, <code>catfishhead</code>, and so on. Note that the path for a regular expression uses regular expression syntax (it is not a quoted string as in the previous cases).</p> + +<pre class="brush: js"><code>app.get(/.*fish$/, function (req, res) { + ... +})</code></pre> + +<div class="note"> +<p><strong>Note:</strong> Most of our routes for the LocalLibrary will simply use strings and not string patterns and regular expressions. We'll also use route parameters as discussed in the next section.</p> +</div> + +<h3 id="라우트_파라미터들(Route_parameters)">라우트 파라미터들(Route parameters)</h3> + +<p>Route parameters are <em>named URL segments</em> used to capture the values specified at their position in the URL. The named segments are prefixed with a colon and then the name (e.g. <code>/<strong>:</strong>your_parameter_name/</code>. The captured values are stored in the <code>req.params</code> object using the parameter names as keys (e.g. <code>req.params.your_parameter_name</code>).</p> + +<p>So for example, consider a URL encoded to contain information about users and books: <code>http://localhost:3000/users/34/books/8989</code>. We can extract this information as shown below, with the <code>userId</code> and <code>bookId</code> path parameters:</p> + +<pre><code>app.get('/users/:userId/books/:bookId', function (req, res) { + // Access userId via: req.params.userId + // Access bookId via: req.params.bookId + res.send(req.params) +}) +</code></pre> + +<p>The names of route parameters must be made up of “word characters” (A-Z, a-z, 0-9, and _).</p> + +<div class="note"> +<p><strong>Note:</strong> The URL <em>/book/create</em> will be matched by a route like <code>/book/:bookId</code> (which will extract a "bookId" value of '<code>create</code>'). The first route that matches an incoming URL will be used, so if you want to process <code>/book/create</code> URLs separately, their route handler must be defined before your <code>/book/:bookId</code> route.</p> +</div> + +<p>That's all you need to get started with routes - if needed you can find more information in the Express docs: <a href="http://expressjs.com/en/starter/basic-routing.html">Basic routing</a> and <a href="http://expressjs.com/en/guide/routing.html">Routing guide</a>. The following sections show how we'll set up our routes and controllers for the LocalLibrary.</p> + +<h2 id="Routes_needed_for_the_LocalLibrary">Routes needed for the LocalLibrary</h2> + +<p>The URLs that we're ultimately going to need for our pages are listed below, where <em>object</em> is replaced by the name of each of our models (book, bookinstance, genre, author), <em>objects</em> is the plural of object, and <em>id</em> is the unique instance field (<code>_id</code>) that is given to each Mongoose model instance by default.</p> + +<ul> + <li><code>catalog/</code> — The home/index page.</li> + <li><code>catalog/<objects>/</code> — The list of all books, bookinstances, genres, or authors (e.g. /<code>catalog/books/</code>, /<code>catalog/genres/</code>, etc.)</li> + <li><code>catalog/<object>/<em><id></em></code> — The detail page for a specific book, bookinstance, genre, or author with the given <code><em>_id</em></code> field value (e.g. <code>/catalog/book/584493c1f4887f06c0e67d37)</code>.</li> + <li><code>catalog/<object>/create</code> — The form to create a new book, bookinstance, genre, or author (e.g. <code>/catalog/book/create)</code>.</li> + <li><code>catalog/<object>/<em><id></em>/update</code> — The form to update a specific book, bookinstance, genre, or author with the given <code><em>_id</em></code> field value (e.g. <code>/catalog/book/584493c1f4887f06c0e67d37/update)</code>.</li> + <li><code>catalog/<object>/<em><id></em>/delete</code> — The form to delete a specific book, bookinstance, genre, author with the given <code><em>_id</em></code> field value (e.g. <code>/catalog/book/584493c1f4887f06c0e67d37/delete)</code>.</li> +</ul> + +<p>The first home page and list pages don't encode any additional information. While the results returned will depend on the model type and the content in the database, the queries run to get the information will always be the same (similarly the code run for object creation will always be similar).</p> + +<p>By contrast the other URLs are used to act on a specific document/model instance—these encode the identity of the item in the URL (shown as <code><em><id></em></code> above). We'll use path parameters to extract the encoded information and pass it to the route handler (and in a later article we'll use this to dynamically determine what information to get from the database). By encoding the information in our URL we only need one route for every resource of a particular type (e.g. one route to handle the display of every single book item).</p> + +<div class="note"> +<p><strong>Note</strong>: Express allows you to construct your URLs any way you like — you can encode information in the body of the URL as shown above or use URL <code>GET</code> parameters (e.g. <code>/book/?id=6</code>). Whichever approach you use, the URLs should be kept clean, logical and readable (<a href="https://www.w3.org/Provider/Style/URI">check out the W3C advice here</a>).</p> +</div> + +<p>Next we create our route handler callback functions and route code for all the above URLs.</p> + +<h2 id="Create_the_route-handler_callback_functions">Create the route-handler callback functions</h2> + +<p>Before we define our routes, we'll first create all the dummy/skeleton callback functions that they will invoke. The callbacks will be stored in separate "controller" modules for Books, BookInstances, Genres, and Authors (you can use any file/module structure, but this seems an appropriate granularity for this project).</p> + +<p>Start by creating a folder for our controllers in the project root (<strong>/controllers</strong>) and then create separate controller files/modules for handling each of the models:</p> + +<pre>/express-locallibrary-tutorial //the project root + <strong>/controllers</strong> + <strong>authorController.js</strong> + <strong>bookController.js</strong> + <strong>bookinstanceController.js</strong> + <strong>genreController.js</strong></pre> + +<h3 id="Author_controller">Author controller</h3> + +<p>Open the <strong>/controllers/authorController.js</strong> file and copy in the following code:</p> + +<pre class="brush: js">var Author = require('../models/author'); + +// Display list of all Authors +exports.author_list = function(req, res) { + res.send('NOT IMPLEMENTED: Author list'); +}; + +// Display detail page for a specific Author +exports.author_detail = function(req, res) { + res.send('NOT IMPLEMENTED: Author detail: ' + req.params.id); +}; + +// Display Author create form on GET +exports.author_create_get = function(req, res) { + res.send('NOT IMPLEMENTED: Author create GET'); +}; + +// Handle Author create on POST +exports.author_create_post = function(req, res) { + res.send('NOT IMPLEMENTED: Author create POST'); +}; + +// Display Author delete form on GET +exports.author_delete_get = function(req, res) { + res.send('NOT IMPLEMENTED: Author delete GET'); +}; + +// Handle Author delete on POST +exports.author_delete_post = function(req, res) { + res.send('NOT IMPLEMENTED: Author delete POST'); +}; + +// Display Author update form on GET +exports.author_update_get = function(req, res) { + res.send('NOT IMPLEMENTED: Author update GET'); +}; + +// Handle Author update on POST +exports.author_update_post = function(req, res) { + res.send('NOT IMPLEMENTED: Author update POST'); +}; +</pre> + +<p>The module first requires the model that we'll later be using to access and update our data. It then exports functions for each of the URLs we wish to handle (the create, update and delete operations use forms, and hence also have additional methods for handling form post requests — we'll discuss those methods in the "forms article" later on).</p> + +<p>All the functions have the standard form of an <em>Express middleware function</em>, with arguments for the request, response, and the <code>next</code> function to be called if the method does not complete the request cycle (in all these cases it does!). The methods simply return a string indicating that the associated page has not yet been created. If a controller function is expected to receive path parameters, these are included in the message string.</p> + +<h4 id="BookInstance_controller">BookInstance controller</h4> + +<p>Open the <strong>/controllers/bookinstanceController.js</strong> file and copy in the following code (this follows an identical pattern to the <code>Author</code> controller module):</p> + +<pre class="brush: js">var BookInstance = require('../models/bookinstance'); + +// Display list of all BookInstances +exports.bookinstance_list = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance list'); +}; + +// Display detail page for a specific BookInstance +exports.bookinstance_detail = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance detail: ' + req.params.id); +}; + +// Display BookInstance create form on GET +exports.bookinstance_create_get = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance create GET'); +}; + +// Handle BookInstance create on POST +exports.bookinstance_create_post = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance create POST'); +}; + +// Display BookInstance delete form on GET +exports.bookinstance_delete_get = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance delete GET'); +}; + +// Handle BookInstance delete on POST +exports.bookinstance_delete_post = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance delete POST'); +}; + +// Display BookInstance update form on GET +exports.bookinstance_update_get = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance update GET'); +}; + +// Handle bookinstance update on POST +exports.bookinstance_update_post = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance update POST'); +}; +</pre> + +<h4 id="Genre_controller">Genre controller</h4> + +<p>Open the <strong>/controllers/genreController.js</strong> file and copy in the following text (this follows an identical pattern to the <code>Author</code> and <code>BookInstance</code> files):</p> + +<pre class="brush: js">var Genre = require('../models/genre'); + +// Display list of all Genre +exports.genre_list = function(req, res) { + res.send('NOT IMPLEMENTED: Genre list'); +}; + +// Display detail page for a specific Genre +exports.genre_detail = function(req, res) { + res.send('NOT IMPLEMENTED: Genre detail: ' + req.params.id); +}; + +// Display Genre create form on GET +exports.genre_create_get = function(req, res) { + res.send('NOT IMPLEMENTED: Genre create GET'); +}; + +// Handle Genre create on POST +exports.genre_create_post = function(req, res) { + res.send('NOT IMPLEMENTED: Genre create POST'); +}; + +// Display Genre delete form on GET +exports.genre_delete_get = function(req, res) { + res.send('NOT IMPLEMENTED: Genre delete GET'); +}; + +// Handle Genre delete on POST +exports.genre_delete_post = function(req, res) { + res.send('NOT IMPLEMENTED: Genre delete POST'); +}; + +// Display Genre update form on GET +exports.genre_update_get = function(req, res) { + res.send('NOT IMPLEMENTED: Genre update GET'); +}; + +// Handle Genre update on POST +exports.genre_update_post = function(req, res) { + res.send('NOT IMPLEMENTED: Genre update POST'); +}; +</pre> + +<h4 id="Book_controller">Book controller</h4> + +<p>Open the <strong>/controllers/bookController.js</strong> file and copy in the following code. This follows the same pattern as the other controller modules, but additionally has an <code>index()</code> function for displaying the site welcome page:</p> + +<pre class="brush: js">var Book = require('../models/book'); + +<strong>exports.index = function(req, res) { + res.send('NOT IMPLEMENTED: Site Home Page'); +};</strong> + +// Display list of all books +exports.book_list = function(req, res) { + res.send('NOT IMPLEMENTED: Book list'); +}; + +// Display detail page for a specific book +exports.book_detail = function(req, res) { + res.send('NOT IMPLEMENTED: Book detail: ' + req.params.id); +}; + +// Display book create form on GET +exports.book_create_get = function(req, res) { + res.send('NOT IMPLEMENTED: Book create GET'); +}; + +// Handle book create on POST +exports.book_create_post = function(req, res) { + res.send('NOT IMPLEMENTED: Book create POST'); +}; + +// Display book delete form on GET +exports.book_delete_get = function(req, res) { + res.send('NOT IMPLEMENTED: Book delete GET'); +}; + +// Handle book delete on POST +exports.book_delete_post = function(req, res) { + res.send('NOT IMPLEMENTED: Book delete POST'); +}; + +// Display book update form on GET +exports.book_update_get = function(req, res) { + res.send('NOT IMPLEMENTED: Book update GET'); +}; + +// Handle book update on POST +exports.book_update_post = function(req, res) { + res.send('NOT IMPLEMENTED: Book update POST'); +}; +</pre> + +<h2 id="Create_the_catalog_route_module">Create the catalog route module</h2> + +<p>Next we create <em>routes</em> for all the URLs <a href="#local_libary_routes">needed by the LocalLibrary website</a>, which will call the controller functions we defined in the previous section.</p> + +<p>The skeleton already has a <strong>./routes</strong> folder containing routes for the <em>index</em> and <em>users</em>. Create another route file — <strong>catalog.js</strong> — inside this folder, as shown.</p> + +<pre>/express-locallibrary-tutorial //the project root + /routes + index.js + users.js + <strong>catalog.js</strong></pre> + +<p>Open <strong>/routes/</strong><strong>catalog.js</strong> and copy in the code below:</p> + +<pre class="brush: js"><strong>var express = require('express'); +var router = express.Router(); +</strong> +// Require controller modules +var book_controller = require('../controllers/bookController'); +var author_controller = require('../controllers/authorController'); +var genre_controller = require('../controllers/genreController'); +var book_instance_controller = require('../controllers/bookinstanceController'); + +/// BOOK ROUTES /// + +/* GET catalog home page. */ +router.get('/', book_controller.index); + +/* GET request for creating a Book. NOTE This must come before routes that display Book (uses id) */ +router.get('/book/create', book_controller.book_create_get); + +/* POST request for creating Book. */ +router.post('/book/create', book_controller.book_create_post); + +/* GET request to delete Book. */ +router.get('/book/:id/delete', book_controller.book_delete_get); + +// POST request to delete Book +router.post('/book/:id/delete', book_controller.book_delete_post); + +/* GET request to update Book. */ +router.get('/book/:id/update', book_controller.book_update_get); + +// POST request to update Book +router.post('/book/:id/update', book_controller.book_update_post); + +/* GET request for one Book. */ +router.get('/book/:id', book_controller.book_detail); + +/* GET request for list of all Book items. */ +router.get('/books', book_controller.book_list); + +/// AUTHOR ROUTES /// + +/* GET request for creating Author. NOTE This must come before route for id (i.e. display author) */ +router.get('/author/create', author_controller.author_create_get); + +/* POST request for creating Author. */ +router.post('/author/create', author_controller.author_create_post); + +/* GET request to delete Author. */ +router.get('/author/:id/delete', author_controller.author_delete_get); + +// POST request to delete Author +router.post('/author/:id/delete', author_controller.author_delete_post); + +/* GET request to update Author. */ +router.get('/author/:id/update', author_controller.author_update_get); + +// POST request to update Author +router.post('/author/:id/update', author_controller.author_update_post); + +/* GET request for one Author. */ +router.get('/author/:id', author_controller.author_detail); + +/* GET request for list of all Authors. */ +router.get('/authors', author_controller.author_list); + +/// GENRE ROUTES /// + +/* GET request for creating a Genre. NOTE This must come before route that displays Genre (uses id) */ +router.get('/genre/create', genre_controller.genre_create_get); + +/* POST request for creating Genre. */ +router.post('/genre/create', genre_controller.genre_create_post); + +/* GET request to delete Genre. */ +router.get('/genre/:id/delete', genre_controller.genre_delete_get); + +// POST request to delete Genre +router.post('/genre/:id/delete', genre_controller.genre_delete_post); + +/* GET request to update Genre. */ +router.get('/genre/:id/update', genre_controller.genre_update_get); + +// POST request to update Genre +router.post('/genre/:id/update', genre_controller.genre_update_post); + +/* GET request for one Genre. */ +router.get('/genre/:id', genre_controller.genre_detail); + +/* GET request for list of all Genre. */ +router.get('/genres', genre_controller.genre_list); + +/// BOOKINSTANCE ROUTES /// + +/* GET request for creating a BookInstance. NOTE This must come before route that displays BookInstance (uses id) */ +router.get('/bookinstance/create', book_instance_controller.bookinstance_create_get); + +/* POST request for creating BookInstance. */ +router.post('/bookinstance/create', book_instance_controller.bookinstance_create_post); + +/* GET request to delete BookInstance. */ +router.get('/bookinstance/:id/delete', book_instance_controller.bookinstance_delete_get); + +// POST request to delete BookInstance +router.post('/bookinstance/:id/delete', book_instance_controller.bookinstance_delete_post); + +/* GET request to update BookInstance. */ +router.get('/bookinstance/:id/update', book_instance_controller.bookinstance_update_get); + +// POST request to update BookInstance +router.post('/bookinstance/:id/update', book_instance_controller.bookinstance_update_post); + +/* GET request for one BookInstance. */ +router.get('/bookinstance/:id', book_instance_controller.bookinstance_detail); + +/* GET request for list of all BookInstance. */ +router.get('/bookinstances', book_instance_controller.bookinstance_list); + +<strong>module.exports = router;</strong> +</pre> + +<p>The module requires Express and then uses it to create a <code>Router</code> object. The routes are all set up on the router, which is then exported.</p> + +<p>The routes are defined either using <code>.get()</code> or <code>.post()</code> methods on the router object. All the paths are defined using strings (we don't use string patterns or regular expressions), routes that act on some specific resource (e.g. book) use path parameters to get the object id from the URL.</p> + +<p>The handler functions are all imported from the controller modules we created in the previous section.</p> + +<h3 id="Update_the_index_route_module">Update the index route module</h3> + +<p>We've set up all our new routes, but we still have a route to the original page. Let's instead redirect this to the new index page that we've created at the path '/catalog'.</p> + +<p>Open <strong>/routes/index.js</strong> and replace the existing route with the function below.</p> + +<pre class="brush: js">/* GET home page. */ +router.get('/', function(req, res) { + res.redirect('/catalog'); +});</pre> + +<div class="note"> +<p><strong>Note:</strong> This is our first use of the <a href="https://expressjs.com/en/4x/api.html#res.redirect">redirect()</a> response method. This redirects to the specified page, by default sending HTTP status code "302 Found". You can change the status code returned if needed, and supply either absolute or relative paths.</p> +</div> + +<h3 id="Update_app.js">Update app.js</h3> + +<p>The last step is to add the routes to the middleware chain. We do this in <code>app.js</code>.</p> + +<p>Open <strong>app.js</strong> and require the catalog route below the other routes (add the third line shown below, underneath the other two):</p> + +<pre class="brush: js">var index = require('./routes/index'); +var users = require('./routes/users'); +<strong>var catalog = require('./routes/catalog'); //Import routes for "catalog" area of site</strong></pre> + +<p>Next, add the catalog route to the middleware stack below the other routes (add the third line shown below, underneath the other two):</p> + +<pre class="brush: js">app.use('/', index); +app.use('/users', users); +<strong>app.use('/catalog', catalog); // Add catalog routes to middleware chain.</strong></pre> + +<div class="note"> +<p><strong>Note:</strong> We have added our catalog module at a path <code>'/catalog'</code>. This is prepended to all of the paths defined in the catalog module. So for example, to access a list of books, the URL will be: <code>/catalog/books/</code>.</p> +</div> + +<p>That's it. We should now have routes and skeleton functions enabled for all the URLs that we will eventually support on the LocalLibrary website.</p> + +<h3 id="Testing_the_routes">Testing the routes</h3> + +<p>To test the routes, first start the website using your usual approach</p> + +<ul> + <li>The default method + <pre class="brush: bash"><code>//Windows +SET DEBUG=express-locallibrary-tutorial:* & npm start + +// Mac OS or Linux +DEBUG=express-locallibrary-tutorial:* npm start</code> +</pre> + </li> + <li>If you previously set up <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">nodemon</a>, you can instead use: + <pre><code>//Windows +SET DEBUG=express-locallibrary-tutorial:* & npm <strong>run devstart</strong> + +// Mac OS or Linux +</code>DEBUG=express-locallibrary-tutorial:* npm <strong style="font-family: inherit; font-size: 1rem;">run devstart</strong> +</pre> + </li> +</ul> + +<p>Then navigate to a number of LocalLibrary URLs, and verify that you don't get an error page (HTTP 404). A small set of URLs are listed below for your convenience:</p> + +<ul> + <li><a href="http://localhost:3000/">http://localhost:3000/</a></li> + <li><a href="http://localhost:3000/catalog">http://localhost:3000/catalog</a></li> + <li><a href="http://localhost:3000/catalog/books">http://localhost:3000/catalog/books</a></li> + <li><a href="http://localhost:3000/catalog/bookinstances/">http://localhost:3000/catalog/bookinstances/</a></li> + <li><a href="http://localhost:3000/catalog/authors/">http://localhost:3000/catalog/authors/</a></li> + <li><a href="http://localhost:3000/catalog/genres/">http://localhost:3000/catalog/genres/</a></li> + <li><a href="http://localhost:3000/catalog/book/5846437593935e2f8c2aa226/">http://localhost:3000/catalog/book/5846437593935e2f8c2aa226</a></li> + <li><a href="http://localhost:3000/catalog/book/create">http://localhost:3000/catalog/book/create</a></li> +</ul> + +<h2 id="Summary">Summary</h2> + +<p>We've now created all the routes for our site, along with dummy controller functions that we can populate with a full implementation in later articles. Along the way we've learned a lot of fundamental information about Express routes, and some approaches for structuring our routes and controllers.</p> + +<p>In our next article we'll create a proper welcome page for the site, using views (templates) and information stored in our models.</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="http://expressjs.com/en/starter/basic-routing.html">Basic routing</a> (Express docs)</li> + <li><a href="http://expressjs.com/en/guide/routing.html">Routing guide</a> (Express docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs")}}</p> diff --git a/files/ko/learn/server-side/express_nodejs/개발_환경/index.html b/files/ko/learn/server-side/express_nodejs/개발_환경/index.html new file mode 100644 index 0000000000..b8c8db8ffa --- /dev/null +++ b/files/ko/learn/server-side/express_nodejs/개발_환경/index.html @@ -0,0 +1,403 @@ +--- +title: Node 개발 환경을 설치하기 +slug: Learn/Server-side/Express_Nodejs/개발_환경 +tags: + - CodingScripting + - Express + - Node + - nodejs + - npm + - 개발 환경 + - 배움 + - 서버-사이드 + - 인트로 + - 초보자 +translation_of: Learn/Server-side/Express_Nodejs/development_environment +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Introduction", "Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">이제 Express에 관한 내용을 알았으니, Windows, Linux (Ubuntu), 그리고 macOS 에서의 Node/Express 개발 환경을 설정하고 테스트하는 법을 보여드리겠습니다. 사용중인 운영 체제가 무엇이든 간에, 이 글은 당신에게 Express 앱 개발을 시작할 수 있도록 필요한 내용을 제공합니다.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">전제 조건:</th> + <td>터미널 혹은 명령어 창을 여는 방법. 당신의 개발 컴퓨터의 운영 체제에 소프트웨어 패키지를 설치하는 방법을 알고 있어야 합니다.</td> + </tr> + <tr> + <th scope="row">목표:</th> + <td>당신의 컴퓨터에 Express (X.XX) 을 위한 개발 환경을 설치하는 것.</td> + </tr> + </tbody> +</table> + +<h2 id="Express_개발_환경_개요">Express 개발 환경 개요</h2> + +<p><em>Node</em> and <em>Express</em> make it very easy to set up your computer in order to start developing web applications. This section provides an overview of what tools are needed, explains some of the simplest methods for installing Node (and Express) on Ubuntu, macOS, and Windows, and shows how you can test your installation.</p> + +<h3 id="Express_개발_환경이란_무엇입니까">Express 개발 환경이란 무엇입니까?</h3> + +<p>The <em>Express</em> development environment includes an installation of <em>Nodejs</em>, the <em>NPM package manager</em>, and (optionally) the <em>Express Application Generator</em> on your local computer.</p> + +<p><em>Node</em> and the <em>NPM</em> package manager are installed together from prepared binary packages, installers, operating system package managers or from source (as shown in the following sections). <em>Express</em> is then installed by NPM as a dependency of your individual <em>Express</em> web applications (along with other libraries like template engines, database drivers, authentication middleware, middleware to serve static files, etc.)</p> + +<p><em>NPM</em> can also be used to (globally) install the <em>Express Application Generator</em>, a handy tool for creating skeleton <em>Express</em> web apps that follow the <a href="https://developer.mozilla.org/en-US/docs/Glossary/MVC">MVC pattern</a>. The application generator is optional because you don't <em>need</em> to use this tool to create apps that use Express, or construct Express apps that have the same architectural layout or dependencies. We'll be using it though, because it makes getting started a lot easier, and promotes a modular application structure.</p> + +<div class="note"> +<p><strong>Note:</strong> Unlike for some other web frameworks, the development environment does not include a separate development web server. In <em>Node</em>/<em>Express</em> a web application creates and runs its own web server!</p> +</div> + +<p>There are other peripheral tools that are part of a typical development environment, including <a href="/en-US/docs/Learn/Common_questions/Available_text_editors">text editors</a> or IDEs for editing code, and source control management tools like <a href="https://git-scm.com/">Git</a> for safely managing different versions of your code. We are assuming that you've already got these sorts of tools installed (in particular a text editor).</p> + +<h3 id="What_operating_systems_are_supported">What operating systems are supported?</h3> + +<p><em>Node</em> can be run on Windows, macOS, many "flavours" of Linux, Docker, etc. (there is a full list on the nodejs <a href="https://nodejs.org/en/download/">Downloads</a> page). Almost any personal computer should have the necessary performance to run Node during development. <em>Express</em> is run in a <em>Node</em> environment, and hence can run on any platform that runs <em>Node</em>.</p> + +<p>In this article we provide setup instructions for Windows, macOS, and Ubuntu Linux.</p> + +<h3 id="What_version_of_NodeExpress_should_you_use">What version of Node/Express should you use?</h3> + +<p>There are many <a href="https://nodejs.org/en/blog/release/">releases of Node</a> — newer releases contain bug fixes, support for more recent versions of ECMAScript (JavaScript) standards, and improvements to the Node APIs. </p> + +<p>Generally you should use the most recent <em>LTS (long-term supported)</em> release as this will be more stable than the "current" release while still having relatively recent features (and is still being actively maintained). You should use the <em>Current</em> release if you need a feature that is not present in the LTS version.</p> + +<p>For <em>Express</em> you should always use the latest version.</p> + +<h3 id="What_about_databases_and_other_dependencies">What about databases and other dependencies?</h3> + +<p>Other dependencies, such as database drivers, template engines, authentication engines, etc. are part of the application, and are imported into the application environment using the NPM package manager. We'll discuss them in later app-specific articles.</p> + +<h2 id="Node_설치하기">Node 설치하기</h2> + +<p>In order to use <em>Express</em> you will first have to install <em>Nodejs</em> and the <a href="https://docs.npmjs.com/">Node Package Manager (NPM)</a> on your operating system. The following sections explain the easiest way to install the Long Term Supported (LTS) version of Nodejs on Ubuntu Linux 16.04, macOS, and Windows 10.</p> + +<div class="note"> +<p><strong>Tip:</strong> The sections below show the easiest way to install <em>Node</em> and <em>NPM</em> on our target OS platforms. If you're using another OS or just want to see some of the other approaches for the current platforms then see <a href="https://nodejs.org/en/download/package-manager/">Installing Node.js via package manager</a> (nodejs.org).</p> +</div> + +<h3 id="Windows_and_macOS">Windows and macOS</h3> + +<p>Installing <em>Node</em> and <em>NPM</em> on Windows and macOS is straightforward because you can just use the provided installer:</p> + +<ol> + <li>Download the required installer: + <ol> + <li>Go to <a href="https://nodejs.org/en/">https://nodejs.org/en/</a></li> + <li>Select the button to download the LTS build that is "Recommended for most users".</li> + </ol> + </li> + <li>Install Node by double-clicking on the downloaded file and following the installation prompts.</li> +</ol> + +<h3 id="Ubuntu_18.04">Ubuntu 18.04</h3> + +<p>The easiest way to install the most recent LTS version of Node 10.x is to use the <a href="https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions">package manager</a> to get it from the Ubuntu <em>binary distributions</em> repository. This can be done very simply by running the following two commands on your terminal:</p> + +<pre class="brush: bash">curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash - +sudo apt-get install -y nodejs +</pre> + +<div class="warning"> +<p><strong>Warning:</strong> Don't install directly from the normal Ubuntu repositories because they contain very old versions of node.</p> +</div> + +<ol> +</ol> + +<h3 id="Testing_your_Nodejs_and_NPM_installation">Testing your Nodejs and NPM installation</h3> + +<p>The easiest way to test that node is installed is to run the "version" command in your terminal/command prompt and check that a version string is returned:</p> + +<pre class="brush: bash">>node -v +v10.15.1</pre> + +<p>The <em>Nodejs</em> package manager <em>NPM</em> should also have been installed, and can be tested in the same way:</p> + +<pre class="brush: bash">>npm -v +6.4.1</pre> + +<p>As a slightly more exciting test let's create a very basic "pure node" server that simply prints out "Hello World" in the browser when you visit the correct URL in your browser:</p> + +<ol> + <li>Copy the following text into a file named <strong>hellonode.js</strong>. This uses pure <em>Node</em> features (nothing from Express) and some ES6 syntax: + + <pre class="brush: js">//Load HTTP module +const http = require("http"); +const hostname = '127.0.0.1'; +const port = 3000; + +//Create HTTP server and listen on port 3000 for requests +const server = http.createServer((req, res) => { + + //Set the response HTTP header with HTTP status and Content type + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + res.end('Hello World\n'); +}); + +//listen for request on port 3000, and as a callback function have the port listened on logged +server.listen(port, hostname, () => { + console.log(`Server running at http://${hostname}:${port}/`); +}); +</pre> + + <p>The code imports the "http" module and uses it to create a server (<code>createServer()</code>) that listens for HTTP requests on port 3000. The script then prints a message to the console about what browser URL you can use to test the server. The <code>createServer()</code> function takes as an argument a callback function that will be invoked when an HTTP request is received — this simply returns a response with an HTTP status code of 200 ("OK") and the plain text "Hello World".</p> + + <div class="note"> + <p><strong>Note:</strong> Don't worry if you don't understand exactly what this code is doing yet! We'll explain our code in greater detail once we start using Express!</p> + </div> + </li> + <li>Start the server by navigating into the same directory as your <code>hellonode.js</code> file in your command prompt, and calling <code>node</code> along with the script name, like so: + <pre class="brush: bash">>node hellonode.js +Server running at http://127.0.0.1:3000/ +</pre> + </li> + <li>Navigate to the URL <a href="http://127.0.0.1:3000">http://127.0.0.1:3000 </a>. If everything is working, the browser should simply display the string "Hello World".</li> +</ol> + +<h2 id="Using_NPM">Using NPM</h2> + +<p>Next to <em>Node</em> itself, <a href="https://docs.npmjs.com/">NPM</a> is the most important tool for working with<em> Node </em>applications. NPM is used to fetch any packages (JavaScript libraries) that an application needs for development, testing, and/or production, and may also be used to run tests and tools used in the development process. </p> + +<div class="note"> +<p><strong>Note:</strong> From Node's perspective, <em>Express</em> is just another package that you need to install using NPM and then require in your own code.</p> +</div> + +<p>You can manually use NPM to separately fetch each needed package. Typically we instead manage dependencies using a plain-text definition file named <a href="https://docs.npmjs.com/files/package.json">package.json</a>. This file lists all the dependencies for a specific JavaScript "package", including the package's name, version, description, initial file to execute, production dependencies, development dependencies, versions of <em>Node</em> it can work with, etc. The <strong>package.json</strong> file should contain everything NPM needs to fetch and run your application (if you were writing a reusable library you could use this definition to upload your package to the npm respository and make it available for other users).</p> + +<h3 id="Adding_dependencies">Adding dependencies</h3> + +<p>The following steps show how you can use NPM to download a package, save it into the project dependencies, and then require it in a Node application.</p> + +<div class="note"> +<p><strong>Note:</strong> Here we show the instructions to fetch and install the <em>Express</em> package. Later on we'll show how this package, and others, are already specified for us using the <em>Express Application Generator</em>. This section is provided because it is useful to understand how NPM works and what is being created by the application generator.</p> +</div> + +<ol> + <li>First create a directory for your new application and navigate into it: + <pre class="brush: bash">mkdir myapp +cd myapp</pre> + </li> + <li>Use the npm <code>init</code> command to create a <strong>package.json</strong> file for your application. This command prompts you for a number of things, including the name and version of your application and the name of the initial entry point file (by default this is <strong>index.js</strong>). For now, just accept the defaults: + <pre class="brush: bash">npm init</pre> + + <p>If you display the <strong>package.json</strong> file (<code>cat package.json</code>), you will see the defaults that you accepted, ending with the license.</p> + + <pre class="brush: json">{ + "name": "myapp", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} +</pre> + </li> + <li>Now install Express in the <code>myapp</code> directory and save it in the dependencies list of your <strong>package.json</strong> file</li> + <li> + <pre class="brush: bash">npm install express</pre> + + <p>The dependencies section of your <strong>package.json</strong> will now appear at the end of the <strong>package.json</strong> file and will include <em>Express</em>.</p> + + <pre class="brush: json">{ + "name": "myapp", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", +<strong> "dependencies": { + "express": "^4.16.4" + }</strong> +} +</pre> + </li> + <li>To use the Express library you call the <code>require()</code> function in your index.js file to include it in your application. Create this file now, in the root of the "myapp" application directory, and give it the following contents: + <pre class="brush: js">const express = require('express') +const app = express(); + +app.get('/', (req, res) => { + res.send('Hello World!') +}); + +app.listen(8000, () => { + console.log('Example app listening on port 8000!') +}); +</pre> + + <p>This code shows a minimal "HelloWorld" Express web application. This imports the "express" module using <code>require()</code> and uses it to create a server (<code>app</code>) that listens for HTTP requests on port 8000 and prints a message to the console explaining what browser URL you can use to test the server. The <code>app.get()</code> function only responds to HTTP <code>GET</code> requests with the specified URL path ('/'), in this case by calling a function to send our <em>Hello World!</em> message.</p> + </li> + <li>You can start the server by calling node with the script in your command prompt: + <pre class="brush: bash">>node index.js +Example app listening on port 8000 +</pre> + </li> + <li>Navigate to the URL (<a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>). If everything is working, the browser should simply display the string "Hello World!".</li> +</ol> + +<h3 id="Development_dependencies">Development dependencies</h3> + +<p>If a dependency is only used during development, you should instead save it as a "development dependency" (so that your package users don't have to install it in production). For example, to use the popular JavaScript Linting tool <a href="http://eslint.org/">eslint</a> you would call NPM as shown:</p> + +<pre class="brush: bash"><code>npm install eslint --save-dev</code></pre> + +<p>The following entry would then be added to your application's <strong>package.json</strong>:</p> + +<pre class="brush: js"> "devDependencies": { + "eslint": "^4.12.1" + } +</pre> + +<div class="note"> +<p><strong>Note:</strong> "<a href="https://en.wikipedia.org/wiki/Lint_(software)">Linters</a>" are tools that perform static analysis on software in order to recognise and report adherence/non-adherance to some set of coding best practice.</p> +</div> + +<h3 id="Running_tasks">Running tasks</h3> + +<p>In addition to defining and fetching dependencies you can also define <em>named</em> scripts in your <strong>package.json</strong> files and call NPM to execute them with the <a href="https://docs.npmjs.com/cli/run-script">run-script</a> command. This approach is commonly used to automate running tests and parts of the development or build toolchain (e.g., running tools to minify JavaScript, shrink images, LINT/analyse your code, etc).</p> + +<div class="note"> +<p><strong>Note:</strong> Task runners like <a href="http://gulpjs.com/">Gulp</a> and <a href="http://gruntjs.com/">Grunt</a> can also be used to run tests and other external tools.</p> +</div> + +<p>For example, to define a script to run the <em>eslint</em> development dependency that we specified in the previous section we might add the following script block to our <strong>package.json</strong> file (assuming that our application source is in a folder /src/js):</p> + +<pre class="brush: js">"scripts": { + ... + "lint": "eslint src/js" + ... +} +</pre> + +<p>To explain a little further, <code>eslint src/js</code> is a command that we could enter in our terminal/command line to run <code>eslint</code> on JavaScript files contained in the <code>src/js</code> directory inside our app directory. Including the above inside our app's package.json file provides a shortcut for this command — <code>lint</code>.</p> + +<p>We would then be able to run <em>eslint</em> using NPM by calling:</p> + +<pre class="brush: bash"><code>npm run-script lint +# OR (using the alias) +npm run lint</code> +</pre> + +<p>This example may not look any shorter than the original command, but you can include much bigger commands inside your npm scripts, including chains of multiple commands. You could identify a single npm script that runs all your tests at once.</p> + +<h2 id="Installing_the_Express_Application_Generator">Installing the Express Application Generator</h2> + +<p>The <a href="https://expressjs.com/en/starter/generator.html">Express Application Generator</a> tool generates an Express application "skeleton". Install the generator using NPM as shown (the <code>-g</code> flag installs the tool globally so that you can call it from anywhere):</p> + +<pre><code>npm install express-generator -g</code></pre> + +<p>To create an <em>Express</em> app named "helloworld" with the default settings, navigate to where you want to create it and run the app as shown:</p> + +<pre class="brush: bash">express helloworld</pre> + +<div class="note"> +<p><strong>Note: </strong>You can also specify the template library to use and a number of other settings. Use the <code>help</code> command to see all the options:</p> + +<pre class="brush: bash">express --help +</pre> +</div> + +<p>NPM will create the new Express app in a sub folder of your current location, displaying build progress on the console. On completion, the tool will display the commands you need to enter to install the Node dependencies and start the app.</p> + +<div class="note"> +<p>The new app will have a <strong>package.json</strong> file in its root directory. You can open this to see what dependencies are installed, including Express and the template library Jade:</p> + +<pre class="brush: js">{ + "name": "helloworld", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "node ./bin/www" + }, + "dependencies": { + "cookie-parser": "~1.4.3", + "debug": "~2.6.9", + "express": "~4.16.0", + "http-errors": "~1.6.2", + "jade": "~1.11.0", + "morgan": "~1.9.0" + } +} +</pre> + +<p> </p> +</div> + +<p>Install all the dependencies for the helloworld app using NPM as shown:</p> + +<pre class="brush: bash">cd helloworld +npm install +</pre> + +<p>Then run the app (the commands are slightly different for Windows and Linux/macOS), as shown below:</p> + +<pre class="brush: bash"># Run the helloworld on Windows with Command Prompt +SET DEBUG=helloworld:* & npm start + +# Run the helloworld on Windows with PowerShell +SET DEBUG=helloworld:* | npm start + +# Run helloworld on Linux/macOS +DEBUG=helloworld:* npm start +</pre> + +<p>The DEBUG command creates useful logging, resulting in an output like that shown below.</p> + +<pre class="brush: bash">>SET DEBUG=helloworld:* & npm start + +> helloworld@0.0.0 start D:\Github\expresstests\helloworld +> node ./bin/www + + helloworld:server Listening on port 3000 +0ms</pre> + +<p>Open a browser and navigate to <a href="http://127.0.0.1:3000/">http://127.0.0.1:3000/</a> to see the default Express welcome page.</p> + +<p><img alt="Express - Generated App Default Screen" src="https://mdn.mozillademos.org/files/14331/express_default_screen.png" style="border-style: solid; border-width: 1px; display: block; height: 301px; margin: 0px auto; width: 675px;"></p> + +<p>We'll talk more about the generated app when we get to the article on generating a skeleton application.</p> + +<ul> +</ul> + +<h2 id="Summary">Summary</h2> + +<p>You now have a Node development environment up and running on your computer that can be used for creating Express web applications. You've also seen how NPM can be used to import Express into an application, and also how you can create applications using the Express Application Generator tool and then run them.</p> + +<p>In the next article we start working through a tutorial to build a complete web application using this environment and associated tools.</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://nodejs.org/en/download/">Downloads</a> page (nodejs.org)</li> + <li><a href="https://nodejs.org/en/download/package-manager/">Installing Node.js via package manager</a> (nodejs.org)</li> + <li><a href="http://expressjs.com/en/starter/installing.html">Installing Express</a> (expressjs.com)</li> + <li><a href="https://expressjs.com/en/starter/generator.html">Express Application Generator</a> (expressjs.com)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Introduction", "Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs")}}</p> + +<p> </p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li> +</ul> + +<p> </p> diff --git a/files/ko/learn/server-side/express_nodejs/스켈레톤_웹사이트/index.html b/files/ko/learn/server-side/express_nodejs/스켈레톤_웹사이트/index.html new file mode 100644 index 0000000000..ca72e39124 --- /dev/null +++ b/files/ko/learn/server-side/express_nodejs/스켈레톤_웹사이트/index.html @@ -0,0 +1,512 @@ +--- +title: 'Express Tutorial Part 2: 스켈레톤 웹사이트 만들기' +slug: Learn/Server-side/Express_Nodejs/스켈레톤_웹사이트 +translation_of: Learn/Server-side/Express_Nodejs/skeleton_website +--- +<div>{{LearnSidebar}}</div> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs")}}</p> + +<p class="summary">This second article in our <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial</a> shows how you can create a "skeleton" website project which you can then go on to populate with site-specific routes, templates/views, and database calls.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Set up a Node development environment</a>. Review the Express Tutorial.</td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td><em>Express 앱 제너레이터를 사용하여 자신만의 새로운 웹사이트 프로젝트를 시작할 수 있다.</em></td> + </tr> + </tbody> +</table> + +<h2 id="Overview">Overview</h2> + +<p>이 아티클은 당신이 <a href="https://expressjs.com/en/starter/generator.html">Express Application Generator</a> 도구를 이용하여 스켈레톤(최소한의 프레임 모형만 갖춘) 웹사이트를 만드는 방법을 보여줍니다. 이는 사이트에 맞춘 라우트, 뷰/템플릿 그리고 데이터 베이스를 사용할 수 있게 합니다. In this case, we'll use the tool to create the framework for our <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Local Library website</a>, to which we'll later add all the other code needed by the site. The process is extremely simple, requiring only that you invoke the generator on the command line with a new project name, optionally also specifying the site's template engine and CSS generator.</p> + +<p><span style="line-height: 1.5;">The following sections show you how to call the application generator, and provides a little explanation about the different view/CSS options. We'll also explain how the skeleton website is structured. At the end, we'll show how you can run the website to verify that it works.</span></p> + +<div class="note"> +<p><span style="line-height: 1.5;"><strong>Note</strong>: The <em>Express Application Generator</em> is not the only generator for Express applications, and the generated project is not the only viable way to structure your files and directories. The generated site does however have a modular structure that is easy to extend and understand. For information about a <em>minimal</em> Express application, see <a href="https://expressjs.com/en/starter/hello-world.html">Hello world example</a> (Express docs).</span></p> +</div> + +<h2 id="Using_the_application_generator">Using the application generator</h2> + +<p>You should already have installed the generator as part of <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">setting up a Node development environment</a>. As a quick reminder, you install the generator tool site-wide using the NPM package manager, as shown:</p> + +<pre class="brush: bash"><code>npm install express-generator -g</code></pre> + +<p>The generator has a number of options, which you can view on the command line using the <code>--help</code> (or <code>-h</code>) command:</p> + +<pre class="brush: bash">> express --help + + Usage: express [options] [dir] + + + Options: + + --version output the version number + -e, --ejs add ejs engine support + --pug add pug engine support + --hbs add handlebars engine support + -H, --hogan add hogan.js engine support + -v, --view <engine> add view <engine> support (dust|ejs|hbs|hjs|jade|pug|twig|vash) (defaults to jade) + --no-view use static html instead of view engine + -c, --css <engine> add stylesheet <engine> support (less|stylus|compass|sass) (defaults to plain css) + --git add .gitignore + -f, --force force on non-empty directory + -h, --help output usage information +</pre> + +<p>You can simply specify <code>express</code> to create a project inside the <em>current</em> directory using the <em>Jade</em> view engine and plain CSS (if you specify a directory name then the project will be created in a sub-folder with that name).</p> + +<pre class="brush: bash"><code>express</code></pre> + +<p>You can also choose a view (template) engine using <code>--view</code> and/or a CSS generation engine using <code>--css</code>.</p> + +<div class="note"> +<p><strong>Note:</strong> The other options for choosing template engines (e.g. <code>--hogan</code>, <code>--ejs</code>, <code>--hbs</code> etc.) are deprecated. Use <code>--view</code> (or<code> -v</code>)!</p> +</div> + +<h3 id="What_view_engine_should_I_use">What view engine should I use?</h3> + +<p>The <em>Express Application Generator</em> allows you to configure a number of popular view/templating engines, including <a href="https://www.npmjs.com/package/ejs">EJS</a>, <a href="http://github.com/donpark/hbs">Hbs</a>, <a href="https://pugjs.org/api/getting-started.html">Pug</a> (Jade), <a href="https://www.npmjs.com/package/twig">Twig</a>, and <a href="https://www.npmjs.com/package/vash">Vash</a>, although it chooses Jade by default if you don't specify a view option. Express itself can also support a large number of other templating languages <a href="https://github.com/expressjs/express/wiki#template-engines">out of the box</a>.</p> + +<div class="note"> +<p><strong>Note:</strong> If you want to use a template engine that isn't supported by the generator then see <a href="https://expressjs.com/en/guide/using-template-engines.html">Using template engines with Express</a> (Express docs) and the documentation for your target view engine.</p> +</div> + +<p>Generally speaking, you should select a templating engine that delivers all the functionality you need and allows you to be productive sooner — or in other words, in the same way that you choose any other component! Some of the things to consider when comparing template engines:</p> + +<ul> + <li>Time to productivity — If your team already has experience with a templating language then it is likely they will be productive faster using that language. If not, then you should consider the relative learning curve for candidate templating engines.</li> + <li>Popularity and activity — Review the popularity of the engine and whether it has an active community. It is important to be able to get support for the engine when you have problems over the lifetime of the website.</li> + <li>Style — Some template engines use specific markup to indicate inserted content within "ordinary" HTML, while others construct the HTML using a different syntax (for example, using indentation and block names).</li> + <li>Performance/rendering time.</li> + <li>Features — you should consider whether the engines you look at have the following features available: + <ul> + <li>Layout inheritance: Allows you to define a base template and then "inherit" just the parts of it that you want to be different for a particular page. This is typically a better approach than building templates by including a number of required components or building a template from scratch each time.</li> + <li>"Include" support: Allows you to build up templates by including other templates.</li> + <li>Concise variable and loop control syntax.</li> + <li>Ability to filter variable values at template level (e.g. making variables upper-case, or formatting a date value).</li> + <li>Ability to generate output formats other than HTML (e.g. JSON or XML).</li> + <li>Support for asynchronous operations and streaming.</li> + <li>Can be used on the client as well as the server. If a templating engine can be used on the client this allows the possibility of serving data and having all or most of the rendering done client-side.</li> + </ul> + </li> +</ul> + +<div class="note"> +<p><strong>Tip:</strong> There are many resources on the Internet to help you compare the different options!</p> +</div> + +<p>For this project, we'll use the <a href="https://pugjs.org/api/getting-started.html">Pug</a> templating engine (this is the recently-renamed Jade engine), as this is one of the most popular Express/JavaScript templating languages and is supported out of the box by the generator.</p> + +<h3 id="What_CSS_stylesheet_engine_should_I_use">What CSS stylesheet engine should I use?</h3> + +<p>The <em>Express Application Generator</em> allows you to create a project that is configured to use the most common CSS stylesheet engines: <a href="http://lesscss.org/">LESS</a>, <a href="http://sass-lang.com/">SASS</a>, <a href="http://compass-style.org/">Compass</a>, <a href="http://stylus-lang.com/">Stylus</a>.</p> + +<div class="note"> +<p><strong>Note: </strong>CSS has some limitations that make certain tasks difficult. CSS stylesheet engines allow you to use more powerful syntax for defining your CSS and then compile the definition into plain-old CSS for browsers to use.</p> +</div> + +<p>As with templating engines, you should use the stylesheet engine that will allow your team to be most productive. For this project, we'll use the ordinary CSS (the default) as our CSS requirements are not sufficiently complicated to justify using anything else.</p> + +<h3 id="What_database_should_I_use">What database should I use?</h3> + +<p>The generated code doesn't use/include any databases. <em>Express</em> apps can use any <a href="https://expressjs.com/en/guide/database-integration.html">database mechanism</a> supported by <em>Node</em> (<em>Express</em> itself doesn't define any specific additional behavior/requirements for database management).</p> + +<p>We'll discuss how to integrate with a database in a later article.</p> + +<h2 id="Creating_the_project">Creating the project</h2> + +<p><em>Local Library</em> 샘플 앱을 위해서 우리는 <em>express-locallibrary-tutorial </em>라는 이름의 프로젝트를 생성할 것입니다. <em>Pug</em> 라는(jade의 후속격) 템플릿 라이브러리를 사용할 것이며, CSS stylesheet 엔진은 사용하지 않습니다.</p> + +<p>First, navigate to where you want to create the project and then run the <em>Express Application Generator</em> in the command prompt as shown:</p> + +<pre class="brush: bash">express express-locallibrary-tutorial --view=pug +</pre> + +<p>The generator will create (and list) the project's files.</p> + +<pre class="brush: bash"> create : express-locallibrary-tutorial\ + create : express-locallibrary-tutorial\public\ + create : express-locallibrary-tutorial\public\javascripts\ + create : express-locallibrary-tutorial\public\images\ + create : express-locallibrary-tutorial\public\stylesheets\ + create : express-locallibrary-tutorial\public\stylesheets\style.css + create : express-locallibrary-tutorial\routes\ + create : express-locallibrary-tutorial\routes\index.js + create : express-locallibrary-tutorial\routes\users.js + create : express-locallibrary-tutorial\views\ + create : express-locallibrary-tutorial\views\error.pug + create : express-locallibrary-tutorial\views\index.pug + create : express-locallibrary-tutorial\views\layout.pug + create : express-locallibrary-tutorial\app.js + create : express-locallibrary-tutorial\package.json + create : express-locallibrary-tutorial\bin\ + create : express-locallibrary-tutorial\bin\www + + change directory: + > cd express-locallibrary-tutorial + + install dependencies: + > npm install + + run the app: + > SET DEBUG=express-locallibrary-tutorial:* & npm start</pre> + +<p>At the end of the output, the generator provides instructions on how you install the dependencies (as listed in the <strong>package.json</strong> file) and then how to run the application (the instructions above are for Windows; on Linux/macOS they will be slightly different).</p> + +<div class="blockIndicator note"> +<p><strong>Note:</strong> When using Windows, the && and & assumes you are using the Command Prompt. If you are using the new default PowerShell terminal do not concatenate the commands with && and &. Instead set the DEBUG environment variable with $ENV:DEBUG = "express-locallibrary-tutorial:*";. The npm start can be followed by the npm start. </p> +</div> + +<h2 id="Running_the_skeleton_website">Running the skeleton website</h2> + +<p>At this point, we have a complete skeleton project. The website doesn't actually <em>do</em> very much yet, but it's worth running it to show how it works.</p> + +<ol> + <li>First, install the dependencies (the <code>install</code> command will fetch all the dependency packages listed in the project's<strong> package.json</strong> file). + + <pre class="brush: bash">cd express-locallibrary-tutorial +npm install</pre> + </li> + <li>Then run the application. + <ul> + <li>On Windows, use this command: + <pre class="brush: bash">SET DEBUG=express-locallibrary-tutorial:* & npm start</pre> + </li> + <li>On macOS or Linux, use this command: + <pre class="brush: bash">DEBUG=express-locallibrary-tutorial:* npm start +</pre> + </li> + </ul> + </li> + <li>Then load <a href="http://localhost:3000/">http://localhost:3000/</a> in your browser to access the app.</li> +</ol> + +<p>You should see a browser page that looks like this:</p> + +<p><img alt="Browser for default Express app generator website" src="https://mdn.mozillademos.org/files/14375/ExpressGeneratorSkeletonWebsite.png" style="display: block; height: 403px; margin: 0px auto; width: 576px;"></p> + +<p>You have a working Express application, serving itself to <em>localhost:3000</em>.</p> + +<div class="note"> +<p><strong>Note:</strong> You could also start the app just using the <code>npm start</code> command. Specifying the DEBUG variable as shown enables console logging/debugging. For example, when you visit the above page you'll see debug output like this:</p> + +<pre class="brush: bash">>SET DEBUG=express-locallibrary-tutorial:* & npm start + +> express-locallibrary-tutorial@0.0.0 start D:\github\mdn\test\exprgen\express-locallibrary-tutorial +> node ./bin/www + + express-locallibrary-tutorial:server Listening on port 3000 +0ms +GET / 304 490.296 ms - - +GET /stylesheets/style.css 200 4.886 ms - 111 +</pre> +</div> + +<h2 id="Enable_server_restart_on_file_changes">Enable server restart on file changes</h2> + +<p>Any changes you make to your Express website are currently not visible until you restart the server. It quickly becomes very irritating to have to stop and restart your server every time you make a change, so it is worth taking the time to automate restarting the server when needed.</p> + +<p>One of the easiest such tools for this purpose is <a href="https://github.com/remy/nodemon">nodemon</a>. This is usually installed globally (as it is a "tool"), but here we'll install and use it locally as a <em>developer dependency</em>, so that any developers working with the project get it automatically when they install the application. Use the following command in the root directory for the skeleton project:</p> + +<pre class="brush: bash">npm install --save-dev nodemon</pre> + +<p>If you still choose to install <a href="https://github.com/remy/nodemon">nodemon</a> globally to your machine, and not only to your project's <strong>package.json</strong> file:</p> + +<pre class="brush: bash">npm install -g nodemon</pre> + +<p>If you open your project's <strong>package.json</strong> file you'll now see a new section with this dependency:</p> + +<pre class="brush: json"> "devDependencies": { + "nodemon": "^1.18.10" +} +</pre> + +<p>Because the tool isn't installed globally we can't launch it from the command line (unless we add it to the path) but we can call it from an NPM script because NPM knows all about the installed packages. Find the the <code>scripts</code> section of your package.json. Initially, it will contain one line, which begins with <code>"start"</code>. Update it by putting a comma at the end of that line, and adding the <code>"devstart"</code> line seen below:</p> + +<pre class="brush: json"> "scripts": { + "start": "node ./bin/www"<strong>,</strong> +<strong> "devstart": "nodemon ./bin/www", + "serverstart": "</strong>DEBUG=express-locallibrary-tutorial:* npm <strong>run devstart"</strong> + }, +</pre> + +<p>We can now start the server in almost exactly the same way as previously, but with the <code>devstart</code> command specified:</p> + +<ul> + <li>On Windows, use this command: + <pre class="brush: bash">SET DEBUG=express-locallibrary-tutorial:* & npm <strong>run devstart</strong></pre> + </li> + <li>On macOS or Linux, use this command: + <pre class="brush: bash">DEBUG=express-locallibrary-tutorial:* npm <strong>run devstart</strong> +</pre> + </li> +</ul> + +<div class="note"> +<p><strong>Note:</strong> Now if you edit any file in the project the server will restart (or you can restart it by typing <code>rs</code> on the command prompt at any time). You will still need to reload the browser to refresh the page.</p> + +<p>We now have to call "<code>npm run <em><scriptname></em></code>" rather than just <code>npm start</code>, because "start" is actually an NPM command that is mapped to the named script. We could have replaced the command in the <em>start</em> script but we only want to use <em>nodemon</em> during development, so it makes sense to create a new script command.</p> + +<p>The <code>serverstart</code> command added to the scripts in the <strong>package.json</strong> above is a very good example. Using this approach means you no longer have to type a long command shown to start the server. Note that the particular command added to the script works for macOS or Linux only.</p> +</div> + +<h2 id="The_generated_project">The generated project</h2> + +<p>Let's now take a look at the project we just created.</p> + +<h3 id="Directory_structure">Directory structure</h3> + +<p>The generated project, now that you have installed dependencies, has the following file structure (files are the items <strong>not</strong> prefixed with "/"). The <strong>package.json</strong> file defines the application dependencies and other information. It also defines a startup script that will call the application entry point, the JavaScript file <strong>/bin/www</strong>. This sets up some of the application error handling and then loads <strong>app.js</strong> to do the rest of the work. The app routes are stored in separate modules under the <strong>routes/</strong> directory. The templates are stored under the /<strong>views</strong> directory.</p> + +<pre>/express-locallibrary-tutorial + <strong>app.js</strong> + /bin + <strong>www</strong> + <strong>package.json</strong> + <strong>package-lock.json</strong> + /node_modules + [about 6700 subdirectories and files] + /public + /images + /javascripts + /stylesheets + <strong>style.css</strong> + /routes + <strong>index.js</strong> + <strong>users.js</strong> + /views + <strong>error.pug</strong> + <strong>index.pug</strong> + <strong>layout.pug</strong> + +</pre> + +<p>The following sections describe the files in a little more detail.</p> + +<h3 id="package.json">package.json</h3> + +<p>The <strong>package.json </strong>file defines the application dependencies and other information:</p> + +<pre class="brush: json">{ + "name": "express-locallibrary-tutorial", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "node ./bin/www", + "devstart": "nodemon ./bin/www" + }, + "dependencies": { + "cookie-parser": "~1.4.3", + "debug": "~2.6.9", + "express": "~4.16.0", + "http-errors": "~1.6.2", + "morgan": "~1.9.0", + "pug": "2.0.0-beta11" + }, + "devDependencies": { + "nodemon": "^1.18.10" + } +} +</pre> + +<p>The dependencies include the <em>express</em> package and the package for our selected view engine (<em>pug</em>). In addition, we have the following packages that are useful in many web applications:</p> + +<ul> + <li><a href="https://www.npmjs.com/package/cookie-parser">cookie-parser</a>: Used to parse the cookie header and populate <code>req.cookies</code> (essentially provides a convenient method for accessing cookie information).</li> + <li><a href="https://www.npmjs.com/package/debug">debug</a>: A tiny node debugging utility modeled after node core's debugging technique.</li> + <li><a href="https://www.npmjs.com/package/morgan">morgan</a>: An HTTP request logger middleware for node.</li> + <li><a href="https://www.npmjs.com/package/http-errors">http-errors</a>: Create HTTP errors where needed (for express error handling).</li> +</ul> + +<p>The scripts section defines a "<em>start</em>" script, which is what we are invoking when we call <code>npm start</code> to start the server. From the script definition, you can see that this actually starts the JavaScript file <strong>./bin/www</strong> with <em>node</em>. It also defines a "<em>devstart</em>" script, which we invoke when calling <code>npm run devstart</code> instead. This starts the same <strong>./bin/www</strong> file, but with <em>nodemon</em> rather than <em>node</em>.</p> + +<pre class="brush: json"> "scripts": { + "start": "node ./bin/www", + "devstart": "nodemon ./bin/www" + }, +</pre> + +<h3 id="www_file">www file</h3> + +<p>The file <strong>/bin/www</strong> is the application entry point! The very first thing this does is <code>require()</code> the "real" application entry point (<strong>app.js</strong>, in the project root) that sets up and returns the <code><a href="http://expressjs.com/en/api.html">express()</a></code> application object.</p> + +<pre class="brush: js">#!/usr/bin/env node + +/** + * Module dependencies. + */ + +<strong>var app = require('../app');</strong> +</pre> + +<div class="note"> +<p><strong>Note:</strong> <code>require()</code> is a global node function that is used to import modules into the current file. Here we specify <strong>app.js</strong> module using a relative path and omitting the optional (.<strong>js</strong>) file extension.</p> +</div> + +<p>The remainder of the code in this file sets up a node HTTP server with <code>app</code> set to a specific port (defined in an environment variable or 3000 if the variable isn't defined), and starts listening and reporting server errors and connections. For now you don't really need to know anything else about the code (everything in this file is "boilerplate"), but feel free to review it if you're interested.</p> + +<h3 id="app.js">app.js</h3> + +<p>This file creates an <code>express</code> application object (named <code>app</code>, by convention), sets up the application with various settings and middleware, and then exports the app from the module. The code below shows just the parts of the file that create and export the app object:</p> + +<pre class="brush: js"><code>var express = require('express'); +var app = express(); +... +</code>module.exports = app; +</pre> + +<p>Back in the <strong>www</strong> entry point file above, it is this <code>module.exports</code> object that is supplied to the caller when this file is imported.</p> + +<p>Let's work through the <strong>app.js</strong> file in detail. First, we import some useful node libraries into the file using <code>require()</code>, including http-errors, <em>express</em>, <em>morgan</em> and <em>cookie-parser</em> that we previously downloaded for our application using NPM; and <em>path</em>, which is a core Node library for parsing file and directory paths.</p> + +<pre class="brush: js">var createError = require('http-errors'); +var express = require('express'); +var path = require('path'); +var cookieParser = require('cookie-parser'); +var logger = require('morgan'); +</pre> + +<p>Then we <code>require()</code> modules from our routes directory. These modules/files contain code for handling particular sets of related "routes" (URL paths). When we extend the skeleton application, for example to list all books in the library, we will add a new file for dealing with book-related routes.</p> + +<pre class="brush: js">var indexRouter = require('./routes/index'); +var usersRouter = require('./routes/users'); +</pre> + +<div class="note"> +<p><strong>Note:</strong> At this point, we have just <em>imported</em> the module; we haven't actually used its routes yet (this happens just a little bit further down the file).</p> +</div> + +<p>Next, we create the <code>app</code> object using our imported <em>express</em> module, and then use it to set up the view (template) engine. There are two parts to setting up the engine. First, we set the '<code>views</code>' value to specify the folder where the templates will be stored (in this case the subfolder <strong>/views</strong>). Then we set the '<code>view engine</code>' value to specify the template library (in this case "pug").</p> + +<pre class="brush: js">var app = express(); + +// view engine setup +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'pug'); +</pre> + +<p>The next set of functions call <code>app.use()</code> to add the <em>middleware</em> libraries into the request handling chain. In addition to the 3rd party libraries we imported previously, we use the <code>express.static</code> middleware to get <em>Express</em> to serve all the static files in the <strong>/public</strong> directory in the project root.</p> + +<pre class="brush: js">app.use(logger('dev')); +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); +app.use(cookieParser()); +<strong>app.use(express.static(path.join(__dirname, 'public')));</strong> +</pre> + +<p>Now that all the other middleware is set up, we add our (previously imported) route-handling code to the request handling chain. The imported code will define particular routes for the different <em>parts</em> of the site:</p> + +<pre class="brush: js">app.use('/', indexRouter); +app.use('/users', usersRouter); +</pre> + +<div class="note"> +<p><strong>Note:</strong> The paths specified above ('/' and '<code>/users'</code>) are treated as a prefix to routes defined in the imported files. So for example, if the imported <strong>users</strong> module defines a route for <code>/profile</code>, you would access that route at <code>/users/profile</code>. We'll talk more about routes in a later article.</p> +</div> + +<p id="error_handling">The last middleware in the file adds handler methods for errors and HTTP 404 responses.</p> + +<pre class="brush: js">// catch 404 and forward to error handler +app.use(function(req, res, next) { + next(createError(404)); +}); + +// error handler +app.use(function(err, req, res, next) { + // set locals, only providing error in development + res.locals.message = err.message; + res.locals.error = req.app.get('env') === 'development' ? err : {}; + + // render the error page + res.status(err.status || 500); + res.render('error'); +}); +</pre> + +<p>The Express application object (app) is now fully configured. The last step is to add it to the module exports (this is what allows it to be imported by <strong>/bin/www</strong>).</p> + +<pre class="brush: js">module.exports = app;</pre> + +<h3 id="Routes">Routes</h3> + +<p>The route file <strong>/routes/users.js</strong> is shown below (route files share a similar structure, so we don't need to also show <strong>index.js</strong>). First, it loads the <em>express</em> module and uses it to get an <code>express.Router</code> object. Then it specifies a route on that object and lastly exports the router from the module (this is what allows the file to be imported into <strong>app.js</strong>).</p> + +<pre class="brush: js">var express = require('express'); +var router = express.Router(); + +/* GET users listing. */ +<strong>router.get('/', function(req, res, next) { + res.send('respond with a resource');</strong> +}); + +module.exports = router; +</pre> + +<p>The route defines a callback that will be invoked whenever an HTTP <code>GET</code> request with the correct pattern is detected. The matching pattern is the route specified when the module is imported ('<code>/users</code>') plus whatever is defined in this file ('<code>/</code>'). In other words, this route will be used when an URL of <code>/users/</code> is received.</p> + +<div class="note"> +<p><strong>Tip:</strong> Try this out by running the server with node and visiting the URL in your browser: <a href="http://localhost:3000/users/">http://localhost:3000/users/</a>. You should see a message: 'respond with a resource'.</p> +</div> + +<p>One thing of interest above is that the callback function has the third argument '<code>next</code>', and is hence a middleware function rather than a simple route callback. While the code doesn't currently use the <code>next</code> argument, it may be useful in the future if you want to add multiple route handlers to the <code>'/'</code> route path.</p> + +<h3 id="Views_templates">Views (templates)</h3> + +<p>The views (templates) are stored in the <strong>/views</strong> directory (as specified in <strong>app.js</strong>) and are given the file extension <strong>.pug</strong>. The method <code><a href="http://expressjs.com/en/4x/api.html#res.render">Response.render()</a></code> is used to render a specified template along with the values of named variables passed in an object, and then send the result as a response. In the code below from <strong>/routes/index.js</strong> you can see how that route renders a response using the template "index" passing the template variable "title".</p> + +<pre class="brush: js">/* GET home page. */ +router.get('/', function(req, res, next) { + res.render('index', { title: 'Express' }); +}); +</pre> + +<p>The corresponding template for the above route is given below (<strong>index.pug</strong>). We'll talk more about the syntax later. All you need to know for now is that the <code>title</code> variable (with value <code>'Express'</code>) is inserted where specified in the template.</p> + +<pre>extends layout + +block content + h1= title + p Welcome to #{title} +</pre> + +<h2 id="Challenge_yourself">Challenge yourself</h2> + +<p>Create a new route in <strong>/routes/users.js</strong> that will display the text "<em>You're so cool"</em> at URL <code>/users/cool/</code>. Test it by running the server and visiting <a href="http://localhost:3000/users/cool/">http://localhost:3000/users/cool/</a> in your browser</p> + +<ul> +</ul> + +<h2 id="Summary">Summary</h2> + +<p>You have now created a skeleton website project for the <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Local Library</a> and verified that it runs using <em>node</em>. Most importantly, you also understand how the project is structured, so you have a good idea where we need to make changes to add routes and views for our local library.</p> + +<p>Next, we'll start modifying the skeleton so that it works as a library website.</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://expressjs.com/en/starter/generator.html">Express application generator</a> (Express docs)</li> + <li><a href="https://expressjs.com/en/guide/using-template-engines.html">Using template engines with Express</a> (Express docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li> +</ul> diff --git a/files/ko/learn/server-side/first_steps/client-server_overview/index.html b/files/ko/learn/server-side/first_steps/client-server_overview/index.html new file mode 100644 index 0000000000..cc39e1802e --- /dev/null +++ b/files/ko/learn/server-side/first_steps/client-server_overview/index.html @@ -0,0 +1,324 @@ +--- +title: Client-Server overview +slug: Learn/Server-side/First_steps/Client-Server_overview +tags: + - 서버측 프로그래밍 +translation_of: Learn/Server-side/First_steps/Client-Server_overview +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/First_steps/Introduction", "Learn/Server-side/First_steps/Web_frameworks", "Learn/Server-side/First_steps")}}</div> + +<p class="summary">이제 서버 측 프로그래밍의 목적과 잠재적 이점을 알았으니 서버가 브라우저에서 "동적 요청"을받을 때 어떤 일이 발생하는지 자세히 살펴 보겠습니다. 대부분의 웹 사이트 서버 측 코드는 요청 및 응답을 비슷한 방식으로 처리하므로 대부분의 코드를 작성할 때 수행해야 할 작업을 이해하는 데 도움이됩니다</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">선수 과목:</th> + <td> + <p>기본적인 컴퓨터 활용법,</p> + + <p>웹서버에 대한 기초적인 이해</p> + </td> + </tr> + <tr> + <th scope="row">목적:</th> + <td> + <p>클라이언트-서버가 동적 웹사이트에서 어떻게 상호작용을 하는지 이해하고, </p> + + <p>특히 서버 측 코드로 수행해야하는 작업을 이해합니다.</p> + </td> + </tr> + </tbody> +</table> + +<p>이 기사는 실제 코드가 없습니다. 왜냐하면 우리는 아직 어떤 웹프레임 워크를 사용하여 코드를 작성할지 선택하지 않았기 때문입니다. 그러나 설명된 동작은 사용자가 선택하는 언어나 웹 프레임워크와 관계없이 서버 측 코드에 의해 구현 되야하므로 이 기사는 여전히 관계가 있습니다.</p> + +<h2 id="Web_servers_and_HTTP_(a_primer)">Web servers and HTTP (a primer)</h2> + +<p>웹 브라우저는 <strong>H</strong>yper<strong>T</strong>ext <strong>T</strong>ransport <strong>P</strong>rotocol (<a href="https://developer.mozilla.org/ko/docs/Web/HTTP">HTTP</a>)을 사용 하여 웹 서버와 통신을 합니다 . 당신이 웹 페이지의 링크를 클릭하거나, form을 전송 하거나, 검색을 시작하거나 할 때 웹 브라우저는 <em>HTTP Request</em>를 서버에 보냅니다.</p> + +<p>이 요청이 포함 하는것은 다음과 같습니다:</p> + +<ul> + <li>대상 서버및 리소스를 식별하는 URL. (예: HTML파일, 서버의 특별한 데이터 요소, 또는 실행할 도구)</li> + <li>필요한 행동을 정의할 메소드 (예를 들면, 파일을 얻거나 어떤 데이터를 저장하거나 업데이트하는 것). 여러 메소드/단어 그리고 그들과 관련된 행동이 아래에 나열 되어있습니다: + <ul> + <li><code>GET</code>: 특정 리소스를 얻습니다. (예: 제품의 대한 정보 또는 제품 목록을 제공하는 HTML파일). </li> + <li><code>POST</code>: 새로운 리소스를 만듭니다. (예: 위키에 새로운 기사를 추가 하기, 데이터베이스와 새로운 연결을 추가하기). </li> + <li><code>HEAD</code>: 특정 리소스의 내용 부분을 제외한 메타데이터 정보를 가져옵니다. 예를 들어 HEAD 요청을 사용하여 리소스가 마지막으로 업데이트 된 시간을 확인한 다음 리소스가 변경된 경우에만 (더 비싼) GET 요청을 사용하여 리소스를 다운로드 할 수 있습니다. </li> + <li><code>PUT</code>: 기존에 있던 리소스를 업데이트 합니다. (또는 그 리소스가 없었다면 새로 만들어줍니다)</li> + <li><code>DELETE</code>: 특정 리소스를 삭제합니다.</li> + <li><code>TRACE</code>, <code>OPTIONS</code>, <code>CONNECT</code>, <code>PATCH</code>: 이 메소드는 덜 일반적인 / 고급 작업을위한 것이므로 여기서 다루지는 않습니다.</li> + </ul> + </li> + <li>요청과 함께 추가 정보 (예 : HTML 양식 데이터)를 인코딩 할 수 있습니다. 정보는 다음과 같이 인코딩 될 수 있습니다:</li> +</ul> + +<p>URL 매개변수들: <code>GET</code> requests는 이름/값 쌍을 끝에 추가하여 서버에 보낸 URL 데이터를 인코딩 합니다. 예를 들어<code>http://mysite.com<strong>?name=Fred&age=11</strong></code>. 물음표(<code>?</code>)를 항상 볼수 있는데 이것은 URL과 URL 매개변수를 분리 합니다, (<code>=</code>)은 매개변수의 이름과 값을 분리합니다. 그리고(<code>&</code>)는 이름/값 쌍을 분리합니다. URL 매개 변수는 사용자가 변경하여 다시 제출할 수 있으므로 본질적으로 "안전하지 않음"입니다. 그런 이유로URL 매개변수들/<code>GET</code> 요청은 서버에 데이터를 업데이트를 하는 요청에 사용되지 않습니다.</p> + +<ul> + <li><code>POST</code> 데이터. POST 요청은 요청 본문 내에 인코딩 된 데이터를 새 리소스에 추가합니다.</li> + <li>클라이언트 측 쿠키. 쿠키는 서버가 로그인 상태 및 권한 / 자원에 대한 액세스를 결정하는 데 사용할 수있는 키를 포함하여 클라이언트에 대한 세션 데이터를 포함합니다.</li> +</ul> + +<p>웹 서버는 클라이언트의 요청 메시지를 기다리고, 메시지가 오면 그것들을 처리하고, 웹 브라우저에 HTTP Response메시지를 응답합니다. 응답에는 요청 성공여부를 나타내는 <a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Status">HTTP Response status code</a>를 포함합니다.(예 "<code>200 OK</code>" 응답 성공, "<code>404 Not Found</code>" 해당 리소스를 찾을 수 없음, "<code>403 Forbidden</code>" 사용자가 해당 리소스를 볼 자격을 증명하지 않음, 기타 등등). 성공적인 응답이라면 GET request에서 요청한 리소스를 포함한 본문 일 것 입니다.</p> + +<p>HTML 페이지가 반환 되면 웹 브라우저에 의해 렌더링 될 것입니다. 그 과정에서 브라우저는 다른 리소스와 링크된 것들을 찾을수도 있습니다.(예 HTML page 종종 JavaScript나 CSS pages를 참조합니다), 그리고 별도의 HTTP Requests로 그 파일들을 다운로드 합니다.</p> + +<p>정적 웹사이트및 동적 웹사이트는 (다음 섹션에서 설명하는) 정확히 같은 통신 프로토콜/패턴을 사용합니다.</p> + +<h3 id="GET_requestresponse_example">GET request/response example</h3> + +<p>당신은 링크를 클릭하거나 사이트를 검색하는 간단한 <code>GET</code> request를 만들 수 있습니다.(검색엔진의 홈페이지 같은). 예를 들자면, 만약 당신이 MDN에서 "클라이언트 서버 개요"를 검색하면 전송되는 HTTP request는 아래의 텍스트와 매우 유사할 것 입니다(메시지의 일부가 브라우저/설정에 따라 다르므로 같지는 않습니다).</p> + +<div class="note"> +<p>HTTP 메시지의 형식은 "웹 표준" (<a href="http://www.rfc-editor.org/rfc/rfc7230.txt">RFC7230</a>)에 정의 되어 있습니다. 이 수준의 세부사항을 알 필요는 없지만 적어도 이제는 이 모든 것이 어디서 왔는지를 알 수 있습니다!</p> +</div> + +<h4 id="The_request">The request</h4> + +<p>각 라인의 요청들은 그것이 어떤 정보인지 포함 하고 있습니다. 이러한 첫 부분을 <strong>header</strong>라고 합니다, <a href="/en-US/docs/Learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML">HTML head</a>가 HTML 문서의 유용한 정보를 포함하는 것과 같은 방식으로 유용한 정보를 포함합니다. (실제로 본문에 있는 콘텐츠 자체는 아닙니다.):</p> + +<pre>GET https://developer.mozilla.org/en-US/search?q=client+server+overview&topic=apps&topic=html&topic=css&topic=js&topic=api&topic=webdev HTTP/1.1 +Host: developer.mozilla.org +Connection: keep-alive +Pragma: no-cache +Cache-Control: no-cache +Upgrade-Insecure-Requests: 1 +User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 +Referer: https://developer.mozilla.org/en-US/ +Accept-Encoding: gzip, deflate, sdch, br +<code>Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7</code> +Accept-Language: en-US,en;q=0.8,es;q=0.6 +Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; csrftoken=zIPUJsAZv6pcgCBJSCj1zU6pQZbfMUAT; dwf_section_edit=False; dwf_sg_task_completion=False; _gat=1; _ga=GA1.2.1688886003.1471911953; ffo=true +</pre> + +<p>첫번째와 두번째라인은 위에서 우리가 말한 많은 종류의 정보를 포함 하고 있습니다:</p> + +<ul> + <li>요청의 타입(<code>GET</code>).</li> + <li>대상 리소스 URL (<code>/en-US/search</code>).</li> + <li>URL 인자들 (<code>q=client%2Bserver%2Boverview&topic=apps&topic=html&topic=css&topic=js&topic=api&topic=webdev</code>).</li> + <li>대상/호스트 웹 사이트 (developer.mozilla.org).</li> + <li>첫번째 줄의 끝 부분은 특정 프로토콜 버전을 식별하는 짧은 문자열도 포함 합니다 (<code>HTTP/1.1</code>).</li> +</ul> + +<p>마지막 줄은 서버측 쿠키의 대한 정보를 포함합니다. — 이번 케이스는 쿠키가 세션을 관리하는 id를 포함하는 것을 볼 수 있습니다. (<code>Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; ...</code>).</p> + +<p>나머지 줄은 사용하는 브라우저의 정보와 다룰수 있는 요청의 정렬한 정보들을 포함 하고 있습니다. 이것들을 예로 볼 수 있습니다:</p> + +<ul> + <li>나의 브라우저는 (<code>User-Agent</code>) Mozilla Firefox입니다(<code>Mozilla/5.0</code>).</li> + <li>gzip 의 압축된 정보들을 받아들일 수 있습니다(<code>Accept-Encoding: gzip</code>).</li> + <li>지정된 문자집합(<code>Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7</code>) 과 언어들을 받아 들일 수 있습니다(<code>Accept-Language: de,en;q=0.7,en-us;q=0.3</code>).</li> + <li><code>Referer</code> 줄은 리소스가 링크된 웹 페이지를 포함하는 주소를 지시하고 있습니다 (i.e. the origin of the request, <code>https://developer.mozilla.org/en-US/</code>).</li> +</ul> + +<p>HTTP requests는 본문을 가질수 있지만 이번 케이스에서는 비어 있습니다.</p> + +<h4 id="The_response">The response</h4> + +<p>이 요청의 대한 응답의 첫번째 부분은 밑에서 볼 수 있습니다. header에는 다음과 같이 정보들을 포함 하고 있습니다:</p> + +<ul> + <li>응답 코드의 첫번째 줄은 <code>200 OK</code>를 포함 하고 있습니다, 이것은 우리에게 응답이 성공하였다는 것을 말해주고 있습니다.</li> + <li>응답 포맷은 <code>text/html</code>이라는 것을 볼 수 있습니다. (<code>Content-Type</code>).</li> + <li>또한 문자집합을 UTF-8로 사용하는 것을 볼 수 있습니다(<code>Content-Type: text/html; charset=utf-8</code>).</li> + <li>또한 이 header는 body가 얼마나 큰지 알려주고 있습니다. (<code>Content-Length: 41823</code>).</li> +</ul> + +<p>메시지의 마지막에는 요청에 의해 반환된 실제 HTML을 포함하는 <strong>본문 </strong>콘텐츠를 볼 수 있습니다.</p> + +<pre class="brush: html">HTTP/1.1 200 OK +Server: Apache +X-Backend-Server: developer1.webapp.scl3.mozilla.com +Vary: Accept,Cookie, Accept-Encoding +Content-Type: text/html; charset=utf-8 +Date: Wed, 07 Sep 2016 00:11:31 GMT +Keep-Alive: timeout=5, max=999 +Connection: Keep-Alive +X-Frame-Options: DENY +Allow: GET +X-Cache-Info: caching +Content-Length: 41823 + + + +<!DOCTYPE html> +<html lang="en-US" dir="ltr" class="redesign no-js" data-ffo-opensanslight=false data-ffo-opensans=false > +<head prefix="og: http://ogp.me/ns#"> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=Edge"> + <script>(function(d) { d.className = d.className.replace(/\bno-js/, ''); })(document.documentElement);</script> + ... +</pre> + +<p>응답 header의 나머지 부분은 응답(예. 생성 되었을 때), 서버및 브라우저가 처리하는 방법의 대한 정보를 포함하고 있습니다(예. <code>X-Frame-Options: DENY</code>행은 브라우저가 페이지를 다른 사이트의 {{htmlelement ( "iframe")}}에 삽입하는 것을 허용하지 않는 것을 지시합니다).</p> + +<h3 id="POST_requestresponse_example">POST request/response example</h3> + +<p>HTTP <code>POST</code>는 당신이 정보를 포함한 폼을 작성하여 서버에 저장하기 위해 전송할때 만들어집니다.</p> + +<h4 id="The_request_2">The request</h4> + +<p>아래의 텍스트는 사용자가 새로운 프로필 정보를 사이트에 전송할때 만들어지는 HTTP request를 보여줍니다. 이 요청의 대한 포맷은 이전 <code>GET</code> request의 예시와 거의 비슷해 보입니다, 그렇지만 첫번째 줄은 이것이 <code>POST</code> 요청임을 보여주고 있습니다. </p> + +<pre class="brush: html">POST https://developer.mozilla.org/en-US/profiles/hamishwillee/edit HTTP/1.1 +Host: developer.mozilla.org +Connection: keep-alive +Content-Length: 432 +Pragma: no-cache +Cache-Control: no-cache +Origin: https://developer.mozilla.org +Upgrade-Insecure-Requests: 1 +User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 +Content-Type: application/x-www-form-urlencoded +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 +Referer: https://developer.mozilla.org/en-US/profiles/hamishwillee/edit +Accept-Encoding: gzip, deflate, br +Accept-Language: en-US,en;q=0.8,es;q=0.6 +Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; _gat=1; csrftoken=zIPUJsAZv6pcgCBJSCj1zU6pQZbfMUAT; dwf_section_edit=False; dwf_sg_task_completion=False; _ga=GA1.2.1688886003.1471911953; ffo=true + +csrfmiddlewaretoken=zIPUJsAZv6pcgCBJSCj1zU6pQZbfMUAT&user-username=hamishwillee&user-fullname=Hamish+Willee&user-title=&user-organization=&user-location=Australia&user-locale=en-US&user-timezone=Australia%2FMelbourne&user-irc_nickname=&user-interests=&user-expertise=&user-twitter_url=&user-stackoverflow_url=&user-linkedin_url=&user-mozillians_url=&user-facebook_url=</pre> + +<p>가장 큰 차이점은 URL이 인자를 가지고 있지 않다는 겁니다. 보다시피, 폼에서 온 정보들은 요청 본문에 인코딩 되어있습니다(예를 들면, 새 사용자의 전체 이름은 다음을 사용하여 설정 합니다: <code>&user-fullname=Hamish+Willee</code>).</p> + +<h4 id="The_response_2">The response</h4> + +<p>요청에서 온 응답은 아래와 같이 보여집니다. "<code>302 Found</code>"의 상태 코드는 브라우저에게 post가 성공했고, <code>Location</code> 필드가 지정된 페이지를 로드하기 위해 두번째 HTTP request를 실행해야 하는 것을 알려줍니다. 그렇지 않은 경우 정보는 <code>GET</code> request에 대한 응답정보와 유사합니다.</p> + +<pre class="brush: html">HTTP/1.1 302 FOUND +Server: Apache +X-Backend-Server: developer3.webapp.scl3.mozilla.com +Vary: Cookie +Vary: Accept-Encoding +Content-Type: text/html; charset=utf-8 +Date: Wed, 07 Sep 2016 00:38:13 GMT +Location: https://developer.mozilla.org/en-US/profiles/hamishwillee +Keep-Alive: timeout=5, max=1000 +Connection: Keep-Alive +X-Frame-Options: DENY +X-Cache-Info: not cacheable; request wasn't a GET or HEAD +Content-Length: 0 +</pre> + +<div class="note"> +<p><strong>Note</strong>: HTTP responses 그리고 requests를 보여주는 예시들은 <a href="https://www.telerik.com/download/fiddler">Fiddler </a>어플리케이션을 사용하여 캡처하였습니다, 그렇지만 당신은 스니퍼(e.g. <a href="http://web-sniffer.net/">http://web-sniffer.net/</a>)나 브라우저 확장 기능인 <a href="https://addons.mozilla.org/en-US/firefox/addon/httpfox/">HttpFox </a>같은 것을 사용하여 확인 할 수 있습니다. 이러한 것을 직접 시도해보세요. 링크된 도구들을 사용하여 여러 사이트들을 돌아다니고 프로필 정보를 수정 하여 다양한 requests 와 responses를 확인해보세요. 현대의 대부분 브라우저는 네트워크 요청을 모니터 할수 있는 도구 또한 가지고 있습니다 (예를들면, Firefox가 가진 도구인 <a href="/en-US/docs/Tools/Network_Monitor">Network Monitor</a> ).</p> +</div> + +<h2 id="Static_sites">Static sites</h2> + +<p>정적 사이트는 특정 리소스가 요청될 때마다 서버에서 하드 코딩된 동일한 컨텐츠를 반환하는 사이트 입니다. 예를 들면 당신이 <code>/static/myproduct1.html</code> 에서 제품에 대한 정보페이지를 가지고 있다면 , 이 페이지는 어떤 유저에게나 동일하게 반환될 것입니다. 만약 당신이 비슷한 제품을 사이트에 추가 하고 싶다면 당신은 다른 페이지를 추가하여야 합니다(예. <code>myproduct2.html</code>) . 이것은 실제로 비효율적이 될것입니다 — 제품 페이지가 수천이 된다면? 각 페이지에 (기본 페이지 템플릿, 구조체 등)걸쳐 많은 코드를 반복하고 페이지에 대한 구조를 변경하려는 경우 — 예를 들어 새로운 관련제품에 대한 섹션을 추가하는 경우 — 모든 페이지를 개별적으로 변경해야 합니다.</p> + +<div class="note"> +<p><strong>Note</strong>: 정적사이트는 작은수의 페이지만 가지고 있거나 같은 콘텐츠를 모든 유저에게 보내고 싶을때는 좋을 수 있습니다. 그렇지만 페이지 수가 많아지기 시작하면 상당한 비용의 유지비가 필요합니다.</p> +</div> + +<p>어떻게 작동하는지 다시 살펴보겠습니다, 마지막 기사에서 살펴본 정적 사이트의 아키텍쳐는다이어그램을 다시 살펴보면</p> + +<p><img alt="A simplified diagram of a static web server." src="https://mdn.mozillademos.org/files/13841/Basic%20Static%20App%20Server.png"></p> + +<p>유저가 페이지를 탐색하기를 원할 때, 브라우저는 지정된 HTML 페이지의 URL에 HTTP <code>GET</code> request를 보냅니다. 서버는 요청한 문서를 파일 시스템에서 탐색하고 문서와<a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Status"> HTTP Response status code </a>"<code>200 OK</code>" (성공을 알려주는)를 포함하는 HTTP응답을 반환합니다. 만약 서버가 다른 상태 코드를 반환한다면, 예를들면 "<code>404 Not Found</code>"는 파일이 서버에 없는 경우이고 "<code>301 Moved Permanently</code>"는 파일은 존재하지만 다른 위치로 리다이렉트된 경우입니다 .</p> + +<p>이 정적 사이트는 오직 GET requests만 필요합니다, 왜냐하면 이 서버는 변경 할 수 있는 데이터는 저장하지 않기 때문입니다. 또한 HTTP Request 데이터에 기반한 응답을 바꿀 필요가 없습니다(예. URL 인자들 또는 쿠키). </p> + +<p>서버측 프로그래밍을 할때는 정적사이트가 어떻게 동작하는지 이해하는 것이 여전히 유용합니다, 왜냐하면 동적사이트가 정적 파일(CSS, JavaScript, static images, etc.)에 대한 요청을 다루는 방법이 거의 동일하기 때문입니다.</p> + +<h2 id="Dynamic_sites">Dynamic sites</h2> + +<p>동적 사이트는 특별한 URL과 데이터 요청에 의해 생성되거나 콘텐츠를 반환할 수 있습니다(특정 URL에 대해 항상 동일한 하드 코딩 된 파일을 반환하는 것이 아닙니다). 제품 사이트를 이용하는 예를 들면, 서버는 각 제품에 대한 HTML파일을 만들기보다 제품 "데이터"를 데이터베이스에 저장할 것입니다. 제품에 대한HTTP <code>GET</code> Request를 받으면, 서버는 제품ID를 결정하고, 데이터베이스에서 데이터를 가져오고, HTML 템플릿에 가져온 데이터를 집어넣은 HTML페이지를 생성할 것입니다. 이것은 정적 사이트보다 주요한 장점이 있습니다:</p> + +<p>데이터베이스를 통해 제품 정보를 쉽게 확장하거나 수정하거나 검색가능한 방식으로 효율적으로 저장 할 수있습니다.</p> + +<p>HTML 템플릿을 통해 HTML 구조를 매우 쉽게 바꿀 수 있도록 만들 수 있습니다, 왜냐하면 하나의 위치에 하나의 템플릿만 수행되어야하며 잠재적인 수천개의 정적 페이지에서 수행되지 않아야 하기 때문입니다.</p> + +<h3 id="Anatomy_of_a_dynamic_request">Anatomy of a dynamic request</h3> + +<p>이 섹션은 "동적" HTTP request와 response 사이클에 대한 단계별 개요를 제공하고, 마지막 기사에서 살펴본 내용을 훨씬 더 자세하게 소개합니다. "실제 상황을 유지하기 위해"우리는 코치가 HTML 형식으로 팀 이름과 팀 크기를 선택하고 다음 게임을 위해 제안 된 "최고의 라인업"을 얻을 수있는 스포츠 팀 매니저 웹 사이트의 컨텍스트를 사용합니다.</p> + +<p>아래 다이어그램은 "팀 코치"웹 사이트의 주요 요소와 코치가 "최고의 팀"목록에 액세스 할 때 일련의 작업 순서에 대한 번호가 매겨진 레이블을 보여줍니다. <em>사이트를 동적으로 만드는 부분들은</em> 웹 애플리케이션(이것은 HTTP 요청을 처리하고 HTTP 응답을 반환하는 서버 측 코드를 참조하는 방법입니다), 선수의 정보나 팀 코치의 정보를 포함한 데이터베이스 그리고 <em>HTML 템플릿입니다</em>.</p> + +<p><img alt="This is a diagram of a simple web server with step numbers for each of step of the client-server interaction." src="https://mdn.mozillademos.org/files/13829/Web%20Application%20with%20HTML%20and%20Steps.png" style="height: 584px; width: 1226px;"></p> + +<p>코치가 팀 이름과 플레이어의 수를 포함한 폼을 전송하면 작업 시퀀스는 다음과 같습니다:</p> + +<ol> + <li>웹 브라우저는 리소스에 대한 기본 URL을 사용하여 서버에 HTTP <code>GET</code> request을 생성합니다 (<code>/best</code>) , 그리고 팀 및 플레이어의 수를 URL인자로 인코딩하거나(예. <code>/best?team=my_team_name&show=11)또는 URL패턴</code> (예. <code>/best/my_team_name/11/</code>) 으로 인코딩 합니다. 이 요청은 데이터를 꺼내오는데에만 사용 하므로(데이터가 수정 되지 않음) GET request를 사용합니다. </li> + <li>웹 서버는 이 요청이 "동적"임을 감지하고 처리를 위해 웹 어플리케이션에 전달합니다(웹서버는 정의된 설정에 의한 패턴 매칭 방법에 기반한 다양한 URL들을 처리하는 방법을 결정 합니다).</li> + <li>웹 애플리케션은 이 요청의 대한 의도가 URL에 기반한 "최고의 팀 목록"을 얻는 것 인지 확인하고 (<code>/best/</code>) URL에서 필요한 팀 이름과 플레이어의 숫자를 찾아냅니다. 웹 어플리케이션은 데이터베이스를 통해 필요한 정보를 가져옵니다 (추가 "내부의" 인자들을 사용하여 어떤 플레이어가 "최고"인지 정의하고, 또한 클라이언트측 쿠키에서 로그인한 코치의 신분을 확인 할 수 있습니다).</li> + <li>웹 어플리케이션은 HTML template의 자리 표시 자에 데이터(데이터베이스를 통해)를 넣은 HTML 페이지를 동적으로 생성 합니다.</li> + <li>웹 어플리케이션은 생성된 HTML을 (웹 서버를 경유하여) 브라우저에 HTTP 상태코드 200 ("success")와 함께 반환합니다. 만약 어떤 것이 HTML을 반환 하는 것을 막았다면 웹 어플리케이션은 다른 코드를 반환할 것 입니다 —예를들자면 "404"는 팀이 존재 하지 않음을 지시합니다.</li> + <li>웹 브라우저가 반환한 HTML을 처리하고 참조하는 다른 CSS파일이나 Javascript 파일을 얻기위해 별도의 요청을 보냅니다(7단계를 보십시오).</li> + <li>웹 사이트는 파일 시스템에 있는 정적 파일을 로드하고 즉시 브라우저에 반환합니다(올바른 파일 처리는 설정 방법과 URL패턴 매칭을 기반으로 합니다).</li> +</ol> + +<p>데이터베이스에 업데이트를 기록하는 조작도 이와 비슷하게 처리합니다, 단 데이터베이스 업데이트는 브라우저의 HTTP request가 <code>POST</code> request로 인코딩 되어야 합니다. </p> + +<h3 id="Doing_other_work">Doing other work</h3> + +<p>웹 어플리케이션의 일은 HTTP requests를 전달 받고 HTTP responses를 반환 하는 것 입니다. 데이터베이스와 상호작용 하여 정보를 얻어오거나 업데이트를 하는것은 매우 평범한 작업이지만 코드는 같은 시간에 다른 것들을 하거나 데이터 베이스와 상호작용을 하지 않을 수 있습니다.</p> + +<p>웹 어플리케이션의 수행할 수 있는 추가적인 작업에 대한 좋은 예는 사용자가 사이트에 등록 되어있는지 확인 하기위해 이메일을 보내는 것 입니다. 사이트는 로깅 또는 다른 작업을 수행 할 수 있습니다. </p> + +<h3 id="Returning_something_other_than_HTML">Returning something other than HTML</h3> + +<p>서버측 웹사이트 코드는 HTML 스니펫/파일만을 반환하지는 않습니다. 그 대신 다양한 파일들(text, PDF, CSV, etc.) 과 데이터(JSON, XML, etc.)를 동적으로 생성하여 반환 할 수 있습니다.</p> + +<p>({{glossary("AJAX")}})를 동적으로 업데이트 할 수 있도록 데이터를 웹 브라우저로 반환하는 아이디어는 꽤 오래있었습니다. 최근에는 "단일 페이지 앱" 인기가 있기 사작하였고, 전체의 웹 사이트는 필요한 경우 동적으로 업데이트되는 단일 HTML페이지로 작성 되었습니다. 이 스타일의 애플리케이션을 사용하여 만든 웹 사이트는 서버에서 해야 할 계산을 웹 브라우저로 이동시켜 웹 사이트가 기본 애플리케이션처럼 작동하는 것처럼 보일 수 있습니다 (반응이 빠른, 기타 등등.).</p> + +<h2 id="Web_frameworks_simplify_server-side_web_programming">Web frameworks simplify server-side web programming</h2> + +<p>서버측 웹 프레임 워크는 위에서 설명한 작업을 훨씬 쉽게 처리할 수 있는 코드를 제공합니다.</p> + +<p>그들이 수행하는 가장 중요한 작업 중 하나는 다양한 리소스 / 페이지에 대한 URL을 특정 처리기 기능에 매핑하는 간단한 메커니즘을 제공하는 것입니다. 이는 분리된 리소스의 각각 타입들이 관련된 코드를 보관하는 것을 쉽게 만들어 줍니다. 또한 유지 보수 측면에서도 이점이 있습니다, 핸들러 기능을 변경하지 않고도 한 곳에서 특정 기능을 제공하는 데 사용되는 URL을 변경할 수 있기 때문입니다.</p> + +<p>예를 들어, 2개의 뷰 함수를 2개의 URL에 매핑하는 다음의 Django (Python)코드를 보면. 첫 번째 패턴은 리소스 URL이 / best 인 HTTP 요청이 views 모듈의 index () 함수에 전달되도록합니다. "/ best / junior"패턴을 가진 요청은 대신 junior ()보기 기능으로 전달됩니다.</p> + +<pre class="brush: python"># file: best/urls.py +# + +from django.conf.urls import url + +from . import views + +urlpatterns = [ + # example: /best/ + url(r'^$', views.index), + # example: /best/junior/ + url(r'^junior/$', views.junior), +]</pre> + +<div class="note"> +<p><strong>Note</strong>: url () 함수의 첫 번째 매개 변수는 "정규 표현식"(RegEx 또는 RE)이라는 패턴 일치 기술을 사용하기 때문에 조금 이상하게 보일 수 있습니다 (예 : r' ^ junior / $ '). 위의 하드 코딩 된 값 대신 URL에서 패턴을 일치시키고 뷰 기능에서 매개 변수로 사용할 수있는 것 외에는 정규 표현식이 어떻게 작동하는지 알 필요가 없습니다. 예를 들어, 정말 간단한 RegEx는 "하나의 대문자와 일치하고 4 ~ 7 자의 소문자가옵니다"라고 말할 수 있습니다</p> +</div> + +<p>웹프레임 워크는 뷰 함수가 데이터베이스안의 정보를 가져오는 것 또한 쉽게 만들어 줍니다. 데이터 구조는 기본 데이터베이스에 저장할 필드를 정의하는 Python 클래스 인 모델에서 정의됩니다. "team_type"필드를 가진 Team이라는 모델이 있다면 간단한 쿼리 구문을 사용하여 특정 유형의 모든 팀을 되 찾을 수 있습니다.</p> + +<p>아래 예제는 정확한 (대소 문자 구분) <code>team_type</code>이 "junior"인 모든 팀의 목록을 가져옵니다. - 필드 이름 (<code>team_type</code>)과 두 번째 밑줄, 그리고 사용할 일치 유형 (이 경우 <code>정확한</code>). 다른 많은 유형의 경기가 있으며 데이지 체인 방식으로 경기를 진행할 수 있습니다. 우리는 또한 반환 된 결과의 순서와 수를 제어 할 수 있습니다. </p> + +<pre class="brush: python">#best/views.py + +from django.shortcuts import render + +from .models import Team + + +def junior(request): + list_teams = Team.objects.filter(team_type__exact="junior") + context = {'list': list_teams} + return render(request, 'best/index.html', context) +</pre> + +<p><code>junior()</code> 함수가 주니어 팀 목록을 얻은 후에 원래 <code>HttpRequest</code>, HTML 템플리트 및 템플리트에 포함될 정보를 정의하는 "컨텍스트"객체를 전달하여<code>render()</code> 함수를 호출합니다. <code>render()</code>함수는 컨텍스트와 HTML 템플릿을 사용하여 HTML을 생성하고 <code>HttpResponse</code>객체에 반환하는 편리한 함수입니다.</p> + +<p>분명히 웹 프레임 워크는 많은 다른 작업을 도와 줄 수 있습니다. 우리는 다음 기사에서 더 많은 이점과 일부 대중적인 웹 프레임 워크 선택에 대해 논의합니다.</p> + +<h2 id="Summary">Summary</h2> + +<p>이 시점에서 서버 측 코드가 수행해야하는 작업을 잘 살펴보고 서버 측 웹 프레임 워크가 이를 쉽게 수행 할 수있는 몇 가지 방법을 알고 있어야합니다.</p> + +<p>다음 모듈에서는 당신이 첫 번째 사이트에 가장 적합한 웹 프레임 워크를 선택할 수 있도록 도와 드리겠습니다</p> + +<p>{{PreviousMenuNext("Learn/Server-side/First_steps/Introduction", "Learn/Server-side/First_steps/Web_frameworks", "Learn/Server-side/First_steps")}}</p> diff --git a/files/ko/learn/server-side/first_steps/index.html b/files/ko/learn/server-side/first_steps/index.html new file mode 100644 index 0000000000..a8faa4352b --- /dev/null +++ b/files/ko/learn/server-side/first_steps/index.html @@ -0,0 +1,39 @@ +--- +title: Server-side website programming first steps +slug: Learn/Server-side/First_steps +translation_of: Learn/Server-side/First_steps +--- +<div>{{LearnSidebar}}</div> + +<p>서버사이드 프로그래밍 모듈에서 우리는 서버사이드 프로그래밍에 대해 몇 가지 근본적인 질문을 합니다. — "그게 뭐야?", "클라이언트 사이드 프로그래밍과 뭐가 달라?", "왜 쓸만해?". 여기서 우리는 여러분의 첫 웹사이트를 만드는 데에 필요한 가장 적합한 프레임워크를 어떻게 정하는 지에 대한 적절한 지도와 함께 가장 인기있는 서버 사이드 웹 프레임워크들의 개요를 제공합니다. 끝으로 높은 수준의 웹 서버 보안에 대한 소개를 제공합니다.</p> + +<h2 id="전제_조건">전제 조건</h2> + +<p>이 모듈을 시작하기 전에, 서버사이드 프로그래밍이나 혹은 사실상 다른 어떠한 프로그래밍 지식도 필요 하지 않습니다.</p> + +<p>"웹이 어떻게 동작하는가" 를 이해해야 합니다. 다음의 토픽들을 우선 읽어보는 것을 추천합니다.</p> + +<ul> + <li><a href="/en-US/docs/Learn/Common_questions/What_is_a_web_server">웹 서버란 무엇인가 (What is a web server</a>)</li> + <li><a href="/en-US/docs/Learn/Common_questions/What_software_do_I_need">웹사이트를 만들기 위해서는 어떤 소프트웨어가 필요한가 (What software do I need to build a website?</a>)</li> + <li><a href="/en-US/docs/Learn/Common_questions/Upload_files_to_a_web_server">어떻게 웹 서버에 파일을 업로드 하는가 (How do you upload files to a web server?</a>)</li> +</ul> + +<p>이러한 기본적인 이해가 있다면, 이 섹션의 모듈들을 공부할 준비가 되었습니다.</p> + +<h2 id="가이드">가이드</h2> + +<dl> + <dt>서버 사이드에 대한 소개 (<a href="https://developer.mozilla.org/ko/docs/Learn/Server-side/First_steps/Introduction">Introduction to the server side</a>)</dt> + <dd>MDN의 초급자 서버 사이드 프로그래밍 코스에 오신 것을 환영합니다! 첫 글에서는, '서버사이드란 무엇인가', '클라이언트 사이드 프로그래밍과 어떤 점이 다른가', 그리고 '왜 유용한가' 와 같은 질문들에 답하며 서버 사이드 프로그래밍을 상위 단계부터 살펴봅니다. 이 글을 읽고 난 후, 서버 사이드 코딩을 통해 웹사이트에 추가적인 작업들을 할 수 있다는 것을 이해하게 될 것입니다.</dd> + <dt>클라이언트 - 서버 개요 (<a href="https://developer.mozilla.org/ko/docs/Learn/Server-side/First_steps/Client-Server_overview">Client-Server overview</a>)</dt> + <dd>당신이 서버 사이드 프로그래밍의 목적과 잠재적인 이점에 대해 알고 있기 때문에, 이제 서버가 브라우저로 부터 "동적 요청"을 받으면 어떤 일이 일어나는 지를 자세히 알아보고자 합니다. 대부분의 웹사이트 서버 사이드 코드는 요청 및 응답을 비슷한 방식으로 다루기 때문에, 이것은 당신이 코드를 짤 때 무엇을 해야 하는지 이해하도록 도울 것입니다.</dd> + <dt>서버 사이드 웹 프레임워크 (<a href="/en-US/docs/Learn/Server-side/First_steps/Web_frameworks">Server-side web frameworks</a>)</dt> + <dd>마지막 글은 서버 사이드 웹 어플리케이션이 웹 브라우저의 요청에 응답하기 위해 무엇을 해야하는 지를 보여줍니다. 이제 웹 프레임워크가 어떻게 이러한 일들을 간소화 하는지 보여주고, 당신이 첫 서버 사이드 웹 어플리케이션에 맞는 프레임워크를 선택하도록 도울 것입니다. </dd> + <dt>웹사이트 보안 (<a href="/en-US/docs/Learn/Server-side/First_steps/Website_security">Website security</a>)</dt> + <dd>웹사이트 보안은 웹사이트 설계 및 이용의 모든 측면에서 주의를 필요로 합니다. 이 입문 글이 당신을 웹사이트 보안 전문가로 만들어주지는 않지만, 가장 일반적인 위협들로 부터 웹 어플리케이션을 튼튼하게 만드는 첫 중요한 단계들을 이해하게끔 도울 것입니다. </dd> +</dl> + +<h2 id="평가">평가</h2> + +<p>이 "개요" 모듈은 아직 어떠한 코드도 보여주지 않았기 때문에 어떠한 평가도 없습니다. 우리는 현 시점에서, 당신이 서버 사이드 프로그래밍을 통해 어떤 기능들을 보여줄 수 있는지 제대로 이해하고, 당신의 첫 웹사이트를 만드는데에 어떤 서버 사이드 웹 프레임 워크를 사용할 지 결정했기를 바랍니다.</p> diff --git a/files/ko/learn/server-side/first_steps/introduction/index.html b/files/ko/learn/server-side/first_steps/introduction/index.html new file mode 100644 index 0000000000..aa635c6d37 --- /dev/null +++ b/files/ko/learn/server-side/first_steps/introduction/index.html @@ -0,0 +1,184 @@ +--- +title: Introduction to the server side +slug: Learn/Server-side/First_steps/Introduction +tags: + - 서버 + - 서버측 프로그래밍 + - 초보자 +translation_of: Learn/Server-side/First_steps/Introduction +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps")}}</div> + +<p class="summary">MDN의 초심자용 서버측 프로그래밍 코스에 오신 것을 환영합니다! 첫번째 기사는 높은 수준의 서버측 프로그래밍을 살펴보고, "이게 뭐야?" , "클라이언트 측 프로그래밍과 어떻게 다르지?", 그리고 "이게 왜 유용한데?"같은 질문에 답할 것입니다. 이 기사를 읽은 뒤 당신은 웹 사이트가 서버측 코딩을 통해 이용 할 수 있는 추가적인 효과를 이해 할 수 있을것 입니다.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">선수 과목:</th> + <td>기본적인 컴퓨터 활용법. 웹 서버가 무엇인지에 대한 기본적인 이해.</td> + </tr> + <tr> + <th scope="row">목적:</th> + <td>서버 측 프래그래밍 무엇이고 어떤 일을 할 수 있는지 그리고 클라이언트 프로그래밍과 어떤점이 다른지에 대해 익숙해 지기.</td> + </tr> + </tbody> +</table> + +<p>대부분의 큰 사이트들은 동적으로 보여주기 위한 다양한 데이터가 필요 할 때 서버측 코드를 사용합니다, 일반적으로 서버에있는 데이터베이스에 저장된 데이터를 빼내어 일부 코드를 통해 보일 수 있도록 클라이언트에게 송신합니다(예: HTML 그리고 JavaScript). 아마도 서버측 코드의 큰 장점은 개별 사용자를 위한 맞춤 웹사이트 컨텐츠를 제공 한다는 것 입니다. 동적 사이트는 사용자의 선호도 및 습관에 따라 더 관련성이 높은 콘텐츠를 강조 표시 할 수 있습니다. 또한 이것은 사이트를 저장된 개인 선호와 정보를 사용하기 쉽게 만들어 줍니다 — 예를 들면 후속 지급을 간소화 하기 위한 저장된 신용카드 정보의 재사용. 심지어 사이트 외부 사용자와의 상호 작용을 허용하고 전자 메일 또는 다른 채널을 통해 알림 및 업데이트를 보냅니다. 이러한 모든 기능을 통해 사용자와의 관계를 더욱 깊게 할 수 있습니다.</p> + +<p>현대의 전 세계 웹 개발자들은 서버측 개발을 공부하는 것을 권고 하고 있습니다.</p> + +<h2 id="서버측_웹_사이트_프로그래밍이_무엇인가요">서버측 웹 사이트 프로그래밍이 무엇인가요?</h2> + +<p><a href="/en-US/docs/Learn/Common_questions/What_is_a_web_server">web servers</a>와 통신하는 웹 브라우저는 <strong>H</strong>yper<strong>T</strong>ext <strong>T</strong>ransport <strong>P</strong>rotocol ({{glossary("HTTP")}})을사용하고 있습니다. 당신이 웹 페이지의 링크를 클릭하거나 폼을 전송하거나 검색을 시작할 때 당신의 웹 브라우저는<strong> HTTP request</strong>를 목적 서버에 전달합니다. 요청에는 영향을받는 리소스를 식별하는 URL, 필요한 작업을 정의하는 메서드가 포함됩니다 (예를 들면 리소스를 가져 오거나, 삭제하거나 게시하는 방법), 그리고 URL매개변수(<a href="https://en.wikipedia.org/wiki/Query_string">query 문자열</a>을 통해 전송된 필드 값-쌍), POST 데이터 (<a href="/en-US/docs/Web/HTTP/Methods/POST">HTTP POST method</a>에 의해 전송된 데이터), 또는{{glossary("Cookie", "associated cookies")}}를 이용하여 인코딩된 추가 정보를 포함 할 수 있습니다.</p> + +<p>웹 서버는 클라이언트의 요청이 오길 기다리고, 요청이 도착하면 작업을 진행하여, 웹 브라우저에 <strong>HTTP 응답</strong> 메시지를 보냅니다. 그 응답은 요청이 성공 또는 실패를 지시하는 상태 라인을 포함하고 있습니다 (예: "HTTP/1.1 200 OK" for success). 요청에 대한 응답이 성공적이라면 본문은 요청 리소스(예. 새로운 HTML 페이지, 또는 이미지, 기타 등등...)를 포함 할 것이며 이는 웹 브라우저에 보여질 수 있습니다.</p> + +<h3 id="정적_웹_사이트(Static_sites)">정적 웹 사이트(Static sites)</h3> + +<p><em>아래의 다이어그램은 정적사이트의 기본적인 웹 구조를 보여주고 있습니다</em>(정적 사이트는 특별한 리소스 요청이 들어올 때 서버에서 하드 코딩된 동일한 콘텐츠를 반환합니다). 사용자가 페이지를 탐색하거나, 브라우저가 지정된 URL에 HTTP "GET"요청을 보낼 때 서버는 파일 시스템에서 요청한 문서를 검색하고 문서와 <a href="/en-US/docs/Web/HTTP/Status#Successful_responses">success status</a> (보통 200 OK)를 포함한 HTTP응답을 반환 합니다. 만약 어떠한 이유 때문에 파일을 검색하면 error status(<a href="/en-US/docs/Web/HTTP/Status#Client_error_responses">client error responses</a> 그리고 <a href="/en-US/docs/Web/HTTP/Status#Server_error_responses">server error responses</a>를 참고 하십시오)가 반환됩니다.</p> + +<p><img alt="A simplified diagram of a static web server." src="https://mdn.mozillademos.org/files/13841/Basic%20Static%20App%20Server.png" style="height: 223px; width: 800px;"></p> + +<h3 id="동적_웹_사이트(Dynamic_sites)">동적 웹 사이트(Dynamic sites)</h3> + +<p>동적 웹 사이트는 필요할 때에 동적으로 응답 콘텐츠가 생성 됩니다. 동적 웹사이트의 웹 페이지는 보통 HTML 템플릿에 있는 자리 표시자에 데이터베이스에서 가져온 데이터를 넣어 생성 됩니다 (이 방법은 많은 양의 콘텐츠를 저장하기에 정적 웹 사이트를 이용 하는 것 보다 효과적 입니다). 동적 웹사이트는 사용자또는 저장된 환경을 기반으로 URL에 대해 다른 데이터를 반환 할 수 있으며, 응답을 반환하는 과정에서 다른 작업을 수행 할 수 있습니다(예: 알림 보내기).</p> + +<p>동적 웹사이트를 지원하는 코드는 서버에서 실행 되어야 합니다. 이러한 코드를 만드는 것은 "<strong>server-side programming</strong>"이라고 알려져 있습니다 (또는 "<strong>back-end scripting</strong>"이라고 불리기도 합니다).</p> + +<p>아래의 다이어그램은 동적 웹 사이트의 간단한 구조를 보여주고 있습니다. 이전 다이어 그램과 같이, 브라우저는 HTTP 요청을 서버에 보내고, 서버는 요청을 처리하고 적절한 HTTP응답을 반환합니다. 정적 리소스의 요청은 정적 사이트와 같은 방법으로 처리 합니다(정적 파일은 변하지 않는 파일입니다 — 전형적으로: CSS, JavaScript, Images, pre-created PDF files 등등). </p> + +<p><img alt="A simplified diagram of a web server that uses server-side programming to get information from a database and construct HTML from templates. This is the same diagram as is in the Client-Server overview." src="https://mdn.mozillademos.org/files/13839/Web%20Application%20with%20HTML%20and%20Steps.png"></p> + +<p>동적 리소스를 위한 요청은 (2) 서버측 코드에 대신 전달 됩니다(<em>다이어그램에서 </em> <em>Web Application</em>으로 보이는 부분). "동적 응답"을 위해 서버는 응답을 해석하여 필요한 정보를 데이터 베이스에서 읽고(3), 탐색한 데이터와 HTML템플릿을 결합하고(4), 생성된 HTML을 포함한 응답을 다시 보내줍니다(5,6). </p> + +<div> +<h2 id="서버측과_클라이언트측의_프로그래밍은_같은가요">서버측과 클라이언트측의 프로그래밍은 같은가요?</h2> +</div> + +<p>다시 돌아와서 서버측에 관여하는 코드와 클라이언트 측에 관여하는 코드를 살펴 봅시다. 각각의 케이스마다 코드는 명확히 다릅니다:</p> + +<ul> + <li>그들은 관심과 목적이 서로 다릅니다.</li> + <li>일반적으로 같은 프로그래밍 언어를 쓰지 않습니다( JavaScript는 예외적으로 서버와 클라이언트측 둘다 사용 할 수 있습니다).</li> + <li>그들은 다른 운영체제 환경에서 수행 됩니다.</li> +</ul> + +<p>브라우저에서 실행되는 코드는 <strong>client-side code</strong>라고 알려져 있습니다. <strong>client-side code</strong>의 주 관심사는 렌더링된 웹페이지의 모양과 행동을 개선시키는 것 입니다. 이것은 UI 구성 요소 선택 및 스타일 지정, 레이아웃 만들기, 탐색, 양식 유효성 검사 등을 포함 하고 있습니다. 대조적으로, server-side 웹 사이트 프로그래밍은 대부분 브라우저의 요청에 대한 응답으로 어떤 컨텐츠를 반환하는지 선택하는것을 포함 합니다. Server-side code는 제출 된 데이터 및 요청의 유효성 검사, 데이터 저장 및 검색을위한 데이터베이스 사용, 필요에 따라 올바른 데이터 전송과 같은 작업을 처리합니다.</p> + +<p>클라이언트 측 코드는 <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, 그리고 <a href="/en-US/docs/Learn/JavaScript">JavaScript</a>로 작성됩니다— 이것들은 웹 브라우저 안에서 실행되고 기본운영체제와 연결되지 않거나 아주 약간 연결 됩니다(파일 시스템의 연결의 제한이 포함 되어 있습니다). 웹 개발자는 모든 사용자가 웹 사이트를 보는 데 사용할 수있는 브라우저를 조작 할 수 없습니다 —브라우저는 클라이언트 측 코드 기능과 일관성없는 수준의 호환성을 제공하며, 클라이언트 측 프로그래밍의 어려움은 브라우저 지원의 차이를 정상적으로 처리 하는 것 입니다.</p> + +<p>서버측 코드는 다양한 프로그래밍 언어로 작성 될 수 있습니다 — 대중적인 서버측 웹 언어를 포한 한 예로 PHP, Python, Ruby 그리고 C#. 서버측 코드는 서버의 운영체제와 모든 접속권한을 가지며, 개발자는 그들이 원하는 프로그래밍 언어(그리고 특정 버전)를 사용할 수 있습니다.</p> + +<p>개발자는 일반적으로 <strong>web frameworks</strong>를 이용하여 코드를 작성 합니다. 웹 프레임 워크는 일반적인 문제를 해결하고 개발 속도를 높이며 특정 도메인에서 직면하는 다양한 유형의 작업을 단순화하도록 설계된 함수, 객체, 규칙 및 기타 코드 구성 요소의 모음입니다. 다시 말하지만 클라이언트와 서버 측 코드 모두 프레임 워크를 사용하지만 도메인은 매우 다르므로 프레임 워크도 다릅니다. 클라이언트 측 웹 프레임 워크는 레이아웃 및 프리젠 테이션 작업을 단순화하는 반면 서버 측 웹 프레임 워크는 직접 구현해야하는 많은 "공통"웹 서버 기능을 제공합니다(예: 세션 지원, 사용자와 인증을 지원, 데이터베이스와 쉬운 연결, 템플리트 라이브러리, 기타 등등.).</p> + +<div class="note"> +<p><strong>Note</strong>: 클라이언트 측 프레임워크는 때때로 클라이언트측 코드를 개발하는 속도를 올릴 수 있게 도와주도록 사용하지만, 당신은 모든 코드를 직접 작성 할 수도 있습니다; 사실은 당신이 작고, 간단한 사이트의 UI를 만든다면 당신이 직접 작성하는 코드가 더 빠르고 효과적일수 있습니다. 이와 대조적으로, 당신은 서버측 웹 앱의 컴포넌트를 프레임 워크 없이 작성 하는것은 거의 고려 하지 않을 것 입니다 — 파이썬에서 HTTP 서버와 같은 중요한 기능구현을 처음부터 하는 것은 어렵지만 Django와 같은 Python 웹 프레임 워크는 다른 유용한 도구와 함께 즉시 사용할 수있는 도구를 제공합니다.</p> +</div> + +<div> +<h2 id="서버측에서_무엇을_할_수_있나요">서버측에서 무엇을 할 수 있나요?</h2> + +<p>서버 측 프로그래밍은 개별 사용자에 맞게 정보를 효율적으로 전달할 수있게 해주고 사용자 경험을 훨씬 향상시킬 수 있기 때문에 매우 유용합니다.</p> +</div> + +<p>Amazon과 같은 회사는 서버 측 프로그래밍을 사용하여 제품 검색 결과를 작성하고 고객 선호도 및 이전 구매 습관을 기반으로 한 제품 제안, 구매 단순화 등을 수행합니다. 은행은 고객 정보를 저장하고 권한이있는 사용자만 보고 거래 할 수 있도록 서버 측 프로그래밍을 사용합니다. Facebook, Twitter, Instagram 및 Wikipedia와 같은 기타 서비스는 서버 측 프로그래밍을 사용하여 재미있는 컨텐츠에 대한 액세스를 강조, 공유 및 제어합니다.</p> + +<p>서버 측 프로그래밍의 일반적인 용도와 이점은 다음과 같습니다. 겹치는 부분이 있음을 알 수 있습니다!</p> + +<h3 id="효율적인_저장소와_정보_전달">효율적인 저장소와 정보 전달</h3> + +<p>Amazon에서 얼마나 많은 제품을 사용할 수 있는지 상상해보십시오. Facebook에 얼마나 많은 게시물이 작성되었는지 상상해보십시오. 각 제품 또는 게시물에 대해 별도의 정적 페이지를 만드는 것은 완전히 비효율적입니다.</p> + +<p>서버 측 프로그래밍을 사용하면 정보를 데이터베이스에 저장하고 HTML 및 기타 유형의 파일(예: PDF, 이미지, etc.)을 동적으로 생성하고 반환 할 수 있습니다. 또한 적절한 클라이언트 웹 프레임워크로 렌더링하기 위해 간단한 데이터({{glossary("JSON")}}, {{glossary("XML")}}, etc.)도 반환 할 수 있습니다 (이렇게하면 서버의 처리 부담과 전송해야하는 데이터의 양이 줄어 듭니다.).</p> + +<p>서버는 데이터베이스에서 정보를 전송하는 것에 국한되지 않고, 소프트웨어 도구의 결과 또는 통신 서비스의 데이터를 반환 할 수도 있습니다. 콘텐츠는 수신중인 클라이언트 장치의 유형을 대상으로 할 수도 있습니다.</p> + +<p>왜냐하면 데이터베이스의 정보는 다른 비즈니스 시스템에 의해 간단히 공유되고 업데이트 될 수 있기 때문입니다(예를 들어, 제품이 온라인 또는 상점에서 판매 될 때, 상점은 재고 데이터베이스를 갱신 할 수 있습니다).</p> + +<div class="note"> +<p><strong>Note</strong>: 효율적인 저장 및 정보 전달을위한 서버 측 코드의 이점을보기 위해 상상력을 집중하지 않아도됩니다:</p> + +<ol> + <li>Amazon 또는 기타 전자 상거래 사이트로 이동하십시오.</li> + <li>여러 개의 키워드를 검색하고 결과가 나타나더라도 페이지 구조가 변하지 않는지 유의 하십시오</li> + <li>2 개 또는 3 개의 다른 제품을 엽니 다. 공통된 구조와 레이아웃을 갖는지에 대해 다시 유의하십시오. 다른 제품의 내용은 데이터베이스에서 가져 왔습니다.</li> +</ol> + +<p>일반적인 검색 용어 ( "fish", say)의 경우 문자 그대로 수백만 개의 반환 값을 볼 수 있습니다. 데이터베이스를 사용하면 이러한 정보를 효율적으로 저장하고 공유 할 수 있으며 정보의 표시를 한 곳에서만 제어 할 수 있습니다.</p> +</div> + +<h3 id="맞춤형_사용자_경험(user_experience)">맞춤형 사용자 경험(user experience)</h3> + +<p>서버는 사용자에게 편안함과 맞춤형 사용자 경험을 제공하기 위해 사용자의 정보를 사용하거나 저장 할 수 있습니다. 예를 들어 많은 사이트들은 신용카드 세부 정보를 저장하므로 그것을 다시 입력 할 필요가 없습니다. Google Maps와 같은 사이트는 라우팅 정보를 제공하고 검색 결과에서 지역 비즈니스를 강조 표시하기 위해 집과 현재 위치를 사용합니다.</p> + +<p>사용자의 습관에 대한 면밀한 분석은 그들의 흥미를 예측하거나 사용자 맞춤 응답과 알림을 주는데에 사용 될 수 있습니다, 예를 들어 이전에 방문했거나 인기있는 위치 목록을지도에서 볼 수 있습니다.</p> + +<div class="note"> +<p><strong>Note</strong>: 익명의 사용자로 <a href="https://maps.google.com/">Google Maps</a>로 이동하고 <strong>길 찾기</strong> 버튼을 선택한 다음 여행의 시작 및 종료 지점을 입력하십시오. 이제 Google 계정을 사용하여 시스템에 로그인하십시오 (이 과정에 대한 정보는 아래에서 방향을 선택하는 패널에 나타납니다). 이제 웹 사이트에서 시작 지점과 종료 지점으로 집과 직장 위치를 선택할 수 있습니다 (아직 완료하지 않았다면이 세부 정보를 저장하십시오).</p> +</div> + +<h3 id="컨텐츠에_대한_접근_제한">컨텐츠에 대한 접근 제한</h3> + +<p>서버측 프로그래밍을 통해 사용자만 접근 할 수 있도록 제한하거나 사용자가 볼수 있는 정보만 제공 할 수 있습니다.</p> + +<p>실제 사례는 다음과 같습니다:</p> + +<ul> + <li>Facebook과 같은 소셜 네트워크는 사용자가 자신의 데이터를 완벽하게 제어 할 수 있도록 허용하지만 친구 만 보거나 댓글을 달 수 있습니다. 사용자는 자신의 데이터를 볼 수있는 사용자를 결정하고, 확장자별로 피드에 데이터가 표시됩니다 — 승인은 사용자 환경의 핵심 부분입니다!</li> + <li> + <p>현재 당신이 보고 있는 이 사이트는 모든 사람들이 기사를 볼 수 있지만 로그인 한 사용자만 내용을 편집 할 수 있습니다. 이를 시도하려면 이 페이지 상단의 편집 버튼을 클릭하십시오 - 로그인되어있는 경우 편집보기가 표시됩니다. 로그인하지 않으면 가입 페이지로 이동합니다.</p> + </li> +</ul> + +<div class="note"> +<p><strong>Note</strong>: 콘텐츠에 대한 액세스가 제어되는 다른 실제 사례를 고려하십시오. 예를 들어 은행의 온라인 사이트로 이동하면 무엇을 볼 수 있습니까? 계정에 로그인 - 어떤 추가 정보를보고 수정할 수 있습니까? 은행만 변경 될 수 있음을 알 수 있는 정보는 무엇입니까?</p> +</div> + +<h3 id="세션과_상태_저장">세션과 상태 저장</h3> + +<p>개발자는 서버 프로그래밍을 사용하여<strong> sessions</strong>을 만들 수 있습니다 — 서버가 현재 사이트의 사용자 정보를 저장하거나 정보에 기반한 다른 응답을 보낼 수 있는 기본적인 메커니즘 입니다. 예를 들어, 사이트에서 사용자가 이전에 로그인하여 이메일 또는 주문 내역에 대한 링크를 표시하거나 간단한 게임의 상태를 저장하여 사용자가 사이트를 다시 이용할 때 떠났던 부분부터 다시 할 수 있습니다.</p> + +<div class="note"> +<p><strong>Note</strong>: 구독 모델이있는 신문 사이트를 방문하여 많은 수의 탭을 엽니다(예: <a href="http://www.theage.com.au/">The Age</a>). 몇 시간 / 일 동안 사이트를 계속 방문하십시오. 결국 구독 방법을 설명하는 페이지로 리디렉션되기 시작하고 기사에 액세스 할 수 없게됩니다. 이 정보는 쿠키에 저장된 세션 정보의 예 입니다.</p> +</div> + +<h3 id="알림과_대화">알림과 대화</h3> + +<p>서버는 일반적이거나 유저에 특화된 알림을 웹사이트 자신이나 이메일, SMS, 인스턴트 메시징, 비디오 대화, 또는 다른 통신 서비스를 통해 보낼 수 있습니다</p> + +<p>몇개의 예가 포함 되어 있습니다:</p> + +<ul> + <li>Facebook과 Twitter 당신의 새로운 연락에 대한 알림을 이메일이나 SMS로 보내 줍니다.</li> + <li>Amazon은 당신 관심을 가질만한 이미 구입했거나 본 제품과 유사한 제품을 제안하는 제품 전자 메일을 정기적으로 보냅니다.</li> + <li>웹서버는 사이트의 관리자에게 서버의 메모리가 부족하거나 의심스러운 활동을 하는 사용자가 있을때 메시지를 보내서 경고 할 수 있습니다.</li> +</ul> + +<div class="note"> +<p><strong>Note</strong>: 가장 일반적인 유형의 알림은 "등록 확인"입니다. 관심있는 큰 사이트 (Google, Amazon, Instagram 등)를 선택하고 이메일 주소를 사용하여 새 계정을 만드십시오. 귀하는 귀하의 등록을 확인하거나 귀하의 계정을 인증하기 위해 승인을 요구하는 이메일을 곧 받게 될 것입니다.</p> +</div> + +<h3 id="정보_분석">정보 분석</h3> + +<p>웹사이트는 사용자에대한 많은 정보를 수집할 수 있습니다: 그들이 뭘 검색하는지, 그들이 뭘 사는지, 그들이 뭘 추천하는지, 그들이 각 페이지 마다 얼마나 머무르는지. 서버 측 프로그래밍을 사용하여 데이터의 분석을 기반으로 응답을 구체화 할 수 있습니다.</p> + +<p>그 예로, Amazon과 Google둘다 전에 검색하였던 결과(및 구매)를 바탕으로 제품 광고를 합니다.</p> + +<div class="note"> +<p><strong>Note</strong>: Facebook 사용자인 경우 기본 피드로 이동하여 게시물 스트림을 확인하십시오. 일부 게시물의 숫자 순서가 어긋나는 것에 유의하십시오. 특히 "좋아요"가 많은 게시물은 최근 게시물보다 목록에서 더 자주 표시됩니다. 또한 어떤 광고가 게재 되고 있는지 살펴보십시오. 다른 사이트에서 봤던 광고를 볼 수 있습니다. 콘텐츠 및 광고를 강조하는 Facebook의 알고리즘은 약간의 수수께끼 일 수 있지만 좋아하는 것과 보는 습관에 달려 있다는 것은 분명합니다.</p> +</div> + +<h2 id="요약">요약</h2> + +<p>축하합니다, 당신은 첫번째 기사인 서버측 프로그래밍을 끝까지 읽으셨습니다. </p> + +<p>당신은 이제 서버측 코드가 웹 서버 위에서 실행되고 그것의 주 역할은 사용자에게 전달하는 정보를 컨트롤 한다는걸 알게 되었습니다(클라이언트 측 코드는 사용자에게 데이터의 구조와 표현을 처리합니다). 또한 사용자별로 맞춤 설정된 정보를 효율적으로 제공하고 서버 측 프로그래머 일 때 수행 할 수있는 작업에 대한 좋은 아이디어가있는 웹 사이트를 만들 수 있으므로 유용하다는 점도 이해해야합니다.</p> + +<p>마지막으로, 서버 측 코드는 여러 프로그래밍 언어로 작성 될 수 있으며 전체 프로세스를보다 쉽게하기 위해 웹 프레임 워크를 사용해야한다는 것을 이해해야합니다. </p> + +<p>향후 기사에서는 첫 번째 사이트에 가장 적합한 웹 프레임 워크를 선택할 수 있도록 도와 드리겠습니다. 다음은 주요 클라이언트 - 서버 상호 작용에 대해 좀 더 자세하게 설명하겠습니다.</p> + +<p>{{NextMenu("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps")}}</p> diff --git a/files/ko/learn/server-side/first_steps/web_frameworks/index.html b/files/ko/learn/server-side/first_steps/web_frameworks/index.html new file mode 100644 index 0000000000..24c252285a --- /dev/null +++ b/files/ko/learn/server-side/first_steps/web_frameworks/index.html @@ -0,0 +1,312 @@ +--- +title: Server-side web frameworks +slug: Learn/Server-side/First_steps/Web_frameworks +translation_of: Learn/Server-side/First_steps/Web_frameworks +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps/Website_security", "Learn/Server-side/First_steps")}}</div> + +<p class="summary">이전 기사에서는 웹 클라이언트와 서버 간의 통신 모습, HTTP 요청 및 응답의 성격, 서버 측 웹 애플리케이션이 웹 브라우저의 요청에 응답하기 위해 수행해야하는 작업에 대해 설명했습니다. 이러한 지식을 바탕으로, 지금 시간에는 웹 프레임 워크가 어떻게 그러한 작업을 간단히 만드는지 탐색하고, 당신의 첫 서버측 애플리케이션을 위한 프레임 워크를 어떻게 선택하는지 의견을 드리겠습니다.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td>기본적인 컴퓨터 활용법. HTTP 요청을 서버측 코드가 어떻게 다루고 응답하는지에 대한 높은 수준의 이해 (<a href="https://developer.mozilla.org/ko/docs/Learn/Server-side/First_steps/Client-Server_overview">Client-Server overview</a>를 참고 하십시오).</td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>웹 프레임 워크가 어떻게 서버측 코드를 개발/유지하기 간단하게 만들 수 있는지 이해하고 , 독자들이 그들의 개발에 어떤 프레임워크를 선택 할지에 대해 생각해보게 합니다.</td> + </tr> + </tbody> +</table> + +<p>이 섹션에서 실제 웹 프레임 워크에서 가져온 코드 조각을 설명할 것 입니다. 지금 <strong>전부</strong> 이해가 가지 않는것에 대해 당황하지 마세요; 우리는 프레임 워크 특정 모듈의 코드를 통해 작업 할 것입니다.</p> + +<h2 id="개요">개요</h2> + +<p>서버측 웹 프레임워크("웹 어플리케이션 프레임워크"라고 알려진)는 작성하기 쉽고, 웹 어플리케이션을 유지및 보수하기 쉽게 만드는 소프트웨어 프레임 워크입니다. 적절한 URL핸들러로 라우팅, 데이테베이스와 상호작용, 유저 인증과 세션 지원, 출력 형식(예: HTML, JSON, XML), 웹 공격에 대처하기 위한 보안 강화 같은 일반적인 웹 개발 작업을 단순화하는 도구와 라이브러리를 제공합니다.</p> + +<p>다음 섹션은 어떻게 웹 프레임워크가 웹 애플리케이션 개발을 쉽게 하는지 더 상세히 살펴 보겠습니다. 우리는 당신이 사용할 웹 프레임워크를 선택하는 기준을 설명하고 몇 가지 옵션을 나열하겠습니다.</p> + +<h2 id="웹_프레임워크는_무엇을_지원하는가">웹 프레임워크는 무엇을 지원하는가?</h2> + +<p>웹 프레임워크는 일반적인 웹 개발 작업을 단순화 하는 도구와 라이브러리를 제공합니다. 당신은 서버측 웹 프레임 워크를 사용하지 않을 수 있지만 이는 권고 되지 않습니다 — 서버측 웹 프레임워크를 사용하면 당신의 삶이 더 편해질 것입니다.</p> + +<p>이 섹션에서는 웹 프레임워크가 제공하는 몇몇 기능에 대해 논의 하겠습니다(모든 프레임 워크가 그 기능을 제공하지는 않습니다!)</p> + +<h3 id="Work_directly_with_HTTP_requests_and_responses">Work directly with HTTP requests and responses</h3> + +<p>우리가 마지막 기사에서 봤듯이 웹 서버와 브라우저는 HTTP protocol을 통해 통신합니다 — 서버는 브라우저에서 오는 HTTP요청을 기다리고, HTTP응답에 정보를 반환 합니다. 웹 프레임워크는 이러한 요청과 응답을 할 서버측 코드를 만드는데 작성할 문법을 단순화 합니다. 이것은 당신의 일과 상호작용을 쉽게 하고, 저수준 네트워킹 프리미티브보다 높은 수준의 코드를 이용한다는 것을 의미합니다.</p> + +<p>Django (Python) 웹 프레임워크가 어떻게 작동 하는지의 대한 예가 아래에 나와 있습니다. 모든 "view"함수는(요청 핸들러) 요청 정보가 포함된<code>HttpRequest</code>객체를 받고, 형식화된 출력(이번 케이스에선 string)이 있는<code>HttpResponse</code> 객체를 반환합니다.</p> + +<pre class="brush: python"># Django view function +from django.http import HttpResponse + +def index(request): + # Get an HttpRequest (request) + # perform operations using information from the request. + # Return HttpResponse + return HttpResponse('Output string to return') +</pre> + +<h3 id="Route_requests_to_the_appropriate_handler">Route requests to the appropriate handler</h3> + +<p>대부분의 사이트는 여러개의 다른 리소스를 특정된 URL을 통해 접근 할 수 있도록 제공합니다. 통합된 함수로 모든것을 처리하는건 유지하기가 매우 힘듭니다, 그래서 웹 프레임워크는 특별한 처리 함수로 URL패턴을 매핑하는 기능을 제공합니다. 이러한 접근 방법은 유지 보수 기간에 이점이 있습니다. 왜냐하면 기본 코드를 변경하지 않고도 특정 기능을 제공하는 데 사용되는 URL을 변경할 수 있기 때문입니다.</p> + +<p>각각의 프레임 워크들은 다른 매핑 메커니즘을 사용합니다. 예를 들면 Flask (Python) 웹 프레임 워크는데코레이터를 사용하여 view함수에 경로를 추가합니다.</p> + +<pre class="brush: python">@app.route("/") +def hello(): + return "Hello World!"</pre> + +<p>Django는 개발자가 URL 패턴과 뷰 함수 사이에 URL 매핑 목록을 정의 할 것을 기대합니다.</p> + +<pre class="brush: python">urlpatterns = [ + url(r'^$', views.index), + # example: /best/myteamname/5/ + url(r'^(?P<team_name>\w.+?)/(?P<team_number>[0-9]+)/$', views.best), +] +</pre> + +<h3 id="Make_it_easy_to_access_data_in_the_request">Make it easy to access data in the request</h3> + +<p>데이터는 다양한 방법으로 HTTP응답에 인코딩 될 수 있습니다. 서버에서 파일이나 데이터를 얻기 위한 HTTP GET 요청은 URL인자나 URL구조를 요구한 데이터를 인코딩 할 수 있습니다. 서버에 있는 리소스를 업데이트를 요청하는 HTTP <code>POST</code>는 요청 본문에 "POST data"로 업데이트 정보를 대신 포함합니다. 또한 HTTP 요청은 클라이언트 측 쿠키에서 현재 세션 또는 사용자에 관한 정보를 포함 할 수있습니다.</p> + +<p>웹 프레임 워크는 정보에 액세스하기위한 프로그래밍 언어에 적합한 메커니즘을 제공합니다. 예를 들어 Django가 모든 뷰 함수에 전달하는 HttpRequest 객체는 대상 URL, 요청 유형 (예 : HTTP GET), GET 또는 POST 매개 변수, 쿠키 및 세션 데이터 등에 접근 하기 위한 메소드 및 속성을 포함합니다. Django는 URL 매퍼에 "캡처 패턴"을 정의한 URL 구조로 인코딩 된 정보를 전달할 수도 있습니다 (위 섹션의 마지막 코드 단편 참조).</p> + +<h3 id="Abstract_and_simplify_database_access">Abstract and simplify database access</h3> + +<p>웹 사이트는 사용자와 사용자에 대한 정보 공유를 위한 데이터를 저장 하기 위해서 데이터베이스를 사용합니다. 웹 프레임 워크는 종종 데이터베이스 읽기, 쓰기, 쿼리, 삭제 조작을 추상화 할 수 있는 데이터베이스 계층을 제공 합니다. 이러한 추상 계층을 객체 관계형 매퍼(ORM)라고 합니다.</p> + +<p>ORM을 사용 하는 것은 2가지 장점이 있습니다:</p> + +<ul> + <li>사용하던 코드를 수정하지 않고 기본 데이터베이스를 바꿀 수 있습니다. 이것을 통해 개발자가 사용법에 따른 데이터베이스의 특성을 최적화 할 수 있습니다.</li> + <li>기본적인 데이터의 확인은 프레임워크 안에서 구현 될 수 있습니다. 이를 통해 올바른 타입의 데이터베이스 필드에 데이터를 저장하는지, 올바른 타입인지(예: 이메일 주소), 악의적인 방식 (크래커는 특정 코드 패턴을 사용하여 데이터베이스 레코드를 지우는 것과 같은 나쁜 행동을 할 수 있습니다)인지 쉽고 안전하게 확인 할 수 있습니다.</li> +</ul> + +<p>예를들어 Django 웹 프레임워크는 ORM을 제공하고 레코드 구조 모델로 정의하는데 사용한 객체를 참조합니다. 모델은 저장 될 필드 유형을 지정하며, 저장 될 수있는 정보에 대한 필드 레벨 검증을 제공 할 수 있습니다(예 : 이메일 입력란은 유효한 이메일 주소 만 허용). 또한 필드 정의는 최대 크기, 기본 값, 선택 목록 옵션, 문서를 위한 도움말, 양식 레이블 텍스트 등을 지정할 수도 있습니다. 모델은 코드와 별도로 변경 될 수있는 구성 설정이므로 기본 데이터베이스에 대한 정보는 명시하지 않습니다.</p> + +<p>첫번째 코드 스니펫은 아래에 보이는 매우 간단한 <code>Django</code> 모델인 <code>Team</code>객체 입니다. 이 객체는 팀 이름과 팀의 레벨을 문자 필드로 저장하고 각각의 레코드마다 최대 한도의 문자 길이를 저장 합니다. <code>team_level</code>은 선택 필드이므로 우리는 디폴트 값과 함께 표시할 선택 항목과 데이터를 저장하는 것 사이를 매핑하는 것을 제공합니다. </p> + +<pre class="brush: python">#best/models.py + +from django.db import models + +class Team(models.Model): + team_name = models.CharField(max_length=40) + + TEAM_LEVELS = ( + ('U09', 'Under 09s'), + ('U10', 'Under 10s'), + ('U11, 'Under 11s'), + ... #list our other teams + ) + team_level = models.CharField(max_length=3,choices=TEAM_LEVELS,default='U11') +</pre> + +<p>Django 모델은 데이터베이스 검색을 위한 간단한 쿼리를 제공 합니다. 다른 기준을 사용하여 한 번에 여러 필드와 일치시킬 수 있습니다. (예 : 대소 문자를 구분하지 않음,보다 큼, 등), 그리고 복잡한 명령문을 지원 할 수 있습니다 (예를 들어 당신이 "Fr"로 시작하거나 "al"로 끝나는 특별한 U11팀을 찾을 수 있습니다). </p> + +<p>두번째 코드 스니펫은 U09의 모든 팀을 보여주는 view function(요청 핸들러)을 보겠습니다. 이 경우 우리는 <code>team_level</code> 필드의 텍스트가 정확히 'U09'인 모든 레코드를 필터링하도록 지정합니다. ( 이 기준이 필드 이름과 일치 유형이 두 개의 밑줄로 구분 된 인수로 <code>filter()</code> 함수에 전달되는 방법을 아래에 기록하십시오. team_level__exact ).</p> + +<pre class="brush: python">#best/views.py + +from django.shortcuts import render +from .models import Team + +def youngest(request): + <strong>list_teams = Team.objects.filter(team_level__exact="U09")</strong> + context = {'youngest_teams': list_teams} + return render(request, 'best/index.html', context) +</pre> + +<dl> +</dl> + +<h3 id="Rendering_data">Rendering data</h3> + +<p>웹 프레임워크는 종종 템플릿 시스템을 제공합니다. 이것은 페이지가 생성될 때 데이터를 추가하기 위한 자리 표시 자를 사용하여 출력 문서 구조를 지정 할 수 있습니다. 템플릿들은 보통 HTML로 만들어지지만, 다른 형식의 문서로도 작성될 수 있습니다.</p> + +<p>웹 프레임워크는 보통 저장된 데이터를 다른 형식으로 쉽게 생성 할 수 있는,{{glossary("JSON")}}, {{glossary("XML")}}을 포함한, 틀을 제공합니다.</p> + +<p>예를들어, Django 템플릿 시스템은 구체화된 "double-handlebars" 구조 (예를 들어 <code>{</code><code>{ <em>variable_name</em> </code><code>}</code><code>}</code>)를 사용하도록 허용하는데, 이것은 페이지가 로딩될 때 뷰 함수의 값들로 대체될 수 있습니다. 템플릿 시스템은 또한 다양한 표현식을 지원하는데 (예를 들어 : <code>{% <em>expression</em> %}</code>), 템플리트가 템플리트에 전달 된 목록 값을 반복하는 것과 같은 간단한 조작을 수행 할 수 있습니다.</p> + +<div class="note"> +<p><strong>Note</strong>: 다른 대부분의 템플릿 시스템들은 비슷한 문법을 사용합니다, 예: Jinja2 (Python), handlebars (JavaScript), moustache (JavaScript), 등등.</p> +</div> + +<p>아래의 코드 스니펫은 그것이 어떻게 작동 하는지 보여줍니다. 이전 섹션에 사용한 "youngest team" 예제를 다시 보겠습니다, HTML 템플릿은 뷰에서 <code>youngest_teams</code>이라고 불리는 목록 변수를 전달 받습니다. HTML 골격 내에는 <code>youngest_teams</code>이 있는지 체크하는 표현식이 있고, 있다면 <code>for</code> 루프를 통해 반복문을 만드는 것을 볼 수 있습니다. 각 반복당 템플릿은 팀리스트에 있는 <code>team_name</code>을 출력해줍니다.</p> + +<pre class="brush: html">#best/templates/best/index.html + +<!DOCTYPE html> +<html lang="en"> +<body> + + {% if youngest_teams %} + <ul> + {% for team in youngest_teams %} + <li>\{\{ team.team_name \}\}</li> + {% endfor %} + </ul> +{% else %} + <p>No teams are available.</p> +{% endif %} + +</body> +</html> +</pre> + +<h2 id="웹프레임워크를_선택하는_방법">웹프레임워크를 선택하는 방법</h2> + +<p>여러분이 쓰고싶은 언어 대부분에 많은 웹 프레임워크들이 존재합니다(그중 보다 인기 있는 몇개의 프레임워크들을 다음 섹션에 나열합니다). 선택의 폭이 넓어지기 때문에 새로운 웹 애플리케이션을위한 최고의 출발점을 제공하는 프레임워크를 찾기가 어려울 수 있습니다.</p> + +<p>여러분의 선택에 영향을 줄 수 있는 요인은 다음과 같습니다.</p> + +<ul> + <li><strong>학습 노력 :</strong> 웹 프레임워크를 배우기 위한 노력은 당신이 얼마나 프로그래밍 언어에 익숙한지, 그 언어의 API가 일관성 있는지, 그 언어의 문서의 품질이 괜찮은지, 그리고 그 언어의 커뮤니티의 크기와 활동량에 따라 다릅니다. 만약 여러분이 아무런 프로그래밍 경험이 없다면, 장고를 고려해보세요(장고는 위의 기준에 맞는 가장 배우기 쉬운 프레임 워크입니다). 만약 여러분이 개발팀의 일원이며 이미 특정 웹프레임워크와 언어에 상당한 경험이 있다면, 그것에 충실하는 것이 좋습니다.</li> + <li><strong>생산성:</strong> 생산성은 프레임 워크에 익숙해지면 얼마나 빨리 새 기능을 만들 수 있는지 측정하는 척도이며 코드를 작성하고 유지 관리하는 노력(이전 기능이 손상된 상태에서 새 기능을 작성할 수 없기 때문에)을 모두 포함합니다. "학습 노력"에서 다루었던 요인들과 비슷한 많은 요인들이 생산성에 영향을 미칩니다. — 예를들어 문서, 커뮤니티, 프로그래밍 경험 등. — 다른 요인들: + <ul> + <li><em>프레임워크의 목적/출처</em>: 몇몇 웹 프레임워크들은 처음엔 특정 타입의 문제들을 풀기위해 만들어졌고, 여전히 비슷한 제약조건을 가진 웹앱을 만드는 데 좋습니다. 예를 들어 Django는 신문형식의 웹사이트 개발을 지원하기 위해 만들어졌기 때문에 블로그나 무언가를 출판하는 사이트들에 쓰기 좋습니다. 반대로 Flask는 모다 가벼운 무게의 프레임워크이고 임베디드 장치에서 실행되는 웹앱을 만드는데 좋습니다.</li> + <li><em>의도적인 vs 의도를 갖지 않는</em>: 의도적인 프레임워크는 특정유형의 문제를 풀기에 가장 좋은 방법으로 추천됩니다. 의도적인 프레임워크들은 일반적인 문제들을 풀려고 할 때 생산적으로 기능하기 때문에 여러분을 옳은 방향으로 이끌어주지만 유연성이 부족하기도 합니다.</li> + <li><em>배터리 포함 vs 직접 구매</em>: 몇몇 웹프레임워크는 개발자들이 "기본 적으로" 생각할 수 있는 모든 문제들을 해결하는 툴과 라이브러리를 포함합니다. 반면에 보다 가벼운 프레임웨크들은 웹개발자들이 직접 분리된 라이브러리들을 고르고 쓰기를 기대합니다. (Django는 전자의 예고 Flask는 아주 가벼운 프레임워크의 예입니다). 모든 것을 포함하는 프레임워크는 여러분이 필요한 모든 것을 이미 가지고 있고, 잘 병합/문서화되어있어 종종 시작하기에 더 쉽습니다. 그러나 더 작은 프레임 워크에 필요한 모든 것이 있다면 제약이 많은 환경에서 실행할 수 있으며 배우기에 더 작고 쉬울 것입니다. </li> + <li><em>프레임워크는 좋은 개발 관행을 장려 할 수 있습니다: </em>예를 들어, <br> + <a href="/en-US/docs/Web/Apps/Fundamentals/Modern_web_app_architecture/MVC_architecture">Model-View-Controller</a> 아키텍처를 권장하는 프레임 워크는 코드를 로직 함수로 분리하여 개발자에게 기대하지 않는 코드보다 더 유지 관리가 쉬운 코드가 생성됩니다. 비슷하게도, 프레임워크의 디자인은 테스트 하기 쉽고 재사용 하기 쉬운 코드를 만드는데에 큰 영향을 줄 수 있습니다.</li> + </ul> + </li> + <li><strong>프레임워크/프로그래밍 언어의 성능:</strong> 종종 "속도"는 선택의 중요한 한 요소가 되지 않습니다. 왜냐하면 Python과 같은 비교적 느린 런타임도 중간정도의 하드웨어에서 실행되는 중간 크기의 사이트에서 사용 하기에 "충분" 이상으로 좋기 때문입니다. 속도에 강점이 있는 언어들(예: C++ 또는 JavaScript)은 교육 비용이나 유지 비용에 의해 상쇄 될 수 있습니다.</li> + <li><strong>캐싱 지원:</strong> 웹 사이트가 더욱 성공적이 되면 당신은 웹 사이트에 액세스 할 때 수신하는 요청 수에 더 이상 대처할 수 없다는 것을 알게 될 것입니다. 이 시점에 캐싱 지원을 추가하는 것을 고려 할 수 있습니다. 캐싱은 웹 요청의 전체 또는 일부를 저장하여 후속 요청에서 다시 계산할 필요가 없도록하는 최적화 방법 입니다. 캐시된 요청에 대한 응답은 처음에 요청을 계산하는 것보다 훨씬 빠릅니다. 캐싱은 당신의 코드에 구현 하거나 서버에 구현 할 수 있습니다(<a href="https://en.wikipedia.org/wiki/Reverse_proxy">reverse proxy</a>를 참조하세요). 웹 프레임 워크는 캐시 할 수있는 컨텐츠를 정의하기 위해 서로 다른 레벨의 지원을 제공합니다.</li> + <li><strong>확장성:</strong> 웹 사이트가 환상적인 성공을하면 캐싱의 이점을 모두 활용하고 <em>수직 확장(</em>웹 애플리케이션을 보다 강력한 하드웨어에서 실행<em>)</em>의 한계에 도달 할 수 있습니다. 이 시점에서 일부 고객은 서버와 멀리 떨어져 있기 때문에 <em>수평 확장</em>(여러 웹 서버와 데이터베이스에 사이트를 분산 시켜서 부하를 공유)하거나 "지리적으로" 확장해야 할 수도 있습니다. 선택한 웹 프레임워크는 사이트 확장을 쉽게 하는지에 대해 큰 차이를 만들 수 있습니다.</li> + <li><strong>웹 보안:</strong> 어떤 웹 프레임워크는 일반적인 웹 공격 처리에 대한 더 나은 지원을 제공 합니다. 장고을 예를 들자면 사용자 입력에 들어간 모든 HTML 템플릿을 삭제하여 사용자가 작성한 JavaScript를 실행하지 못하게 합니다. 다른 프레임 워크도 유사한 보호 기능을 제공하지만 기본적으로 항상 활성화되어있는 것은 아닙니다.</li> +</ul> + +<p>라이센싱, 프레임워크가 활발하게 개발 중인지 여부 등 여러 가지 가능한 요소가 있습니다.</p> + +<p>프로그래밍을 처음 접하는 생초보라면 "학습 편의성"에 따라 프레임 워크를 선택하게 될 것입니다. 언어 자체의 "사용 편의성"외에도 고품질 문서 / 자습서 및 새로운 사용자를 돕는 활동적인 커뮤니티가 가장 소중한 리소스입니다. 우리는 <a href="https://www.djangoproject.com/">Django</a> (Python) 와 <a href="http://expressjs.com/">Express</a> (Node/JavaScript)를 선택하여 강의 후반에 예제를 작성했습니다. 배우기 쉽고 지원을 잘 받고 있기 때문입니다.</p> + +<div class="note"> +<p><strong>Note</strong>: <a href="https://www.djangoproject.com/">Django</a> (Python) 와 <a href="http://expressjs.com/">Express</a> (Node/JavaScript)의 메인 웹사이트로 가보십시오, 그리고 문서와 커뮤니티를 확인하십시오.</p> + +<ol> + <li>(위 링크들의) 메인 사이트를 둘러보기 + <ul> + <li>Documentation 메뉴에 링크들(Documentation, Guide, API Reference, Getting Started등)을 클릭해보십시오.</li> + <li>URL routing, templates, and databases/models등을 설정하는 주제들이 보이십니까?</li> + <li>해당 문서들은 명료하게 작성이 되어있습니까?</li> + </ul> + </li> + <li>각각의 사이트에서 mailing lists(해당 커뮤니티의 링크들을 통해서 접근할 수 있습니다)를 둘러보기 + <ul> + <li>지난 며칠동안 얼마나 많은 질문들이 올라왔습니까?</li> + <li>얼마나 많은 답변이 있습니까?</li> + <li>왕성한 활동을 보이는 커뮤니티를 갖고 있습니까?</li> + </ul> + </li> +</ol> +</div> + +<h2 id="A_few_good_web_frameworks">A few good web frameworks?</h2> + +<p>더 나아가, 몇몇 server-side 웹 프레임워크들에 대해 다루어보겠습니다.</p> + +<p>밑에 있는 server-side 프레임워크들은 이 글을 쓰는 시점에 인기 있는 프레임워크들 중 일부입니다. 몇이 프레임워크들은 생산성을 위해 필요한 것들을 갖추었습니다. — they are open source, are under active development, have enthusiastic communities creating documentation and helping users on discussion boards, and are used in large numbers of high-profile websites. 인터넷 검색을 통해 여기서 언급하지 않은 좋은 프레임워크들 찾을 수 있습니다. </p> + +<p><strong>Note</strong>: 여기에 기술되어있는 (일부) 내용들은 프레임워크의 사이트로부터 왔습니다!</p> + +<h3 id="Django_Python">Django (Python)</h3> + +<p><a href="https://www.djangoproject.com/">Django</a> is a high-level Python Web framework that encourages rapid development and clean, pragmatic design. Built by experienced developers, it takes care of much of the hassle of web development, so you can focus on writing your app without needing to reinvent the wheel. It’s free and open source.</p> + +<p>Django follows the "Batteries included" philosophy and provides almost everything most developers might want to do "out of the box". Because everything is included, it all works together, follows consistent design principles, and has extensive and up-to-date documentation. It is also fast, secure, and very scalable. Being based on Python, Django code is easy to read and to maintain.</p> + +<p>Popular sites using Django (from Django home page) include: Disqus, Instagram, Knight Foundation, MacArthur Foundation, Mozilla, National Geographic, Open Knowledge Foundation, Pinterest, Open Stack.</p> + +<h3 id="Flask_Python">Flask (Python)</h3> + +<p><a href="http://flask.pocoo.org/">Flask</a> is a microframework for Python. </p> + +<p>While minimalist, Flask can create serious websites out of the box. It contains a development server and debugger, and includes support for <a href="https://github.com/pallets/jinja">Jinja2</a> templating, secure cookies, <a href="https://en.wikipedia.org/wiki/Unit_testing">unit testing</a>, and <a href="http://www.restapitutorial.com/lessons/restfulresourcenaming.html">RESTful</a> request dispatching. It has good documentation and an active community. </p> + +<p>Flask has become extremely popular, particularly for developers who need to provide web services on small, resource-constrained systems (e.g. running a web server on a <a href="https://www.raspberrypi.org/">Raspberry Pi</a>, <a href="http://blogtarkin.com/drone-definitions-learning-the-drone-lingo/">Drone controllers</a>, etc.)</p> + +<h3 id="Express_Node.jsJavaScript">Express (Node.js/JavaScript)</h3> + +<p><a href="http://expressjs.com/">Express</a> is a fast, unopinionated, flexible and minimalist web framework for <a href="https://nodejs.org/en/">Node.js</a> (node is a browserless environment for running JavaScript). It provides a robust set of features for web and mobile applications and delivers useful HTTP utility methods and <a href="https://wiki.developer.mozilla.org/en-US/docs/Glossary/Middleware">middleware</a>.</p> + +<p>Express is extremely popular, partially because it eases the migration of client-side JavaScript web programmers into server-side development, and partially because it is resource-efficient (the underlying node environment uses lightweight multitasking within a thread rather than spawning separate processes for every new web request). </p> + +<p>Because Express is a minimalist web framework it does not incorporate every component that you might want to use (for example, database access and support for users and sessions are provided through independent libraries). There are many excellent independent components, but sometimes it can be hard to work out which is the best for a particular purpose! </p> + +<p>Many popular server-side and full stack frameworks (comprising both server and client-side frameworks) are based on Express, including <a href="http://feathersjs.com/">Feathers</a>, <a href="https://www.itemsapi.com/">ItemsAPI</a>, <a href="http://keystonejs.com/">KeystoneJS</a>, <a href="http://krakenjs.com/">Kraken</a>, <a href="http://loopback.io/">LoopBack</a>, <a href="http://mean.io/">MEAN</a>, and <a href="http://sailsjs.org/">Sails</a>.</p> + +<p>A lot of high profile companies use Express, including: Uber, Accenture, IBM, etc. (a list is provided <a href="http://expressjs.com/en/resources/companies-using-express.html">here</a>).</p> + +<h3 id="Ruby_on_Rails_Ruby">Ruby on Rails (Ruby)</h3> + +<p><a href="http://rubyonrails.org/">Rails</a> (usually referred to as "Ruby on Rails") is a web framework written for the Ruby programming language.</p> + +<p>Rails follows a very similar design philosophy to Django. Like Django it provides standard mechanisms for routing URLs, accessing data from a database, generating HTML from templates and formatting data as {{glossary("JSON")}} or {{glossary("XML")}}. It similarly encourages the use of design patterns like DRY ("dont repeat yourself" — write code only once if at all possible), MVC (model-view-controller) and a number of others.</p> + +<p>There are of course many differences due to specific design decisions and the nature of the languages.</p> + +<p>Rails has been used for high profile sites, including:<strong> </strong><a href="https://basecamp.com/">Basecamp</a>, <a href="https://github.com/">GitHub</a>, <a href="https://shopify.com/">Shopify</a>, <a href="https://airbnb.com/">Airbnb</a>, <a href="https://twitch.tv/">Twitch</a>, <a href="https://soundcloud.com/">SoundCloud</a>, <a href="https://hulu.com/">Hulu</a>, <a href="https://zendesk.com/">Zendesk</a>, <a href="https://square.com/">Square</a>, <a href="https://highrisehq.com/">Highrise</a>.</p> + +<h3 id="Laravel_PHP">Laravel (PHP)</h3> + +<p><a href="https://laravel.com/">Laravel</a> is a web application framework with expressive, elegant syntax. Laravel attempts to take the pain out of development by easing common tasks used in the majority of web projects, such as:</p> + +<ul> + <li><a href="https://laravel.com/docs/routing" rel="nofollow">Simple, fast routing engine</a>.</li> + <li><a href="https://laravel.com/docs/container" rel="nofollow">Powerful dependency injection container</a>.</li> + <li>Multiple back-ends for <a href="https://laravel.com/docs/session" rel="nofollow">session</a> and <a href="https://laravel.com/docs/cache" rel="nofollow">cache</a> storage.</li> + <li>Expressive, intuitive <a href="https://laravel.com/docs/eloquent" rel="nofollow">database ORM</a>.</li> + <li>Database agnostic <a href="https://laravel.com/docs/migrations" rel="nofollow">schema migrations</a>.</li> + <li><a href="https://laravel.com/docs/queues" rel="nofollow">Robust background job processing</a>.</li> + <li><a href="https://laravel.com/docs/broadcasting" rel="nofollow">Real-time event broadcasting</a>.</li> +</ul> + +<p>Laravel is accessible, yet powerful, providing tools needed for large, robust applications.</p> + +<h3 id="ASP.NET">ASP.NET</h3> + +<p><a href="http://www.asp.net/">ASP.NET</a> is an open source web framework developed by Microsoft for building modern web applications and services. With ASP.NET you can quickly create web sites based on HTML, CSS, and JavaScript, scale them for use by millions of users and easily add more complex capabilities like Web APIs, forms over data, or real time communications.</p> + +<p>One of the differentiators for ASP.NET is that it is built on the <a href="https://en.wikipedia.org/wiki/Common_Language_Runtime">Common Language Runtime</a> (CLR), allowing programmers to write ASP.NET code using any supported .NET language (C#, Visual Basic, etc.). Like many Microsoft products it benefits from excellent tools (often free), an active developer community, and well-written documentation.</p> + +<p>ASP.NET is used by Microsoft, Xbox.com, Stack Overflow, and many others.</p> + +<h3 id="Mojolicious_Perl">Mojolicious (Perl)</h3> + +<p><a href="http://mojolicious.org/">Mojolicious</a> is a next generation web framework for the Perl programming language.</p> + +<p>Back in the early days of the web, many people learned Perl because of a wonderful Perl library called <a href="https://metacpan.org/module/CGI">CGI</a>. It was simple enough to get started without knowing much about the language and powerful enough to keep you going. Mojolicious implements this idea using bleeding edge technologies.</p> + +<p>Some of the features provided by Mojolicious are: <strong>Real-time web framework</strong>, to easily grow single file prototypes into well-structured MVC web applications; RESTful routes, plugins, commands, Perl-ish templates, content negotiation, session management, form validation, testing framework, static file server, CGI/<a href="http://plackperl.org/">PSGI</a> detection, first class Unicode support; Full stack HTTP and WebSocket client/server implementation with IPv6, TLS, SNI, IDNA, HTTP/SOCKS5 proxy, UNIX domain socket, Comet (long polling), keep-alive, connection pooling, timeout, cookie, multipart and gzip compression support; JSON and HTML/XML parsers and generators with CSS selector support; Very clean, portable and object-oriented pure-Perl API with no hidden magic; Fresh code based upon years of experience, free and open source.</p> + +<h3 id="Spring_Boot_Java">Spring Boot (Java)</h3> + +<p><a href="https://spring.io/projects/spring-boot">Spring Boot</a> is one of a number of projects provided by <a href="http://spring.io/">Spring</a>. It is a good starting point for doing server-side web development using <a href="https://www.java.com/">Java</a>.</p> + +<p>Although definitely not the only framework based on <a href="https://www.java.com/">Java</a> it is easy to use to create stand-alone, production-grade Spring-based Applications that you can "just run". It is an opinionated view of the Spring platform and third-party libraries but allows to start with minimum fuss and configuration.</p> + +<p>It can be used for small problems but its strength is building larger scale applications that use a cloud approach. Usually multiple applications run in parallel talking to each other, with some providing user interaction and others just do back end work (e.g. accessing databases or other services). Load balancers help to ensure redundancy and reliability or allow geolocated handling of user requests to ensure responsiveness.</p> + +<h2 id="Summary">Summary</h2> + +<p>This article has shown that web frameworks can make it easier to develop and maintain server-side code. It has also provided a high level overview of a few popular frameworks, and discussed criteria for choosing a web application framework. You should now have at least an idea of how to choose a web framework for your own server-side development. If not, then don't worry — later on in the course we'll give you detailed tutorials on Django and Express to give you some experience of actually working with a web framework.</p> + +<p>For the next article in this module we'll change direction slightly and consider web security.</p> + +<p>{{PreviousMenuNext("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps/Website_security", "Learn/Server-side/First_steps")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="https://wiki.developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Introduction">Introduction to the server side</a></li> + <li><a href="https://wiki.developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview">Client-Server overview</a></li> + <li><a href="https://wiki.developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Web_frameworks">Server-side web frameworks</a></li> + <li><a href="https://wiki.developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Website_security">Website security</a></li> +</ul> diff --git a/files/ko/learn/server-side/first_steps/website_security/index.html b/files/ko/learn/server-side/first_steps/website_security/index.html new file mode 100644 index 0000000000..6fcc0fbb56 --- /dev/null +++ b/files/ko/learn/server-side/first_steps/website_security/index.html @@ -0,0 +1,164 @@ +--- +title: Website security +slug: Learn/Server-side/First_steps/Website_security +tags: + - 가이드 + - 보안 + - 서버측 프로그래밍 + - 웹 보안 + - 초보자 + - 학습 +translation_of: Learn/Server-side/First_steps/Website_security +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenu("Learn/Server-side/First_steps/Web_frameworks", "Learn/Server-side/First_steps")}}</div> + +<p class="summary">웹사이트 보안은 설계와 사용의 모든 측면에서 주의를 기울여야 한다. 이 글은 입문자용이므로 당신을 웹사이트 보안 전문가로 만들어주지는 않지만, 보안 위협 요소가 어디에서 발생하는지와, 가장 일반적인 공격으로부터 웹 응용 프로그램을 어떻게 강화할 수 있는지 이해하는데 도움을 줄 것이다.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">사전 요구 지식 :</th> + <td>기본적인 컴퓨터 활용 능력.</td> + </tr> + <tr> + <th scope="row">학습목표 :</th> + <td> + <p>웹 애플리케이션 보안을 위협하는 가장 일반적인 요소들과, 해킹 당할 위험을 줄이는 방법을 이해한다.</p> + </td> + </tr> + </tbody> +</table> + +<h2 id="웹사이트_보안이란_무엇인가">웹사이트 보안이란 무엇인가?</h2> + +<p>인터넷은 위험한 곳이다! 구체적인 사례로, 우리는 정기적으로 웹사이트가 서비스 거부 공격으로 마비되거나, 홈페이지에 수정된 (그리고 종종 위해한) 정보가 게시된 소식을 듣는다. 또 다른 영향력이 큰 사례로, 수백만 개의 비밀번호, 이메일 주소, 신용카드 정보가 공용 도메인으로 유출되어, 웹사이트 사용자들에게 심리적 당혹감과 경제적인 위험을 유발한다.</p> + +<p>웹사이트 보안의 목적은 이러한 (또는 어떤) 종류의 공격을 방지하는 것이다. 딱딱하게 말하자면, <em>웹사이트 보안이란, 비승인된 접근, 사용, 수정, 파괴, 중단으로부터 웹사이트를 보호하는 행동 또는 실행을 가리킨다.</em></p> + +<p>웹사이트 보안을 효과적으로 하기 위해서는 웹사이트를 설계하는 과정의 전반에 걸쳐 노력을 기울여야 한다: 웹 애플리케이션에서, 웹 서버 설정에서, 비밀번호를 생성하고 재발급하는 정책에서, 클라이언트측 코드에서 말이다. 이 말이 보안에 필요한 많은 작업들을 당신이 직접 세세하게 해주어야 한다는 불길한 말로 들릴 수 있다. 하지만 다행스럽게도 서버측 웹 프레임워크를 사용한다면, 일반적인 보안 공격에 대해 이미 견고하고 잘 검증 된 방어 메커니즘이 "기본으로" 제공된다. 다른 공격들은 HTTPS를 활성화하는 등의 웹 서버 설정을 통해 방지할 수 있다. 마지막으로, 공개된 취약점 스캐너 도구를 사용하여 알려진 보안 실수들은 잡아낼 수 있을 것이다.</p> + +<p>이 글의 나머지 부분은 몇 가지 일반적인 보안 위협들과 사이트를 보호하기 위해 취할 수 있는 간단한 지침들을 제공한다.</p> + +<p><strong>Note</strong>: 입문자를 위한 주제이고, 웹사이트 보안에 대해 처음 생각해보는 이들을 돕기 위해 작성되었다. 완전하지 않으니 유의하길 바란다.</p> + +<h2 id="웹사이트_보안_위협들">웹사이트 보안 위협들</h2> + +<p>이 부분은 몇가지 일반적인 웹사이트 위협 요소들의 목록과 그 위협들을 어떻게 최소화할 수 있는지를 다룬다. 다음을 상기하며 읽자. 브라우저에서 전달되는 데이터를 신뢰하기만 하고 <em>충분한 피해망상을 가지지 않으면 </em>보안 위협 요소는 꼭 성공한다는 것을 상기해두자.</p> + +<h3 id="사이트_간_스크립팅_(Cross-Site_Scripting_XSS)">사이트 간 스크립팅 (Cross-Site Scripting, XSS)</h3> + +<p>XSS는 공격자가 클라이언트 측 스크립트를 웹 사이트에 삽입하여 다른 사용자의 부라우저에서 수행되게 하는 공격의 유형을 말한다. 삽입된 코드는 웹 사이트에서 피해 사용자의 브라우저로 전송이 됨으로 피해 사용자에게 의심받지 않는다. 따라서, 그 삽입된 코드는 피해 사용자의 사이트 권한 쿠키를 공격자에게 보내는 종류의 악성 작업을 수행할 수 있다. 그리고 그것을 전달 받은 공격자는 마치 피해 사용자인 것처럼 위장하여 사이트에 로그인하고 피해 사용자가 할 수 있는 모든 작업을 수행할 수 있다. 가령, 신용 카드 세부 정보에 접근하거나, 연락처 세부 정보를 확인하거나, 암호를 변경하는 작업 등을 수행할 수 있다.</p> + +<div class="note"> +<p><strong>Note</strong>: XSS 취약점은 전통적으로 다른 유형보다 일반적이었다.</p> +</div> + +<p>삽입된 스크립트를 브라우저에 반환하도록 사이트를 속이는 데에는 두 가지 주요 접근방법이 있다. 그것은 반사적 XSS 취약점과 지속적 XSS 취약점이다.</p> + +<ul> + <li>A <em>reflected</em> XSS vulnerability occurs when user content that is passed to the server is returned <em>immediately</em> and <em>unmodified</em> for display in the browser — any scripts in the original user content will be run when the new page is loaded!<br> + For example, consider a site search function where the search terms are encoded as URL parameters, and these terms are displayed along with the results. An attacker can construct a search link containing a malicious script as a parameter (e.g. <code>http://mysite.com?q=beer<script%20src="http://evilsite.com/tricky.js"></script></code>) and email it to another user. If the target user clicks this "interesting link", the script will be executed when the search results are displayed. As discussed above, this gives the attacker all the information they need to enter the site as the target user — potentially making purchases as the user or sharing their contact information.</li> + <li>A <em>persistent</em> XSS vulnerability is one where the malicious script is <em>stored</em> by the website and then later redisplayed unmodified for other users to unwittingly execute.<br> + For example, a discussion board that accepts comments containing unmodified HTML could store a malicious script from an attacker. When the comments are displayed the script is executed and can then send the attacker information required to access the user's account. This sort of attack is extremely popular and powerful, because the attacker doesn't have to have any direct engagement with the victims.<br> + <br> + While <code>POST</code> or <code>GET</code> data is the most common source of XSS vulnerabilities, any data from the browser is potentially vulnerable (including cookie data rendered by the browser, or user files that are uploaded and displayed).</li> +</ul> + +<p>The best defence against XSS vulnerabilities is to remove or disable any markup that can potentially contain instructions to run code. For HTML this includes tags like <code><script></code>, <code><object></code>, <code><embed></code>, and <code><link></code>.</p> + +<div> +<p>The process of modifying user data so that it can't be used to run scripts or otherwise affect the execution of server code is known as input sanitization. Many web frameworks automatically sanitize user input from HTML forms by default.</p> +</div> + +<h3 id="SQL_injection">SQL injection</h3> + +<p>SQL injection vulnerabilities enable malicious users to execute arbitrary SQL code on a database, allowing data to be accessed, modified or deleted irrespective of the user's permissions. A successful injection attack might spoof identities, create new identities with administration rights, access all data on the server, or destroy/modify the data to make it unusable.</p> + +<p>This vulnerability is present if user input that is passed to an underlying SQL statement can change the meaning of the statement. For example, consider the code below, which is intended to list all users with a particular name (<code>userName</code>) that has been supplied from an HTML form:</p> + +<pre class="brush: sql">statement = "SELECT * FROM users WHERE name = '" + <strong>userName</strong> + "';"</pre> + +<p>If the user enters a real name, this will work as intended. However a malicious user could completely change the behaviour of this SQL statement to the new statement below, simply by specifying the "<strong>bold</strong>" text below for the <code>userName</code>. The modified statement creates a valid SQL statement that deletes the <code>users</code> table and selects all data from the <code>userinfo</code> table (revealing the information of every user). This works because the first part of the injected text (<code>a';</code>) completes the original statement (' is the symbol to deliniate a string literal in SQL).</p> + +<pre class="brush: sql">SELECT * FROM users WHERE name = '<strong>a';DROP TABLE users; SELECT * FROM userinfo WHERE 't' = 't'</strong>; +</pre> + +<p>The way to avoid this sort of attack is to ensure that any user data that is passed to an SQL query cannot change the nature of the query. One way to do this is to <a href="https://en.wikipedia.org/wiki/Escape_character">escape</a> all the characters in the user input that have a special meaning in SQL.</p> + +<div class="note"> +<p><strong>Note</strong>: The SQL statement treats the ' character as the beginning and end of a string literal. By putting a backslash in front we "escape" the symbol (\'), and tell SQL to instead treat it as a character (just part of the string).</p> +</div> + +<p>In the statement below we escape the ' character. The SQL will now interpret the name as the whole string shown in bold (a very odd name indeed, but not harmful!)</p> + +<pre class="brush: sql">SELECT * FROM users WHERE name = '<strong>a\';DROP TABLE users; SELECT * FROM userinfo WHERE \'t\' = \'t'</strong>; + +</pre> + +<p>Web frameworks will often take care of this escaping for you. Django, for example, ensures that any user-data passed to querysets (model queries) is escaped.</p> + +<div class="note"> +<p><strong>Note</strong>: This section draws heavily on the information in <a href="https://en.wikipedia.org/wiki/SQL_injection">Wikipedia here</a>.</p> +</div> + +<h3 id="Cross_Site_Request_Forgery_(CSRF)">Cross Site Request Forgery (CSRF)</h3> + +<p>CSRF attacks allow a malicious user to execute actions using the credentials of another user without that user’s knowledge or consent.</p> + +<p>This type of attack is best explained by example. John is a malicious user who knows that a particular site allows logged-in users to send money to a specified account using an HTTP <code>POST</code> request that includes the account name and an amount. John constructs a form that includes his bank details and an amount of money as hidden fields, and emails it to other site users (with the <em>Submit</em> button disguised as a link to a "get rich quick" site).</p> + +<p>If a user clicks the submit button, an HTTP <code>POST</code> request will be sent to the server containing the transaction details and <em>any client-side cookies that the browser associates with the site</em> (adding associated site cookies to requests is normal browser behaviour). The server will check the cookies, and use them to determine whether or not the user is logged in and has permission to make the transaction.</p> + +<p>The result is that a<span style="line-height: 1.5;">ny user who clicks the <em>Submit</em> button while they are logged in to the trading site will make the transaction. John gets rich!</span></p> + +<div class="note"> +<p><strong>Note</strong>: The trick here is that John doesn't need to have access to the user's cookies (or access credentials) — the user's browser stores this information, and automatically includes it in all requests to the associated server.</p> +</div> + +<p>One way to prevent this type of attack is for the server to require that <code>POST</code> requests includes a user-specific site-generated secret (the secret would be supplied by the server when sending the web form used to make transfers). This approach prevents John from creating his own form because he would have to know the secret that the server is providing for the user. Even if he found out the secret and created a form for a particular user, he would no longer be able to use that same form to attack every user.</p> + +<p>Web frameworks often include such CSRF prevention mechanisms.</p> + +<h3 id="Other_threats">Other threats</h3> + +<p>Other common attacks/vulnerabilities include:</p> + +<ul> + <li><a href="https://www.owasp.org/index.php/Clickjacking">Clickjacking</a>. In this attack a malicious user hijacks clicks meant for a visible top level site and routes them to a hidden page beneath. This technique might be used, for example, to display a legitimate bank site but capture the login credentials into an invisible {{htmlelement("iframe")}} controlled by the attacker. It could alternatively be used to get the user to click a button on a visible site, but in doing so actually unwittingly click a completely different button. As a defence your site can prevent itself from being embedded in an iframe in another site by setting appropriate HTTP headers.</li> + <li><a href="/en-US/docs/Glossary/Distributed_Denial_of_Service">Denial of Service</a> (DoS). DoS is usually achieved by flooding a target site with spurious requests so that access to a site is disrupted for legitimate users. The requests may simply be numerous, or they may individually consume large amounts of resource (e.g. slow reads, uploading of large files, etc.) DoS defences usually work by identifying and blocking "bad" traffic while allowing legitimate messages through. These defences are typically within or before the web server (they are not part of the web application itself).</li> + <li><a href="https://en.wikipedia.org/wiki/Directory_traversal_attack">Directory Traversal</a>/File and disclosure. In this type of attack a malicious user attempts to access parts of the web server file system that they should not be able to access. This vulnerability occurs when the user is able to pass filenames that include file system navigation characters (e.g. <code>../../</code>). The solution is to sanitize input before using it.</li> + <li><a href="https://en.wikipedia.org/wiki/File_inclusion_vulnerability">File Inclusion</a>. In this attack a user is able to specify an "unintended" file for display or execution in data passed to the server. Once loaded this file might be executed on the web server or in the client-side (leading to an XSS attack). The solution is to sanitize input before using it.</li> + <li><a href="https://www.owasp.org/index.php/Command_Injection">Command Injection</a>. Command injection attacks allow a malicious user to execute arbitrary system commands on the host operating system. The solution is to sanitize user input before it might be used in system calls.</li> +</ul> + +<p>There are many more. For a comprehensive listing see <a href="https://en.wikipedia.org/wiki/Category:Web_security_exploits">Category:Web security exploits</a> (Wikipedia) and <a href="https://www.owasp.org/index.php/Category:Attack">Category:Attack</a> (Open Web Application Security Project).</p> + +<h2 id="A_few_key_messages">A few key messages</h2> + +<p>Almost all the exploits in the previous sections are successful when the web application trusts data from the browser. Whatever else you do to improve the security of your website, you should sanitize all user-originating data before it is displayed in the browser, used in SQL queries, or passed to an operating system or file system call.</p> + +<div class="warning"> +<p>Important: The single most important lesson you can learn about website security is to <strong>never trust data from the browser</strong>. This includes <code>GET</code> request data in URL parameters, <code>POST</code> data, HTTP headers and cookies, user-uploaded files, etc. Always check and sanitize all incoming data. Always assume the worst.</p> +</div> + +<p>A number of other concrete steps you can take are:</p> + +<ul> + <li>Use more effective password management. Encourage strong passwords that are changed regularly. Consider two-factor authentication for your site, so that in addition to a password the user must enter another authentication code (usually one that is delivered via some physical hardware that only the user will have, such as a code in an SMS sent to their phone).</li> + <li>Configure your web server to use <a href="/en-US/docs/Glossary/https">HTTPS</a> and <a href="/en-US/docs/Web/Security/HTTP_strict_transport_security">HTTP Strict Transport Security</a> (HSTS). HTTPS encrypts data sent between your client and server. This ensures that login credentials, cookies, <code>POST</code> data and header information are all much less available to attackers.</li> + <li>Keep track of the most popular threats (the <a href="/en-US/docs/">current OWASP list is here</a>) and address the most common vulnerabilities first.</li> + <li>Use <a href="https://www.owasp.org/index.php/Category:Vulnerability_Scanning_Tools">vulnerability scanning tools</a> to perform automated security testing on your site (later on, your very successful website may also find bugs by offering a bug bounty <a href="https://www.mozilla.org/en-US/security/bug-bounty/faq-webapp/">like Mozilla does here</a>).</li> + <li>Only store and display data that you need to. For example, if your users must store sensitive information like credit card details, only display enough of the card number that it can be identified by the user, and not enough that it can be copied by an attacker and used on another site. The most common pattern these days is to only display the last 4 digits of a credit card number.</li> +</ul> + +<p>Web frameworks can help mitigate many of the more common vulnerabilities.</p> + +<h2 id="Summary">Summary</h2> + +<p>This article has explained the concept of web security and some of the more common threats that your website should attempt to protect against. Most importantly, you should understand that a web application cannot trust any data from the web browser! All user data should be sanitized before it is displayed, or used in SQL queries or file system calls.</p> + +<p>That's the end of <a href="/en-US/docs/Learn/Server-side/First_steps">this module</a>, covering your first steps in server-side website programming. We hope you've enjoyed learning the fundamental concepts, and you're now ready to select a Web Framework and start programming.</p> + +<p>{{PreviousMenu("Learn/Server-side/First_steps/Web_frameworks", "Learn/Server-side/First_steps")}}</p> diff --git a/files/ko/learn/server-side/index.html b/files/ko/learn/server-side/index.html new file mode 100644 index 0000000000..4cdb1454cd --- /dev/null +++ b/files/ko/learn/server-side/index.html @@ -0,0 +1,57 @@ +--- +title: Server-side website programming +slug: Learn/Server-side +tags: + - Beginner + - CodingScripting + - Intro + - Landing + - Learn + - Server + - Server-side programming + - Topic +translation_of: Learn/Server-side +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">The <strong><em>Dynamic Websites(동적 웹사이트) </em></strong>–<em><strong> Server-side programming(서버-사이드 프로그래밍)</strong></em> 에 대한 주제는 동적 웹사이트를 생성하는 방법을 보여주는 과목(module) 시리즈이다; (Dynamic Websites: HTTP 요구(requests)에 응답하여 요구에 맞는 정보를 전달하는 웹사이트) 각 과목들은 서버-사이드 프로그래밍의 포괄적인 소개를 제공한다. 기본적인 어플리케이션들을 생성하기 위한 Django (Python) 와Express (Node.js/JavaScript) 같은 웹 프레임워크를 사용하는 방법에 대한 초보자 레벨(beginner level)의 가이드도 포함한다.</p> + +<p>대부분의 주요 웹사이트들은 요구에 따라 동적으로 다른 데이터들을 보여주기 위해 여러 종류의 서버-사이드 기술을 사용한다. 예를 들어, Amazon에서 이용가능한 물품들이 얼마나 많을까? 그리고 Facebok에 얼마나 많은 게시글이 있을까? 를 상상해보자. 완전히 다른 static pages(정적 페이지)를 사용하여 이런 것들을 보여주는 것은 완전히 비효율적이다, 그래서 대신에 그런 정적인 템플릿(<a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> 를 사용하여 구성된 템플릿)을 보여준 다음, 필요할 때마다, 템플릿 안에 보이는 데이터들을 동적으로 업데이트한다. 예) 당신이 Amazon에서 다른 물품을 보고 싶어할 때 물품만 다른 것으로 업데이트한다.</p> + +<p>현대의 웹 개발에서는, 서버-사이드 개발에 대해 배우는 것을 매우 추천한다.</p> + +<h2 id="Learning_pathway(학습_경로)">Learning pathway(학습 경로)</h2> + +<p>서버-사이드 프로그래밍을 시작하는 것은 보통 클라이언트-사이드(Client-side) 개발보다 더 쉽다. 왜냐하면, 동적 웹사이트는 비슷한 작업들을 많이 수행하는 경향이 있기 때문이다. (데이터베이스에서 데이터를 검색하고 페이지에 보여주는 것, 유저가 입력한 데이터터가 유효한 지 확인하고 데이터 베이스에 저장하는 것, 로그인한 유저의 권환을 체크하는 것 등) 그리고 이런 작업들과 다른 일반적인 웹 서버 기능을 쉽게 해주는 웹 프레임워크가 있기 때문이다.</p> + +<p>기본적인 프로그래밍 개념에 대한 지식 (또는 특정한 프로그래밍 언어에 대한 지식) 은 유용하나, 필수는 아니다. 비슷하게, 클라이언트-사이드 코딩에 대한 전문지식이 요구되지 않는다, 그러나 기본적인 지식은 클라이언트-사이드 웹 "프론트 엔드(Front End)"를 생성하는 개발자와 일 하는데 도움이 될 것이다.</p> + +<p>"웹을 작동하는 방법"을 이해할 필요가 있다. 다음 주제들을 먼저 읽는 것을 추천한다.</p> + +<ul> + <li><a href="/ko/docs/Learn/Common_questions/What_is_a_web_server">What is a web server(웹서버는 무엇인가)</a></li> + <li><a href="/ko/docs/Learn/Common_questions/What_software_do_I_need">What software do I need to build a website?(웹사이트를 만들기 위해 필요한 소프트웨어는 무엇인가)</a></li> + <li><a href="/ko/docs/Learn/Common_questions/Upload_files_to_a_web_server">How do you upload files to a web server?(웹 서버에 파일을 업로드하는 방법은 무엇인가)</a></li> +</ul> + +<p>이런 기본적인 이해를 통해, 이 섹션의 과목들을 공부할 준비가 될 것이다.</p> + +<h2 id="Modules(과목)">Modules(과목)</h2> + +<p>이번 주제에는 다음의 과목들을 포함하고 있다. 우선 첫번째 과목을 시작해야한다. 그 후에 두 개의 과목들 중 하나를 선택해 나아가야한다. 두 개의 과목은 가장 대중적인 서버-사이드 언어와 그에 맞는 적절한 웹 프레임워크를 사용하는 방법을 보여준다.</p> + +<dl> + <dt><a href="/ko/docs/Learn/Server-side/First_steps">Server-side website programming first steps(서버-사이드 웹사이트 프로그래밍의 첫 단계)</a></dt> + <dd>이 과목은 서버 기술 문외한을 위한 서버-사이드 웹사이트 프로그래밍에 대한 정보를 제공한다. 또한, 서버-사이드 프로그래밍에 대한 근본적인 질문에 대한 답을 포함하고 있다. (그 질문은 서버가 무엇인지, 클라이언트-프로그래밍과 어떻게 다른 지, 서버가 왜 그렇게 유용한 지에 대한 것이다.) 그리고 몇몇의 더 대중적인 서버-사이드 웹 프레임워크에 대한 개요와 당신의 사이트에 가장 적합한 것을 선택하는 방법에 대한 가이드도 있다. 마지막으로, 웹 서버 보안에 대한 기본적인 섹션도 제공한다.</dd> + <dt><a href="/ko/docs/Learn/Server-side/Django">Django Web Framework (Python)</a></dt> + <dd>Django는 매우 대중적이고 Python으로 쓰여진 매우 중요한 서버-사이드 웹 프레임워크이다. 이 과목은 Django가 좋은 웹 서버 프레임워크인 이유, 개발 환경을 구축하는 방법, 그리고 Django로 일반적인 업무를 수행하는 방법을 설명해준다.</dd> + <dt><a href="/ko/docs/Learn/Server-side/Express_Nodejs">Express Web Framework (Node.js/JavaScript)</a></dt> + <dd>Express는 JavaScript로 쓰여진 대중적인 웹 프레임워크이며, node.js 런 타임 환경에서 호스트된다. 이 과목은 이 프레임 워크의 주요 장점, 개발 환경을 구축하는 법, 일반적인 웹 개발과 배치(deployment) 작업을 수행하는 법에 대해 설명해준다.</dd> +</dl> + +<h2 id="다른_주제">다른 주제</h2> + +<dl> + <dt><a href="/ko/docs/Learn/Server-side/Node_server_without_framework">Node server without framework(프레임워크 없는 노드 서버)</a></dt> + <dd>이 글은 프레임워크를 사용하고 싶지 않는 사람들을 위해, 순수한 Node.js로 구성된 단순한 정적 파일 서버를 제공한다.</dd> +</dl> |