angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

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.

24 Januar 2020
post main image
https://unsplash.com/@heftiba

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

Einen Kommentar hinterlassen

Kommentieren Sie anonym oder melden Sie sich zum Kommentieren an.

Kommentare

Eine Antwort hinterlassen

Antworten Sie anonym oder melden Sie sich an, um zu antworten.