Возможно ли добавить поле ввода в пользовательские массовые действия Wagtails?

Возможно ли добавить поле ввода в Wagtails custom bulk actions?

В шаблоне из примера документации есть блок под названием form_section. Здесь я хочу добавить отдельную форму для добавления еще одного поля ввода. Разумеется, возможна и другая позиция.

<!-- /path/to/confirm_bulk_import.html -->

# ...

{% block form_section %}
{% if images %}
    {% trans 'Yes, import' as action_button_text %}
    {% trans "No, don't import" as no_action_button_text %}
    # Can I use my own confirmation form here? How about its view?:
    {% include 'wagtailadmin/bulk_actions/confirmation/form.html' with action_button_class="serious" %}
{% else %}
    {% include 'wagtailadmin/bulk_actions/confirmation/go_back.html' %}
{% endif %}
{% endblock form_section %}

Я хотел бы массово выбрать Image экземпляры, чтобы добавить их в Page. Поэтому мне нужно иметь ChoiceField для выбора Page. Для этого также потребуется настраиваемая View для логики, лежащей в основе этого "импорта". Последнее не является вопросом. Мне просто интересно, как я могу добавить это поле ввода и изменить вид этих чудесных массовых действий.

Админ портал

Wagtail использует чистый HTML и CSS. Поэтому все, что приходит на сторону python, принимается через HTML форму. Это означает, что каждое нажатие кнопки в UI должно ассоциироваться с HTML form и со стороны wagtail вы можете найти его в request.

Метод выполнения действия

Если вы просмотрели документацию bulk action documentation, то обнаружили, что после отправки form будет выполнен метод класса execute_action. Теперь вам нужно понять параметры этого метода.

@classmethod
def execute_action(cls, objects, **kwargs):
    raise NotImplementedError("execute_action needs to be implemented")

Поскольку это метод класса, первым параметром является тип класса, на котором работает этот метод. Вы можете узнать больше о методах класса в документации python.

Второй параметр objects - это список объектов, которые вы выбрали для этой массовой операции. Если быть точным, это список объектов, которые вы выбрали с правильным уровнем разрешения. В реализации по умолчанию разрешение дается для всех объектов. Но вы можете переопределить это поведение.

def check_perm(self, obj):
    return True

Вы можете переопределить этот метод в своем пользовательском классе массового действия и проверять разрешение для каждого объекта. В качестве параметра objects вы получите только те объекты, которые имеют check_perm(obj)==True, из списка выбранных вами объектов.

Третьим параметром метода класса execute_action является список аргументов с ключевым словом (точнее, словарь). Этот словарь получается при вызове следующего метода.

def get_execution_context(self):
    return {}

По умолчанию этот метод возвращает пустой словарь. Но вы можете переопределить его для отправки чего угодно. Поскольку execute_action является методом класса, он не может получить доступ к переменным экземпляра. Поэтому этот метод очень полезен для передачи переменных экземпляра методу класса execute_action.

Рассмотрим пример.

@hooks.register('register_bulk_action')
class CustomBulkAction(ImageBulkAction):
    display_name = _("A Thing")
    aria_label = _("A thing to do")
    action_type = "thing"
    template_name = "appname/bulk/something.html"

    def get_execution_context(self):
        print(self.request)
        return super().get_execution_context()

Если вы запустите этот пример, вы увидите данные, представленные из HTML form.

<WSGIRequest: POST '/admin/bulk/image/customimage/thing/?next=%2Fadmin%2Fimages%2F&id=1'>

Определение HTML-формы

В шаблоне массового действия вы не можете найти ни одного тега HTML <form></form>. Это потому, что форма с кнопками действий находится в файле wagtailadmin/bulk_actions/confirmation/form.html, который вы импортировали в шаблон. Вы можете создать копию этого файла и изменить его поведение.

<form action="{{ submit_url }}" method="POST">
    {% include 'wagtailadmin/shared/non_field_errors.html' %}
    {% csrf_token %}
    {% block form_fields %}
        <!-- Custom Fields goes here -->
    {% endblock form_fields %}
    <input type="submit" value="{{ action_button_text }}" class="button {{ action_button_class }}" />
    <a href="{{ next }}" class="button button-secondary">{{ no_action_button_text }}</a>
</form>

Вы можете добавить нужные вам пользовательские поля в области, которую я указал выше в примере кода, и значения этих дополнительных полей будут находиться в параметре self.request.POST. Это самый простой способ получить что-то из шаблона на стороне python.

Django Forms

Но это не самый лучший способ. Django рекомендует использовать формы для этих целей. Подробнее о формах Django вы можете узнать в документации.

Почти везде, где есть форма в шаблоне wagtail, есть связанная с ней форма Django. В данном случае переменная экземпляра form_class используется для ассоциации шаблона bulk action с формой Django.

class MyForm(forms.Form):
    extra_field = forms.CharField(
            max_length=100,
            required=True,
        )

@hooks.register('register_bulk_action')
class CustomBulkAction(ImageBulkAction):
    display_name = _("A Thing")
    aria_label = _("A thing to do")
    action_type = "thing"
    template_name = "appname/bulk/something.html"
    form_class = MyForm

    def get_execution_context(self):
        print(self.cleaned_form.data)
        return super().get_execution_context()

И очень просто, я добавлю все поля формы в шаблон, как в приведенном ниже примере кода.

<form action="{{ submit_url }}" method="POST">
    {% include 'wagtailadmin/shared/non_field_errors.html' %}
    {% csrf_token %}
    {% block form_fields %}
        {% for field in form %}
            <div class="fieldWrapper">
                {{ field.label_tag }} {{ field }}
                {{ field.errors }}
            </div>
        {% endfor %}
    {% endblock form_fields %}
    <input type="submit" value="{{ action_button_text }}" class="button {{ action_button_class }}" />
    <a href="{{ next }}" class="button button-secondary">{{ no_action_button_text }}</a>
</form>

Теперь это выведет данные, полученные из HTML-формы. Нам нужно передать данные формы как kwargs в метод класса execute_action.

Окончательный пример

@hooks.register('register_bulk_action')
class CustomBulkAction(ImageBulkAction):
    display_name = _("A Thing")
    aria_label = _("A thing to do")
    action_type = "thing"
    template_name = "appname/bulk/something.html"
    form_class = MyForm

    def get_execution_context(self):
        data = super().get_execution_context()
        data['form'] = self.cleaned_form
        return data
    
    @classmethod
    def execute_action(cls, objects, **kwargs):
        print("KWARGS:", kwargs)
        print(kwargs['form'].cleaned_data['extra_field'])
        # Do what you want
        return 0, 0

Я считаю, что это было полезно и ответило на все вопросы, связанные с массовым представлением действий.

Используя forms.ModelChoiceField в вашей форме, вы можете получать значения из Django Model и передавать их в поле HTML. Вы должны передать queryset в конструкторе.

extra_field = forms.ModelChoiceField(
        required=True,
        queryset=Collection.objects.order_by("name"),
    )
Вернуться на верх