Общие представления
Wagtail предоставляет ряд общих представлений для выполнения распространённых задач, таких как создание/редактирование экземпляров модели и модальные окна выбора. Поскольку они часто включают несколько связанных представлений с общими свойствами (например, моделью, с которой мы работаем, и её связанным значком), Wagtail также реализует понятие набора представлений, которое позволяет коллективно определять набор представлений и регистрировать их URL-адреса в приложении администрирования как одну операцию с помощью крючка register_admin_viewset.
ModelViewSet
Класс wagtail.admin.viewsets.model.ModelViewSet предоставляет представления для отображения, создания, редактирования и удаления экземпляров модели. Например, если у нас есть следующая модель:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
def __str__(self):
return "%s %s" % (self.first_name, self.last_name)
Следующее определение (которое необходимо разместить в views.py приложения) сгенерирует набор представлений для управления экземплярами Person:
from wagtail.admin.viewsets.model import ModelViewSet
from .models import Person
class PersonViewSet(ModelViewSet):
model = Person
form_fields = ["first_name", "last_name"]
icon = "user"
person_viewset = PersonViewSet("person") # defines /admin/person/ as the base URL
Этот набор представлений затем можно зарегистрировать в администрировании Wagtail, чтобы сделать его доступным по URL-адресу /admin/person/, добавив следующее в wagtail_hooks.py:
from wagtail import hooks
from .views import person_viewset
@hooks.register("register_admin_viewset")
def register_viewset():
return person_viewset
Доступны различные дополнительные атрибуты для настройки набора представлений — см. Наборы представлений.
ChooserViewSet
Класс wagtail.admin.viewsets.chooser.ChooserViewSet предоставляет представления, которые составляют интерфейс модального окна выбора, позволяя пользователям выбирать из списка экземпляров модели для заполнения поля ForeignKey. Используя ту же модель Person, следующее определение (которое необходимо разместить в views.py ) сгенерирует представления для модального окна выбора человека:
from wagtail.admin.viewsets.chooser import ChooserViewSet
class PersonChooserViewSet(ChooserViewSet):
# The model can be specified as either the model class or an "app_label.model_name" string;
# using a string avoids circular imports when accessing the StreamField block class (see below)
model = "myapp.Person"
icon = "user"
choose_one_text = "Choose a person"
choose_another_text = "Choose another person"
edit_item_text = "Edit this person"
form_fields = ["first_name", "last_name"] # fields to show in the "Create" tab
person_chooser_viewset = PersonChooserViewSet("person_chooser")
Опять же, это можно зарегистрировать с помощью крючка register_admin_viewset:
from wagtail import hooks
from .views import person_chooser_viewset
@hooks.register("register_admin_viewset")
def register_viewset():
return person_chooser_viewset
Регистрация набора представлений выбора также настроит виджет выбора для использования всякий раз, когда поле ForeignKey для этой модели появляется в WagtailAdminModelForm — см. Использование форм в представлениях администрирования. В частности, это означает, что определение панели, такое как FieldPanel("author"), где author — это внешний ключ к модели Person, будет автоматически использовать этот интерфейс выбора. Класс виджета выбора также можно получить непосредственно (например, для использования в обычных формах Django) как свойство widget_class набора представлений. Например, размещение следующего кода в widgets.py сделает виджет выбора доступным для импорта с помощью from myapp.widgets import PersonChooserWidget:
from .views import person_chooser_viewset PersonChooserWidget = person_chooser_viewset.widget_class
Набор представлений также предоставляет класс блока выбора StreamField через метод get_block_class. Размещение следующего кода в blocks.py сделает блок выбора доступным для использования в определениях StreamField, импортировав from myapp.blocks import PersonChooserBlock:
from .views import person_chooser_viewset
PersonChooserBlock = person_chooser_viewset.get_block_class(
name="PersonChooserBlock", module_path="myapp.blocks"
)
Наборы представлений выбора для источников данных, не являющихся моделями
Хотя общие представления выбора в первую очередь предназначены для использования моделей Django в качестве источника данных, можно реализовать выборки на основе других источников, таких как конечные точки REST API, переопределяя отдельные методы, которые обрабатывают получение данных.
Внутри wagtail.admin.views.generic.chooser:
-
BaseChooseView.get_object_list()— возвращает список записей, которые будут отображаться в окне выбора. (В реализации по умолчанию это Django QuerySet, а записи — экземпляры модели.) -
BaseChooseView.columns— список объектовwagtail.admin.ui.tables.Column, определяющих поля записи, которые необходимо отобразить в окончательной таблице. -
BaseChooseView.apply_object_list_ordering(objects)— принимает список записей, возвращаемыйget_object_list, и возвращает список с применённой необходимой сортировкой. -
ChosenViewMixin.get_object(pk)— возвращает запись, идентифицированную заданным первичным ключом. -
ChosenResponseMixin.get_chosen_response_data(item)— принимает запись и возвращает словарь данных, который будет передан обратно виджету выбора для его заполнения (состоящий из элементовidиtitle, если JavaScript виджета выбора не был настроен).
Внутри wagtail.admin.widgets:
-
BaseChooser.get_instance(value)— принимает значение, которое может быть записью, первичным ключом или None, и возвращает соответствующую запись или None. -
BaseChooser.get_value_data_from_instance(item)— принимает запись и возвращает словарь данных, который будет заполнять виджет выбора (состоящий из элементовidиtitle, если JavaScript виджета не был настроен).
Например, следующий код реализует окно выбора, которое выполняется против конечной точки JSON для модели User по адресу http://localhost:8000/api/users/, настроенной с помощью Django REST Framework с использованием конфигурации по умолчанию и без пагинации:
from django.views.generic.base import View
import requests
from wagtail.admin.ui.tables import Column, TitleColumn
from wagtail.admin.views.generic.chooser import (
BaseChooseView, ChooseViewMixin, ChooseResultsViewMixin, ChosenResponseMixin, ChosenViewMixin, CreationFormMixin
)
from wagtail.admin.viewsets.chooser import ChooserViewSet
from wagtail.admin.widgets import BaseChooser
class BaseUserChooseView(BaseChooseView):
@property
def columns(self):
return [
TitleColumn(
"title",
label="Title",
accessor='username',
id_accessor='id',
url_name=self.chosen_url_name,
link_attrs={"data-chooser-modal-choice": True},
),
Column(
"email", label="Email", accessor="email"
)
]
def get_object_list(self):
r = requests.get("http://localhost:8000/api/users/")
r.raise_for_status()
results = r.json()
return results
def apply_object_list_ordering(self, objects):
return objects
class UserChooseView(ChooseViewMixin, CreationFormMixin, BaseUserChooseView):
pass
class UserChooseResultsView(ChooseResultsViewMixin, CreationFormMixin, BaseUserChooseView):
pass
class UserChosenViewMixin(ChosenViewMixin):
def get_object(self, pk):
r = requests.get("http://localhost:8000/api/users/%d/" % int(pk))
r.raise_for_status()
return r.json()
class UserChosenResponseMixin(ChosenResponseMixin):
def get_chosen_response_data(self, item):
return {
"id": item["id"],
"title": item["username"],
}
class UserChosenView(UserChosenViewMixin, UserChosenResponseMixin, View):
pass
class BaseUserChooserWidget(BaseChooser):
def get_instance(self, value):
if value is None:
return None
elif isinstance(value, dict):
return value
else:
r = requests.get("http://localhost:8000/api/users/%d/" % int(value))
r.raise_for_status()
return r.json()
def get_value_data_from_instance(self, instance):
return {
"id": instance["id"],
"title": instance["username"],
}
class UserChooserViewSet(ChooserViewSet):
icon = "user"
choose_one_text = "Choose a user"
choose_another_text = "Choose another user"
edit_item_text = "Edit this user"
choose_view_class = UserChooseView
choose_results_view_class = UserChooseResultsView
chosen_view_class = UserChosenView
base_widget_class = BaseUserChooserWidget
user_chooser_viewset = UserChooserViewSet("user_chooser", url_prefix="user-chooser")
Если источник данных реализует собственную пагинацию — то есть механизм пагинации, встроенный в окно выбора, должен быть пропущен — можно переопределить метод BaseChooseView.get_results_page(request) вместо get_object_list. Он должен возвращать экземпляр django.core.paginator.Page. Например, если API в приведённом выше примере соответствует соглашениям API Wagtail, реализуя пагинацию с параметрами URL offset и limit и возвращая словарь, состоящий из meta и results, реализацию BaseUserChooseView можно изменить следующим образом:
from django.core.paginator import Page, Paginator
class APIPaginator(Paginator):
"""
Customisation of Django's Paginator class for use when we don't want it to handle
slicing on the result set, but still want it to generate the page numbering based
on a known result count.
"""
def __init__(self, count, per_page, **kwargs):
self._count = int(count)
super().__init__([], per_page, **kwargs)
@property
def count(self):
return self._count
class BaseUserChooseView(BaseChooseView):
@property
def columns(self):
return [
TitleColumn(
"title",
label="Title",
accessor='username',
id_accessor='id',
url_name=self.chosen_url_name,
link_attrs={"data-chooser-modal-choice": True},
),
Column(
"email", label="Email", accessor="email"
)
]
def get_results_page(self, request):
try:
page_number = int(request.GET.get('p', 1))
except ValueError:
page_number = 1
r = requests.get("http://localhost:8000/api/users/", params={
'offset': (page_number - 1) * self.per_page,
'limit': self.per_page,
})
r.raise_for_status()
result = r.json()
paginator = APIPaginator(result['meta']['total_count'], self.per_page)
page = Page(result['items'], page_number, paginator)
return page
© 2014-present Torchbox Ltd and individual contributors.
All rights are reserved.
Licensed under the BSD License.
https://docs.wagtail.org/en/stable/extending/generic_views.html