# 관리자 뷰 생성하기
Wagtail 관리자에 사용자 정의 뷰를 추가하는 가장 일반적인 용도는 Django 모델 관리를 위한 인터페이스를 제공하는 것입니다. Wagtail은 [](snippets)를 사용하여 최소한의 설정으로 Django 모델을 나열, 생성 및 편집할 수 있는 기성 뷰를 제공합니다.
이 패턴에 맞지 않는 다른 종류의 관리자 뷰의 경우, 직접 Django 뷰를 작성하고 [훅(hooks)](admin_hooks)을 통해 Wagtail 관리자의 일부로 등록할 수 있습니다. 이 예제에서는 Python 표준 라이브러리의 [calendar 모듈](inv:python#library/calendar)을 사용하여 현재 연도의 달력을 표시하는 뷰를 구현해 보겠습니다.
## 뷰 정의하기
Wagtail 프로젝트 내에서 `./manage.py startapp wagtailcalendar` 명령으로 새로운 `wagtailcalendar` 앱을 생성하고 프로젝트의 `INSTALLED_APPS` 에 추가합니다. (이 경우, 표준 라이브러리의 `calendar` 모듈과의 충돌을 피하기 위해 'wagtailcalendar'라는 이름을 사용합니다. 일반적으로 'wagtail' 접두사를 사용할 필요는 없습니다.)
`views.py` 를 다음과 같이 편집합니다. 이것은 Wagtail 관련 코드가 없는 순수 Django 뷰입니다.
```python
import calendar
from django.http import HttpResponse
from django.utils import timezone
def index(request):
current_year = timezone.now().year
calendar_html = calendar.HTMLCalendar().formatyear(current_year)
return HttpResponse(calendar_html)
```
## URL 라우트 등록하기
이 시점에서 Django 프로젝트의 표준적인 방법은 프로젝트의 최상위 URL 설정 모듈에 이 뷰에 대한 URL 라우트를 추가하는 것입니다. 하지만 이 경우에는 뷰가 로그인한 사용자에게만 제공되고, Wagtail이 관리하는 `/admin/` URL 네임스페이스 내에 나타나기를 원합니다. 이는 [Register Admin URLs](register_admin_urls) 훅을 통해 수행됩니다.
시작 시 Wagtail은 각 설치된 앱 내에서 `wagtail_hooks` 서브 모듈을 찾습니다. 이 서브 모듈에서는 관리자용 URL 설정을 빌드하고 주 메뉴를 구성하는 등 Wagtail 작동의 다양한 시점에서 실행될 함수를 정의할 수 있습니다.
`wagtailcalendar` 앱 내에 `wagtail_hooks.py` 파일을 만들고 다음 내용을 포함합니다.
```python
from django.urls import path
from wagtail import hooks
from .views import index
@hooks.register('register_admin_urls')
def register_calendar_url():
return [
path('calendar/', index, name='calendar'),
]
```
이제 달력은 `/admin/calendar/` URL에서 볼 수 있습니다.

## 템플릿 추가하기
현재 이 뷰는 순수한 HTML 조각을 출력하고 있습니다. Wagtail의 기본 템플릿인 `"wagtailadmin/base.html"` 을 확장하는 템플릿을 만들어 이를 일반적인 Wagtail 관리자 페이지 구조에 삽입해 보겠습니다.
```{note}
기본 템플릿과 HTML 구조는 Wagtail API의 안정적인 부분으로 간주되지 않으며 향후 릴리스에서 변경될 수 있습니다.
```
`views.py` 를 다음과 같이 업데이트합니다.
```python
import calendar
from django.shortcuts import render
from django.utils import timezone
def index(request):
current_year = timezone.now().year
calendar_html = calendar.HTMLCalendar().formatyear(current_year)
return render(request, 'wagtailcalendar/index.html', {
'current_year': current_year,
'calendar_html': calendar_html,
})
```
이제 `wagtailcalendar` 앱 내에 `templates/wagtailcalendar/` 폴더를 만들고, 그 안에 `index.html` 과 `calendar.css` 를 다음과 같이 포함시킵니다.
```html+django
{% extends "wagtailadmin/base.html" %}
{% load static %}
{% block titletag %}{{ current_year }} calendar{% endblock %}
{% block extra_css %}
{{ block.super }}
{% endblock %}
{% block content %}
{% include "wagtailadmin/shared/header.html" with title="Calendar" icon="date" %}
{{ calendar_html|safe }}
{% endblock %}
```
```css
/* calendar.css */
table.month {
margin: 20px;
}
table.month td,
table.month th {
padding: 5px;
}
```
여기서는 기본 템플릿에 정의된 세 개의 블록을 재정의하고 있습니다: `titletag`(HTML `` 태그의 내용을 설정), `extra_css`(이 페이지에 특정한 추가 CSS 스타일을 제공), 그리고 `content`(페이지의 주 콘텐츠 영역). 또한 표준 헤더 바 컴포넌트를 포함하고 제목과 아이콘을 설정하고 있습니다. 인식되는 아이콘 식별자 목록은 [스타일 가이드](styleguide)를 참조하세요.
`/admin/calendar/` 를 다시 방문하면 이제 Wagtail 관리자 페이지 구조 내에 달력이 표시됩니다.

## 메뉴 항목 추가하기
이제 달력 뷰는 완성되었지만, 관리자 백엔드의 다른 곳에서 접근할 방법이 없습니다. 사이드바 메뉴에 항목을 추가하기 위해 또 다른 훅인 [Register Admin Menu Item](register_admin_menu_item)을 사용하겠습니다. `wagtail_hooks.py` 를 다음과 같이 업데이트합니다.
```python
from django.urls import path, reverse
from wagtail.admin.menu import MenuItem
from wagtail import hooks
from .views import index
@hooks.register('register_admin_urls')
def register_calendar_url():
return [
path('calendar/', index, name='calendar'),
]
@hooks.register('register_admin_menu_item')
def register_calendar_menu_item():
return MenuItem('Calendar', reverse('calendar'), icon_name='date')
```
이제 메뉴에 'Calendar' 항목이 나타납니다.

## 메뉴 항목 그룹 추가하기
때로는 사이드바의 단일 메뉴 항목에 사용자 정의 뷰들을 그룹화하고 싶을 수 있습니다. 현재 달력 월만 표시하는 또 다른 뷰를 만들어 보겠습니다.
```{code-block} python
:emphasize-lines: 15-23
import calendar
from django.shortcuts import render
from django.utils import timezone
def index(request):
current_year = timezone.now().year
calendar_html = calendar.HTMLCalendar().formatyear(current_year)
return render(request, 'wagtailcalendar/index.html', {
'current_year': current_year,
'calendar_html': calendar_html,
})
def month(request):
current_year = timezone.now().year
current_month = timezone.now().month
calendar_html = calendar.HTMLCalendar().formatmonth(current_year, current_month)
return render(request, 'wagtailcalendar/index.html', {
'current_year': current_year,
'calendar_html': calendar_html,
})
```
또한 `wagtail_hooks.py` 를 업데이트하여 관리자 인터페이스에 URL을 등록해야 합니다.
```{code-block} python
:emphasize-lines: 11
from django.urls import path
from wagtail import hooks
from .views import index, month
@hooks.register('register_admin_urls')
def register_calendar_url():
return [
path('calendar/', index, name='calendar'),
path('calendar/month/', month, name='calendar-month'),
]
```
이제 달력은 `/admin/calendar/month/` URL에서 볼 수 있습니다.

마지막으로, `wagtail_hooks.py` 를 변경하여 사용자 정의 메뉴 항목 그룹을 포함할 수 있습니다. 이는 단일 항목을 추가하는 것과 유사하지만 `Menu` 와 `SubmenuMenuItem` 이라는 두 개의 클래스를 더 가져와야 합니다.
```{code-block} python
:emphasize-lines: 3,20-25
from django.urls import path, reverse
from wagtail.admin.menu import Menu, MenuItem, SubmenuMenuItem
from wagtail import hooks
from .views import index, month
@hooks.register('register_admin_urls')
def register_calendar_url():
return [
path('calendar/', index, name='calendar'),
path('calendar/month/', month, name='calendar-month'),
]
@hooks.register('register_admin_menu_item')
def register_calendar_menu_item():
submenu = Menu(items=[
MenuItem('Calendar', reverse('calendar'), icon_name='date'),
MenuItem('Current month', reverse('calendar-month'), icon_name='date'),
])
return SubmenuMenuItem('Calendar', submenu, icon_name='date')
```
이제 'Calendar' 항목이 메뉴 항목 그룹으로 나타납니다. 확장하면 'Calendar' 항목에 두 개의 사용자 정의 메뉴 항목이 표시됩니다.

(using_base_viewset)=
## `ViewSet` 을 사용하여 사용자 정의 관리자 뷰 그룹화하기
URL 및 메뉴 항목과 함께 관리자 뷰를 등록하는 것은 Wagtail에서 일반적인 패턴입니다. 이는 종종 우리가 작업하는 모델 및 관련 아이콘과 같은 공유 속성을 가진 여러 관련 뷰를 포함합니다. 이 패턴을 지원하기 위해 Wagtail은 _뷰셋(viewset)_ 개념을 구현합니다. 이를 통해 뷰와 해당 URL 묶음을 집합적으로 정의하고, [`register_admin_viewset`](register_admin_viewset) 훅을 통해 단일 작업으로 관리자 앱에 메뉴 항목을 등록할 수 있습니다.
예를 들어, 이전 예제의 달력 뷰들을 `views.py` 에 {class}`~wagtail.admin.viewsets.base.ViewSet` 서브클래스를 만들어 단일 메뉴 항목으로 그룹화할 수 있습니다.
```{code-block} python
from wagtail.admin.viewsets.base import ViewSet
...
class CalendarViewSet(ViewSet):
add_to_admin_menu = True
menu_label = "Calendar"
icon = "date"
# `name` 은 URL 접두사와 URL 네임스페이스 모두에 사용됩니다.
# `url_prefix` 와 `url_namespace` 를 통해 개별적으로 사용자 정의할 수 있습니다.
name = "calendar"
def get_urlpatterns(self):
return [
# 이것은 `/admin/calendar/` 에서 접근할 수 있으며
# `calendar:index` 이름으로 역-확인(reverse-resolved)될 수 있습니다.
# 이 첫 번째 URL은 메뉴 항목에 사용되지만,
# `menu_url` 속성을 재정의하여 사용자 정의할 수 있습니다.
path('', index, name='index'),
# 이것은 `/admin/calendar/month/` 에서 접근할 수 있으며
# `calendar:month` 이름으로 역-확인될 수 있습니다.
path('month/', month, name='month'),
]
```
그런 다음, `wagtail_hooks.py` 에서 `register_admin_urls` 와 `register_admin_menu_item` 훅을 제거하고, `register_admin_viewset` 훅으로 `ViewSet` 서브클래스를 등록합니다.
```{code-block} python
from .views import CalendarViewSet
@hooks.register("register_admin_viewset")
def register_viewset():
return CalendarViewSet()
```
두 개의 개별 훅을 사용한 이전 예제와 비교하여, 이것은 `/admin/calendar/` URL로 이동하는 단일 메뉴 항목 "Calendar"를 생성합니다. 두 번째 URL은 자체 메뉴 항목을 갖지 않지만, 여전히 `/admin/calendar/month/` 에서 접근할 수 있습니다. 이는 반드시 자체 메뉴 항목이 필요하지 않은 관련 뷰들을 함께 그룹화하는 데 유용합니다.
추가적인 사용자 정의는 {class}`~wagtail.admin.viewsets.base.ViewSet` 문서를 참조하세요.
(using_base_viewsetgroup)=
## `ViewSetGroup` 을 사용하여 여러 `ViewSet` 결합하기
{class}`~wagtail.admin.viewsets.base.ViewSetGroup` 클래스는 여러 `ViewSet` 을 최상위 메뉴 항목 내에 그룹화하는 데 사용할 수 있습니다. 예를 들어, 이전 예제의 `CalendarViewSet` 과 그룹화하려는 다른 뷰셋(예: `EventViewSet`)이 있는 경우, `views.py` 에 `ViewSetGroup` 서브클래스를 만들어 그렇게 할 수 있습니다.
```{code-block} python
from wagtail.admin.viewsets.base import ViewSetGroup
...
class AgendaViewSetGroup(ViewSetGroup):
menu_label = "Agenda"
menu_icon = "table"
# `items` 에 `ViewSet` 의 인스턴스나 서브클래스를 지정할 수 있습니다.
items = (CalendarViewSet(), EventViewSet)
```
그런 다음, 뷰셋에서 `add_to_admin_menu` 를 제거하고 `wagtail_hooks.py` 의 `register_admin_viewset` 훅을 업데이트하여 개별 뷰셋 대신 `ViewSetGroup` 을 등록합니다.
```{code-block} python
from .views import AgendaViewSetGroup
@hooks.register("register_admin_viewset")
def register_viewset():
return AgendaViewSetGroup()
```
이렇게 하면 "Agenda"라는 최상위 메뉴 항목이 생성되고, 그 아래에 "Calendar"와 "Events"와 같은 두 뷰셋의 메뉴 항목이 하위 항목으로 표시됩니다.
추가적인 사용자 정의는 {class}`~wagtail.admin.viewsets.base.ViewSetGroup` 문서를 참조하세요.
## 관리자 뷰에 링크 추가하기
### 스니펫 (Snippets)
예제로 [Wagtail Bakery 데모](https://github.com/wagtail/bakerydemo/)의 `BreadTypeSnippet` 을 사용하겠습니다.
스니펫 URL 이름은 기본적으로 `wagtailsnippets_{app_label}_{model_name}:{list/edit/inspect/copy/delete}` 패턴을 따릅니다.
Python에서는 {meth}`~wagtail.admin.viewsets.base.ViewSet.get_url_name` 을 사용하여 스니펫 뷰 URL의 이름을 얻을 수 있습니다. (예: `BreadTypeSnippet.get_url_name("list")`)
따라서 `BreadTypeSnippet` URL은 템플릿에서 다음과 같이 보입니다.
```html+django
{% url 'wagtailsnippets_breads_breadtype:list' %}
{% url 'wagtailsnippets_breads_breadtype:edit' object.id %}
{% url 'wagtailsnippets_breads_breadtype:inspect' object.id %}
{% url 'wagtailsnippets_breads_breadtype:copy' object.id %}
{% url 'wagtailsnippets_breads_breadtype:delete' object.id %}
```
### 페이지 (Pages)
새 페이지
```html+django
{% url 'wagtailadmin_pages:add' content_type_app_name content_type_model_name parent_id %}
```
페이지 사용 현황
```html+django
{% url 'wagtailadmin_pages:usage' page_id %}
```
페이지 수정
```html+django
{% url 'wagtailadmin_pages:edit' page_id %}
```
페이지 삭제
```html+django
{% url 'wagtailadmin_pages:delete' page_id %}
```
페이지 복사
```html+django
{% url 'wagtailadmin_pages:copy' page_id }
```
### 이미지 (Images)
이미지 목록
```html+django
{% url 'wagtailimages:index' %}
```
이미지 수정
```html+django
{% url 'wagtailimages:edit' image_id %}
```
이미지 삭제
```html+django
{% url 'wagtailimages:delete' image_id %}
```
새 이미지
```html+django
{% url 'wagtailimages:add' %}
```
이미지 사용 현황
```html+django
{% url 'wagtailimages:image_usage' image_id %}
```
### AdminURLFinder
관리자에서 모든 모델의 URL을 찾기 위해 `AdminURLFinder` 클래스를 사용할 수 있습니다.
```python
from wagtail.admin.admin_url_finder import AdminURLFinder
finder = AdminURLFinder()
finder.get_edit_url(model_instance)
```