Django Tutorial Part 4: Django admin site - Web 개발 학습하기

Django Tutorial Part 4: Django admin site
=========================================

* 이전
* Overview: Django
* 다음

LocalLibrary website의 모델을 만들었으므로, 우리는 Django Admin 을 이용해서 "실제" book data를 추가할 것입니다. 첫째로 우리는 당신에게 관리자 사이트에 모델들을 등록는 방법과, 이후 어떻게 로그인해서 데이터를 만들지를 보여줄 것입니다. 끝으로는 Admin site를 더 개선할 수 있는 방법들에 대해서 알아볼 것입니다.

|  |  |
| --- | --- |
| 사전 준비: |
 첫째로 완료하세요:
 Django Tutorial Part 3: Using models.
  |
| 목적: |
 장고 관리자 사이트의 이득과 한계를 이해하고, 모델을 위한 여러 레코드들을
 생성하기.
  |

개요
--

장고의 관리자 어플리케이션은 모델을 사용하여 당신이 만들고, 보고, 업데이트하고, 그리고 삭제하는 데에 사용할 수 있는 사이트 영역을 자동적으로 만드는 데에 사용할 수 있습니다. 이는 당신이 시간을 많이 절약할 수 있도록 돕고, 모델을 쉽게 테스트 할 수 있게 하며 당신이 정확한 데이터를 가지고 있다는 느낌을 가질수 있도록 돕습니다. 관리자 어플리케이션은 또한 웹 사이트의 유형에 따라 production의 데이터를 관리하는 데 유용합니다. 모델 중심 접근 방식은 모델에 관한 많은 불필요한 세부 사항을 사용자들에게 노출하는, 모든 사용자들에게 가장 좋은 방식이라고 말할 수 없기 때문에, 장고 프로젝트는 internal 데이터 관리만을(즉, 관리자, 또는 당신의 조직 안에 있는 사람들을 위한 사용만을) 위해서 사용하는 것을 추천합니다.

웹사이트 안에 관리자 어플리케이션을 포함시키기 위해 요구되는 모든 설정은 created the skeleton project를 생성했을 때 자동적으로 완료됩니다(실제 종속성에 대한 정보는, 여기 Django docs here를 확인하세요). 결과적으로, 모델을 관리자 어플리케이션에 추가하기 위해서 **꼭 해야만** 하는 것은 그것들을 등록하는(register) 것입니다. 이 글의 마지막에서 모델 데이터를 더 잘 나타내기 위한 관리자 영역의 추가적인 설정을 짧게 보여드리겠습니다.

모델을 등록한 뒤로는 새로운 "superuser"를 만들어서 사이트에 로그인을 하며, 이후에는 books, 저자, book instances 그리고 장르를 만들 것입니다. 이것들은 다음 튜토리얼에서 만들기 시작할 뷰와 템플릿을 테스트할 때 유용할 것입니다.

Models 등록하기
-----------

첫째, catalog 어플리케이션 안의 **admin.py** 파일을 여세요 (**/locallibrary/catalog/admin.py**). 다음과 같을 겁니다 — `django.contrib.admin`을 이미 imoprt하고 있다는 것에 주의하세요:

```python
from django.contrib import admin

# Register your models here.
```

파일의 하단에 아래 텍스트를 복사 붙여넣기 해서 모델을 등록(register)하세요. 이 코드는 모델들을 import하고 그것들을 등록하기 위해 `admin.site.register` 를 호출합니다.

```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)
```

**참고:** **주의**: 책의 언어를 나타내기 위한 모델을 생성하는 도전과제를 수행했다면 (see the models tutorial article), 그 모델도 import 한 후 등록하세요!

이것은 모델이나 모델들을 사이트에 등록(register)하는 가장 간단한 방법입니다. 관리자 사이트는 커스터마이즈 범위가 넓고, 아래에서 모델을 등록하는 다른 방법들에 관해 다르겠습니다.

Superuser 만들기
-------------

관리자 사이트에 로그인하기 위해서는, 직원(*Staff)* 상태가 활성화 된 사용자 계정이 있어야 합니다. 레코드들을 보고 생성하기 위해서는 이 사용자가 모든 객체들을 관리하기 위한 허가가 있어야 합니다. 사이트에 대한 모든 접속 권한과 필요한 허가를 가진 "superuser" 계정을 manage.py를 사용해서만들 수 있습니다.

superuser를 생성하기 위해 **manage.py**와 같은 경로 안에서 아래 명령어를 호출하세요. username, 이메일 주소, 그리고 강력한 비밀번호를 입력해야 할 것입니다.

```bash
python3 manage.py createsuperuser
```

이 명령어가 완료되면 새로운 superuser가 데이터베이스에 추가되었을 것입니다. 이제 로그인을 테스트할 수 있도록 개발 서버를 재시작 하세요:

```bash
python3 manage.py runserver
```

관리자 계정에 로그인하기
-------------

사이트에 로그인하기 위해서는, /admin URL(예: http://127.0.0.1:8000/admin)을 열어서 새로운 superuser의 userid와 password를 입력하세요(세부 정보를 입력한 후에는 로그인 페이지로 리디렉션되고 /admin URL로 돌아갑니다).

이곳은 설치된 어플리케이션에 따라 그룹지어진 모든 모델들을 보여줍니다. 모델 이름을 클릭해서 모든 관련 레코드들의 목록을 볼 수 있고, 그 목록들을 클릭하여 수정할 수 있습니다. 또한 모델 오른쪽의 **Add** 링크를 클릭하여 그 타입의 레코드를 곧바로 생성할 수 있습니다.

![Admin Site - Home page](/ko/docs/Learn/Server-side/Django/Admin_site/admin_home.png)

**Books 모델 오른쪽의 Add** 링크를 클릭해서 새로운 책을 만드세요(아래와 같은 화면이 보일 것입니다). 각 필드의 제목, 사용된 위젯의 타입 그리고 `help_text`(있는 경우)가 모델 안에서 지정한 값과 일치하는 방식에 유의하세요.

필드에 값들을 입력하세요. 해당 필드 옆에 있는 **+** 버튼을 눌러 새로운 저자나 장르를 생성할 수 있습니다 (이미 생성했다면 목록에서 값을 선택하세요). 모두 완료했다면 레코드를 저장하기 위해 **SAVE**, **Save and add another** 또는 **Save and continue editing**을 클릭할 수 있습니다.

![Admin Site - Book Add](/ko/docs/Learn/Server-side/Django/Admin_site/admin_book_add.png)

**참고:** **주의**: 이 지점에서 어플리케이션에 몇 가지 책들, 저자 그리고 장르(예: 판타지)를 추가하면서 시간을 보내보세요. 각각 저자와 장르에 서로 다른 두 가지 책이 포함되어 있는지 확인하세요 (이렇게 하면 나중에 리스트(list)와 디테일 뷰(detail view)들을 구현할 때 그것들을 더욱 흥미롭게 만들어 줍니다).

책을 추가했다면, 상단 북마크 안의 **Home** 링크를 클릭해서 관리자 페이지로 되돌아가세요. 그리고 **Books** 링크를 클릭해서 책들의 현재 목록을 나타내세요(아니면 다른 링크를 클릭해서 다른 모델의 목록을 보세요). 이제 몇 가지 책을 추가했으니, 목록은 아래 스크린샷과 비슷하게 보일 겁니다. 각 책의 제목이 보입니다; 이것은 지난 글에서 다뤘던 Book 모델의 `__str__()` 메소드가 반환한 값입니다.

![Admin Site - List of book objects](/ko/docs/Learn/Server-side/Django/Admin_site/admin_book_list.png)

이 리스트에서 원하지 않는 책의 왼쪽에 있는 체크박스를 선택하고, Action 드랍-다운 목록에서 delete... 동작을 선택한 후, **Go** 버튼을 클릭해 책을 삭제할 수 있습니다. 또한 **ADD BOOK** 버튼을 클릭해서 새로운 책들을 추가할 수 있습니다.

링크 안의 책 이름을 선택해서 책을 편집할 수 있습니다. 아래에 보여지는 책 모델의 편집 페이지는 "Add" 페이지와 거의 동일합니다. 페이지의 제목 (*Change book*)과 **Delete**, **HISTORY** 그리고 **VIEW ON SITE** 버튼들의 추가가 큰 차이점입니다(마지막 버튼은 모델에서 `get_absolute_url()` 메소드를 정의했기 때문에 나타납니다).

![Admin Site - Book Edit](/ko/docs/Learn/Server-side/Django/Admin_site/admin_book_modify.png)

이제 **Home** 페이지로 되돌아가서 (홈 링크를 사용해서) **Author** 와 **Genre** 목록들을 보세요 — 새로운 책들을 추가하면서 이미 몇 가지 목록들이 있겠지만, 조금 더 추가해도 됩니다.

아직 가지고 있지 않은 것은 Book Instance들입니다. 왜냐하면 Books에서 만들어지지 않았기 때문이죠( `BookInstance`에서 `Book`을 만들 수 있긴 하지만 — 이것은 `ForeignKey` 필드의 특성입니다). Home 페이지로 되돌아가서 연관된 **추가**(**Add**)버튼을 눌러 아래의 Add book Instance 화면을 나타내세요. 크고 전역적으로 고유한 id에 주의하세요. 이것은 도서관에서 한 권의 책을 개별적으로 식별하는 데 사용할 수 있습니다.

![Admin Site - BookInstance Add](/ko/docs/Learn/Server-side/Django/Admin_site/admin_bookinstance_add.png)

각각의 책마다 여러 개의 레코드를 만드세요. 상태(Status)를 최소한 몇 개의 레코드는 대여 가능(Available)로 설정하고, 나머지는 대여 중(On loan)으로 설정하세요. 만약 상태가 대여 불가능(**not** Available)이면, 만기 날짜도 같이 설정하세요.

이게 끝입니다! 관리자 사이트를 어떻게 설정하고 사용하는지 배웠습니다. 또한 `Book`, `BookInstance`, `Genre`, 그리고 `Author` 에 대한 레코드들을 생성했고, 뷰와 탬플릿을 생성할 때 이 레코드들을 사용할 수 있을 것입니다.

추가 설정(Advanced configuration)
-----------------------------

장고는 등록된(registered) 모델의 정보를 사용하여 기본적인 관리자 사이트를 만드는 일을 꽤 잘합니다:

* 각각의 모델은 모델의 `__str__()` 메소드로 생성된 문자열로 식별되는, 그리고 편집을 위한 세부 정보(detail views)/양식(forms)에 링크되어 있는 개별적인 레코드들의 목록을 가지고 있습니다. 기본적으로, 이 화면은 레코드에 대한 대량 삭제 작업을 수행할 수 있는 액션 메뉴를 상단에 가지고 있습니다.
* 레코드의 편집과 추가를 위한 모델 디테일 레코드 양식들은 모델 안의 모든 필드를 포함하고 있고, 그것들의 선언 순서에 따라 수직으로 배치되어 있습니다.

사용하기 더욱 편하도록 인터페이스를 추가적으로 사용자화 할 수 있습니다. 당신이 할 수 있는 몇 가지 일은:

* 목록 뷰(List views):
    + 각각의 레코드에 표시되는 추가적인 필드/정보를 추가하기.
    + 날짜나 다른 선택 값(예: 책의 대여 상태)에 기초해서 어떤 레코드들이 목록지어질 지 선택하는 필터들 추가하기.
    + 목록 뷰(list views) 안의 동작 메뉴(actions menu)에 추가적인 옵션을 추가하고 이 메뉴가 양식 위의 어디에 보여져야 할 지 선택하기.
* 세부 뷰(Detail views):
    + 표시할(혹은 제외할)필드, 따를 순서, 그루핑, 편집 가능할지 여부, 사용될 위젯, 방향(orientation) 등등을 선택하기.
    + 인라인 편집(inline editing)기능을 넣기 위해 관련된 필드들을 레코드에 추가하기(예: 책 레코드의 저자 레코드를 생성하는 동안 책 레코드를 추가하고 편집할 수 있는 기능을 추가하기).

이 글에서는 LocalLibrary의 인터페이스를 향상시키기 위한 몇 가지 변경점을 알아볼 겁니다. 그것들은 `Book`과 `Author` 모델 목록에 더 많은 정보를 추가하고, 그리고 그것들의 편집 보기(edit views)의 레이아웃을 향상시키는 것을 포함합니다. `Language` 와 `Genre` 모델은 변경하지 않을 겁니다. 그들은 각각 하나의 필드만을 갖고 있기 때문에 변경하는 것에 대한 이득이 없습니다!

모든 관리자 사이트 사용자화(customisation) 선택들(choices)의 완벽한 레퍼런스(reference)를 The Django Admin site(장고 문서)에서 찾을 수 있습니다.

### ModelAdmin 클래스 등록하기

관리자 인터페이스에서 모델이 보여지는 방식을 바꾸고 싶다면 ModelAdmin 클래스(레이아웃을 나타내는)를 정의한 후 모델 안에 등록합니다.

`Author` 모델부터 시작해 봅시다. catalog 어플리케이션의 **admin.py** 파일을 여세요 (**/locallibrary/catalog/admin.py**). 원래 있던 `Author` 모델에 대한 등록(registration)을 주석처리(#를 앞에 붙여서) 하세요:

```js
# admin.site.register(Author)
```

이제 아래와 같이 새로운 `AuthorAdmin` 과 그것의 등록(registration)을 추가하세요.

```python
# Define the admin class
class AuthorAdmin(admin.ModelAdmin):
    pass

# Register the admin class with the associated model
admin.site.register(Author, AuthorAdmin)
```

이제 우리는 `Book` 그리고 `BookInstance`를 위한 `ModelAdmin` 클래스를 추가할 겁니다. 또다시 원본 등록(registrations)를 주석처리 해야 합니다:

```js
# admin.site.register(Book)
# admin.site.register(BookInstance)
```

이제 새로운 모델들을 생성하고 등록하기 위해서; 이것의 설명을 위해, 우리는 모델들을 등록(register)하기 위해 `@register` 데코레이터(decorator)를 대신 사용합니다(이것은 `admin.site.register()` 구문과 정확히 똑같은 일을 합니다):

```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
```

현재 모든 관리자 클래스들은 비어있기 때문에("`pass"`를 보세요) 관리자 행동은 바뀌지 않았을 것입니다! 이제 우리는 모델 고유의 관리자 행동(behaviour)을 정의하기 위해 이것들을 확장할 수 있습니다.

### 목록 뷰(list view)들을 설정하기

현재 Locallibrary는 모델의 `__str__()` 메소드에서 생성되는 객체 이름을 사용하여 모든 저자들을 목록짓습니다. 이것은 단지 몇 명의 저자만 있을 때는 괜찮지만, 수많은 저자가 있을 때는 겹치는 이름의 서로 다른 저자가 있을 겁니다. 그것들을 구분하거나, 아니면 그저 각각의 저자마다 흥미로운 정보를 보여주고 싶다면, list\_display 를 사용해서 view에 추가적인 필드를 추가할 수 있습니다.

아래 코드로 `AuthorAdmin` 클래스를 대체하십시오. 목록에 선언될 이름들은 필요한 순서대로 튜플(tuple)로 선언됩니다, 아래에 보시는 것 처럼요(이것들은 원래 모델에서 특정된 것과 같은 이름들입니다).

```python
class AuthorAdmin(admin.ModelAdmin):
    list_display = ('last\_name', 'first\_name', 'date\_of\_birth', 'date\_of\_death')
```

이제 웹사이트 안의 저자 목록으로 이동하세요. 위의 필드들이 아래와 같이 보여질 것입니다:

![Admin Site - Improved Author List](/ko/docs/Learn/Server-side/Django/Admin_site/admin_improved_author_list.png)

우리의 `Book` 모델을 위해서 추가적으로 `author` 그리고 `genre`를 표시해 봅시다. `author`는 `Foreignkey` 필드 관계(일-대-다)이기 때문에, 관련된 레코드의 `__str__()` 값에 의해 나타내어질 것입니다. 아래의 버전으로 `BookAdmin` 클래스를 대체하세요.

```python
class BookAdmin(admin.ModelAdmin):
    list_display = ('title', 'author', 'display\_genre')
```

불행하게도 `genre` 필드는 `ManyToManyField`이기 때문에 `list_display`에 직접적으로 특정할 수 없습니다.(거대한 데이터베이스 접근 "비용"이 발생할 수 있기 때문에 장고는 이것을 방지합니다). 대신 정보를 문자열로 받기 위해서 `display_genre` 함수를 정의할 겁니다(이것이 위에서 호출한 함수입니다; 아래에서 함수를 정의하겠습니다).

**참고:** **주의**: `genre` 를 가져오는 것은 좋은 생각이 아닙니다. 왜냐하면 데이터베이스 작동의 "비용"이 너무 높기 때문입니다. 모델 안의 호출 함수들이 다른 이유로도 굉장히 유용하기 때문에 — 예를 들자면 리스트 안의 모든 항목에 delete 링크를 추가하는 것 처럼요.

아래 코드를 `Book` 모델(**models.py**) 안에 추가하세요. 이것은 `genre` 필드의 첫 세 가지 값들의 문자열을 생성합니다(만약 존재한다면요) 그리고 이 메소드를 위해 관리자 사이트에서 사용될 수 있는 `short_description` 을 만듭니다.

```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'
```

모델을 저장하고 관리자를 업데이트 했다면, 웹사이트를 열어서 Books 목록 페이지로 이동하세요; 아래와 같은 책 리스트가 있을 것입니다:

![Admin Site - Improved Book List](/ko/docs/Learn/Server-side/Django/Admin_site/admin_improved_book_list.png)

`Genre` 모델 (그리고 `Language` 모델도, 정의했다면) 은 단일 필드를 갖고 있기 때문에, 추가적인 필드를 보여주기 위해 추가적인 모델을 생성하는 것은 의미가 없습니다.

**참고:** **주의**: 최소한 상태 그리고 예상되는 반납일을 보여주는 `BookInstance` 모델 목록을 업데이트 해봅시다 . 이것은 이 글의 마지막에 도전 과제로 추가되어 있습니다!

### 목록 필터 추가하기

목록 안에 있는 항목들이 아주 많다면, 그 중에 어떤 항목들만이 표시될 지 정할 수 있는 필터를 추가하는 것이 유용합니다. 이것은 `list_filter` 속성 안에서 필드들을 목록지어서 완료됩니다. 현재 `BookInstanceAdmin` 클래스를 아래 코드 조각으로 대체하십시오.

```python
class BookInstanceAdmin(admin.ModelAdmin):
    list_filter = ('status', 'due\_back')
```

목록 뷰(list view)는 이제 오른쪽에 필터 상자를 갖고 있을 겁니다. 값을 필터링하기 위해 날짜와 상태를 선택하는 방법에 주의하세요:

![Admin Site - BookInstance List Filters](/ko/docs/Learn/Server-side/Django/Admin_site/admin_improved_bookinstance_list_filters.png)

### 세뷰 보기 레이아웃(detail view layout) 조직하기

기본적으로, detail view들은 모든 필드들을 모델에 선언된 순서대로, 수직적으로 배치합니다. 당신은 선언 순서, 표시될(혹은 제외될) 필드들, 섹션들이 정보들을 조직하는 데 사용될 지 여부, 필드들이 수직적 또는 수평적으로 표시될 지 여부 그리고 심지어 관리자 양식에서 어떤 편집 위젯이 사용될 지 까지 변경할 수 있습니다.

**참고:** **주의**: LocalLibrary 모델은 상대적으로 간단하기 때문에 레이아웃을 변경할 큰 필요성이 없습니다; 그러나 방법을 보여드리기 위해, 몇 가지 변경점을 만들 것입니다.

#### 어떤 필드들이 보여지고 배치될 지 제어하기

아래와 같이 `fields` 줄(굵은 글씨)을 추가하기 위해 `AuthorAdmin` 클래스를 업데이트 하세요:

```python
class AuthorAdmin(admin.ModelAdmin):
    list_display = ('last\_name', 'first\_name', 'date\_of\_birth', 'date\_of\_death')
    fields = ['first\_name', 'last\_name', ('date\_of\_birth', 'date\_of\_death')]
```

`fields` 속성은 양식에 보여져야 할 필드들만을 순서대로 목록짓습니다. 필드들은 기본적으로 수직적으로 표시되지만, 추가적으로 그것들을 튜플 안에 그룹짓는다면 수평적으로 표시됩니다(위의 "date" 필드처럼요).

웹사이트 안에서 저자 세부 사항 뷰로 이동하세요 — 이제 아래와 같이 보일겁니다:

![Admin Site - Improved Author Detail](/ko/docs/Learn/Server-side/Django/Admin_site/admin_improved_author_detail.png)

**참고:** **주의**: 당신은 또한 양식(form)에서 제외되어야 할 속성들의 목록을 선언하기 위해 `exclude` 속성을 사용할 수 있습니다 (모델 안의 다른 모든 속성들이 표시될 겁니다).

#### 세부 뷰 구역 나누기(Sectioning the detail view)

fieldsets 속성을 사용하여, 세부 양식 안의 연관된 모델 정보를 그룹화하기 위해 "sections"를 추가할 수 있습니다.

`BookInstance` 안에서, 우리는 책이 무엇인지 (i.e. `name`, `imprint`, and `id`) 그리고 이것을 언제 빌릴 수 있는지 (`status`, `due_back`)에 대한 정보를 가지고 있습니다. 우리는 `BookInstanceAdmin` 클래스에 굵게 표시된 텍스트를 추가하여 이것들을 각각 다른 구역에 추가할 수 있습니다.

```python
@admin.register(BookInstance)
class BookInstanceAdmin(admin.ModelAdmin):
    list_filter = ('status', 'due\_back')

    fieldsets = (
        (None, {
            'fields': ('book', 'imprint', 'id')
        }),
        ('Availability', {
            'fields': ('status', 'due\_back')
        }),
    )
```

각각의 섹션엔 고유한 제목(또는 제목을 원하지 않을 경우, `None`) 그리고 사전의 필드와 관련된 튜플이 있습니다 — 양식(format)은 묘사하기 복잡하지만, 바로 위의 코드 조각을 본다면 이해하기 상당히 쉽습니다.

이제 웹사이트 안의 book instance view로 이동하세요; 이제 아래와 같은 양식이 보일겁니다:

![Admin Site - Improved BookInstance Detail with sections](/ko/docs/Learn/Server-side/Django/Admin_site/admin_improved_bookinstance_detail_sections.png)

### 연관 레코드들의 인라인 편집(Inline editing of associated records)

때때로 연관된 레코드들을 동시에 추가하는 것이 가능할 때도 있습니다. 예를 들어, 세부 사항 페이지에서 책 정보 그리고 특정한 복사본에 대한 정보 둘 다 가질 수도 있습니다.

이것은 inlines를 선언해서 할 수 있고, TabularInline (수평적 레이아웃)타입 또는 StackedInline (기본 모델 레이아웃과 같은 수직적 레이아웃)을 선택할 수 있습니다. 아래 `BookAdmin` 근처의 굵게 표시된 줄을 추가하여 `BookInstance` 정보 인라인을 `Book` 세부 사항(detail)에 추가할 수 있습니다:

```python
class BooksInstanceInline(admin.TabularInline):
    model = BookInstance

@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
    list_display = ('title', 'author', 'display\_genre')
    inlines = [BooksInstanceInline]
```

이제 웹사이트의 `Book` 을 위한 화면으로 이동하세요 — 화면의 하단에서 이제 이 책과 연관된 책 인스턴스(book instances)를 볼 수 있을 겁니다(책의 장르 필드 바로 아래에):

![Admin Site - Book with Inlines](/ko/docs/Learn/Server-side/Django/Admin_site/admin_improved_book_detail_inlines.png)

이 경우 우리는 인라인된 모델의 모든 필드들을 추가하는, 테이블화된 인라인 클래스를 선언했습니다. 레이아웃을 위해 모든 종류의 추가적인 정보들을 지정할 수 있습니다. 표시할 필드, 그것들의 순서, 그것들이 읽기 전용인지 아닌지, 등등을 포함해서요. (더 많은 정보를 위해 TabularInline 를 보세요).

**참고:** **주의**: 이 기능에는 몇 가지 고통스러운 한계가 있습니다! 위의 스크린샷에서 우리는 이미 존재하는 세 개의 책 인스턴스(book instance)와 그 아래에 새로운 책 인스턴스를 위한 세 개의 플레이스홀더(placeholder)를 가지고 있습니다(아주 비슷하게 보이지요!). 기본값으로 예비 책 인스턴스를 위한 플레이스를 홀더를 가지지 **않고** 새로운 북 인스턴스마다 **새로운 북 인스턴스 링크 하나씩** 추가하는 것이 좋습니다. 또는 `BookInstance` 를 여기서는 읽기 불가(non-readable) 링크로 목록화하는 것도 좋구요. 전자는 `BookInstanceInline` 모델 안의 `extra` 속성을 0으로 설정하여 완료할 수 있습니다. 직접 해보세요.

도전 과제
-----

이 섹션에서 많은 것을 배웠기 때문에, 이젠 직접 몇 가지를 도전해 볼 차례입니다.

1. `BookInstance` 목록 뷰에 책, 상태, 만기 날짜, 그리고 id(book, status, due back date, id)를 표시하기 위한 코드를 추가해 보세요(기본`__str__()` 텍스트 가 아닌).
2. Book/BookInstance에서 했던 것고 같은 접근법을 사용해서 Author 세부 사항 뷰에 Book 항목들의 인라인 목록(Inline listing)을 추가해 보세요.
요약
--

이게 답니다! 이제 관리자 사이트를 어떻게 설정하는지에 대해 가장 간단한 방법으로 그리고 발전된 방법으로도 배웠습니다. superuser 만드는 방법, 관리자 사이트와 뷰들을 이동하는 방법, 레코드를 삭제 및 업데이트하는 방법 등등을 배웠습니다. 지금까지 여러 Books, BookInstances, Genres 그리고 Authors를 만들었고, 이것들은 우리가 우리만의 view와 탬플릿들을 만들었을 때 그곳에 목록화하여 나타낼 수 있을 것입니다.

추가 자료
-----

* Writing your first Django app, part 2: Introducing the Django Admin (Django docs)
* The Django Admin site (Django Docs)
* 이전
* Overview: Django
* 다음


Django 소개 - Web 개발 학습하기

Django 소개
=========

* Overview: Django
* 다음

Django의 첫번째 문서에서는 "Django가 뭐지?"라는 질문에 답해보고, Django 웹 프레임워크의 특별한 부분에 대해 전반적으로 살펴봅니다. 우리가 이 수업에서 자세히 다루지는 않을 고급 기능들까지 포함하여 간단하게 전반적인 부분을 살펴 볼겁니다. 또한, Django 애플리케이션을 구성하는 중요한 요소도 살펴보겠습니다. (물론 지금 시점에서는 테스트를 할 개발환경을 가지고 있지 않겠지만요.)

|  |  |
| --- | --- |
| 요구 사항 |
 기본적인 컴퓨터 지식.
 server-side website 프로그래밍에 대한 전반적인 이해, 그리고 웹사이트의
 client-server interactions
 의 매커니즘에 대한 특정한 지식.
  |
| 목표 |
 Django란 무엇인지, 어떤 기능이 있는지, Django 어플리케이션의 주요
 구성요소는 어떤것들인지에 대해 익숙해지기
  |

Django란?
--------

Django란 보안이 우수하고 유지보수가 편리한 웹사이트를 신속하게 개발하는 하도록 도움을 주는 파이썬 웹 프레임워크입니다. 훌륭한 개발자에 의해 만들어진 이 프레임워크는, 웹 개발을 하는데 많은 도움을 주기 때문에 새롭게 웹 개발을 시작할 필요없이 그저 프레임워크를 활용하여 앱 개발에만 집중할 수 있게되죠. 무료 오픈소스인데다가, 활발한 커뮤니티들이 있고, 좋은 참고자료와 무료 및 유료 지원을 하는 옵션들이 제공됩니다.

Django는 다음과 같은 소프트웨어를 개발하는데 도움을 줍니다.

Complete(완결성 있는)

Django는 "Batteries included" 의 철학을 기반으로 개발자들이 개발하고 싶은 거의 모든것을 개발하는데 도움을 줍니다. 개발자들이 원하는 것은 모두 하나의 "결과물"의 일부일 것이기 때문에 도달하고자 하는 목표지점은 같으며 이 덕분에 일관된 디자인 룰을 적용하여 광범위한 최신 문서를 제공합니다.

Versatile(다용도의)

Django는 문서관리시스템과 Wiki부터 SNS, 뉴스에 이르기까지 다양한 종류의 웹 사이트를 빌드하는데 사용할 수 있고 사용되어 왔습니다. 또한 어떠한 클라이언트측 프레임워크와도 협업할 수 있고, 대부분의 형식(HTML, RSS 피드, JSON, XML 등)으로 컨텐츠를 전송할 수 있습니다. 당신이 보고 있는 이 사이트도 Django 기반입니다!

내부적으로 Django는 당신이 원하는 대부분의 기능들(몇몇 유명한 데이터베이스들, 템플릿 엔진 등)을 제공하지만, 필요하다면 다른 컴포넌트들을 사용하기 위해 확장될 수 있습니다.

Secure(안전한)

Django 는 개발자들이 웹사이트를 개발할 때 실수하기 쉽지만 고려해야하는 보안 문제에 대해서 많은 도움을 줍니다. 예를 들면, 장고는 유저의 계정과 비밀번호를 관리하는 안전한 방법을 제공합니다. 이 예에서 발생할 수 있는 개발자들의 실수로 세션의 정보를 보안에 취약한 위치에 있는 쿠키(해결책은 쿠키는 그저 key값을 가지도록 하는 반면 실제 데이터는 데이터 베이스에 저장하도록 하는 것입니다)에 넣는 실수를 하는 것입니다. 또 달리 쉽게할 수 있는 실수는 비밀번호를 hash를 통하지 않고 그대로 변형없이 저장하는 것이 있습니다.

*비밀번호에 사용되는 hash 는 cryptographic hash function에 의해 생성된 고정된 길이의 값을 가집니다. Django는 이렇게 변형되어 입력된 비밀번호가 유효한지 hash 함수를 통해 확인할 수 있습니다. 하지만 "단방향" 적인 함수의 특성상, 저장된 hash 값을 웹을 공격하는 사람들이 알아낸다고 하더라도 원본 비밀번호는 알아낼 수 없습니다.*

Django 는 SQL 인젝션, 크로스사이트 스크립팅, 크로스사이트 요청 위조 그리고 클릭 하이젝킹 (이러한 공격 방법에 대한 상세 정보는 Website security (en-US)에서 볼 수 있습니다)과 같은 보안 취약점을 보완할 방법 기본적으로 제공합니다.

Scalable(확장성 있는)

Django는 컴포넌트 기반의 "shared-nothing" 아키텍쳐(각각의 아키텍쳐가 독립적이어서 필요하다면 교체 및 변경할 수 있는)를 사용합니다. 각 부분이 분명하게 분리되면 어떤 레벨에서든(예를 들면 캐싱 서버, 데이터베이스 서버, 혹은 어플리케이션 서버) 하드웨어를 추가해서 발생하는 늘어난 트래픽에 대응해 크기를 변경할 수 있게 됩니다. 사용자가 가장 많은 몇몇 사이트는 요구사항에 맞춰서 Django의 크기를 성공적으로 변경했습니다. (예를들면 Instagram, Disqus 등)

Maintainable(유지보수가 쉬운)

Django 코드는 유지보수가 쉽고 재사용하기 좋게끔 하는 디자인 원칙들과 패턴들을 이용하여 작성됩니다. 특히 Don't Repeat Yourself (DRY) 원칙을 적용해서 불필요한 중복이 없고 많은 양의 코드를 줄였습니다. 또한 Django는 관련된 기능들을 재사용 가능한 "applications"로 그룹화했고, 더 낮은 레벨에서 관련된 코드들을 모듈로 만들었습니다. (Model View Controller (MVC) 패턴과 유사합니다).

Portable(포터블한)

장고는 파이썬으로 작성되어 있으며, 파이썬은 많은 플랫폼에서 작동합니다. 그것은 특정한 서버 플랫폼에 얽매이지 않는다는 것을 의미하며, 리눅스, 윈도우 그리고 맥 OS X 등등 다양한 운영체제에서 작동할 수 있다는 뜻입니다. 나아가, 장고는 많은 웹 호스팅 공급자들에 의해서 지원되고 있습니다. 그들은 장고 사이트의 호스팅과 관련해서 특정한 인프라와 문서를 제공합니다.

탄생 배경은 어떻게 되나요?
---------------

장고는 신문 웹사이트를 제작 및 관리하던 어떤 웹 팀에 의해 2003년에서 2005년 사이에 처음으로 개발이 시작되었습니다. 여러 사이트들을 만들면서 웹 팀은 많은 공통 코드와 설계 패턴을 뽑아내어 재사용하였습니다. 이 공통 코드는 일반 웹 개발 프레임워크로 발전했습니다. 그리고 2005년 7월 "장고" 프로젝트로서 오픈소스화 되었죠..

장고는 2008년 9월 첫 번째 주요 릴리즈(1.0)에서부터 2017년의 최근 버전(2.0)까지 성장하고 발전했습니다. 장고는 각각의 버전에서 기능을 추가하고 버그를 수정했습니다. 새로운 유형의 데이터베이스, 탬플릿 엔진들 그리고 캐싱에 대한 지원에서부터 일반 보기 함수와 클래스들의 추가까지요(이를 통해 여러 프로그래밍 작업을 위해 개발자들이 작성해야 할 코드를 줄여줍니다).

**참고:** 장고를 더 좋게 만들기 위해 어떤 작업이 이루어지고 있는지, 최근 버전에서 어떤 변경이 있었는지 확인하려면 장고 웹사이트의 release notes 를 살펴보세요.

장고는 수많은 사용자와 기여자가 있는 협력적이고 번성하는 프로젝트입니다. 여전히 몇 가지 장고만의 특징이 있지만, 장고는 모든 유형의 웹사이트를 개발할 수 있는 다용도적인 웹 프레임워크로 발전했습니다.

Django의 인기는 어떤가요?
-----------------

사실 서버 측 프레임워크의 인기에 대해 쉽고 확정적인 측정값은 없습니다(다만 Hot Frameworks 와 같은 사이트는 각 플랫폼에 대해 GitHub 프로젝트와 StackOverflow 질문의 숫자를 세는 방법으로 인기에 대해 접근하려고 합니다). 장고가 인기없는 플랫폼의 문제를 피할 수 있을 만큼 "충분히 인기있는지"가 더 좋은 질문입니다. 장고가 계속 발전하나요? 도움이 필요할 때 받을 수 있나요? 장고를 배우면 돈을 받고 일할 기회가 생기나요?

장고를 사용하는 상위 사이트의 숫자, 장고 코드베이스에 기여하는 사람들의 숫자, 그리고 급여가 지불되거나 지불되지 않거나에 상관없이 지원을 제공하는 사람들의 숫자에 근거해서, 맞습니다. 장고는 인기있는 프레임워크 입니다!

장고를 사용하는 상위 사이트는 다음을 포함합니다 : Disqus, Instagram, Knight Foundation, MacArthur Foundation, Mozilla, National Geographic, Open Knowledge Foundation, Pinterest, and Open Stack (출처: Django home page).

Django는 독선적인가요?
---------------

많은 웹 프레임웍들이 흔히 스스로를 "독선적(opinionated)"이라거나 "관용적(unopinionated)"이라고 표현합니다.

독선적인 프레임웍들은 어떤 특정 작업을 다루는 "올바른 방법"에 대한 분명한 의견을 가지고 있습니다. 그것들은 대체로 특정 도메인(특정 타입의 문제를 해결하는)내에서 빠른 개발방법을 제시합니다. 어떤 작업에 대한 올바른 방법이란 보통 잘 알려져있고 문서화가 잘되어있기 때문입니다. 하지만 그것들은 주요 도메인을 벗어난 문제에 대해서는 그리 유연하지 못한 해결책을 제시할 수 있습니다. 또한 이용할수 있는 접근법이나 선택가능한 구성요소가 그리 많지 않을것입니다.

반면에, 관용적인 프레임웍들은, 구성요소를 한데 붙여서 해결해야 한다거나 심지어 어떤 컴퍼넌트를 써야한다는 '올바른 방법'에 대한 제약이 거의 없다시피 합니다. 그것들은 개발자들이 특정 작업을 완수하는데에 가장 적절한 도구들을 이용하기 쉽게 만들어줍니다. 비록 당신 스스로가 그 컴퍼넌트들을 찾아야 한다는 수고는 해야하긴 하지만 말이죠.

Django는 "다소 독선적" 입니다. 그럼으로써 "양쪽 세계의 최선"의 결과를 전달합니다. Django는 대부분의 웹 개발 작업을 다루는 컴퍼넌트 세트와 그 세트를 이용하는 한, 두가지의 인기있는 방법을 제공합니다. 하지만 Django의 비결합 구조 (decoupled architecture) 덕분에 당신은 꽤 많은 옵션들중에서 다른 방법을 선택하거나 원한다면 완전히 새로운 방법을 만들어 낼 수도 있습니다.

Django 코드는 어떻게 생겼나요?
--------------------

전형적인 데이터 기반 웹 사이트에서 웹 어플리케이션은 웹 브라우저(또는 다른 클라이언트)로부터 HTTP 요청(Request)을 기다립니다. 요청을 받으면, 웹 어플리케이션은 URL과 `POST` 데이터 또는 `GET` 데이터의 정보에 기반하여 요구사항을 알아냅니다. 그 다음 무엇이 필요한 지에 따라, 데이터베이스로부터 정보를 읽거나 쓰고, 또는 필요한 다른 작업들을 수행할 것입니다. 그 다음 웹 어플리케이션은 웹 브라우저에 응답(Response)을 반환하는데, 주로 동적인 HTML 페이지를 생성하면서 응답합니다.

Django 웹 어플리케이션은 전형적으로 아래와 같이 분류된 파일들에 대해 일련의 단계를 수행하는 코드로 구성되어 있습니다:

![](/ko/docs/Learn/Server-side/Django/Introduction/basic-django.png)

* **URLs:** 단일 함수를 통해 모든 URL 요청을 처리하는 것이 가능하지만, 분리된 뷰 함수를 작성하는 것이 각각의 리소스를 유지보수하기 훨씬 쉽습니다. URL mapper는 요청 URL을 기준으로 HTTP 요청을 적절한 뷰(view)로 보내주기 위해 사용됩니다. 또한 URL mapper는 URL에 나타나는 특정한 문자열이나 숫자의 패턴을 일치시켜 데이터로서 뷰 함수에 전달할 수 있습니다.
* **View:** 뷰는 HTTP 요청을 수신하고 HTTP 응답을 반환하는 요청 처리 함수입니다. 뷰는 Model을 통해 요청을 충족시키는데 필요한 데이터에 접근합니다. 그리고 탬플릿에게 응답의 서식 설정을 맡깁니다.
* **Models:** 모델은 응용프로그램의 데이터 구조를 정의하고 데이터베이스의 기록을 관리(추가, 수정, 삭제)하고 쿼리하는 방법을 제공하는 파이썬 객체입니다.
* **Templates:** 탬플릿은 파일의 구조나 레이아웃을 정의하고(예: HTML 페이지), 실제 내용을 보여주는 데 사용되는 플레이스홀더를 가진 텍스트 파일입니다. 뷰는 HTML 탬플릿을 이용하여 동적으로 HTML 페이지를 만들고 모델에서 가져온 데이터로 채웁니다. 탬플릿으로 모든 파일의 구조를 정의할 수 있습니다.탬플릿이 꼭 HTML 타입일 필요는 없습니다!

**참고:** 장고는 이 구조를 "모델 뷰 템플릿(Model View Template)(MVT)" 아키텍처라고 부릅니다. 이것은 더 익숙한 Model View Controller 아키텍처와 많은 유사점을 가지고 있습니다.

아래 부문들은 장고 앱의 주요 부분들이 어떻게 보일지에 대한 단서를 보여줄 것입니다 (우리는 개발 환경을 설치한 이후에 세부적인 디테일에 대해 다룰겁니다).

### 요청을 알맞은 뷰로 전달 (urls.py)

URL mapper는 보통 **urls.py**라는 이름의 파일에 저장되어 있습니다. 아래 예시에서 `urlpatterns` 맵퍼는 경로들(특정 URL 패턴들)과 해당하는 뷰 함수에 대한 맵핑 목록들을 정의합니다. 만약 지정된 URL 패턴과 일치하는 HTTP 요청이 수신된다면 관련된 view 함수가 요청을 전달합니다.
```

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),
]
```

`urlpatterns` 객체는 `path()`함수와 `re_path()` 함수를 항목으로 가지는 리스트입니다 (파이썬 리스트는 대괄호를 사용하여 구분되며, 항목은 쉼표로 분리되고 선택적으로 후행 쉼표가 있을 수 있습니다. 예시: [item1, item2, item3, ]).

두 메소드의 첫 번째 인수는 일치시킬 경로(패턴)입니다. `path()` 메소드는 꺾쇠 괄호(<, >)를 사용해서 인수를 정의합니다. 이 인수는 URL의 한 부분으로, 명명된 인수로 수집되어 뷰 함수로 보내집니다. `re_path()` 함수는 정규식이라는 유연한 패턴 매칭 접근을 사용합니다. 이것에 대해서는 나중에 다루도록 하겠습니다!

두 번째 인수는 패턴이 일치할 때 호출되는 다른 함수입니다. `views.book_detail`은 이 함수가 `book_detail()`이며 `views` 모듈 안에서 찾을 수 있다는 것을 나타냅니다 (즉, `views.py`라는 파일 안에서요).

### 요청 처리하기 (views.py)

뷰들은 웹 클라이언트로부터 HTTP 요청을 수신하고 HTTP 응답을 되돌려주는 웹 어플리케이션의 심장입니다. 그 사이에 그들은 데이터베이스에 접근하고 템플릿을 렌더링하기 위해 프레임워크의 다른 자원들을 정리합니다.

아래 예시는 이전 예시의 URL mapper가 불러올 수 있는 최소 뷰 함수 `index()`를 보여줍니다. 다른 모든 뷰 함수처럼 이 함수도 `HttpRequest` 객체를 인자(`request`)로 받고 `HttpResponse` 객체를 반환합니다. 이 예시에서는 요청에 관해서는 아무것도 하지 않고, 단순히 하드코딩된 문자열을 반환합니다. 요청에 관련해서는 이후 글에서 더 자세하게 다루겠습니다.

```python
## 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!')
```

**참고:** 파이썬에 관하여:

* Python modules은 우리가 코드에 쓰고 싶을지도 모르며 분리된 파일로 저장되어 있는 함수의 "라이브러리" 입니다.
* `from django.http import HttpResponse`와 같은 방법으로 `django.http` 모듈에서 `HttpResponse` 객체만 가져와서 뷰에서 사용할 수 있습니다. 모듈에서 여럿, 아니면 전체 모듈을 임포트할 수 있는 몇 가지 방법이 있습니다.
* 함수들은 위에 보여진 것과 같이`def` 키워드로 정의됩니다. 함수의 이름 뒤 괄호 안에 는 명명된 인자들이 나열되어 있습니다. 전체 줄은 콜론으로 끝납니다. 그 아랫 줄이 모두 **들여쓰기 되어있다는 것**에 유의하세요. 들여쓰기는 코드 행이 특정한 블록 안에 있다는 것을 나타내기 때문에 중요합니다(필수적인 들여쓰기는 파이썬의 주요 기능이며, 파이썬 코드가 읽기 쉬운 이유 중 하나이기도 합니다).

뷰들은 보통 **views.py**.라는 파일 안에 저장되어 있습니다.

### 데이터 모델 정의하기 (models.py)

장고 웹 어플리케이션은 모델(models)이라는 파이썬 객체를 통해 데이터를 관리하고 쿼리합니다. 모델은 필드 타입과 그들의 최대 크기, 기본 값들, 선택 목록 옵션, 문서의 도움말 텍스트, 폼(form)을 위한 label text등을 포함하여 저장된 데이터의 구조를 정의합니다. 모델의 정의는 기본 데이터베이스와 별개입니다. 본인의 프로젝트 설정의 일부로써 여러 모델 중 하나를 선택할 수 있습니다. 본인이 사용할 데이터베이스를 정했다면, 그것에 직접적으로 접근할 필요가 없습니다. 그저 모델 구조와 다른 코드들을 작성하면, 장고가 당신과 데이터베이스가 소통하는 데 필요한 모든 더러운 작업들을 처리합니다.

아래 코드는 `Team` 객체를 위한 아주 간단한 장고 모델을 보여줍니다. `Team` 객체는 장고 클래스`models.Model`에서 파생되었습니다. 이 객체는 팀 이름과 팀 레벨을 캐릭터 필드로 정의하고 각각의 기록에 저장될 최대 캐릭터 숫자를 정합니다. `team_level`은 랜덤으로 값이 선정되기 때문에, 우리는 이를 choice 필드로 정의하며, choices들 간에 선택된 값이 보여지고 디폴트 값에 따른 데이터가 저장되도록 합니다.

```python
# 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')
```

**참고:** **참고 : 파이썬에 대해**

파이썬은 코드를 객체로 구성하는 프로그래밍 스타일인 "객체 지향 프로그래밍"을 지원합니다. 여기에는 관련 데이터 및 해당 데이터를 조작하기위한 함수가 포함됩니다. 객체는 다른 객체로부터 상속, 확장, 파생할 수 있어 관련 객체 간의 공통 동작을 공유 할 수 있습니다. 파이썬에서는 키워드 클래스를 사용하여 객체의 "청사진"을 정의합니다. 클래스의 모델을 기반으로 객체 유형의 여러 특정 인스턴스를 만들 수 있습니다.

예를 들어 여기 Model 클래스에서 파생된 Team 클래스가 있습니다. 이는 모델이며 모델의 모든 방법을 포함할 것이지만 고유한 기능도 제공 할 수 있습니다. 이 모델에서는 데이터베이스가 데이터를 저장하는데 필요한 필드를 정의하여 특정 이름을 지정합니다. 장고는 필드 이름을 포함한 이러한 정의를 사용하여 기본 데이터베이스를 만듭니다.

### 데이터 쿼리하기 (views.py)

장고 모델은 데이터베이스를 간단히 탐색하기 위한 쿼리 API를 제공합니다. 이 API는 다양한 조건을 통해 수 많은 필드를 빠르게 매칭시킵니다. (예를 들어, 정확하게 일치(exact), 대소문자 구분없이(case-insensitive), 해당 숫자보다 큰(greater than) 등이 있습니다.) 그리고 복잡한 쿼리문을 지원합니다. 예를 들어, 당신은 팀의 이름이 "Fr"로 시작하거나 "al"로 끝나는 U11 레벨의 팀만을 지정할 수 있습니다.

굵게 표시된 줄은 모델 쿼리 API를 사용하여 `team_level` 필드의 텍스트가 정확히 'U09'인 모든 레코드를 필터링하는 방법을 보여줍니다 (이 기준이 필드 이름의 인수로 `filter()` 함수에 전달되는 방법에 유의하십시오. 일치 유형은 \*\*`team_level__exact`\*\*와 같이 이중 밑줄로 구분됩니다).

```python
## filename: views.py

from django.shortcuts import render
from .models import Team

def index(request):
    list_teams = Team.objects.filter(team_level__exact="U09")
    context = {'youngest\_teams': list_teams}
    return render(request, '/best/index.html', context)
```

이 함수는 `render()` 함수를 사용하여 브라우저로 다시 전송되는 `HttpResponse`를 만듭니다. 지정된 HTML 템플릿과 템플릿에 삽입할 일부 데이터( "컨텍스트"라는 변수에 제공)를 결합하여 HTML 파일을 생성합니다. 다음 섹션에서는 템플릿을 생성하기 위해 템플릿에 데이터를 삽입하는 방법을 보여줍니다.

### 데이터 렌더링 (HTML 템플릿)

템플릿 시스템을 사용하면 페이지가 생성될 때 채워질 데이터에 자리 표시자를 사용하여 출력 문서의 구조를 지정할 수 있습니다. 템플릿은 종종 HTML을 만드는 데 사용되지만 다른 유형의 문서를 만들 수도 있습니다. 장고는 기본 템플릿 시스템과 Jinja2라는 인기있는 파이썬 라이브러리를 모두 지원합니다 (필요한 경우 다른 시스템을 지원하도록 만들 수도 있음).

아래 코드는 이전 섹션의 `render()` 함수가 호출한 HTML 템플릿의 모양을 보여줍니다. 이 템플릿은 렌더링될 때 (위의 `render()` 함수 내의 컨텍스트 변수에 포함 된) "youngest\_teams"라는 목록 변수에 액세스할 수 있다는 가정하에 작성되었습니다. HTML 스켈레톤에는 먼저 youngest\_teams 변수가 있는지 확인한 후 `for` 루프에서 반복하는 표현식이 있습니다. 각 반복에서 템플리트는 각 팀의 `team_name` 값을 `<li>` 태그의 값으로 표시합니다.
```

## 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>
```

또 무엇을 할수 있나요?
-------------

이전 섹션에서는 거의 모든 웹 응용 프로그램에서 사용할 주요 기능인 URL 매핑, 뷰, 모델 및 템플릿을 보여줍니다. 추가로 장고가 제공하는 기능들은 다음과 같습니다.

* **양식** : HTML 양식은 서버에서 처리할 사용자 데이터를 수집하는 데 사용됩니다. 장고는 양식 작성, 유효성 검사 및 처리를 단순화합니다.
* **사용자 인증 및 권한** : 장고에는 보안을 염두에 두고 구축된 강력한 사용자 인증 및 권한 시스템이 포함되어 있습니다.
* **캐싱** : 컨텐츠를 동적으로 작성하는 것은 정적 컨텐츠를 제공하는 것 보다 많은 연산을 필요로 하기 때문에 느립니다. 장고는 유연한 캐싱을 제공하여 렌더링된 페이지 전체 또는 일부를 저장하여 필요할 때를 제외하고 다시 렌더링하지 않도록 할 수 있습니다.
* **관리 사이트** : 기본 스켈레톤을 사용하여 앱을 만들 때 장고 관리 사이트가 기본적으로 포함됩니다. 사이트 관리자가 사이트의 모든 데이터 모델을 작성, 편집 및 볼 수있는 관리 페이지를 쉽게 제공할 수 있습니다.
* **데이터 직렬화** : 장고를 사용하면 데이터를 XML 또는 JSON으로 직렬화하고 제공할 수 있습니다. 이 기능은 웹 서비스 (다른 응용 프로그램이나 사이트에서 사용하기 위해 순수하게 데이터를 제공하고 자체를 표시하지 않는 웹 사이트)를 만들거나 클라이언트 쪽 코드가 모든 데이터 렌더링을 처리하는 웹 사이트를 만들 때 유용할 수 있습니다.
요약하기
----

축하합니다. 이제 장고 여행의 첫발을 떼셨군요! 이제 우리는 장고의 주요 이점과 역사를 조금 알게됐고 장고 응용프로그램의 주요한 부분을 대략 이해했습니다. 또한 목록, 함수 및 클래스 구문을 포함하여 파이썬 프로그래밍 언어에 대해 몇 가지 사실을 배워야합니다.

위의 실제 장고 코드를 이미 보았지만 클라이언트 측 코드와 달리 실행하기 위해서는 개발 환경을 설정해야합니다. 그것이 우리의 다음 단계입니다.

* Overview: Django
* 다음


Django Tutorial Part 3: Using models - Web 개발 학습하기

Django Tutorial Part 3: Using models
====================================

803

* 이전
* Overview: Django
* 다음

이 문서에서는 LocalLibrary 웹사이트의 모델을 어떻게 정의할지 보여줄 것입니다. 모델이라는 것이 무엇인지, 어떻게 선언하는지, 그리고 주된 필드 타입들에 대해서 설명합니다. 그리고 모델의 데이터에 접근할 수 있는 몇몇 방법에 대해서 간단히 보여줄 것입니다.

|  |  |
| --- | --- |
| 사전 준비: | Django Tutorial Part 2: Creating a skeleton website. |
| 목표: | 적절한 필드를 사용하여 모델을 설계 및 생성할 줄 알기. |

개요
--

장고 웹 어플리케이션들은 모델이라는 파이썬 객체를 통해 데이터에 접속하고 관리합니다. 모델은 저장된 데이터의 구조를 정의합니다. 그것엔 필드 타입, 그리고 데이터의 최대 크기, 기본값, 선택 리스트 옵션, 문서를 위한 도움 텍스트, 폼을 위한 라벨 텍스트 등등이 있습니다. 모델의 정의는 기초 데이터베이스에 대해 독립적입니다 — 프로젝트 설정의 일부로 여러 옵션 중 하나를 선택할 수 있습니다. 사용할 데이터베이스를 정했다면 데이터베이스에 직접적으로 말할 필요가 없습니다 — 그저 모델 구조와 기타 코드를 작성하면, 장고가 데이터베이스와 소통하는 모든 더러운 작업을 대신해줍니다.

이 튜토리얼은 LocalLibrary website 예제에서 어떻게 모델을 정의하고 그것에 접근하는지에 대해 보여줍니다.

LocalLibrary models 디자인하기
-------------------------

모델을 코딩하기 전에, 우리가 어떤 데이터를 저장할 것인지, 그리고 다른 객체(object)들에 대한 관계를 어떻게 지정할 것인지 생각해 봅시다.

우리는 책에 관한 정보들을 저장할 필요가 있고 (제목, 요약, 저자, 작성된 언어, 분류, ISBN) 여러 개의 사본을 사용할 수 있어야 합니다(전 세계적으로 고유한 ID, 가용성 상태 등). 저자에 대해서 그들의 이름 뿐만 아니라 더 많은 정보를 저장해야 할 수도 있습니다. 여러 명의 같거나 비슷한 이름의 저자가 있을 수도 있기 때문이죠. 우리는 정보를 책 제목, 저자, 언어, 그리고 분류에 따라 정렬할 수 있기를 원합니다.

모델을 디자인할 때는 각각의 "객체(object: 관련된 정보의 모임)" 마다 분리된 모델을 가지는 것이 타당합니다. 이 예시에서 명백히 확인할 수 있는 객체(object)들은 책, 책 인스턴스, 저자입니다.

선택을 웹사이트 자체에 하드코딩하는 것 보다는 모델을 사용해서 선택-리스트 옵션을 나타내도록(예시: 드롭 다운 목록)할 수 있습니다 — 이것은 모든 옵션들을 미리 알 수 없거나 옵션들이 변할 수 있을 때에 추천됩니다. 이 경우에 명백한 모델의 후보자로 책의 장르(예시: 공상 과학, 프랑스 시, 등등)와 언어(영어, 프랑스어, 한국어)가 있습니다.

우리가 우리의 모델과 필드를 결정하고 나면, 우리는 그 관계에 대해서 생각해야 합니다. 장고는 당신이 그 관계를 다음과 같이 세 가지로 설정할 수 있게 하는데, 일대일(`OneToOneField`), 일대다(`ForeignKey`), 다대다(`ManyToManyField`) 관계가 그것입니다.

그것들을 염두에 두고 아래의 UML 관계 다이어그램을 살펴봅시다. 이 다이어그램은 우리가 이 예시에서 정의할 모델들을 상자로 보여줍니다. 위에서 살펴본 바와 같이, 우리는 책(Book, 책의 일반적인 세부 사항들), 책 인스턴스(BookInstance, 시스템에서 사용 가능한 책의 특정한 물리적 복사본의 상태), 그리고 저자(Author)를 모델로 생성했습니다. 우리는 또한 장르(Genre)에 대한 모델을 만들어서 값들이 관리자 인터페이스에서 생성/선택이 가능하도록 만들었습니다. 우리는 `BookInstance:status`에 대한 모델을 생성하지 않았습니다 — 값들을(`LOAN_STATUS`) 하드코딩 했죠. 왜냐하면 그것은 변하지 않는 값들이기 때문입니다. 각각의 상자 안에서 모델 이름, 필드 이름과 타입, 그리고 또한 함수(method)와 그들의 반환 타입(return type)을 확인할 수 있습니다.

이 다이어그램은 또한 다중도(multiplicities)을 포함한 모델 간의 관계를 보여줍니다. 다중도(multiplicities)는 관계 안에 존재하는 각각의 모델의 숫자(최대 그리고 최소)를 보여주는 다이어그램 위의 숫자입니다. 예를 들어, 상자 사이를 연결하는 선은 책과 장르가 연관되어 있다는 것을 보여줍니다. 장르(Genre) 모델에 가까이 있는 숫자들은 책이 하나 또는 그 이상의 장르(원하는 만큼 많이)를 가지고 있어야 함을 보여주는 반면, 선의 반대편 끝에 있는 책(Book) 모델 옆의 숫자들은 장르 모델이 0 또는 여러 개의 관련된 책 모델을 가질 수 있음을 보여줍니다 .

![LocalLibrary Model UML](/ko/docs/Learn/Server-side/Django/Models/local_library_model_uml.png)

**참고:** **주의**: 다음 섹션은 모델이 어떻게 정의되고 사용되는지에 대한 초보적인 설명입니다. 읽으면서 위 다이어그램의 각각의 모델들을 어떻게 구성할 지 생각해 보십시오.

모델 입문서
------

이 부분에서는 어떻게 모델을 정의하는지, 그리고 더 중요할 지도 모르는 필드와 필드의 인자에 대해서 간단한 개요를 제공합니다.

### 모델의 정의

모델들은 보통 어플리케이션의 **models.py** 파일에서 정의됩니다. 이들은 `django.db.models.Model`의 서브 클래스로 구현되며 필드, 메소드 그리고 메타데이터를 포함할 수 있습니다. 아래의 코드 조각은 `MyModelName`라고 이름지어진 "전형적인" 모델을 보여줍니다:

```python
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
```

아래 섹션에서 모델 안에 있는 각각의 요소들을 세부적으로 다뤄봅시다:

#### 필드(Fields)

모델은 모든 타입의, 임의의 숫자의 필드를 가질 수 있습니다 — 각각의 필드는 우리의 데이터베이스 목록(table)에 저장하길 원하는 데이터 열(column)을 나타냅니다. 각각의 데이터베이스 레코드(행, row)는 각 필드 값들 중 하나로 구성되어 있습니다. 위의 예제를 살펴봅시다:

```python
my_field_name = models.CharField(max_length=20, help_text='Enter field documentation')
```

위 예제는 `my_field_name`이라는 하나의 필드를 가지고 있고, `models.CharField` 타입입니다 — 즉, 이 필드가 영숫자(alphanumeric) 문자열을 포함한다는 뜻이죠. 필드 타입들은 특정한 클래스들을 사용하여 등록되며, HTML 양식(form)에서 값을 수신할 때 사용할 유효성 검증 기준과 함께 데이터베이스에 데이터를 저장하는데 사용되는 레코드의 타입을 결정합니다. 또한 필드 타입은 필드가 어떻게 저장되고 사용될지 지정하는 인수를 사용할 수 있습니다. 이 예제에서는 필드에 두 가지 인수를 줍니다:

* `max_length=20` — 이 필드 값의 최대 길이는 20자임을 알립니다.
* `help_text='Enter field documentation'` — 이 값이 HTML 양식(form)에서 사용자들에게 입력될 때 어떤 값을 입력해야 하는지 사용자들에게 알려주기 위해 보여주는 텍스트 라벨을 제공합니다.

필드 이름은 쿼리 및 탬플릿에서 이를 참조하는데 쓰입니다. 필드는 또한 인수로 지정된 라벨(`verbose_name`)을 가지고 있거나, 또는 필드 변수 이름의 첫자를 대문자로 바꾸고 밑줄을 공백으로 바꿔서 기본 라벨을 추정할 수 있습니다(예를 들어 `my_field_name` 은 My field name을 기본 라벨로 가지고 있습니다) .

필드가 선언된 순서는 모델이 폼에서 렌더링 된다면(예시 : 관리자 사이트) 기본 순서에 영향을 미치지만, 이것은 재정렬될 수 있습니다.

##### 일반적(common) 필드 인수

아래의 일반적인 인수들은 많은/거의 대부분의 서로 다른 필드 타입들을 선언할 때 사용할 수 있습니다:

* help\_text: 위에서 다뤘던 것 처럼, HTML 양식(form)에 대해 텍스트 라벨을 제공합니다 (예시 : 관리자 사이트).
* verbose\_name: 필드 라벨 안에서 사용되는 인간이 읽을 수 있는 필드 이름입니다. 지정되지 않았다면, 장고가 기본 verbose\_name을 필드 이름으로부터 유추합니다.
* default: 필드를 위한 기본값입니다. 이것은 값 또는 호출 가능한 객체일 수 있습니다. 이때 객체는 새로운 레코드가 생성될 때 마다 호출됩니다.
* null: 만약 `True`라면, 장고는 빈 `NULL` 값을 필드를 위한 데이터베이스에 저장할 것입니다(`CharField`는 대신 빈 문자열을 저장할 것입니다). 기본값은 `False`입니다.
* blank: 만약 `True`라면, 필드는 양식(form) 안에서 비워두는 것이 허락됩니다. 기본값은 `False`이며, 이것은 장고의 양식(form) 검증이 값을 입력하도록 강제한다는 뜻입니다. 이것은 종종 `null=True`와 함께 사용됩니다. blank 값을 허락할 때, 데이터베이스에서도 공백값을 적절하게 표시할 수 있어야 하기 때문입니다.
* choices: 필드를 위한 선택들의 모임입니다. 이 인수가 제공된다면, 대응하는 기본 양식(form) 위젯은 표준 텍스트 필드가 아닌 이 선택 항목을 가진 선택 상자입니다.
* primary\_key: 만약 `True`라면, 현재 필드를 모델의 primary key로 설정합니다(primary key는 모든 다른 테이블 레코드들을 고유하게 확인하도록 지정된 특별한 데이터베이스 열입니다). primary key로 지정된 필드가 없다면 장고가 자동적으로 이 목적의 필드를 추가합니다.

다른 많은 옵션들이 있습니다 — 여기(full list of field options here)에서 모든 필드 옵션 목록을 볼 수 있습니다.

##### 일반적인(common) 필드 타입

아래의 목록은 일반적으로 사용되는 필드 타입들을 보여줍니다.

* CharField는 작거나 중간 크기의 고정된 길이의 문자열을 정의할 때 사용합니다. 저장되기 위해서는 데이터의 최대 길이(`max_length`)를 정해주어야 합니다.
* TextField는 임의의 긴 문자열에 사용됩니다. 필드의 최대 길이(`max_length`)를 지정해야 할 수도 있지만, 그것은 필드가 양식(form) 안에 표시될 때만 지정하면 됩니다(데이터베이스 레벨에서 강제되지 않습니다).
* IntegerField은 정수값(모든 숫자)을 저장하는 필드입니다. 그리고 양식(form)에 입력된 값이 정수임을 검증하기도 합니다.
* DateField와 DateTimeField는 날짜와 날짜시간 정보를 저장, 표현하는데 사용됩니다 (각각 파이썬 `datetime.date`와 `datetime.datetime` 객체로). 이 필드들은 추가적으로 (서로 독점적인) `auto_now=True` (모델이 저장될 때 마다 필드를 현재 날짜로 설정하기 위해), `auto_now_add` (모델이 처음 생성되었을 때만 날짜를 설정하기 위해) , 그리고 `default` (사용자에 의해 변경될 수 있는 기본 날짜를 설정하기 위해) 매개 변수를 선언할 수 있습니다.
* EmailField는 이메일 주소를 저장하고 검증하기 위해 사용합니다.
* FileField와 ImageField는 각각 파일과 이미지를 업로드하기 위해 사용됩니다 (`ImageField`는 단지 업로드된 파일이 이미지임을 확인하는 추가 검증을 더할 뿐입니다). 이것들은 업로드된 파일들이 어디에 어떻게 저장되는지 정의하는 매개 변수를 가집니다.
* AutoField는 자동적으로 증가하는 `IntegerField`의 특별한 타입입니다. 이 타입의 primary key는 명시적으로 지정하지 않는 이상 모델에 자동적으로 추가됩니다.
* ForeignKey는 다른 데이터베이스 모델과 일대다 관계를 지정하기 위해 사용됩니다 (예시: 차는 하나의 제조사를 갖고 있지만 제조사는 많은 차들을 만들 수 있습니다). 일대다에서 "일"쪽이 key를 포함하는 모델입니다.
* ManyToManyField는 다대다 관계를 지정하기 위해 사용됩니다 (예시: 책은 여러 장르를 가질 수 있고, 각각의 장르에도 많은 책들이 있습니다). 우리 예제인 도서관 어플리케이션에서는 이 필드를 `ForeignKeys`와 매우 유사하게 사용할 겁니다. 하지만 그룹 사이의 관계를 보여주기 위해서는 더욱 복잡한 방식으로 사용될 수 있습니다. 이것은 레코드가 삭제됐을 때 어떤 일이 일어나는지 정의하기 위해 `on_delete` 매개변수를 가집니다 (예시: `models.SET_NULL`의 값은 단순히 `NULL`값으로 설정될 겁니다).

다른 많은 타입의 필드들이 많이 있습니다. 서로 다른 타입의 숫자를 위한 필드 (큰 정수, 작은 정수, 부동소수(float)), 불리언(booleans), URL, slug, unique id, 그리고 다른 "시간-관련된" 정보들(duration, time, 등등)들을 포함해서요. 모든 목록을 여기(full list here)에서 확인할 수 있습니다.

#### 메타데이터

아래와 같이 `class Meta`를 선언하여 모델에 대한 모델-레벨의 메타데이타를 선언할 수 있습니다.

```python
class Meta:
    ordering = ['-my\_field\_name']
```

이 메타데이터의 가장 유용한 기능들 중 하나는 모델 타입을 쿼리(query)할 때 반환되는 기본 레코드 순서를 제어하는 것입니다. 이렇게 하려면 위와 같이 필드 이름 목록의 일치 순서를 `ordering` 속성에 지정해야 합니다. 순서는 필드의 타입에 따라 달라질 것입니다(문자 필드는 알파벳 순서에 따라 정렬될 것이고, 반면 날짜 필드는 날짜순으로 정렬될 것입니다). 위와 같이, 반대로 정렬하고 싶다면 마이너스 기호(-)를 필드 이름 앞에 접두사로 붙이면 됩니다.

예를 들어, 우리가 기본적으로 아래와 같이 책들을 정렬하려고 한다면:

```python
ordering = ['title', '-pubdate']
```

책들은 A-Z까지 알파벳 순으로 정렬되고, 그 후에는 제목(title) 안에 있는 발행일 별로 가장 최근 것부터 가장 오래된 것 순으로 정렬될 것입니다.

다른 일반적인(common) 속성은 `verbose_name`이며, 단일 및 복수 형식(form)의 클래스를 위한 자세한(verbose) 이름입니다:

```python
verbose_name = 'BetterName'
```

다른 유용한 속성들은 모델을 위한 새로운 "접근 권한"을 생성 및 적용 가능하게 하며(기본 권한은 자동적으로 적용됨), 다른 필드에 기반한 순서 정렬을 허용하거나, 또는 클래스가 "추상(abstract: 레코드를 생성할 수 없고, 대신 다른 모델들을 만들기 위해 파생되는 기본 클래스)"임을 선언할 수 있습니다.

여러가지 메타데이터 옵션들은 모델에 무슨 데이터베이스를 사용해야만 하는가 그리고 데이터가 어떻게 저장되는가를 제어한다 (이것들은 모델을 기존 데이터베이스에 매핑할 때만 유용하다).

메타데이터 옵션의 모든 목록은 여기에서 볼 수 있습니다: Model metadata options (장고 문서).

#### 메소드(Methods)

모델은 또한 메소드를 가질 수 있습니다.

**`최소한, 모든 모델마다 표준 파이썬 클래스의 메소드인 __str__()` 을 정의하여 각각의 object가 사람이 읽을 수 있는 문자열을 반환(return)하도록 합니다.** 이 문자열은 관리자 사이트에 있는 개별적인 레코드들을 보여주는 데 사용됩니다(그리고 모델 인스턴스를 참조해야 하는 다른 모든 곳에도요). 종종 이것은 모델에서 제목(title)이나 이름 필드(name field)를 반환할 것입니다 .

```python
def \_\_str\_\_(self):
    return self.field_name
```

장고 모델에 포함할 다른 일반적인 메소드는 `get_absolute_url()`입니다. 웹사이트의 개별적인 모델 레코드들을 보여주기 위한 URL을 반환하는 메소드입니다(만약 이 메소드를 정의했다면 장고는 관리자 사이트 안의 모델 레코드 수정 화면에 "View on Site" 버튼을 자동적으로 추가할 것입니다). 아래에서 `get_absolute_url()`의 표준적인 사용을 볼 수 있습니다.

```python
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)])
```

**참고:** **주의**: 모델의 개별적인 레코드들을 보여주기 위해서 `/myapplication/mymodelname/2` 와 같은 URL을 사용한다고 가정한다면("2"는 특정한 레코드를 위한 `id` 입니다), 응답과 id를 "모델 디테일 뷰(model detail view)"에 전달하기 위해 (레코드를 표시하기 위한 작업을 할) URL 매퍼를 만들 필요가 있습니다 . 위의 `reverse()` 함수는 알맞은 포맷의 URL을 생성하기 위해서 URL 매퍼를(위 경우에선 'model-detail-view'라고 명명됨) "반전" 시킬 수 있습니다.

물론 이것이 작동하기 위해선 URL 매핑, 뷰, 그리고 탬플릿을 작성해야 합니다!

또한 원하는 메소드를 정의해서 (그들이 어떤 변수도 가지고 있지 않다면) 코드나 탬플릿에서 불러올 수 있습니다.

### 모델 관리(management)

모델 클래스들을 정의한 이후엔 클래스들을 사용해서 레코드들을 생성, 업데이트, 또는 삭제할 수 있고, 모든 레코드 또는 레코드의 특정 하위 집합을 가져오기 위해 쿼리를 실행할 수 있습니다. 튜토리얼에서 뷰를 정의할 때 그 방법을 보여줄 것이지만, 아래에 간략한 요약이 있습니다.

#### 레코드의 생성과 수정

레코드를 생성하려먼 모델의 인스턴스를 정의하고 `save()`를 호출할 수 있습니다.

```python
# Create a new record using the model's constructor.
record = MyModelName(my_field_name="Instance #1")

# Save the object into the database.
record.save()
```

**참고:** 만약 당신이 어떤 필드도 `primary_key`를 선언하지 않았다면, 새로운 레코드는 자동적으로 `id`라는 필드 이름을 가진 `primary_key`가 주어지게 됩니다. 위의 레코드를 저장한 후 이 `id` 필드를 쿼리할 수 있는데, 1의 값을 가질 겁니다.

이 새로운 레코드 안의 필드에 점 구문(.)을 사용해서 접근하여 값을 변경할 수 있습니다. 수정된 값들을 데이터베이스에 저장하기 위해 `save()`를 호출해야 합니다.

```python
# 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()
```

#### 레코드 검색하기

모델의 객체(`objects`) 속성(기본 클래스에서 제공됨)을 사용하여 특정 기준과 일치하는 레코드를 검색할 수 있습니다.

**참고:** **주의:** "추상(abstract)" 모델과 필드 이름을 사용하여 레코드들을 검색하는 방법을 설명하는 것은 조금 혼란스러울 수 있습니다. 아래에서는 `title`과 `genre` 필드가 있는 `Book` 모델을 참조하겠습니다. 이 때 `genre`는 또한 `name`이라는 단일 필드를 가지고 있는 모델입니다.

우리는 `objects.all()`을 사용하여 모델의 모든 레코드들을 `QuerySet`으로 가져올 수 있습니다. `QuerySet`은 반복가능한(iterable) 객체이며, 이것은 반복/루프할 수 있는 많은 객체들을 포함하고 있다는 의미입니다.

```python
all_books = Book.objects.all()
```

장고의 `filter()`는 반환된 `QuerySet`이 지정된 문자(**text**) 또는 숫자(**numeric**) 필드를 특정한 기준에 맞추어 필터링할 수 있게 합니다. 예를 들어서, "wild"를 제목 안에 포함하는 책들을 필터링하여 그 숫자를 세어 보려면, 아래와 같이 하면 됩니다.

```python
wild_books = Book.objects.filter(title__contains='wild')
number_wild_books = Book.objects.filter(title__contains='wild').count()
```

기준이 될 필드와 타입은 필터 매개 변수 이름에서 정의됩니다. 다음 포맷을 이용해서요: `field_name__match_type` (주의: 위의 `title`과 `contains` 사이 밑줄은 두 개입니다). 위에서 우리는 대소문자를 구분하여 `title`을 필터링합니다. 다른 많은 유형의 일치 방법이 있습니다: `icontains`(대소문자를 구분하지 않음), `iexact`(대소문자를 구분하지 않는 정확히 일치), `exact`(대소문자를 구분하는 정확한 일치) 그리고 `in`, `gt`(보다 더 큰(greater than)), `startswith` 등등이 있습니다. 모든 일치방법 목록은 여기(full list is here) 있습니다.

어떤 경우엔 일대다 관계를 다른 모델에 정의하는 필드를 필터링해야 할 때도 있습니다(예:`ForeignKey`). 이 경우에 추가적인 이중 밑줄을 사용하여 관련 모델 안의 필드에 "색인(index)"할 수 있습니다. 예를 들어 특정한 장르 패턴을 가진 책들을 필터링하려면, 아래와 같이 `genre` 필드를 통해서 `name`에 색인(index)해야 할 겁니다.

```python
# Will match on: Fiction, Science fiction, non-fiction etc.
books_containing_genre = Book.objects.filter(genre__name__icontains='fiction')
```

**참고:** **주의**: 밑줄(\_\_)을 사용하여 원하는 만큼 다양한 레벨의 관계(`ForeignKey`/`ManyToManyField`)를 탐색할 수 있습니다. 예를 들어서 추가적인 "cover" 관계를 사용하여 정의된 다른 타입의 `Book`은 다음과 같은 매개 변수 이름을 가질겁니다 :`type__cover__name__exact='hard'.`

관련된 모델의 역방향 검색, 필터 변경, 값의 더 작은 집합 반환하기 등 쿼리로 할 수 있는 일들은 더욱 많이 있습니다. 더 많은 정보를 보려면 Making queries (장고 문서)를 참고하세요.

LocalLibrary 모델 정의하기
--------------------

이 섹션에서는 도서관을 위한 모델을 정의하기 시작할 겁니다. models.py ( /locallibrary/catalog/에 있음)파일을 여세요. 페이지 상단의 표준 코드(boilerplate)는 우리들의 모델이 상속받을 모델 기본 클래스 `models.Model`을 포함하는 models 모듈을 가져옵니다.

```python
from django.db import models

# Create your models here.
```

### 장르(Genre) 모델

아래의 장르(`Genre`) 모델 코드를 복사해서 `models.py` 파일에 붙여넣기 하세요. 이 모델은 책 카테고리에 관한 정보를 저장하는데 사용됩니다 — 예를 들어 소설인지, 논픽션인지, 로맨스인지 군사 역사물인지 등등. 위에서 말했던 것 처럼, 우리는 장르를 자유 텍스트나 선택 목록으로 만들지 않고 모델을 이용해 만들었습니다. 가능한 값들이 하드코딩되기 보다는 데이터베이스를 통해 관리되도록 하기 위해서입니다.

```python
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
```

`모델은 하나의 CharField` 필드(`name`)을 가지고 있습니다. 이것은 장르의 이름을 나타냅니다 . 이것은 200자로 제한되어 있고 `help_text`를 갖고 있습니다. 모델의 마지막에서 우리는 `__str__()` 메소드를 선언합니다. 이 메소드는 특정한 레코드에 의해 정의된 장르의 이름을 단지 반환합니다. 아무런 자세한 이름(verbose name)도 정의되지 않았기 때문에, 필드는 폼(form)에서 `Name`으로 호출(call)될 겁니다.

### 책(Book) 모델

아래의 책(Book) 모델을 복사해서 파일의 아래에 붙여넣기 하세요. 책 모델은 일반적으로 사용 가능한 책에 대한 모든 정보들을 보여주지만, 대여 가능한 특정한 물리적 "인스턴스(instance)"나 "복사본(copy)"은 보여주지 않습니다. 모델은 `CharField`를 사용하여 책의 `title`과 `isbn`을 나타냅니다(`isbn`이 이름을 지정하지 않은 첫 번째 매개변수를 사용하여 라벨을 "ISBN"으로 지정한 것에 주목하세요. 만약 그러지 않았다면 기본 라벨은 "Isbn"이었을 것입니다). 텍스트가 상당히 길 것이기 때문에 `summery`에는 `TextField`를 사용합니다.

```python
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)])
```

장르는 책이 여러 개의 장르를 가지고, 장르도 여러 개의 책을 가질 수 있는 다대다 필드(`ManyToManyField`)입니다. 저자는 `ForeignKey` 로 선언됩니다. 따라서 각각의 책은 하나의 저자만 가질 수 있지만, 저자는 여러 개의 책들을 가질 수 있습니다(실제로는 책이 여러 명의 작가를 가질 수 있지만, 이 구현에서는 아닙니다!).

두 필드 타입들 안에서 관련된 모델 클래스는 모델 클래스나 관련된 모델의 이름을 포함하는 문자열을 사용하여 이름없는 첫 번째 매개 변수로 선언됩니다. 연관된 클래스가 참조되기 전에 파일 안에서 아직 정의되지 않았다면 모델의 이름을 문자열로 사용해야 합니다! 저자 ( `author` )필드에서 관심을 가져야 할 다른 변수는 `null=True`와 `on_delete=models.SET_NULL`입니다. `null=True`는 어떤 저자도 선택되지 않았다면 데이터베이스에 `Null` 값을 저장하도록 하고, `on_delete=models.SET_NULL`은 관련된 저자(author) 레코드가 삭제되었을 때 저자(author)의 값을 `Null`로 설정할 겁니다.

모델은 또한 `Book` 레코드를 나타내는 책의 `title` 필드를 사용하여 `__str__()` 를 정의합니다. 마지막 메소드 `get_absolute_url()` 은 이 모델의 세부 레코드에 접근하는 데에 사용될 수 있는 URL을 반환합니다 (이것이 작동하도록 하기 위해선 `book-detail`이라는 이름의 URL 매핑을 정의하고, 관련 뷰와 탬플릿을 정의해야 합니다).

### 책 인스턴스(BookInstance) 모델

다음으로, `BookInstance` 모델(아래에 있는)을 다른 모델들 아래에 복사하세요. `BookInstance` 은 누군가 빌릴지도 모를 특정한 책의 복사본을 나타냅니다. 그리고 복사본이 사용 가능한지 여부, 언제 되돌려받을 수 있을지, "출판사(imprint)" 또는 버전 세부 사항, 그리고 도서관 안에 있는 책의 고유 id에 대한 정보를 포함합니다.

이제 몇몇 필드와 메소드는 친숙할 겁니다. BookInstance 모델은 아래를 사용합니다

* `ForeignKey` : 연관된 `Book` 을 식별하기 위해(각각의 책은 많은 복사본을 가질 수 있지만, 복사본은 하나의 `Book`만을 가질 수 있음).
* `CharField` : 책의 출판사(imprint)(특정한 발간일)을 나타내기 위해.

```python
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})'
```

추가적으로 몇 가지 새로운 타입의 필드를 선언합니다:

* `UUIDField` 는 `id` 필드가 이 모델의 `primary_key` 로 설정되는 데 사용됩니다. 이 타입의 필드는 각 인스턴스에 전역적으로 고유한 값을 할당합니다 (도서관에서 찾을 수 있는 모든 책 마다 하나씩).
* `DateField` 는 `due_back` (만기일) 날짜에 사용됩니다 (책이 빌려지거나 유지 보수된 이후 사용할 수 있을 것으로 예상되는 날짜). 이 값은 `blank` 나 `null` 이 될 수 있습니다(책을 사용할 수 있는 경우 필요). 메타데이터 모델 (`Class Meta`) 은 레코드들이 쿼리에서 반환되었을 때 레코드들을 정렬하기 위해서 이 필드를 사용합니다 .
* `status` 는 선택/선택 목록(choice/selection list)을 정의하는 `CharField` 입니다. 보시다시피, 우리는 열쇠-값(key-value) 쌍의 튜플(tuple)을 포함하는 튜플을 정의해서 choices 인자에 전달합니다. 열쇠/값(key/value) 쌍에서 값(value)은 사용자가 선택할 수 있는 표시값인 반면, 열쇠(key)는 그 옵션이 선택되었을 때 실제로 저장되는 값입니다. 또한 책이 선반에 저장되기 전에는 사용할 수 없으므로 기본값인 'm'(유지 관리, maintenanace)을 설정했습니다.

모델 `__str__()` 은 그것의 고유한 id 그리고 연관된 `Book`의 제목을 조합하여 `BookInstance` 객체를 나타냅니다.

**참고:** **주의**: 조금의 파이썬:

* 파이썬 3.6에서부터 문자열 보간 구문을 쓸 수 있습니다 (f-strings로 잘 알려진): `f'{self.id} ({self.book.title})'`.
* 이 튜토리얼의 옛 버전에서는 formatted string 구문을 사용했고, 이것 또한 파이썬 안에 문자열을 형식화(formatting)하는 유효한 방법입니다 (예 : `'{0} ({1})'.format(self.id,self.book.title)`).

### 저자(Author) 모델

**models.py** 안에 작성된 코드 아래에 `Author` 모델을 복사 붙여넣기 하세요 (아래에 있습니다).

이제 모든 필드/메소드들이 익숙할 겁니다. 모델은 저자를 이름(first name), 성(last name), 생일, 그리고 (선택적으로) 사망일을 가진다고 정의합니다. 기본적으로 `__str__()` 는 name을 첫째로 성(last name), 그 다음 이름(first name)이 오는 순서로 반환합니다. `get_absolute_url()` 메소드는 개별 저자를 나타내기 위한 URL을 가져오기 위해 `author-detail` URL 매핑을 반대로 합니다.

```python
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}'
```

데이터베이스 마이그레이션 재실행하기
-------------------

모든 모델이 생성되었습니다. 이제 데이터베이스 migration을 재실행하여 모델들을 데이터베이스에 추가하세요.
```

python3 manage.py makemigrations
python3 manage.py migrate
```

도전과제 - 언어(Language) 모델
----------------------

지역 후원자가 다른 언어로 저술된 새로운 책들을 후원한다고 생각해 보세요(아마도, 프랑스어?). 도전과제는 이것이 도서관 웹사이트에서 이것을 가장 잘 나타낼 수 있는 방법을 찾아내고, 모델에 추가하는 것입니다.

고려해야 할 사항들:

* "언어"(language)는 `Book`, `BookInstance`, 아니면 어떤 다른 객체와 연관되어야 할까요?
* 서로 다른 언어들은 모델로 나타내어야 할까요? 아니면 자유 텍스트 필드?그것도 아니라면 하드-코딩된 선택 리스트로 나타내어야 할까요?

결정을 내린 후에, 필드를 추가하세요. 우리가 선택한 것은 여기(here) 깃허브에서 볼 수 있습니다.

요약
--

이 글에서 우리는 모델이 어떻게 정의되는지 배웠고, 이 정보를 지역 도서관 웹사이트를 위한 적절한 모델을 설계하고 구현하기 위해 사용했습니다.

At this point we'll divert briefly from creating the site, and check out the *Django Administration site*. 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.

See also
--------

* Writing your first Django app, part 2 (Django docs)
* Making queries (Django Docs)
* QuerySet API Reference (Django Docs)
* 이전
* Overview: Django
* 다음


Django Tutorial Part 6: Generic list and detail views - Web 개발 학습하기

Django Tutorial Part 6: Generic list and detail views
=====================================================

* 이전
* Overview: Django
* 다음

이 튜토리얼은 LocalLibrary website에 책과 저자의 목록과 세부 페이지를 추가 하여 확장할 것입니다. 이 글에서 우리는 제네릭 클래스-기반 뷰(generic class-based views)에 대해 배울 것이며, 그것이 일반적인 사용 사례를 위해 작성해야 하는 코드들을 줄이는 방법을 보여줄 것입니다. 우리는 또한 URL 처리에 대해 더 세부적으로 알아볼 것이며, 기본 패턴 매칭(basic pattern matching)을 수행하는 방법을 보여줄 것입니다.

|  |  |
| --- | --- |
| 사전 준비: | Django Tutorial Part 5: Creating our home page를 포함한 모든 이전 튜토리얼을 완료하세요. |
| 목표: |
 제네릭 클래스-기반 뷰를 어디 그리고 어떻게 사용하는지, 그리고 어떻게
 URL들로부터 패턴들을 추출하여 정보를 view들에게 전달하는지에 대해
 이해하기.
  |

개요
--

이 튜토리얼에서 우리는 책과 저자에 대한 목록과 세부 페이지(detail page)를 추가하여 LocalLibrary 웹사이트의 첫 번째 버전을 완성할 것입니다(더 정확하게는, 우리는 책 페이지들을 구현하는 방법을 보여주고, 저자 페이지는 스스로 완성하도록 할 것입니다!)

이 과정은 이전 튜토리얼에서 보여준, 색인 페이지(index page)를 만드는 과정과 비슷합니다. 우리는 여전히 URL 맵들, view들, 그리고 탬플릿들을 만들어야 합니다. 주요한 차이점은 세부 페이지(detail pages)에서, 우리는 URL 안의 패턴에서 정보를 추출해서 view로 전달하는 추가적인 도전을 가진다는 점입니다.

이 튜토리얼의 마지막에서는 제네릭 클래스-기반 뷰를 사용할 때 데이터의 페이지를 매기는 방법을 보여드리겠습니다.

책 목록 페이지
--------

책 목록 페이지는 모든 사용 가능한 책 레코드들의 목록을 페이지 안에 나타낼 것이며, 다음 URL을 사용하여 접근합니다: `catalog/books`/. 이 페이지는 각 레코드마다 제목과 저자를 나타낼 것이며, 제목은 관련된 책 페이지로 이동하는 하이퍼링크 처리됩니다. 이 페이지는 사이트의 다른 페이지들과 같은 구조와 내비게이션을 가지고 있어서, 우리는 이전 튜토리얼에서 만들었던 템플릿(**base\_generic.html**)을 확장해서 사용하면 됩니다.

### URL mapping

**/catalog/urls.py** 파일을 열고 아래 코드박스의 굵은 글씨를 복사해 붙여넣으세요. 인덱스 페이지처럼 `path()` 함수는 URL('**books/'**)과 매치되는 패턴, URL이 매치될 때 호출되는 view 함수`(views.BookListView.as_view())`, 그리고 이 특정 매핑에 대한 이름을 정의합니다.

```python
urlpatterns = [
    path('', views.index, name='index'),
    path('books/', views.BookListView.as_view(), name='books'),
]
```

이전 튜토리얼에서 URL은 이미 `/catalog` 와 매치되었을 것입니다. 그래서 사실상 view는 `/catalog/books/` URL에 접속하면 호출됩니다.

View함수는 이전과 다른 형태를 가집니다. 왜냐면 이 view는 사실 클래스로 구현이 되기 때문입니다. 우리는 이 view를 직접 구현하지 않고, 이미 존재하는 generic view 함수를 상속받아 view 함수를 구현할 것입니다. 이 generic view 함수는 우리가 구현하고 싶은 기능들을 거의 다 가지고 있습니다.

Django의 클래스 기반 view에서는, `as_view()`클래스 메소드를 호출해 적절한 view 함수에 접근할 수 있습니다. 이건 클래스의 인스턴스를 생성하는 작업과 모든 HTTP 요청을 처리하는 핸들러 메소드가 제대로 동작하는지를 처리합니다.

### 뷰(클래스 기반)

표준 function으로 우리는 꽤나 쉽게 book list view를 만들 수 있는데 (마치 이전 우리의 index view같이), 모든 책에 대한 데이터 베이스 쿼리를 만들어서 `render()` 함수를 불러 특정 템플릿에 리스트를 보냅니다. 대신 우리는 class-based generic list view (`ListView`) 를 사용하는데— 존재하는 뷰로부터 상속받아온 클래스입니다. generic view가 이미 우리가 필요한 대부분의 기능성을 실행하면서 동시에 Django best-practice이기 때문에, 우리는 코드의 양과 반복을 줄이고 궁극적으로 유지보수에 수고가 덜 드는 견고한 리스트 뷰를 만들 수 있습니다.

**catalog/views.py** 파일을 열고, 아래의 코드를 views.py 파일 가장 아래에 붙여넣기하세요.

```python
from django.views import generic

class BookListView(generic.ListView):
    model = Book
```

이게 전부입니다! Generic view는 명시된 모델(Book)의 모든 레코드를 가져오기 위해 데이터베이스를 쿼리할 것이고, **/locallibrary/catalog/templates/catalog/book\_list.html**(아래에서 만들 예정)경로에 있는 템플릿을 렌더링합니다. 템플릿 안에서 우리는 `object_list`나 `book_list`라는 템플릿 변수를 사용해 도서 목록에 접근할 수 있습니다. (일반적으로 "`the_model_name_list`").

**참고:** 이 어색한 템플릿 경로는 오타가 아닙니다. Generic views는 `/application_name/the_model_name_list.html`(지금 상황에서는 `catalog/book_list.html`)에서 템플릿을 찾습니다. 이 경로는 애플리캐이션의 `/application_name/templates/` 디렉토리 안에 있습니다(`/catalog/templates/)`.

속성이나 디폴트 동작을 추가할 수도 있습니다. 예를 들어, 같은 모델을 사용하지만 여러 개의 뷰를 사용해야 되는 경우 다른 템플릿 파일을 명시할 수 있습니다. 혹은 `book_list` 템플릿 변수명이 직관적이지 않다고 생각해 다른 템플릿 변수명을 사용하고 싶을지도 모릅니다. 아마 가장 유용한 바리에이션은 리턴 값의 결과를 바꾸거나 필터링하는 것입니다. 따라서 모든 책을 나열하는 대신, 유저가 읽은 순으로 5개의 책을 나열할 수도 있습니다.

```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
```

#### 클래스 기반 뷰의 메소드 오버라이딩

굳이 여기서 할 필요는 없지만, 클래스 메소드 오버라이딩을 할 수도 있습니다.

예를 들어, 우리는 `get_queryset()`메소드를 오버라이딩해 반환되는 레코드의 리스트들을 바꿀 수 있습니다. 이건 우리가 이전에 했던 `queryset` 속성을 지정하는 방법보다 더 유연한 방법입니다. (비록 지금 상황에서 별 이득이 되지는 않지만요.)

```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
```

우리는 템플릿에 추가적인 컨텍스트 변수들을 전달하기 위해 `get_context_data()`를 오버라이딩할 수도 있습니다. (도서 목록이 디폴트로 전달됩니다.) 아래의 코드는 `some_data` 라는 이름의 변수를 어떻게 컨텍스트에 추가하는지를 보여줍니다. (이렇게 하면 템플릿 변수로 사용할 수 있습니다.)

```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
```

이렇게 할 때에는, 아래의 패턴을 따르는 것이 중요합니다:

* 첫째로, 슈퍼클래스에서 기존 컨텍스트를 가져옵니다.
* 그리고 새로운 컨텍스트 정보를 추가합니다.
* 마지막으로 새롭게 업데이트된 컨텍스트를 리턴합니다.

**참고:** Built-in class-based generic views (Django docs)를 방문해 다양한 예제를 살펴볼 수 있습니다.

### 리스트 뷰 템플릿 생성하기

**/locallibrary/catalog/templates/catalog/book\_list.html** 경로에 HTML 파일을 만든 다음, 아래의 코드를 복사, 붙여넣기 하세요. 이전에 설명한 것처럼, 이건 제네릭 클래스 기반 리스트 뷰에서 예상되는 기본 템플릿 파일입니다. (`catalog` 애플리케이션 내의 `Book` 모델)

제네릭 뷰의 템플릿은 다른 템플릿과 비슷합니다 (물론 템플릿에 전달되는 컨텍스트나 정보는 다를지도 모르지만요). 다른 index 템플릿처럼, 우리는 첫번째 줄에 base 템플릿을 넣어 확장한 다음, `content`라는 이름의 블록으로 교체합니다.

```django
{% extends "base\_generic.html" %}

{% block content %}
  <h1>Book List</h1>
  {% if book\_list %}
  <ul>
    {% for book in book\_list %}
      <li>
        <a href="{{ book.get\_absolute\_url }}">{{ book.title }}</a> ({{book.author}})
      </li>
    {% endfor %}
  </ul>
  {% else %}
    <p>There are no books in the library.</p>
  {% endif %}
{% endblock %}
```

뷰는 `object_list`나 `book_list`라는 디폴트 aliases로 컨텍스트(도서 목록)를 전달합니다. `object_list`나 `book_list` 둘 중 어느 것을 적어도 상관이 없습니다.

#### 조건부 실행

`if`, `else` 그리고 `endif` 라는 템플릿 태그들은 `book_list` 이 정의되었는지, 그리고 존재하는지 체크합니다. 만약 `book_list`가 없다면, 책이 존재하지 않는다는 `else` 절의 텍스트 문구가 표시될 것입니다. 만약 `book_list`가 존재한다면, 도서 목록의 갯수만큼 반복만큼 반복해서 실행합니다.

```django
{% if book\_list %}
  <!-- code here to list the books -->
{% else %}
  <p>There are no books in the library.</p>
{% endif %}
```

위의 조건문은 단 하나의 상황만 체크합니다. 하지만 `elif`라는 템플릿 태그를 사용해 추가적인 조건을 걸어 테스트할 수 있습니다. (예를 들면 `{% elif var2 %}`) 조건부 연산자에 대한 자세한 내용은 다음 링크에서 확인할 수 있습니다: if, ifequal/ifnotequal, and ifchanged in Built-in template tags and filters (Django Docs).

#### 반복 구문

for 와 `endfor` 라는 템플릿 태그들은 아래와 같이 도서 목록을 살펴보는 루프를 위해 사용합니다. 각각의 반복은 `book` 템플릿 변수에 현재 리스트 아이템에 대한 정보를 채웁니다.

```django
{% for book in book\_list %}
  <li><!-- code here get information from each book item --></li>
{% endfor %}
```

여기에서는 사용되지 않지만, 반복 구문 내에서 Django는 반복을 추적할 수 있는 다른 변수들을 만들 수 있습니다. 예를 들어, `forloop.last`라는 변수로 루프의 마지막 실행에 대한 조건부 처리을 할 수 있습니다.

#### 변수 접근하기

반복 구문 내에서의 코드는 각각의 책에 대한 리스트 아이템을 생성합니다. 이 리스트 아이템은 타이틀(아직 작성되지 않은 상세 뷰의 링크)과 작가의 이름을 나타냅니다.

```html
<a href="{{ book.get\_absolute\_url }}">{{ book.title }}</a> ({{book.author}})
```

우리는 점 표기법(dot notation)을 사용해서 연관된 책의 레코드(예를 들어 `book.title` 과 `book.author`)에 대한 필드에 접근이 가능합니다. `book` 다음의 텍스트는 모델에 정의되어있는 필드의 이름입니다.

우리는 템플릿 안에 모델에서 정의한 함수를 불러올 수도 있습니다. 이 경우, 우리는 `Book.get_absolute_url()` 함수를 호출해 연관된 세부 레코드를 표시하는 URL을 가져옵니다. 이 작업은 함수가 아무 인자를 가지지 않을 때 제공됩니다 (여기에는 인자를 넘길 방법이 없습니다!).

**참고:** 우리는 템플릿 내에서 함수를 호출할 때 발생하는 부작용을 조금 조심해야 합니다. 여기서 우리는 그저 표시하기 위해 URL을 얻었지만, 함수는 그보다 더한 작업도 할 수 있습니다 — (예를 들면) 우리는 그냥 템플릿을 렌더링한다고 해서 우리의 데이터베이스를 삭제하고 싶지 않을 것입니다.

#### 베이스 템플릿 업데이트

베이스 템플릿(**/locallibrary/catalog/templates/*base\_generic.html***)을 열고 **All books**의 URL 링크 부분에 **{% url 'books' %}** 코드를 삽입하세요. 이건 모든 페이지 링크에 적용될 것입니다 (우리는 "books" url mapper를 만들었으니 이렇게 넣을 수 있습니다.)

```python
<li><a href="{% url 'index' %}">Home</a></li>
<li><a href="{% url 'books' %}">All books</a></li>
<li><a href="">All authors</a></li>
```

### 어떻게 보일까요?

우리는 아직 도서 목록을 표시할 수 없습니다. 왜냐면 디펜던시(dependency)가 없기 떄문이죠. 책의 상세 페이지에 대한 URL이 필요한데, 이 URL은 각 도서에 대한 하이퍼 링크입니다. 다음 섹션 이후에는 목록보기와 상세보기가 모두 표시될 것입니다.

Book 상세 페이지
-----------

URL `catalog/book/<id>` ( `<id>` 는 book의 primary key입니다)에 접근해서, Book의 상세 페이지는 특정 책의 정보를 보여줄 것입니다. `Book` 모델 (author, summary, ISBN, language, 그리고 genre) 같은 필드에 추가로, 우리는 가능한 복사본(`BookInstances`) 의 상세부분, 즉 상태와 기대하는 반납일, 기록 그리고 아이디 등을 리스트화 할 것입니다. 이렇게 하면 독자들이 책의 리스트를 확인할 뿐만 아니라, 언제 책을 대여할 수 있는지에 대한 여부를 확인할 수 있게 해줍니다.

### URL 매핑

**/catalog/urls.py** 파일을 열고 아래 두꺼운 글씨로 된 **book-detail** URL mapper를 추가하세요. `path()` 함수는 연관된 제네릭 클래스 기반의 상세 뷰와 이름에 대한 패턴을 정의합니다.

```python
urlpatterns = [
    path('', views.index, name='index'),
    path('books/', views.BookListView.as_view(), name='books'),
    path('book/<uuid:pk>', views.BookDetailView.as_view(), name='book-detail'),
]
```

*book-detail URL 패턴은 우리가 원하는 책의 id를 캡처하기 위해 특별한 구문을 사용합니다. 구문은 간단합니다: 꺾쇠 괄호는* 캡처하는 URL의 일부를 정의하고 뷰가 캡처 된 데이터에 액세스하는 데 사용할 수있는 변수의 이름을 지정합니다. *예를 들어,* \*\*<something>\*\*은 패턴을 캡처해서 "something"이라는 변수에 데이터를 담아 전달합니다. 우리는 선택적으로 변수 이름 앞에 데이터 형식 (int, str, slug, uuid, path)을 정의하는 converter specification을 사용할 수 있습니다.

여기에서 우리는 book id을 캡쳐하기 위해 `'<uuid:pk>'` 라는 특별히 포맷화된 문자열을 활용할 것입니다. 그리고 `pk` (primary key의 단축어)라는 이름의 파라미터로서 뷰로 넘겨줄 것입니다. This is the id that is being used to store the book uniquely in the database, as defined in the Book Model.

(번역 봉사자 주: uuid를 읽지 못한다면[NoReverseMatch] <int:pk>로 해보십시오.)

**참고:** 앞에서 언급했듯이, 관련된 URL 은 실제로는 `catalog/book/<digits>` 입니다.(우리가 **catalog** application 에 있기때문에, `/catalog/` 를 가정합니다).

**경고:** **명심**: 통상 class-based detail view 는 **pk** 라는 이름을 가진 파라미터로 전달됩니다. 만일 자체적으로 function view 를 만든다면 어떤 이름이라도 사용 가능합니다. 혹은 이름이 없는 argument 에 정보를 넣어 전달 할 수도 있습니다.

#### Regular expression 을 이용한 고급 path matching

**참고:** 튜터리얼과는 관련 없습니다. 하지만 향후 Django 스타일로 개발하기 위해선 매우 유용한 팁입니다.

`path()` 를 이용한 패턴 검색은 간단하고 일반적인 경우 - 예를 들어 단지 특정 문자열이나 숫자가 있는지 - 매우 유용합니다. 만일 좀더 세밀한 조건 - 예를 들어 특정 문자열 길이를 갖는 문자열 검색 - 으로 검색 하고자 한다면 . re\_path() 를 사용하길 권고 드립니다.

re\_path() 는 Regular expression 을 사용한다는 점만 빼고 path() 와 똑 같습니다. 예를 들어 앞서 서술한 path 는 다음과 같이 re\_path 로 대체 사용 가능합니다.

```python
re_path(r'^book/(?P<pk>\d+)$', views.BookDetailView.as_view(), name='book-detail'),
```

*Regular expressions* 은 정말로 파워풀한 매핑 툴 입니다. 하지만 솔직히 직관적이지 못하고 초보자에게는 두려운 존재입니다. 아래는 간단한 지침서 입니다!

첫번째로 regular expressions 은 the raw string literal syntax 로 선언되어야 합니다 (즉, 다음처럼 '< >' 로 닫혀 있어야 한다는 겁니다: **r'<your regular expression text goes here>'**).

패턴 매칭을 정의하기 위해 알아야 될 문법의 핵심적인 부분들입니다:

| Symbol | Meaning |
| --- | --- |
| ^ | 기술된 text 로 그 문자열이 시작되는지 |
| $ | 기술된 text 로 그 문자열이 끝나는지 |
| \d | 숫자(0, 1, 2, ... 9) 인지 |
| \w | word character 인지. 즉, 대소문자, 숫자, underscore character (\_) 로만 구성된 단어인지. |
| + | 하나 이상의 선행 문자가 있는지. 예를 들어, 하나 이상의 숫자와 매치한다면 `\d+`.를 하나 이상의 'a' 문자와 매치 한다면 `a+` |
| \* | 매치되는 문자열이 없거나 많은 경우, 예를 들어 매칭이 안되거나 한 단어를 찾고자 할 경우 `\w*` |
| ( ) | 괄호안에 있는 패턴의 일부를 선택할때. 선택된 값은 unnamed parameter 로 view 에게 전달된다. (만일 여러 패턴들이 선택 되었다면 선택된 순서대로 연관된 파라미터로써 전달 될것입니다. |
| (?P<*name*>...) | (...에 표기된) 패턴을 명명한 variable로 변환합니다(이 경우에는 "name" 입니다). 변환한 이름을 view 에 지정한 이름으로 넘깁니다. 그러므로 당신의 view 에서는 반드시 argument명을 동일하게 해주어야 합니다! |
| [ ] | 집합 set 안에 있는 글자중 한개와 매치 될때. 예를 들어 [abc] 는 'a',혹은 'b' 혹은 'c' 와 매치되는지. [-\w] 는 '-' 한 글자 인지 혹은 '-'를 포함한 단어와 매치 하는지를 나타냅니다. |

대부분의 다른 글자들은 문자 그대로 받아 들여 집니다!

몇 가지 실제 패턴 예제를 보도록 합시다:

| Pattern | Description |
| --- | --- |
| **r'^book/(?P<pk>\d+)$'** | 이것은 우리가 URL mapper에서 사용한 Regular Expression입니다. 이 표현식은 먼저 문자열이 `book/` 으로 시작하는지 검사하고 (**^book/**), 그 다음에 한 개이상의 숫자가 오는지 (`\d+`), 그리고 문자열이 끝나기 전에 숫자가 아닌 문자가 들어 있지는 않는 지 검사합니다.또한 이 표현식은 모든 숫자들을 변환하고 **(?P<pk>\d+)** 변환된 값을 view 에 'pk'라는 이름의 parameter로 넘깁니다. **변환된 값은 항상 String type으로 넘어갑니다**!예를 들어, 이 표현식은 `book/1234` 을 매칭합니다. 그리고 변수 `pk='1234'` 를 view에 넘깁니다. |
| **r'^book/(\d+)$'** | 이 표현식은 위의 표현식과 동일한 URL들을 매칭합니다. 변환된 정보는 명명되지 않은 argument로 view에 전달됩니다. |
| **r'^book/(?P<stub>[-\w]+)$'** | 이 표현식은 문자열 처음 부분에 `book/` 으로 시작하는지 검사하고 (**^book/**), 그리고 한 개 또는 그 이상의 '-' 나 word character가 오고 (**[-\w]+**), 그렇게 끝내는지를 매칭합니다. 이 표현식 또한 매칭된 부분을 변환하고 view 에 'stub' 라는 이름의 parameter로 전달합니다.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 `/catalog/book/the-secret-garden` rather than `/catalog/book/33`. |

당신은 다양한 패턴들을 한번의 매칭을 통해 변환시킬 수 있습니다. 그러므로 다양한 정보들을 URL안에 인코딩할 수 있습니다.

**참고:** 추가적으로, 특정 날짜에 출간된 책 목록을 URL에 인코딩할 수 있을지 생각해보세요. 그리고 어떤 Regular Expression이 해당 URL을 매칭할 수 있을까요?

#### Passing additional options in your URL maps

우리가 여태까지 사용하지는 않았지만, 쓸모있을만한 한 기능은 당신이 additional options 을 정의내리고 view에 전달할 수 있다는 것입니다. 이 option들은 dictionary 형태로 정의하고 `path()` 함수의 3번째 명명되지 않은 argument로 전달됩니다. 이 방식은 만약, 당신이 동일한 view 를 다른 곳에서 재활용하려고 하거나, 각 상황에 맞게 조절하려고 할 때 유용합니다. (이 경우, 우리는 각 경우에 따라 다른 template 을 제공합니다).

```python
path('url/', views.my_reused_view, {'my\_template\_name': 'some\_path'}, name='aurl'),
path('anotherurl/', views.my_reused_view, {'my\_template\_name': 'another\_path'}, name='anotherurl'),
```

**참고:** 추가된 options 과 변환된 패턴들 중 명명된 것들은 view 에 명명된 arguments로 전달됩니다. 만약 당신이 **동일한 이름을** 변환된 패턴들과 추가적인 option에 사용한다면, 오직 변환된 패턴들만이 view에 보내지게 됩니다. ( 추가된 option들에 있는 값들은 모두 버려집니다).

### 뷰 (클래스 기반)

**catalog/views.py** 을 열고, 다음 코드를 파일 끝에 넣으세요:

```python
class BookDetailView(generic.DetailView):
    model = Book
```

다됬습니다! 이제 해야될 일은 **/locallibrary/catalog/templates/catalog/book\_detail.html** template를 만들면, view는 template에 URL mapper에 의해 찾고자 하는 데이터베이스에 있는 특정 `Book` 레코드의 정보를 전달할 겁니다. template 안에서 template 변수 `object` 또는 `book`(즉, 일반적으로는 "해당\_모델\_명") 으로 책 목록에 접근할 수 있습니다.

만약 필요하다면, 사용하고 있는 template 또는 template 안에서 book을 참조하는 데 사용되는 context object의 이름을 바꿀 수 있습니다. 또한, 예를 들어 context에 정보를 추가하는 식으로, 메서드를 오버라이드 할 수도 있습니다.

#### 만약 해당 레코드가 존재하지 않는다면 무슨 일이 일어날까요?

만약 요청된 레코드가존재하지 않는다면, 제네릭 클래스 기반의 detail view는 `Http404` exception 이 저절로 발생할 것입니다. — 만들어질 때, 적절한 "resource not found" 페이지를 알아서 보여줄 것입니다. 만약 원한다면 당신이 해당 페이지를 수정할 수 있겠죠.

이런 일이 어떻게 발생하는지 원리를 조금 알려줄게요. 밑에 있는 코드는 만약 당신이 제네릭 클래스 기반의 detail view를 **사용하지 않는다면**, 클래스 기반의 view를 어떻게 함수 형태로 표현 할 수 있는지 보여줍니다.

```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})
```

view는 먼저 model로 부터 특정 book 의 레코드를 얻으려고 할 것입니다. 이 시도가 실패하면, view 는 "해당 책이 존재하지 않음"을 알려주면서 `Http404` exception가 발생할 것입니다. 그리고 마지막 과정은 ,평소처럼, book 정보를 `context` parameter (dictionary 형태로)에 집어 넣고 template 이름과 함께 `render()` 함수를 호출 할 것입니다.

혹, 만약 해당 레코드를 찾지 못한다면, 우리는 손쉬운 방법으로 `Http404` exception을 발생하기 위해`get_object_or_404()` 함수를 사용할 수도 있어요.

```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})
```

### 상세 뷰 템플릿 생성하기

**/locallibrary/catalog/templates/catalog/book\_detail.html** 파일을 만들고, 아래 텍스트를 복사 붙여넣기 하세요. 위에서 설명한대로, 이 파알명은 제네릭 클래스 기반 상세 뷰의 디폴트 파일명입니다. (`catalog` 애플리케이션의 `Book` 모델을 위한 상세 뷰)

```django
{% 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 %}
```

**참고:** 이 템플릿의 작가 링크는 비어있는 URL입니다. 왜냐면 우리는 아직 작가 상세 페이지를 만들지 않았기 때문이죠. 만약 페이지가 존재한다면, URL을 아래와 같이 업데이트 해야합니다.
```

<a href="{% url 'author-detail' book.author.pk %}">{{ book.author }}</a>
```

조금 더 커졌지만, 이 템플릿의 대부분의 것들은 이미 언급된 것들입니다:

* content 블럭을 오버라이드 해서 우리의 기본 템플릿을 extend하였습니다.
* 우리는 조건 판단 처리를 해서 특정 컨텐츠가 있을지 없는지 체크하여 표시합니다.
* `for` 루프를 활용하여 objects들의 리스트를 표현합니다.
* 우리는 context fields를 dot notation를 활용합니다 (왜냐하면 우리는 detail generic view를 사용하는데, context의 이름은 `book` 이기에 우리는 "`object`"를 사용할 수 있습니다)

우리가 본 적 없는 한가지 흥미로운 점은 바로 `book.bookinstance_set.all()` 함수입니다. 이 메소드는 Django에 의해 자동으로 만들어진 메소드입니다. 이 메소드는 `Book` 과 관련된 `BookInstance` 레코드 집합을 반환합니다.

```python
{% for copy in book.bookinstance_set.all %}
  <!-- code to iterate across each copy/instance of a book -->
{% endfor %}
```

이 메소드는 관계의 한쪽("one")에만 `ForeignKey`(one-to many) 필드를 선언했기 때문에 필요합니다. 다른("many") 모델에서 아무것도 선언하지 않았기 때문에 관련 레코드 집합을 가져올 필드가 없습니다. 이 문제를 해결하기 위해, Django는 지금 우리가 사용하고 있는 "reverse lookup"이라는 적당한 이름의 함수를 만들었습니다. 이 함수의 이름은 `ForeignKey`가 선언되어있는 모델 이름을 소문자로 만들고, 그 뒤에 `_set`을 붙이면 됩니다. (따라서 `Book`에서 만든 함수는 `bookinstance_set()`가 되겠죠.)

**참고:** 여기서 우리는 모든 레코드를 가져오기 위해 `all()` 을 사용했습니다(기본값이죠). 반대로 당신은 `filter()` 메서드를 사용해서 레코드의 부분 집합을 가져올 수 있지만, 당신은 template에서 이걸 직접할 수는 없어요. 왜냐하면 함수의 arguments를 정할 수 없으니까요.

만약 순서를 정의하지 않는다면 (클래스 기반 view 또는 model에서), 당신은 개발 서버로 부터 다음과 같은 에러를 받게 될 거라는 것 또한 알아두세요.
```

[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)
```

이것은 paginator object 가 데이터베이스에서 ORDER BY 명령어가 실행되었을 것이라고 예상하기 때문에 발생하는 것입니다. 이러한 것이 없다면, 레코드들이 정확한 순서로 반환되었는지 알 수가 없어요!

이 튜토리얼은 아직 **Pagination** 에 도달하지는 않았습니다.(곧 하게될 거에요) `sort_by()` 에 parameter를 전달하여 사용하는 것은 (위에서 이야기했던 `filter()` 와 동일한 역할을 합니다.) 사용할 수 없기 때문에, 당신은 3개의 선택권중에 하나를 골라야합니다:

1. Add a `ordering` inside a `class Meta` declaration on your model.
2. Add a `queryset` attribute in your custom class-based view, specifying a `order_by()`.
3. Adding a `get_queryset` method to your custom class-based view and also specify the `order_by()`.

만약 `Author` model에 `class Meta` 사용하기를 결정했다면 (커스터마이징 된 클래스 기반 view만큼 유연하진 않겠지만, 쉬운 방법입니다), 아마 밑에 코드와 비슷하게 끝날 거에요:
```

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}'

    class Meta:
        ordering = ['last_name']
```

물론, 해당 field는 `last_name` 이어야 할 필요는 없습니다: 다른 어떤 것도 될 수 있어요.

마지막으로, 그러나 적어도 우리는 정렬(sort)을 attribute/column에 따라 해줘야 합니다. unique 여부와 상관없이 당신의 database의 퍼포먼스 이슈를 위해서 필요합니다. 물론, 이것은 여기서 필요한 것이 아니며 어떻게 보면 약간 진도를 앞서나가는 것 같지만 이런 작은 사용자와 프로젝트에서도 미래의 프로젝트를 위해서 미리 명심해 두는 것이 좋습니다.

어떻게 보일까요?
---------

이 시점에서 책 목록 페이지와 책 세부 사항 페이지를 보기 위한 모든 것을 만들어 놓았어야 합니다. 명령어 (`python3 manage.py runserver`) 를 입력하여 서버를 실행하고 브라우저를 열어 http://127.0.0.1:8000/ 에 접속해보세요.

**경고:** **주의!:** 아직 작가 목록 페이지나 작가 세부 사항 페이지를 클릭하시면 안됩니다. — 당신은 challenge에서 이것들을 만들어보게 될 거에요!

책 목록을 보기 위해 **All books** 링크를 클릭하세요.

![Book List Page](/ko/docs/Learn/Server-side/Django/Generic_views/book_list_page_no_pagination.png)

그리고 당신의 책들 중 하나를 클릭해보세요. 만약, 현재까지의 과정을 성공적으로 따라왔다면, 당신은 다음과 같은 스크린샷을 볼 수 있을 겁니다.

![Book Detail Page](/ko/docs/Learn/Server-side/Django/Generic_views/book_detail_page_no_pagination.png)

Pagination
----------

만약 레코드가 몇 개 없다면, 현재의 책 목록 페이지도 괜찮습니다. 하지만 수십,수백개의 레코드를 갖고 있다면, 페이지는 가져오는 데 점차 시간이 늘어날 겁니다(양이 너무 많아 탐색하는게 체감적으로 힘들어질 정도로요). 해결 방법은 당신의 list view에 pagination을 추가하는 것입니다, 그리고 pagination은 각 페이지마다 보여주는 항목들을 감소시켜줄 것입니다.

장고는 pagination에 대한 훌륭한 지원을 하고 있습니다. 더욱 좋은 점은,이 지원은 제네릭 클래스 기반 list view에 내장되어 있어서, 당신이 pagination을 하기 위해 해야될 것이 많지 않다는 것입니다!

### Views

**catalog/views.py**열고, 밑에 굵은 글씨로 작성되어있는 `paginate_by` 줄을 추가해주세요.

```python
class BookListView(generic.ListView):
    model = Book
    paginate_by = 10
```

이 것이 추가됨에 따라, 당신이 10개 이상의 레코드를 갖게 되면 view는 template에 보내는 데이터에 pagination 하는 것을 시작할 것입니다. 다른 page들을 GET parameter들을 통해 접근할 수 있습니다— 2 페이지에 접속하려면, 다음과 같은 URL을 사용하세요: `/catalog/books/?page=2`.

### Templates

자료들이 pagination되었습니다, 우리는 template에 결과들을 훑어볼 수 있도록 만들어야합니다. 우리는 이러한 기능이 모든 list view들에 필요할 수 있으므로, 우리는 base template에 추가하는 방향으로 작업을 진행하려고 합니다.

**/locallibrary/catalog/templates/*base\_generic.html*** 열고 content block 밑에 다음과 같은 pagination block을 복사하여 추가하세요 (아래에 굵은 글씨로 강조표시를 해놓았습니다). 첫번째로 코드는 현재 페이지가 pagination이 가능한지 체크합니다. 만약 가능하다면, 다음 페이지와 전 페이지를 적절히 추가합니다 (현재 페이지 넘버도요).

```python
{% block content %}{% endblock %}

{% 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 %}
```

만약 현재 페이지에 pagination이 사용중이라면, `page_obj` 가 Paginator 오브젝트 로서 존재합니다. 해당 오브젝트는 현재 페이지, 전 페이지, 페이지 수는 얼마나 되는 지등의 모든 정보를 제공합니다.

pagination 링크를 만들기 위해 우리는 `{{ request.path }}` 를 이용하여 현재 페이지의 URL을 가져오도록 할 겁니다. 우리가 pagination을 하는 객체와 독립적이기 때문에 유용합니다.

다됬네요!

### 어떻게 보일 까요?

밑에 있는 스크린샷은 pagination이 어떻게 보이는지를 알려줍니다 — 만약 데이터베이스에 10개가 넘는 제목을 추가하지 않았다면, **catalog/views.py** 파일에 있는 `paginate_by` 줄에 있는 숫자를 낮춤으로써 쉽게 테스트할 수 있습니다. 밑에 있는 결과는 우리가 `paginate_by = 2`로 바꾼 겨로가입니다.

next/previous 링크와 함께 보이는 밑에 pagination 링크는 당신이 어느 페이지에 있느냐에 따라 달리 표시가 됩니다.

![Book List Page - paginated](/ko/docs/Learn/Server-side/Django/Generic_views/book_list_paginated.png)

도전과제
----

이번 글의 과제는 프로젝트를 완수하기 위해 필요한 작가 세부 사항과 목록 view들을 만드는 것입니다. 이 과제를 통해 해당 URL들을 사용가능하게 만들어야 해요:

* `catalog/authors/` — 작가 목록.
* `catalog/author/<id>`\_ *—*`<id>`\_라는 이름의 기본키를 이용한 작가의 세부 사항

URL mappers에 필요한 코드들과 view들은 ,사실상, 우리가 위에서 만들었던 `Book` 목록과 세부 사항 view들과 동일해야 합니다. template들은 다르겠지만, 비슷한 동작을 가지고 있을 겁니다.

**참고:**

* 작가 목록 페이지를 위한 URL mapper를 만들고나면, **All authors** base template에 있는 **All authors** 링크 또한 업데이트 해야될 필요를 느끼게 될 겁니다. 우리가 **All books** 링크 업데이트 때 했던, 수행 과정을 따라해주세요.
* 작가 세부 사항 페이지에 대한 URL mapper를 만들고나면, 당신은 book detail view template (**/locallibrary/catalog/templates/catalog/book\_detail.html**) 또한 업데이트 해야 합니다. 그래야 작가 링크가 당신이 새로 만든 작가 세부 사항 페이지를 가리키거든요. (비어 있는 URL로 있기 보다는 말이죠). 굵게 되어 있는 부분을 template 내의 태그에 넣어주세요.

```django
<p>
  <strong>Author:</strong>
  <a href="{% url 'author-detail' book.author.pk %}">{{ book.author }}</a>
</p>
```

모두 마치면, 당신의 페이즈들은 아마 밑의 스크린샷과 비슷하게 보일 겁니다.

![Author List Page](/ko/docs/Learn/Server-side/Django/Generic_views/author_list_page_no_pagination.png)

![Author Detail Page](/ko/docs/Learn/Server-side/Django/Generic_views/author_detail_page_no_pagination.png)

요약
--

축하합니다, 도서관의 가장 기본적인 기능들은 모두 끝났어요!

이번 글에서, 우리는 제네릭 클래스 기반의 list view와 detailv view에 대해서 배웠고, 이를 이용하여 책들과 작가들을 보여주기 위한 페이지를 만들었습니다. 이 과정중에 우리는 정규표현식을 이용하여 패턴 매칭을 하는 것도 배웠고, URL로 부터 데이터를 view에 보내는 것도 배웠습니다. 또한, template을 이용하는 몇 가지 트릭도 배웠죠. 마지막에는 list views에 어떻게 paginate를 할 수 있는지도 보았습니다. 이로 인해 우리는 레코드들이 많아져도 리스트를 관리할 수 있게 되었습니다.

다음 글에서 우리는 유저 계정을 지원하도록 도서관을 확장하고, 이를 통해 유저 인증, 권한, 세션, forms을 볼 것입니다.

See also
--------

* Built-in class-based generic views (Django docs)
* Generic display views (Django docs)
* Introduction to class-based views (Django docs)
* Built-in template tags and filters (Django docs).
* Pagination (Django docs)
* 이전
* Overview: Django
* 다음