Flask Macro - Selectbox rendern

Django, Flask, Bottle, WSGI, CGI…
meego
User
Beiträge: 380
Registriert: Montag 4. März 2013, 14:36

Wie bringe ich das Optionsfeld (disabled) im oberen <select> an die erste Stelle des unteren <select>?
http://pastie.org/private/ua9cmxmywdt2rcgdq8cjq
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

ohne das du noch das Macro zeigst kommt hier wohl keiner weiter...

Gruß, noisefloor
meego
User
Beiträge: 380
Registriert: Montag 4. März 2013, 14:36

Hallo Noisefloor

Die Definition vom Macro (_formhelpers.html) steht oben im paste. Meinst du den Aufruf?

Wäre dann einfach:

Code: Alles auswählen

{{ render_selectfield(form.country) }}
LG
BlackJack

Das <select> wird ja durch die Umwandlung von `field` in eine Zeichenkette erzeugt. Wenn Du da etwas anders haben willst, dann musst Du auch *dort* ansetzen. Also was ist `field` für ein Datentyp und wie kann man die Umwandlung da beeinflussen, falls man das überhaupt kann. Wenn man das nicht kann, dann musst Du das durch eine Funktion oder ein Makro ersetzen das `field` so in HTML umwandelt wie Du das gerne hättest.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Die Definition vom Macro (_formhelpers.html) steht oben im paste. Meinst du den Aufruf?
Nee - war ein Denkfehler meinerseits, sorry.

BlackJack es ja schon gesagt: das Makro ist im Prinzip zu "high level". Wenn du die Optionen des Select-Fields per Schleife im Makro erzeugst, dann ist auch das setzen der nicht-wählbaren Default-Option nicht das Problem.

Gruß, noisefloor
meego
User
Beiträge: 380
Registriert: Montag 4. März 2013, 14:36

Ich verstehe da gerade nur Bahnhof. Das ich das obere Select idealerweise irgendwie beim Field als Argument übergeben müsste ist mir klar.

Das WTForm field ist:

Code: Alles auswählen

country = QuerySelectField('Country', query_factory=lambda: Country.query, get_label=lambda x: x.name)
Es stammt aus dem Paket:
wtforms.ext.sqlalchemy.fields
BlackJack

@meego: Da würde man dann wohl diesen Typ und das zugeordnete widget entsprechend ableiten und modifizieren müssen damit das dann entsprechend gerendert wird. Das `Field` müsste bei `iter_choices()` diese spezielle Option als erstes liefern und das Widget müsste in seiner `render_option()`-Methode dieses erste spezielle Feld mit dem `disabled`-HTML-Attribut rendern.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

@meego: vom Prinzip her so (das ist kein echter Jinja2 Code):

Code: Alles auswählen

<select>
<option value="" disabled selected>Choose an option</option>
{% for field in fields %}
<option value="{{ field.value }}">{{ field.name }}</option>
{% endfor %}
</select>
Gruß, noisefloor
meego
User
Beiträge: 380
Registriert: Montag 4. März 2013, 14:36

@blackjack: Over my head.

@noisefloor: Folgendes funktioniert nicht:

Code: Alles auswählen

{% macro render_selectfield(field) %}
<div class="input-field col s6">
	<select>
    	<option value="" disabled selected>{{ _('Wähle deine Option') }}</option>
	    {% for f in field %}
	    	<option value="{{ f.value }}">{{ f.name }}</option>
	    {% endfor %}
	</select>
</div>
{% endmacro %}
Resultat:

Code: Alles auswählen

3 x <option value="">country</option>
Habe ich einen Fehler drin?
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Habe ich einen Fehler drin?
Natürlich, sonst würde es ja funktionieren ;-)

Ob `f.value` und `f.name` bei dir richtig ist weiß niemand außer dir, weil wir den restlichen Code nicht kennen. Das musst du halt ggf. noch anpassen, je nach dem, was in `field` so "drin" ist.

Gruß, noisefloor
meego
User
Beiträge: 380
Registriert: Montag 4. März 2013, 14:36

Hallo Noisy

Das WTForm?:

Code: Alles auswählen

country = QuerySelectField('Country', query_factory=lambda: Country.query, get_label=lambda x: x.name)
Oder das Modell?:

Code: Alles auswählen

class Country(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
Da wäre das Attribut .name eigentlich vorhanden.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

ich habe zwar früher auch mal WTForms und SQLAlchemy benutzt - aber nie so, mit dem dynamischen befüllen von WTForms Feldern aus SA (und, das muss ich dann an dieser Stelle doch mal sagen: jetzt weiß ich auch wieder, warum ich heute Django nehme ;-) ).

Letzte Idee von mir: auf der Konsole debuggen bzw. inspizieren. Also alles importieren, und mal in die Form-Klasse reinschauen bzw. schauen, was hinter den Attributen des Klasse so für Inhalt steckt.

Gruß, noisefloor
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@meego: WTForms ist ja ganz schön verschachtelt. Als erstes brauchst Du ein neues Select-Widget, dann ein QuerySelectField, das auf dieses Widget verweist:

Code: Alles auswählen

from wtforms import widgets

class SelectWithChooseOption(widgets.Select):
    def __call__(self, field, **kwargs):
        kwargs.setdefault('id', field.id)
        if self.multiple:
            kwargs['multiple'] = True
        html = ['<select %s>' % html_params(name=field.name, **kwargs)]
        any_selected = False
        for val, label, selected in field.iter_choices():
            html.append(self.render_option(val, label, selected))
            any_selected |= selected
        html.insert(1, self.render_option('', 'Choose your option', selected=not any_selected, disabled=True)
        html.append('</select>')
        return HTMLString(''.join(html))

class QuerySelectFieldWithChooseOption(QuerySelectField):
    widget = SelectWithChooseOption()
meego
User
Beiträge: 380
Registriert: Montag 4. März 2013, 14:36

Huch, Sirius, das ist aber kompliziert. Und das ist tatsächlich die einfachste Art und Weise zum Ergebnis zu kommen?

@noisefloor: Django habe ich mir auch angesehen, das Tutorial war aber nicht gerade Newbiefreundlich und didaktisch eher verwirrend, immer vom Spezialfall zur normalen Anwendung. Jetzt würde ich vielleicht schon etwas mehr davon verstehen.
BlackJack

@meego: Den ”einfacheren” Weg hatte ich ja schon angerissen, da muss man dann aber sowohl das `Field` als auch das passende `Widget` ableiten. Dafür muss man nicht den grossteil der `__call__()`-Methode vom originalen `Select` abschreiben.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
das ist aber kompliziert. Und das ist tatsächlich die einfachste Art und Weise zum Ergebnis zu kommen?
Auf anderem Wege habe ich das zumindest nicht geschafft. Und die Lösung von Sirius findet man so oder in sehr ähnlicher Form auch in diversen Threads bei SO.
Und kompliziert ist es für doch doch nicht wirklich - Code per C&P übernehmen und gut ist :-)

Gruß, noisefloor
meego
User
Beiträge: 380
Registriert: Montag 4. März 2013, 14:36

@Blackjack, ich weiss nicht einmal, wozu die __call__ methode gut ist. Meinst du, mit einem Programmier-Profi zu sprechen? :wink:

@Noisefloor: C & P mache ich nur ungern, wenn ich den Code selber nicht verstehe. Was ist SO? Eine andere Alternative wäre einfach die normale Selectbox rendern, zumal das ganze JS-zeug etwas langsam ist.
BlackJack

@meego: Was die `__call__()`-Methode generell macht kann man in der Python-Dokumentation nachlesen und was sie bei WTForms semantisch macht, sollte man dort in der Dokumentation nachlesen können. Die wandelt das `Field` in HTML-Quelltext um. Wenn man also beeinflussen will *wie* das passiert, dann muss man sich die halt anschauen und zum Beispiel kopieren und den eigenen Wünschen anpassen. Alternativ kann man schauen was die so von sich und anderen Objekten verwendet und dort die entsprechenden Methoden überschreiben/verändern.

SO = StackOverflow.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
C & P mache ich nur ungern, wenn ich den Code selber nicht verstehe.
Sagen wir mal so: die meisten von uns - und da schließe ich mich explizit mit ein - werden den Code der Module, die wir einsetzen, nicht verstehen :-)
Bei WTForms z.B. leitest du dir auch eigne Klassen ab und zu wissen, was die Basisklasse macht. Und warum da die Attribute so definiert werden, wie es halt gemacht wird, ist den meisten wahrscheinlich auch nicht klar.

Die Alternative zur Lösung von Sirius wäre noch, dass du den "Choose an Option" Eintrag regulär einfügst und dann auf der Webseite per JavaScript deaktivierst. Wobei das eigene Widget IMHO deutlich eleganter ist.

Und nochmal kurz zu Django: in einer meiner Apps leite ich eine Form aus einem Model für die DB ab. Beim Rendern der Form im Template ist der 1. automatisch `-------`. Diese ist zwar auswählbar, allerdings validiert die Form nicht, wenn man diesen Eintrag wählt.
Ich habe früher ja auch Bottle + WTForms + SQLAlchemy eingesetzt. Was ich selber aber bei Django _wesentlich_ angenehmer finde ist, dass alles unter einer Haube ist und damit sehr gut integriert. Was nicht heißt, dass Django per se besser und alles andere keine Existenzberechtigung.

Gruß, noisefloor
meego
User
Beiträge: 380
Registriert: Montag 4. März 2013, 14:36

Gerade mal schnell ausprobiert. Sieht aus wie im Widgetsbeispiel in der Doku, was ist das Problem?:

Code: Alles auswählen

Traceback (most recent call last):
  File "/../forms.py", line 76
    html.append('</select>')
@Blackjack: Danke, werde mir wtforms mal etwas genauer ansehen.

@Noisy: Ein '-------' finde ich aber jetzt aber etwas schräg (wenn man das nicht ändern kann). Ja, ich habe mir auch schon die Sinnfrage gestellt, weil man ja letztlich doch alle Erweiterungen benötigt (Migrationen, Admin, wtforms, alchemy, babel). Allerdings haben die selbst im Djangoforum gesagt, SQLalchemy würden sie lieber verwenden und irgendwie ist's bis jetzt einfach näher an der Pythonsyntax, also gerade als Newbie vielleicht besser zum lernen.
Antworten