4343]
4444import decimal
4545import warnings
46- from typing import Callable , Dict , List , Optional , Type , Union
46+ from typing import Callable , List , Optional , Type , Union
4747
4848from bson import ObjectId
4949from mongoengine import fields
5050
51+ from flask_mongoengine .decorators import wtf_required
52+
5153try :
5254 from wtforms import fields as wtf_fields
5355 from wtforms import validators as wtf_validators_
@@ -75,7 +77,7 @@ class WtfFieldMixin:
7577
7678 DEFAULT_WTF_FIELD = None
7779 DEFAULT_WTF_CHOICES_FIELD = wtf_fields .SelectField if wtf_fields else None
78- DEFAULT_WTF_CHOICES_FIELD_COERCE = str
80+ DEFAULT_WTF_CHOICES_COERCE = str
7981
8082 def __init__ (
8183 self ,
@@ -85,7 +87,8 @@ def __init__(
8587 wtf_field_class : Optional [Type ] = None ,
8688 wtf_filters : Optional [Union [List , Callable ]] = None ,
8789 wtf_validators : Optional [Union [List , Callable ]] = None ,
88- wtf_options : Optional [Dict ] = None ,
90+ wtf_choices_coerce : Optional [Callable ] = None ,
91+ wtf_options : Optional [dict ] = None ,
8992 ** kwargs ,
9093 ):
9194 """
@@ -98,6 +101,8 @@ def __init__(
98101 :attr:`DEFAULT_WTF_FIELD` and :attr:`DEFAULT_WTF_CHOICES_FIELD`
99102 :param wtf_filters: wtf form field filters.
100103 :param wtf_validators: wtf form field validators.
104+ :param wtf_choices_coerce: Callable function to replace
105+ :attr:`DEFAULT_WTF_CHOICES_COERCE` for choices fields.
101106 :param wtf_options: Dictionary with WTForm Field settings.
102107 Applied last, takes precedence over any generated field options.
103108 :param kwargs: keyword arguments silently bypassed to normal mongoengine fields
@@ -131,7 +136,7 @@ def __init__(
131136 wtf_filters or filters , "wtf_filters"
132137 )
133138 self .wtf_options = wtf_options
134-
139+ self . wtf_choices_coerce = wtf_choices_coerce or self . DEFAULT_WTF_CHOICES_COERCE
135140 # Some attributes that will be updated by parent methods
136141 self .required = False
137142 self .default = None
@@ -140,7 +145,6 @@ def __init__(
140145
141146 # Internals
142147 self ._wtf_field_class = wtf_field_class
143- self ._wtf_options = {}
144148
145149 super ().__init__ (** kwargs )
146150
@@ -153,6 +157,47 @@ def wtf_field_class(self):
153157 return self .DEFAULT_WTF_CHOICES_FIELD
154158 return self .DEFAULT_WTF_FIELD
155159
160+ @property
161+ @wtf_required
162+ def wtf_generated_options (self ) -> dict :
163+ """
164+ WTForm Field options generated by class, not updated by user provided :attr:`wtf_options`.
165+ """
166+ wtf_field_kwargs : dict = {
167+ "label" : getattr (self , "verbose_name" , self .name ),
168+ "description" : getattr (self , "help_text" , None ) or "" ,
169+ "default" : self .default ,
170+ # Create a copy of the lists with list() call, since we will be modifying it
171+ "validators" : list (self .wtf_validators ) or [],
172+ "filters" : list (self .wtf_filters ) or [],
173+ }
174+
175+ if self .required :
176+ wtf_field_kwargs ["validators" ].append (wtf_validators_ .InputRequired ())
177+ else :
178+ wtf_field_kwargs ["validators" ].append (wtf_validators_ .Optional ())
179+
180+ if self .choices :
181+ wtf_field_kwargs ["choices" ] = self .choices
182+ wtf_field_kwargs ["coerce" ] = self .wtf_choices_coerce
183+
184+ return wtf_field_kwargs
185+
186+ @property
187+ @wtf_required
188+ def wtf_field_options (self ):
189+ """
190+ Final WTForm Field options that will be applied as :attr:`wtf_field_class` kwargs.
191+
192+ It is not recommended to overwrite this property, for logic update overwrite
193+ :attr:`wtf_generated_options`
194+ """
195+ wtf_field_kwargs = self .wtf_generated_options
196+ if self .wtf_options is not None :
197+ wtf_field_kwargs .update (self .wtf_options )
198+
199+ return wtf_field_kwargs
200+
156201 @staticmethod
157202 def _ensure_callable_or_list (argument , msg_flag : str ) -> Optional [List ]:
158203 """
@@ -162,7 +207,7 @@ def _ensure_callable_or_list(argument, msg_flag: str) -> Optional[List]:
162207 :param msg_flag: Argument string name for error message.
163208 """
164209 if argument is None :
165- return None
210+ return []
166211
167212 if callable (argument ):
168213 return [argument ]
@@ -208,7 +253,7 @@ class BooleanField(WtfFieldMixin, fields.BooleanField):
208253 """
209254
210255 DEFAULT_WTF_FIELD = wtf_fields .BooleanField if wtf_fields else None
211- DEFAULT_WTF_CHOICES_FIELD_COERCE = bool
256+ DEFAULT_WTF_CHOICES_COERCE = bool
212257
213258 def to_wtf_field (self , model , field_kwargs ):
214259 """
@@ -300,7 +345,7 @@ class DecimalField(WtfFieldMixin, fields.DecimalField):
300345 """
301346
302347 DEFAULT_WTF_FIELD = wtf_fields .DecimalField if wtf_fields else None
303- DEFAULT_WTF_CHOICES_FIELD_COERCE = decimal .Decimal
348+ DEFAULT_WTF_CHOICES_COERCE = decimal .Decimal
304349
305350 def to_wtf_field (self , model , field_kwargs ):
306351 """
@@ -451,7 +496,7 @@ class FloatField(WtfFieldMixin, fields.FloatField):
451496 """
452497
453498 DEFAULT_WTF_FIELD = wtf_fields .FloatField if wtf_fields else None
454- DEFAULT_WTF_CHOICES_FIELD_COERCE = float
499+ DEFAULT_WTF_CHOICES_COERCE = float
455500
456501 def to_wtf_field (self , model , field_kwargs ):
457502 """
@@ -573,7 +618,7 @@ class IntField(WtfFieldMixin, fields.IntField):
573618 """
574619
575620 DEFAULT_WTF_FIELD = wtf_fields .IntegerField if wtf_fields else None
576- DEFAULT_WTF_CHOICES_FIELD_COERCE = int
621+ DEFAULT_WTF_CHOICES_COERCE = int
577622
578623 def to_wtf_field (self , model , field_kwargs ):
579624 """
@@ -730,7 +775,7 @@ class ObjectIdField(WtfFieldMixin, fields.ObjectIdField):
730775 All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
731776 """
732777
733- DEFAULT_WTF_CHOICES_FIELD_COERCE = ObjectId
778+ DEFAULT_WTF_CHOICES_COERCE = ObjectId
734779
735780 def to_wtf_field (self , model , field_kwargs ):
736781 """
0 commit comments