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.

Einfaches Beispiel. step="0.01" definiert das es eine Zahl mit maximal 2 Dezimalstellen sein kann und das die Schrittweite 0,01 beträgt
<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>
invalid value message
Figure 1. Eingabe von größeren Zahlen ist zwar möglich, resultiert aber in einem Fehler mit entsprechendem Hinweis wenn man das Formular versucht abzusenden
send value
Figure 2. Auch wenn man lokale Dezimaltrennzeichen - der Wert war hier "5,36" eingibt - im gesendeten Formular werden sie einheitlich in "."-Schreibweise konvertiert

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