MongoEngine/flask-mongoengine

View on GitHub
flask_mongoengine/db_fields.py

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
"""Responsible for mongoengine fields extension, if WTFForms integration used."""
__all__ = [
    "WtfFieldMixin",
    "BinaryField",
    "BooleanField",
    "CachedReferenceField",
    "ComplexDateTimeField",
    "DateField",
    "DateTimeField",
    "DecimalField",
    "DictField",
    "DynamicField",
    "EmailField",
    "EmbeddedDocumentField",
    "EmbeddedDocumentListField",
    "EnumField",
    "FileField",
    "FloatField",
    "GenericEmbeddedDocumentField",
    "GenericLazyReferenceField",
    "GenericReferenceField",
    "GeoJsonBaseField",
    "GeoPointField",
    "ImageField",
    "IntField",
    "LazyReferenceField",
    "LineStringField",
    "ListField",
    "LongField",
    "MapField",
    "MultiLineStringField",
    "MultiPointField",
    "MultiPolygonField",
    "ObjectIdField",
    "PointField",
    "PolygonField",
    "ReferenceField",
    "SequenceField",
    "SortedListField",
    "StringField",
    "URLField",
    "UUIDField",
]
import decimal
import warnings
from typing import Callable, List, Optional, Type, Union

from bson import ObjectId
from mongoengine import fields

from flask_mongoengine.decorators import wtf_required

try:
    from wtforms import fields as wtf_fields
    from wtforms import validators as wtf_validators_

    from flask_mongoengine.wtf import fields as custom_fields
except ImportError:  # pragma: no cover
    custom_fields = None
    wtf_fields = None
    wtf_validators_ = None


@wtf_required
def _setup_strings_common_validators(options: dict, obj: fields.StringField) -> dict:
    """
    Extend :attr:`base_options` with common validators for string types.

    :param options: dict, usually from :class:`WtfFieldMixin.wtf_generated_options`
    :param obj: Any :class:`mongoengine.fields.StringField` subclass instance.
    """
    assert isinstance(obj, fields.StringField), "Improperly configured"
    if obj.min_length or obj.max_length:
        options["validators"].insert(
            0,
            wtf_validators_.Length(
                min=obj.min_length or -1,
                max=obj.max_length or -1,
            ),
        )

    if obj.regex:
        options["validators"].insert(0, wtf_validators_.Regexp(regex=obj.regex))

    return options


@wtf_required
def _setup_numbers_common_validators(
    options: dict, obj: Union[fields.IntField, fields.DecimalField, fields.FloatField]
) -> dict:
    """
    Extend :attr:`base_options` with common validators for number types.

    :param options: dict, usually from :class:`WtfFieldMixin.wtf_generated_options`
    :param obj: Any :class:`mongoengine.fields.IntField` or
        :class:`mongoengine.fields.DecimalField` or
        :class:`mongoengine.fields.FloatField` subclasses instance.
    """
    assert isinstance(
        obj, (fields.IntField, fields.DecimalField, fields.FloatField)
    ), "Improperly configured"

    if obj.min_value or obj.max_value:
        options["validators"].insert(
            0, wtf_validators_.NumberRange(min=obj.min_value, max=obj.max_value)
        )

    return options


class WtfFieldMixin:
    """
    Extension wrapper class for mongoengine BaseField.

    This enables flask-mongoengine wtf to extend the number of field parameters, and
    settings on behalf of document model form generator for WTForm.

    **Class variables:**

    :cvar DEFAULT_WTF_CHOICES_FIELD: Default WTForms Field used for db fields when
        **choices** option specified.
    :cvar DEFAULT_WTF_FIELD: Default WTForms Field used for db field.
    """

    DEFAULT_WTF_FIELD = None
    DEFAULT_WTF_CHOICES_FIELD = wtf_fields.SelectField if wtf_fields else None
    DEFAULT_WTF_CHOICES_COERCE = str

    def __init__(
        self,
        *,
        validators: Optional[Union[List, Callable]] = None,
        filters: Optional[Union[List, Callable]] = None,
        wtf_field_class: Optional[Type] = None,
        wtf_filters: Optional[Union[List, Callable]] = None,
        wtf_validators: Optional[Union[List, Callable]] = None,
        wtf_choices_coerce: Optional[Callable] = None,
        wtf_options: Optional[dict] = None,
        **kwargs,
    ):
        """
        Extended :func:`__init__` method for mongoengine db field with WTForms options.

        :param filters:     DEPRECATED: wtf form field filters.
        :param validators:  DEPRECATED: wtf form field validators.
        :param wtf_field_class: Any subclass of :class:`wtforms.forms.core.Field` that
            can be used for form field generation. Takes precedence over
            :attr:`DEFAULT_WTF_FIELD`  and :attr:`DEFAULT_WTF_CHOICES_FIELD`
        :param wtf_filters:     wtf form field filters.
        :param wtf_validators:  wtf form field validators.
        :param wtf_choices_coerce: Callable function to replace
            :attr:`DEFAULT_WTF_CHOICES_COERCE` for choices fields.
        :param wtf_options: Dictionary with WTForm Field settings.
            Applied last, takes precedence over any generated field options.
        :param kwargs: keyword arguments silently bypassed to normal mongoengine fields
        """
        if validators is not None:
            warnings.warn(
                (
                    "Passing 'validators' keyword argument to field definition is "
                    "deprecated and will be removed in version 3.0.0. "
                    "Please rename 'validators' to 'wtf_validators'. "
                    "If both values set, 'wtf_validators' is used."
                ),
                DeprecationWarning,
                stacklevel=2,
            )
        if filters is not None:
            warnings.warn(
                (
                    "Passing 'filters' keyword argument to field definition is "
                    "deprecated and will be removed in version 3.0.0. "
                    "Please rename 'filters' to 'wtf_filters'. "
                    "If both values set, 'wtf_filters' is used."
                ),
                DeprecationWarning,
                stacklevel=2,
            )
        self.wtf_validators = self._ensure_callable_or_list(
            wtf_validators or validators, "wtf_validators"
        )
        self.wtf_filters = self._ensure_callable_or_list(
            wtf_filters or filters, "wtf_filters"
        )
        self.wtf_options = wtf_options
        self.wtf_choices_coerce = wtf_choices_coerce or self.DEFAULT_WTF_CHOICES_COERCE
        # Some attributes that will be updated by super()
        self.required = False
        self.default = None
        self.name = ""
        self.choices = None

        # Internals
        self._wtf_field_class = wtf_field_class

        super().__init__(**kwargs)

    @property
    def wtf_field_class(self) -> Type:
        """Final WTForm Field class, that will be used for field generation."""
        if self._wtf_field_class:
            return self._wtf_field_class
        if self.choices and self.DEFAULT_WTF_CHOICES_FIELD:
            return self.DEFAULT_WTF_CHOICES_FIELD
        return self.DEFAULT_WTF_FIELD

    @property
    @wtf_required
    def wtf_generated_options(self) -> dict:
        """
        WTForm Field options generated by class, not updated by user provided :attr:`wtf_options`.
        """
        wtf_field_kwargs: dict = {
            "label": getattr(self, "verbose_name", self.name),
            "description": getattr(self, "help_text", None) or "",
            "default": self.default,
            # Create a copy of the lists with list() call, since we will be modifying it
            "validators": list(self.wtf_validators) or [],
            "filters": list(self.wtf_filters) or [],
        }

        if self.required:
            wtf_field_kwargs["validators"].append(wtf_validators_.InputRequired())
        else:
            wtf_field_kwargs["validators"].append(wtf_validators_.Optional())

        if self.choices:
            wtf_field_kwargs["choices"] = self.choices
            wtf_field_kwargs["coerce"] = self.wtf_choices_coerce

        return wtf_field_kwargs

    @property
    @wtf_required
    def wtf_field_options(self) -> dict:
        """
        Final WTForm Field options that will be applied as :attr:`wtf_field_class` kwargs.

        Can be overwritten by :func:`to_wtf_field` if
        :func:`~flask_mongoengine.documents.WtfFormMixin.to_wtf_form` called with related
        field name in :attr:`fields_kwargs`.

        It is not recommended to overwrite this property, for logic update overwrite
        :attr:`wtf_generated_options`
        """
        wtf_field_kwargs = self.wtf_generated_options
        if self.wtf_options is not None:
            wtf_field_kwargs.update(self.wtf_options)

        return wtf_field_kwargs

    @staticmethod
    def _ensure_callable_or_list(argument, msg_flag: str) -> Optional[List]:
        """
        Ensure submitted argument value is a callable object or valid list value.

        :param argument: Argument input to make verification on.
        :param msg_flag: Argument string name for error message.
        """
        if argument is None:
            return []

        if callable(argument):
            return [argument]
        elif not isinstance(argument, list):
            raise TypeError(f"Argument '{msg_flag}' must be a list value")

        return argument

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Default WTFFormField generator for most of the fields.

        :param model:
            Document of model from :mod:`~flask_mongoengine.documents`, passed by
            :func:`~flask_mongoengine.documents.WtfFormMixin.to_wtf_form` for field
            types with other Document type dependency signature compatibility.
        :param field_kwargs:
            Final field generation adjustments, passed for custom Forms generation from
            :func:`~flask_mongoengine.documents.WtfFormMixin.to_wtf_form`
            :attr:`fields_kwargs` parameter.
        """
        field_kwargs = field_kwargs or {}
        wtf_field_kwargs = self.wtf_field_options
        wtf_field_class = (
            field_kwargs.pop("wtf_field_class", None) or self.wtf_field_class
        )
        if field_kwargs:
            wtf_field_kwargs.update(field_kwargs)

        return wtf_field_class(**wtf_field_kwargs)


class BinaryField(WtfFieldMixin, fields.BinaryField):
    """
    Extends :class:`mongoengine.fields.BinaryField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    DEFAULT_WTF_FIELD = custom_fields.BinaryField if custom_fields else None

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class BooleanField(WtfFieldMixin, fields.BooleanField):
    """
    Extends :class:`mongoengine.fields.BooleanField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    DEFAULT_WTF_FIELD = custom_fields.MongoBooleanField if custom_fields else None


class CachedReferenceField(WtfFieldMixin, fields.CachedReferenceField):
    """
    Extends :class:`mongoengine.fields.CachedReferenceField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class ComplexDateTimeField(WtfFieldMixin, fields.ComplexDateTimeField):
    """
    Extends :class:`mongoengine.fields.ComplexDateTimeField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.

    .. important::
        During WTForm generation this field uses :class:`wtforms.fields.DateTimeLocalField`
        with milliseconds accuracy. Direct microseconds not supported by browsers for
        this type of field. If exact microseconds support required, please use
        :class:`wtforms.fields.DateTimeField` with extended text format set. Examples
        available in example app.

        This does not affect on in database accuracy.
    """

    DEFAULT_WTF_FIELD = wtf_fields.DateTimeLocalField if wtf_fields else None

    @property
    @wtf_required
    def wtf_generated_options(self) -> dict:
        """Extend form date time field with milliseconds support."""
        options = super().wtf_generated_options
        options["format"] = [
            "%Y-%m-%d %H:%M:%S",
            "%Y-%m-%dT%H:%M:%S",
            "%Y-%m-%d %H:%M:%S.%f",
            "%Y-%m-%dT%H:%M:%S.%f",
        ]
        options["render_kw"] = {"step": "0.000001"}

        return options


class DateField(WtfFieldMixin, fields.DateField):
    """
    Extends :class:`mongoengine.fields.DateField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    DEFAULT_WTF_FIELD = wtf_fields.DateField if wtf_fields else None


class DateTimeField(WtfFieldMixin, fields.DateTimeField):
    """
    Extends :class:`mongoengine.fields.DateTimeField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    DEFAULT_WTF_FIELD = wtf_fields.DateTimeLocalField if wtf_fields else None

    @property
    @wtf_required
    def wtf_generated_options(self) -> dict:
        """Extend form date time field with milliseconds support."""
        options = super().wtf_generated_options
        options["format"] = [
            "%Y-%m-%d %H:%M:%S",
            "%Y-%m-%dT%H:%M:%S",
            "%Y-%m-%d %H:%M:%S.%f",
            "%Y-%m-%dT%H:%M:%S.%f",
        ]
        options["render_kw"] = {"step": "1"}

        return options


class DecimalField(WtfFieldMixin, fields.DecimalField):
    """
    Extends :class:`mongoengine.fields.DecimalField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    DEFAULT_WTF_FIELD = wtf_fields.DecimalField if wtf_fields else None
    DEFAULT_WTF_CHOICES_COERCE = decimal.Decimal

    @property
    @wtf_required
    def wtf_generated_options(self) -> dict:
        """
        Extend form validators with :class:`wtforms.validators.NumberRange`.
        """
        options = super().wtf_generated_options
        options = _setup_numbers_common_validators(options, self)

        return options


class DictField(WtfFieldMixin, fields.DictField):
    """
    Extends :class:`mongoengine.fields.DictField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    DEFAULT_WTF_FIELD = custom_fields.MongoDictField if custom_fields else None

    @property
    def wtf_generated_options(self) -> dict:
        """Extends default field options with `null` bypass."""
        options = super().wtf_generated_options
        options["null"] = self.null
        return options


class DynamicField(WtfFieldMixin, fields.DynamicField):
    """
    Extends :class:`mongoengine.fields.DynamicField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class EmailField(WtfFieldMixin, fields.EmailField):
    """
    Extends :class:`mongoengine.fields.EmailField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.

    .. versionchanged:: 2.0.0
        Default form field output changed from :class:`.NoneStringField` to
        :class:`flask_mongoengine.wtf.fields.MongoEmailField`
    """

    DEFAULT_WTF_FIELD = custom_fields.MongoEmailField if custom_fields else None

    @property
    @wtf_required
    def wtf_generated_options(self) -> dict:
        """Extend form validators with :class:`wtforms.validators.Email`"""
        options = super().wtf_generated_options
        options = _setup_strings_common_validators(options, self)
        options["validators"].insert(0, wtf_validators_.Email())

        return options


class EmbeddedDocumentField(WtfFieldMixin, fields.EmbeddedDocumentField):
    """
    Extends :class:`mongoengine.fields.EmbeddedDocumentField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    DEFAULT_WTF_FIELD = wtf_fields.FormField if wtf_fields else None

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class EmbeddedDocumentListField(WtfFieldMixin, fields.EmbeddedDocumentListField):
    """
    Extends :class:`mongoengine.fields.EmbeddedDocumentListField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class EnumField(WtfFieldMixin, fields.EnumField):
    """
    Extends :class:`mongoengine.fields.EnumField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class FileField(WtfFieldMixin, fields.FileField):
    """
    Extends :class:`mongoengine.fields.FileField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    DEFAULT_WTF_FIELD = wtf_fields.FileField if wtf_fields else None

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class FloatField(WtfFieldMixin, fields.FloatField):
    """
    Extends :class:`mongoengine.fields.FloatField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.

    .. versionchanged:: 2.0.0
        Default form field output changed from :class:`wtforms.fields.FloatField` to
        :class:`flask_mongoengine.wtf.fields.MongoFloatField` with 'numbers' input type.
    """

    DEFAULT_WTF_FIELD = custom_fields.MongoFloatField if wtf_fields else None
    DEFAULT_WTF_CHOICES_COERCE = float

    @property
    @wtf_required
    def wtf_generated_options(self) -> dict:
        """
        Extend form validators with :class:`wtforms.validators.NumberRange`.
        """
        options = super().wtf_generated_options
        options = _setup_numbers_common_validators(options, self)

        return options


class GenericEmbeddedDocumentField(WtfFieldMixin, fields.GenericEmbeddedDocumentField):
    """
    Extends :class:`mongoengine.fields.GenericEmbeddedDocumentField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class GenericLazyReferenceField(WtfFieldMixin, fields.GenericLazyReferenceField):
    """
    Extends :class:`mongoengine.fields.GenericLazyReferenceField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class GenericReferenceField(WtfFieldMixin, fields.GenericReferenceField):
    """
    Extends :class:`mongoengine.fields.GenericReferenceField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class GeoJsonBaseField(WtfFieldMixin, fields.GeoJsonBaseField):
    """
    Extends :class:`mongoengine.fields.GeoJsonBaseField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class GeoPointField(WtfFieldMixin, fields.GeoPointField):
    """
    Extends :class:`mongoengine.fields.GeoPointField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class ImageField(WtfFieldMixin, fields.ImageField):
    """
    Extends :class:`mongoengine.fields.ImageField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class IntField(WtfFieldMixin, fields.IntField):
    """
    Extends :class:`mongoengine.fields.IntField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    DEFAULT_WTF_FIELD = wtf_fields.IntegerField if wtf_fields else None
    DEFAULT_WTF_CHOICES_COERCE = int

    @property
    @wtf_required
    def wtf_generated_options(self) -> dict:
        """
        Extend form validators with :class:`wtforms.validators.NumberRange`.
        """
        options = super().wtf_generated_options
        options = _setup_numbers_common_validators(options, self)

        return options


class LazyReferenceField(WtfFieldMixin, fields.LazyReferenceField):
    """
    Extends :class:`mongoengine.fields.LazyReferenceField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class LineStringField(WtfFieldMixin, fields.LineStringField):
    """
    Extends :class:`mongoengine.fields.LineStringField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class ListField(WtfFieldMixin, fields.ListField):
    """
    Extends :class:`mongoengine.fields.ListField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    DEFAULT_WTF_FIELD = wtf_fields.FieldList if wtf_fields else None

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class LongField(WtfFieldMixin, fields.LongField):
    """
    Extends :class:`mongoengine.fields.LongField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class MapField(WtfFieldMixin, fields.MapField):
    """
    Extends :class:`mongoengine.fields.MapField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class MultiLineStringField(WtfFieldMixin, fields.MultiLineStringField):
    """
    Extends :class:`mongoengine.fields.MultiLineStringField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class MultiPointField(WtfFieldMixin, fields.MultiPointField):
    """
    Extends :class:`mongoengine.fields.MultiPointField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class MultiPolygonField(WtfFieldMixin, fields.MultiPolygonField):
    """
    Extends :class:`mongoengine.fields.MultiPolygonField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class ObjectIdField(WtfFieldMixin, fields.ObjectIdField):
    """
    Extends :class:`mongoengine.fields.ObjectIdField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    DEFAULT_WTF_CHOICES_COERCE = ObjectId

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class PointField(WtfFieldMixin, fields.PointField):
    """
    Extends :class:`mongoengine.fields.PointField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class PolygonField(WtfFieldMixin, fields.PolygonField):
    """
    Extends :class:`mongoengine.fields.PolygonField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class ReferenceField(WtfFieldMixin, fields.ReferenceField):
    """
    Extends :class:`mongoengine.fields.ReferenceField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    DEFAULT_WTF_FIELD = custom_fields.ModelSelectField if custom_fields else None

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class SequenceField(WtfFieldMixin, fields.SequenceField):
    """
    Extends :class:`mongoengine.fields.SequenceField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class SortedListField(WtfFieldMixin, fields.SortedListField):
    """
    Extends :class:`mongoengine.fields.SortedListField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    DEFAULT_WTF_FIELD = wtf_fields.FieldList if wtf_fields else None

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")


class StringField(WtfFieldMixin, fields.StringField):
    """
    Extends :class:`mongoengine.fields.StringField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.

    .. versionchanged:: 2.0.0
        Default form field output changed from :class:`.NoneStringField` to
        :class:`flask_mongoengine.wtf.fields.MongoTextAreaField`
    """

    DEFAULT_WTF_FIELD = custom_fields.MongoTextAreaField if custom_fields else None

    def __init__(
        self,
        *,
        password: bool = False,
        textarea: bool = False,
        validators: Optional[Union[List, Callable]] = None,
        filters: Optional[Union[List, Callable]] = None,
        wtf_field_class: Optional[Type] = None,
        wtf_filters: Optional[Union[List, Callable]] = None,
        wtf_validators: Optional[Union[List, Callable]] = None,
        wtf_choices_coerce: Optional[Callable] = None,
        wtf_options: Optional[dict] = None,
        **kwargs,
    ):
        """
        Extended :func:`__init__` method for mongoengine db field with WTForms options.

        :param password:
            DEPRECATED: Force to use :class:`~.MongoPasswordField` for field generation.
            In case of :attr:`password` and :attr:`wtf_field_class` both set, then
            :attr:`wtf_field_class` will be used.
        :param textarea:
            DEPRECATED: Force to use :class:`~.MongoTextAreaField` for field generation.
            In case of :attr:`textarea` and :attr:`wtf_field_class` both set, then
            :attr:`wtf_field_class` will be used.
        :param filters:     DEPRECATED: wtf form field filters.
        :param validators:  DEPRECATED: wtf form field validators.
        :param wtf_field_class: Any subclass of :class:`wtforms.forms.core.Field` that
            can be used for form field generation. Takes precedence over
            :attr:`DEFAULT_WTF_FIELD`  and :attr:`DEFAULT_WTF_CHOICES_FIELD`
        :param wtf_filters:     wtf form field filters.
        :param wtf_validators:  wtf form field validators.
        :param wtf_choices_coerce: Callable function to replace
            :attr:`DEFAULT_WTF_CHOICES_COERCE` for choices fields.
        :param wtf_options: Dictionary with WTForm Field settings.
            Applied last, takes precedence over any generated field options.
        :param kwargs: keyword arguments silently bypassed to normal mongoengine fields
        """
        if password:
            if textarea:
                raise ValueError("Password field cannot use TextAreaField class.")

            warnings.warn(
                (
                    "Passing 'password' keyword argument to field definition is "
                    "deprecated and will be removed in version 3.0.0. "
                    "Please use 'wtf_field_class' parameter to specify correct field "
                    "class. If both values set, 'wtf_field_class' is used."
                ),
                DeprecationWarning,
                stacklevel=2,
            )
            wtf_field_class = wtf_field_class or custom_fields.MongoPasswordField

        if textarea:
            warnings.warn(
                (
                    "Passing 'textarea' keyword argument to field definition is "
                    "deprecated and will be removed in version 3.0.0. "
                    "Please use 'wtf_field_class' parameter to specify correct field "
                    "class. If both values set, 'wtf_field_class' is used."
                ),
                DeprecationWarning,
                stacklevel=2,
            )
            wtf_field_class = wtf_field_class or custom_fields.MongoTextAreaField

        super().__init__(
            validators=validators,
            filters=filters,
            wtf_field_class=wtf_field_class,
            wtf_filters=wtf_filters,
            wtf_validators=wtf_validators,
            wtf_choices_coerce=wtf_choices_coerce,
            wtf_options=wtf_options,
            **kwargs,
        )

    @property
    def wtf_field_class(self) -> Type:
        """Parent class overwrite with support of class adjustment by field size."""
        if self._wtf_field_class:
            return self._wtf_field_class
        if self.max_length or self.min_length:
            return custom_fields.MongoStringField
        return super().wtf_field_class

    @property
    @wtf_required
    def wtf_generated_options(self) -> dict:
        """
        Extend form validators with :class:`wtforms.validators.Regexp` and
        :class:`wtforms.validators.Length`.
        """
        options = super().wtf_generated_options
        options = _setup_strings_common_validators(options, self)

        return options


class URLField(WtfFieldMixin, fields.URLField):
    """
    Extends :class:`mongoengine.fields.URLField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.

    .. versionchanged:: 2.0.0
        Default form field output changed from :class:`.NoneStringField` to
        :class:`~flask_mongoengine.wtf.fields.MongoURLField`

    .. versionchanged:: 2.0.0
        Now appends :class:`~wtforms.validators.Regexp` and use regexp provided to
        __init__ :attr:`url_regex`, instead of using non-configurable regexp from
        :class:`~wtforms.validators.URL`. This includes configuration conflicts, between
        modules.
    """

    DEFAULT_WTF_FIELD = custom_fields.MongoURLField if custom_fields else None

    @property
    @wtf_required
    def wtf_generated_options(self) -> dict:
        """Extend form validators with :class:`wtforms.validators.Regexp`"""
        options = super().wtf_generated_options
        options = _setup_strings_common_validators(options, self)
        options["validators"].insert(
            0, wtf_validators_.Regexp(regex=self.url_regex, message="Invalid URL.")
        )

        return options


class UUIDField(WtfFieldMixin, fields.UUIDField):
    """
    Extends :class:`mongoengine.fields.UUIDField` with wtf required parameters.

    For full list of arguments and keyword arguments, look parent field docs.
    All arguments should be passed as keyword arguments, to exclude unexpected behaviour.
    """

    def to_wtf_field(
        self,
        *,
        model: Optional[Type] = None,
        field_kwargs: Optional[dict] = None,
    ):
        """
        Protection from execution of :func:`to_wtf_field` in form generation.

        :raises NotImplementedError: Field converter to WTForm Field not implemented.
        """
        raise NotImplementedError("Field converter to WTForm Field not implemented.")