Eigene Felder
Ziel
Für die Eingabe von Euro-Werten brauchte ich ein Form-Feld welches nur 2 Ziffern akzeptiert.
Der Nachteil eines normalen HTML-Input-Felds vom Typ "text" ist das Benutzer dort auch Text eingeben kann, und nicht definiert werden kann wie viel Nachkommastellen vorgesehen sind.
In HTML5 wurde ein Feld mit dem Input-Type "number" definiert, welches nur Zahlen als Wert akzeptiert - auf Mobilgeräten passt sich ggf. die Tastatur an und präsentiert nur Zahlen. Über das Attribut "step" lässt sich die Granularität der erlaubten Werte setzen.
step="0.01"
Obiges würde zum Beispiel Dezimalzahlen mit 2 Nachkommastellen erlauben, wobei der Wert (durch Pfeile an der Seite) in Stufen von "0,01" nach oben und unten angepasst werden kann.
Darüber hinaus erlaubt der Typ auch das definieren von Minimal- und Maximalwerten für das Feld.
Und was auch eine große Erleichterung ist, ist das es Dezimal-Trenner, also im englischen "." und im europäischen Raum "," je nach Browser-Locale gesetzt wird und in den gesendeten Form-Daten dann aber einheitlich nach "." als Trenner konvertiert wird.
<html>
<head>
</head>
<body>
<form action="http://127.0.0.1:5000/somewhere" method="POST">
<input type="number" step="0.01"/>
<input type="submit" value="Absenden"/>
</form>
</body>
</html>
Auf Seiten von WTFForms gibt es ein Float-Field, welches aber nicht die Limitierung der Nachkommastellen vorsieht, außerdem benutzt es auf HTML-Seite ein Feld vom Input-Typ "text".
Es gibt ein weiteres Feld DecimalField, welches tatsächlich den Input-Type "number" benutzt, leider unterstützt es nicht das setzen von Parametern. Es setzt "step" explizit auf "any" und die Einstellung kann nicht überschrieben werden:
class DecimalField(LocaleAwareNumberField):
widget = widgets.NumberInput(step="any")
def __init__(
self, label=None, validators=None, places=unset_value, rounding=None, **kwargs
):
Lösung
Man klaue sich einfach ein bestehendes Feld und modifiziert es nach seinen Wünschen :)
class TwoDecimalField(FloatField):
widget = wtforms.widgets.NumberInput(step="0.01")
def process_formdata(self, valuelist):
super().process_formdata(valuelist)
self.data = round(self.data, 2)
Baut man ein komplett neues Feld sollte man von "Field" erben, was das Gerüst zur Verfügung stellt.
Darüber hinaus sollte die Klasse ein "widget"-Attribut haben, welches definiert welches Widget (die definieren die HTML-Elemente für die Darstellung) genutzt werden soll. Alternativ kann man auch das init von Field aufrufen und den Parameter widget setzen.
Die Methode _value muss definiert werden, sie wird vom definierten Widget aufgerufen um einen Default-Wert zu erhalten → der in HTML im Attribut "value" gerendert wird. In der Regel wird man die Daten aus self.data auslesen und einen String zurückliefern (hängt vom Widget ab).
Die Methode process_formdata konvertiert die Daten die für das Feld vom Formular zurückkommen (also nach dem Submit) von einem String in was auch immer das Feld zurückgeben soll. Dabei bekommt sie eine Liste übergeben, wobei bei den meisten Widgets nur ein Wert vorhanden. Die Werte in der Liste sind alle strings (weil Formulare nur Strings übertragen) und müssen ggf. durch die Methode konvertiert werden. Die Daten werden anschließend in self.data gespeichert.
Ein ausführliches Beispiel kann man hier finden: https://wtforms.readthedocs.io/en/3.2.x/fields/#custom-fields