Spec-Zone.ru › Wagtail

Общие представления

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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API