Bottle - WTForms Problem

Django, Flask, Bottle, WSGI, CGI…
Antworten
Benutzeravatar
noisefloor
User
Beiträge: 3854
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

ich habe ein Problem in der Kombi Bottle - WTForms, und zwar ein doofes Unicode-Problem.

Code:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from bottle import route, request, template, run, debug
from wtforms import Form, SelectField, SubmitField

choices_list = [(u'Jochen',u'Jochen'),(u'G\xfcnter', u'G\xfcnter')]

class MyForm(Form):
    name = SelectField(u'Name')
    senden = SubmitField(u'Senden')

@route('auswahl')
@route('auswahl', method='POST')
def auswahl():

    req = request.forms
    form = MyForm(req)
    if not req:
        return template('auswahl.tpl', form = form, choices_list=choices_list)
    else:
        return u'Der Name ist {0}'.format(form.name.data)

if __name__ == '__main__':
    debug(True)
    run(reloader=True)
und das Template:

Code: Alles auswählen

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Bottle WTForms Test</title>
</head>
<body>
<h1>Bottle WTForms Test</h1>
<p>Formular:</p>
<form action="/auswahl" method="post">
%form.name.choices = choices_list
<p>{{!form.name.label}} {{!form.name}}</p>
<p>{{!form.senden}}</p>
</form>
</body>
</html>
Start man das Prog, wirft Bottle einen Fehler:

Code: Alles auswählen

UnicodeEncodeError('ascii', u'<select id="name" name="name"><option value="Jochen">Jochen</option><option value="G\xfcnter">G\xfcnter</option></select>', 84, 85, 'ordinal not in range(128)')

Traceback (most recent call last):
  File "/usr/local/lib/python2.6/dist-packages/bottle-0.8.5-py2.6.egg/bottle.py", line 499, in handle
    return handler(**args)
  File "bottle_wtform_test.py", line 21, in auswahl
    req=req,choices_list=choices_list)
  File "/usr/local/lib/python2.6/dist-packages/bottle-0.8.5-py2.6.egg/bottle.py", line 1795, in template
    return TEMPLATES[tpl].render(**kwargs)
  File "/usr/local/lib/python2.6/dist-packages/bottle-0.8.5-py2.6.egg/bottle.py", line 1774, in render
    self.execute(stdout, **args)
  File "/usr/local/lib/python2.6/dist-packages/bottle-0.8.5-py2.6.egg/bottle.py", line 1762, in execute
    eval(self.co, env)
  File "/home/jochen/code/test/views/auswahl.tpl", line 13, in <module>
    <p>{{!form.name.label}} {{!form.name}}</p>
  File "/usr/local/lib/python2.6/dist-packages/bottle-0.8.5-py2.6.egg/bottle.py", line 1649, in <lambda>
    self._str = lambda x: touni(x, enc)
  File "/usr/local/lib/python2.6/dist-packages/bottle-0.8.5-py2.6.egg/bottle.py", line 131, in touni
    return x if isinstance(x, unicode) else unicode(str(x), encoding=enc)
UnicodeEncodeError: 'ascii' codec can't encode character u'\xfc' in position 84: ordinal not in range(128)
Ändert man "choices_list" in

Code: Alles auswählen

choices_list = [('Jochen','Jochen'),('Günter', 'Günter')]
dann wirft WTForms den Fehler:

Code: Alles auswählen

UnicodeDecodeError('ascii', 'G\xc3\xbcnter', 1, 2, 'ordinal not in range(128)')

Traceback (most recent call last):
  File "/usr/local/lib/python2.6/dist-packages/bottle-0.8.5-py2.6.egg/bottle.py", line 499, in handle
    return handler(**args)
  File "bottle_wtform_test.py", line 21, in auswahl
    req=req,choices_list=choices_list)
  File "/usr/local/lib/python2.6/dist-packages/bottle-0.8.5-py2.6.egg/bottle.py", line 1795, in template
    return TEMPLATES[tpl].render(**kwargs)
  File "/usr/local/lib/python2.6/dist-packages/bottle-0.8.5-py2.6.egg/bottle.py", line 1774, in render
    self.execute(stdout, **args)
  File "/usr/local/lib/python2.6/dist-packages/bottle-0.8.5-py2.6.egg/bottle.py", line 1762, in execute
    eval(self.co, env)
  File "/home/jochen/code/test/views/auswahl.tpl", line 13, in <module>
    <p>{{!form.name.label}} {{!form.name}}</p>
  File "/usr/local/lib/python2.6/dist-packages/bottle-0.8.5-py2.6.egg/bottle.py", line 1649, in <lambda>
    self._str = lambda x: touni(x, enc)
  File "/usr/local/lib/python2.6/dist-packages/bottle-0.8.5-py2.6.egg/bottle.py", line 131, in touni
    return x if isinstance(x, unicode) else unicode(str(x), encoding=enc)
  File "/usr/local/lib/python2.6/dist-packages/WTForms-0.6.2-py2.6.egg/wtforms/fields.py", line 119, in __str__
    return self()
  File "/usr/local/lib/python2.6/dist-packages/WTForms-0.6.2-py2.6.egg/wtforms/fields.py", line 135, in __call__
    return self.widget(self, **kwargs)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 1: ordinal not in range(128)
Und ich habe so gar keine Idee, wie ich das Lösen kann bzw. was ich machen muss, damit das funktioniert...

Gruß, noisefloor
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Kann man bei Bottle angeben, welches Encoding die Template-Engine verwenden soll? Die erste Fehlermeldung besagt ja, dass Bottle ASCII nimmt - Du willst ja sicher utf-8 o.ä. haben.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
noisefloor
User
Beiträge: 3854
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

in der Kurzform, wie man es überlicherweise verwendet, also

Code: Alles auswählen

...
return template(foo)
nicht, aber in der langen Version schon... Zumindest soweit ich die API-Doku verstehe. Probiere ich heute Abend mal.

Gruß, noisefloor
Benutzeravatar
noisefloor
User
Beiträge: 3854
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

also, nach ein bisschen experimentieren auf der Konsole glaube ich, dass es an WTForms liegt - bzw. an vielleicht auch an mir, weil ich nicht verstehe, wann und wie WTForms Unicodedaten haben will.

Bottle verwendet intern UTF-8 als Default-Encoding. Das ist in der Klasse "BaseTemplate" hinterlegt. Die oben besagte lange Form gibt das gleiche Ergebnis wie die kurze...

Interessanter Weise funktioniert es mit Fungiform:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from bottle import route, request, template, run, debug, SimpleTemplate
from fungiform.forms import FormBase, ChoiceField

choices_list = [u'Jochen',u'G\xfcnter']

class MyForm(FormBase):
    name = ChoiceField()
    
@route('auswahl')
@route('auswahl', method='POST')
def auswahl():

    req = request.forms
    form = MyForm()
    form.name.choices = choices_list
    widget = form.as_widget()
    if not req:
        return template('auswahl_fungi.tpl', widget = widget)
    else:
        return u'Der Name ist {0}'.format(req['name'].decode('utf8'))

if __name__ == '__main__':
    debug(True)
    run(reloader=True)
Template:

Code: Alles auswählen

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Bottle Fungiform Test</title>
</head>
<body>
<h1>Bottle Fungiform Test</h1>
<p>Formular:</p>
{{widget.render()}}
</body>
</html>
 
EIGENTLICH wäre mir zwar WTForms lieber, weil ich das woanders auch im Einsatz habe und Fungiform noch Beta ist bzw. bestimmte Sachen noch nicht implementiert sind - @mitsuhiko: Bitte weiter entwickeln! :-)

Ansonsten wäre ich nach wie vor dankbar, wenn jemand eine Lösung für WTForms + Bottle hätte...

Gruß, noisefloor
frabron
User
Beiträge: 306
Registriert: Dienstag 31. März 2009, 14:36

Hast du schon mal probiert, die choices_list im View an das Selectfeld zu binden? Ich wusste z.B. gar nicht, dass man das auch im Template kann ...

Edit, also quasi hier:

Code: Alles auswählen

# ...
@route('auswahl')
@route('auswahl', method='POST')
def auswahl():

    req = request.forms
    form = MyForm(req)
    form.name.choices = choices_list
    # ...
Benutzeravatar
noisefloor
User
Beiträge: 3854
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

das ist egal bzw. das Ergebnis ist das gleiche.

Man kann den Fehler BTW auch in der Python-Konsole "generieren":

Code: Alles auswählen

>>> from wtforms import Form, SelectField
>>> class MyForm(Form):
...     name = SelectField()
... 
>>> choices_list = [(u'Jochen',u'Jochen'),(u'G\xfcnter',u'G\xfcnter')]
>>> form = MyForm()
>>> form.name.choices = choices_list
>>> print form.name.choices
[(u'Jochen', u'Jochen'), (u'G\xfcnter', u'G\xfcnter')]
>>> form.name
<wtforms.fields.SelectField object at 0x7f34a2e37510>
>>> print form.name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xfc' in position 84: ordinal not in range(128)
Gruß, noisefloor
Benutzeravatar
noisefloor
User
Beiträge: 3854
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

Nachtrag: Das Template funktioniert, wenn man es so macht:

Code: Alles auswählen

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Bottle WTForms Test</title>
</head>
<body>
<h1>Bottle WTForms Test</h1>
<p>Formular:</p>
<form action="/auswahl" method="post">
<p>{{!form.name.label}} {{!unicode(form.name)}}</p>
<p>{{!form.senden}}</p>
</form>
</body>
</html>
also "unicode" davor.

Dafür zeigt die Ausgabe von "form.name.data" aber "None" an...

"req" sieht nach dem Senden der Form so aus:

Code: Alles auswählen

('senden','Senden')
'name','G\xc3\xbcnter')
Außerdem validiert die Form nicht...

Ist doch alles Mist.

Gruß, noisefloor
frabron
User
Beiträge: 306
Registriert: Dienstag 31. März 2009, 14:36

Was willst du eigentlich erreichen, indem du "form.name" aufrufst? Eigentlich erzeugt man das Formfeld doch, indem man "form.name()" mittels __call__ aufruft ...
Benutzeravatar
noisefloor
User
Beiträge: 3854
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

tja... in der Doku (z.B. auf dieser Seite) rufen die form.foo ohne () auf.

Aber du hast recht: mit () funktioniert zumindest die Darstellung des Auswahlmenüs. :-)

Nächstes Problem ist aber nach wir vor, dass nach dem Senden des Formulars "form.name.data" gleich "None" ist, wenn man "Günter" auswählt. Was vermutlich daran liegt, dass die Seite "Günter" in UTF-8 kodiert zurück gibt, WTForms aber Unicode erwartet.

Wenn man dann noch über req iteriert und dabei die Werte mit ".decode('utf8') in Unicode wandelt funktioniert zwar alles, aber nach wie vor Validiert die Form nicht, wenn man "Günter" auswählt. Bei "Jochen" schon.
Fehler: "Invalid choice: Could not coerce"

Gruß, noisefloor
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Hier steht zumindest schon mal, was wir vermutet haben: WTForms benötigt Unicode - wenn das Framework den Content nicht selber wandelt, muss man das wohl manuell machen.

Im übrigen ist der Fehler auf der Konsole auch einfach zu erklären: Deine Shell will eben ASCII als default nutzen.

Kannst Du für das Validieren mal einen lauffähigen Schnipsel zeigen?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
noisefloor
User
Beiträge: 3854
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

so, hier komplett lauffähiger Code: :-)

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from bottle import route, request, template, run, debug, SimpleTemplate, MultiDict
from wtforms import Form, SelectField, SubmitField

choices_list = [(u'Jochen',u'Jochen'),(u'G\xfcnter', u'G\xfcnter')]

class MyForm(Form):
    name = SelectField(u'Name')
    senden = SubmitField(u'Senden')

@route('auswahl')
@route('auswahl', method='POST')
def auswahl():

    req = request.forms
    req_unicode = MultiDict()
    for k,v in req.iteritems():
        req_unicode[k] = v.decode('utf8')
    form = MyForm(req_unicode)
    form.name.choices = choices_list
    if not req and not form.validate():
        return template('auswahl2.tpl', form = form)
    else:
        return u'Der Name ist {0}'.format(form.name.data)

if __name__ == '__main__':
    debug(True)
    run(reloader=True)
Und das Template:

Code: Alles auswählen

<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
</head>
<p>Test</p>
<form action="/auswahl" method="post">
{{!form.name()}}{{!form.senden()}}
</form>
</html>
Das in der Doku die Klammern fehlen (s.o.) ist ärgerlich - THX to fabron für den Hinweis. :-)
Das WTForms den Request nicht selber wandelt - na ja, nicht komfortabel, aber auch nicht so schlimm.

Gruß, noisefloor
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Also in ner Shell kann ich es (nicht) nachvollziehen:

Code: Alles auswählen

>>> choices_list = [(u'Jochen',u'Jochen'),(u'Günter', u'Günter')]
>>> form = MyForm(name=u"Günter")
>>> form.name.choices = choices_list
>>> form.validate()
True
So gehts natürlich nicht:

Code: Alles auswählen

>>> form = MyForm(name="Günter")
>>> form.name.choices = choices_list
>>> form.validate()
False
Bist Du sicher, dass beim Wandeln wirklich alles (korrekt) in Unicode übersetzt wird?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
noisefloor
User
Beiträge: 3854
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

auf der Shell war ja das Problem, dass das Formfeld nicht gerendert wurde, nicht die Valdierung.

Das Problem mit der Validierung war, das WTForms das UTF8-kodierte "request.forms" nicht verarbeiten kann und man dieses daher vor der Übergabe explizit in Unicode dekodieren muss.

Hab's inzwischen auf in mein Programm eingebaut - funktioniert. :-)

@mitsuhiko: Fungiform bitte trotzdem weiter entwickeln ;-)

Gruß, noisefloor
Antworten