WTForms image picker widget для Flask с Bootstrap 4 без лишних Javascript и CSS
Модифицируя формы WTforms RadioField ListWidget и используя кнопки Bootstrap 4, мы можем построить красивый image picker.
Когда вы регистрируетесь на этом сайте, вам присваивается изображение аватара. Конечно, вы можете изменить аватар в 'вашем аккаунте', и это делается с помощью image picker. Многие примеры image pickers можно найти в интернете. Но это сайт Flask , включая WTForms , и я хочу, чтобы image picker был сгенерирован замечательным макросом Jinja , который я использую, смотрите также ссылку ниже, ладно, я немного изменил его. С помощью этого макроса нанесение формы на шаблон выглядит как ветерок:
{% from "macros/wtf_bootstrap.html" import bootstrap_form %}
{% extends "content_full_width.html" %}
{% block content %}
{{ bootstrap_form(form) }}
{% if back_button %}
<a href="{{ back_button.url }}" class="btn btn-outline-dark btn-lg mt-4" role="button">
{{ back_button.name }}
</a>
{% endif %}
{% endblock %}
Сайт также использует Bootstrap 4, поэтому я предпочитаю, чтобы у нас не было лишних Javascript и/или CSS, мы уже получили достаточно! В интернете много image picker, но при фильтрации результатов WTForms остается не так уж много.
Решение: создать WTForms widget
Если мы хотим использовать доступные ресурсы, то нет другого выбора, тогда создайте WTForms image picker widget. Проблема в том, что документация по этому вопросу ограничена и примеров мало. Ну и как дальше? Форма image picker подобна группе radio buttons. Выберите один и отправьте форму. При копании в код WTForms класс RadioField выглядит следующим образом:
lib64/python3.6/site-packages/wtforms/fields/core.py
class RadioField(SelectField):
"""
Like a SelectField, except displays a list of radio buttons.
Iterating the field will produce subfields (each containing a label as
well) in order to allow custom rendering of the individual radio fields.
"""
widget = widgets.ListWidget(prefix_label=False)
option_widget = widgets.RadioInput()
ListWidget используется для вывода кода radio buttons HTML . Код WTForms :
lib64/python3.6/site-packages/wtforms/widgets/core.py
class ListWidget(object):
"""
Renders a list of fields as a `ul` or `ol` list.
This is used for fields which encapsulate many inner fields as subfields.
The widget will try to iterate the field to get access to the subfields and
call them to render them.
If `prefix_label` is set, the subfield's label is printed before the field,
otherwise afterwards. The latter is useful for iterating radios or
checkboxes.
"""
def __init__(self, html_tag='ul', prefix_label=True):
assert html_tag in ('ol', 'ul')
self.html_tag = html_tag
self.prefix_label = prefix_label
def __call__(self, field, **kwargs):
kwargs.setdefault('id', field.id)
html = ['<%s %s>' % (self.html_tag, html_params(**kwargs))]
for subfield in field:
if self.prefix_label:
html.append('<li>%s %s</li>' % (subfield.label, subfield()))
else:
html.append('<li>%s %s</li>' % (subfield(), subfield.label))
html.append('</%s>' % self.html_tag)
return HTMLString(''.join(html))
Что мы должны сделать, так это 1. сделать копию ListWidget и 2. изменить ее так, чтобы она также выводила изображения аватары. Тогда мы можем использовать это следующим образом:
class ListImagesWidget(object):
...
our modified ListWidget code
...
class SelectImageField(SelectField):
widget = ListImagesWidget(prefix_label=False)
option_widget = RadioInput()
class AccountAvatarEditForm(FlaskForm):
avatar_id = SelectImageField(_('Select your avatar'))
submit = SubmitField(_l('Update'))
Выбор avatar_id генерируется в функции просмотра как список tuples. Выбранное значение также назначается, например, функцией просмотра:
form.avatar_id.choices = [('default.png', 'default.png'), ('avatar1.png', 'avatar1.png'), ...]
form.avatar_id.data = 'default.png'
На самом деле нетрудно увидеть, как ListWidget генерирует HTML. Функция __call__() начинается с кода открытия в списке 'html':
html = ['<%s %s>' % (self.html_tag, html_params(**kwargs))]
Затем код HTML radio button по очереди добавляется в список 'html':
html.append('<li>%s %s</li>' % (subfield(), subfield.label))
Прилагается закрывающий тег:
html.append('</%s>' % self.html_tag)
А HTML возвращается путем присоединения к списку 'html':
return HTMLString(''.join(html))
Что такое subfield?
Если мы хотим собрать наш пользовательский HTML , нам нужна вся информация, но как мы можем ее получить? Он должен быть в subfield. Я сделал то, что обычно делаю, а именно распечатываю атрибуты subfield :
for subfield in field:
current_app.logger.debug(fname + ': subfield.__dict__ = {}'.format(subfield.__dict__))
Это дало следующую информацию, показанную только для одного из subfield:
subfield.__dict__ = {
'meta': <wtforms.form.Meta object at 0x7ff388eb6750>,
'default': None,
'description': '',
'render_kw': None,
'filters': (),
'flags': <wtforms.fields.Flags: {}>,
'name': 'avatar_id',
'short_name': 'avatar_id',
'type': '_Option',
'validators': [],
'id': 'avatar_id-0',
'label': Label('avatar_id-0', 'default.png'),
'widget': <wtforms.widgets.core.RadioInput object at 0x7ff38989fb10>,
'process_errors': [],
'object_data': 'default.png',
'data': 'default.png',
'checked': True}
Обратите внимание на отмеченный атрибут, он сигнализирует о выбранном элементе. Мы используем это для создания нашего собственного HTML.
Использование кнопок Bootstrap 4
Я использовал Bootstrap в течение некоторого времени и подумал, что кнопка Bootstrap может быть хорошим кандидатом на image picker widget. Изображения аватара, используемые на сайте, имеют прозрачный фон, который приятен при использовании кнопок Bootstrap . Выбранная, зависшая кнопка Bootstrap показывает более темный фон и рамку.
Фокус в том, чтобы поместить radio button и изображение аватара внутрь кнопки. Я также скрываю radio button , используя класс d-none. Наконец-то я прикладываю пуговицы к ныряльщику. Виджет ListImagesWidget теперь выглядит так:
class ListImagesWidget(object):
def __init__(self, html_tag='ul', prefix_label=True):
assert html_tag in ('ol', 'ul')
self.html_tag = html_tag
self.prefix_label = prefix_label
def __call__(self, field, **kwargs):
fname = 'ListImagesWidget - __call__'
kwargs.setdefault('id', field.id)
html = ['<div data-toggle="buttons">']
for subfield in field:
if self.prefix_label:
# never used, see caller: prefix_label=False
else:
checked = ''
active = ''
if subfield.checked:
checked = 'checked="checked"'
active = 'active'
avatar_img = '<img src="' + avatars_images_url() + '/' + str(subfield.label.text) + '" class="img-fluid rounded-circle w-75" alt="">'
button_html = '''
<button class="btn btn-light border-secondary mt-2 mr-2 mb-2 {active}">
<input type="radio" class="d-none" name="{subfield_name}" id="{subfield_id}" autocomplete="off" value="{subfield_data}" {checked}>
{avatar_img}
</button>
'''.format( active=active,
subfield_name=subfield.name,
subfield_id=subfield.id,
subfield_data=subfield.data,
checked=checked,
avatar_img=avatar_img,
)
html.append(button_html)
html.append('</div>')
return HTMLString(''.join(html))
Все это работает очень хорошо. Он показывает все изображения и кнопку "Отправить" под ними. Выбранное изображение немного темнее.
Резюме
Это всего лишь один из многих способов реализации этого. Здесь я скопировал немного кода WTForms и модифицировал его. Приятно то, что нам не нужно добавлять дополнительные Javascript, jQuery и/или CSS. Если вы хотите увидеть это в действии, вы должны зарегистрироваться на этом сайте и перейти в вашу "Учетную запись".
Ссылки / кредиты
bootstrap-wtf
https://github.com/carlnewton/bootstrap-wtf
Buttons - Bootstrap
https://getbootstrap.com/docs/4.4/components/buttons/
WTForms - widgets
https://wtforms.readthedocs.io/en/stable/widgets.html
Недавний
- Скрытие первичных ключей базы данных UUID вашего веб-приложения
- Don't Repeat Yourself (DRY) с Jinja2
- SQLAlchemy, PostgreSQL, максимальное количество строк для user
- Показать значения в динамических фильтрах SQLAlchemy
- Безопасная передача данных с помощью шифрования Public Key и pyNaCl
- rqlite: альтернатива dist с высокой степенью готовности и SQLite
Большинство просмотренных
- Используя Python pyOpenSSL для проверки SSL-сертификатов, загруженных с хоста
- Использование UUID вместо Integer Autoincrement Primary Keys с SQLAlchemy и MariaDb
- Подключение к службе на хосте Docker из контейнера Docker
- Использование PyInstaller и Cython для создания исполняемого файла Python
- SQLAlchemy: Использование Cascade Deletes для удаления связанных объектов
- Flask Удовлетворительный запрос API проверка параметров запроса с помощью схем Маршмэллоу