# 관리자 뷰 생성하기 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로 표시된 달력](../_static/images/adminviews_calendar.png) ## 템플릿 추가하기 현재 이 뷰는 순수한 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 관리자 페이지 구조 내에 달력이 표시됩니다. ![스타일이 적용되지 않은 HTML로 표시된 달력](../_static/images/adminviews_calendar_template.png) ## 메뉴 항목 추가하기 이제 달력 뷰는 완성되었지만, 관리자 백엔드의 다른 곳에서 접근할 방법이 없습니다. 사이드바 메뉴에 항목을 추가하기 위해 또 다른 훅인 [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' 항목이 나타납니다. ![날짜 아이콘과 함께 "Calendar" 메뉴 항목을 보여주는 Wagtail 관리자 사이드바 메뉴](../_static/images/adminviews_menu.png) ## 메뉴 항목 그룹 추가하기 때로는 사이드바의 단일 메뉴 항목에 사용자 정의 뷰들을 그룹화하고 싶을 수 있습니다. 현재 달력 월만 표시하는 또 다른 뷰를 만들어 보겠습니다. ```{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에서 볼 수 있습니다. ![단일 달력 월](../_static/images/adminviews_calendarmonth.png) 마지막으로, `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' 항목에 두 개의 사용자 정의 메뉴 항목이 표시됩니다. ![날짜 아이콘이 있는 확장된 "Calendar" 그룹 메뉴 항목을 보여주는 Wagtail 관리자 사이드바 메뉴, 'Calendar'와 'Month'라는 두 개의 하위 메뉴 항목을 보여줌.](../_static/images/adminviews_menu_group_expanded.png) (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) ```