WTForms image picker widget für Flask mit Bootstrap 4 ohne zusätzliche Javascript und CSS
Durch Modifizierung der WTforms RadioField ListWidget und Verwendung von Bootstrap 4 Schaltflächen können wir ein schönes image picker bauen.
Wenn Sie sich für diese Website anmelden, wird Ihnen ein Avatar-Bild zugewiesen. Natürlich können Sie den Avatar in 'Ihrem Konto' ändern, und dies geschieht mit Hilfe eines image picker. Viele Beispiele für image pickers sind im Internet zu finden. Aber dies ist eine Flask Seite, die WTForms enthält, und ich möchte, dass das image picker durch das wunderbare Jinja Makro, das ich benutze, erzeugt wird, siehe auch den Link unten, ok, ich habe es ein bisschen modifiziert. Mit diesem Makro ist es ein Kinderspiel, ein Formular auf die Vorlage zu setzen, und sieht so aus:
{% 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 %}
Die Website verwendet auch Bootstrap 4, also ziehe ich es vor, keine zusätzlichen Javascript und/oder CSS zu haben, wir haben schon genug! Es gibt viele image pickers im Internet, aber beim Filtern der Ergebnisse mit WTForms bleibt nicht viel übrig.
Lösung: Erstellen Sie ein WTForms widget
Wenn wir die verfügbaren Ressourcen nutzen wollen, gibt es keine andere Wahl, als ein WTForms image picker widget zu erstellen. Das Problem ist, dass die Dokumentation dazu begrenzt ist und es nicht viele Beispiele gibt. Also, wie soll ich vorgehen? Das Formular image picker ist wie eine Gruppe von radio buttons. Wählen Sie eine aus und schicken Sie das Formular ab. Wenn Sie den Code WTForms ausgraben, lautet die Klasse RadioField wie folgt:
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()
Der ListWidget wird zur Ausgabe des radio buttons HTML Codes verwendet. Der Code 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))
Was wir tun müssen, ist 1. eine Kopie des ListWidget zu erstellen und 2. sie so zu modifizieren, dass sie auch die Avatar-Bilder ausgibt. Dann können wir dies wie folgt verwenden:
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'))
Die Avatar_id-Auswahl wird in der Ansichtsfunktion als Liste von tuples generiert. Der ausgewählte Wert wird z.B. auch durch die Ansichtsfunktion zugewiesen:
form.avatar_id.choices = [('default.png', 'default.png'), ('avatar1.png', 'avatar1.png'), ...]
form.avatar_id.data = 'default.png'
Es ist nicht wirklich schwierig zu sehen, wie der ListWidget den HTML erzeugt. Die Funktion __call__() beginnt mit dem Eröffnungscode in der 'html'-Liste:
html = ['<%s %s>' % (self.html_tag, html_params(**kwargs))]
Dann wird einer nach dem anderen der Code HTML radio button an die 'html'-Liste angehängt:
html.append('<li>%s %s</li>' % (subfield(), subfield.label))
Das abschließende Tag wird angehängt:
html.append('</%s>' % self.html_tag)
Und der HTML wird zurückgegeben, indem er der 'html'-Liste hinzugefügt wird:
return HTMLString(''.join(html))
Was steht in einem subfield?
Wenn wir unsere eigene HTML bauen wollen, brauchen wir alle Informationen, aber wie können wir diese bekommen? Sie muss in subfield stehen. Was ich getan habe, ist das, was ich normalerweise tue, nämlich die Attribute subfield ausdrucken:
for subfield in field:
current_app.logger.debug(fname + ': subfield.__dict__ = {}'.format(subfield.__dict__))
Daraus ergaben sich die folgenden Informationen, die nur für eines der subfields gezeigt wurden:
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}
Beachten Sie das angekreuzte Attribut, es signalisiert das ausgewählte Element. Wir verwenden dies, um unser eigenes HTML zu bauen.
Verwendung von Bootstrap 4 Tasten
Ich benutze Bootstrap schon seit einiger Zeit und dachte, dass die Taste Bootstrap ein guter Kandidat für die Taste image picker widget sein könnte. Die auf der Website verwendeten Avatar-Bilder haben einen transparenten Hintergrund, was bei der Verwendung von Bootstrap -Buttons sehr schön ist. Eine ausgewählte, schwebende Taste Bootstrap zeigt einen dunkleren Hintergrund und Rand.
Der Trick besteht darin, den radio button und das Avatar-Bild in den Button zu setzen. Ich verstecke auch die radio button mit der Klasse d-none. Schließlich schließe ich die Schaltflächen mit einem div. Das ListImagesWidget sieht jetzt so aus:
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))
Das funktioniert alles sehr gut. Es zeigt alle Bilder und die Schaltfläche zum Einreichen darunter. Das ausgewählte Bild ist etwas dunkler.
Zusammenfassung
Dies ist nur eine von vielen Möglichkeiten, dies umzusetzen. Hier habe ich etwas WTForms -Code kopiert und modifiziert. Das Schöne daran ist, dass wir nicht zusätzlich Javascript, jQuery und/oder CSS hinzufügen müssen. Wenn Sie dies in Aktion sehen möchten, müssen Sie sich auf dieser Website registrieren und zu Ihrem 'Konto' gehen.
Links / Impressum
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
Neueste
- Ausblenden der Primärschlüssel der Datenbank UUID Ihrer Webanwendung
- Don't Repeat Yourself (DRY) mit Jinja2
- SQLAlchemy, PostgreSQL, maximale Anzahl von Zeilen pro user
- Anzeige der Werte in den dynamischen Filtern SQLAlchemy
- Sichere Datenübertragung mit Public Key Verschlüsselung und pyNaCl
- rqlite: eine hochverfügbare und distverteilte SQLite -Alternative
Meistgesehen
- Verwendung von Pythons pyOpenSSL zur Überprüfung von SSL-Zertifikaten, die von einem Host heruntergeladen wurden
- Verwendung von UUIDs anstelle von Integer Autoincrement Primary Keys mit SQLAlchemy und MariaDb
- PyInstaller und Cython verwenden, um eine ausführbare Python-Datei zu erstellen
- Verbindung zu einem Dienst auf einem Docker -Host von einem Docker -Container aus
- SQLAlchemy: Verwendung von Cascade Deletes zum Löschen verwandter Objekte
- Flask RESTful API Validierung von Anfrageparametern mit Marshmallow-Schemas