11"""Extended version of :mod:`mongoengine.document`."""
2+ import logging
3+ from typing import List , Optional , Type , Union
4+
25import mongoengine
36from flask import abort
47from mongoengine .errors import DoesNotExist
58from mongoengine .queryset import QuerySet
69
10+ from flask_mongoengine .decorators import wtf_required
711from flask_mongoengine .pagination import ListFieldPagination , Pagination
812
13+ try :
14+ from flask_mongoengine .wtf .models import ModelForm
15+ except ImportError : # pragma: no cover
16+ ModelForm = None
17+ logger = logging .getLogger ("flask_mongoengine" )
18+
919
1020class BaseQuerySet (QuerySet ):
1121 """Extends :class:`~mongoengine.queryset.QuerySet` class with handly methods."""
@@ -43,7 +53,7 @@ def first_or_404(self, _message_404=None):
4353 """
4454 return self .first () or self ._abort_404 (_message_404 )
4555
46- def paginate (self , page , per_page , ** kwargs ):
56+ def paginate (self , page , per_page ):
4757 """
4858 Paginate the QuerySet with a certain number of docs per page
4959 and return docs for a given page.
@@ -64,8 +74,89 @@ def paginate_field(self, field_name, doc_id, page, per_page, total=None):
6474 )
6575
6676
67- class Document (mongoengine .Document ):
68- """Abstract document with extra helpers in the queryset class"""
77+ class WtfFormMixin :
78+ """Special mixin, for form generation functions."""
79+
80+ @classmethod
81+ def _get_fields_names (
82+ cls : Union ["WtfFormMixin" , mongoengine .document .BaseDocument ],
83+ only : Optional [List [str ]],
84+ exclude : Optional [List [str ]],
85+ ):
86+ """
87+ Filter fields names for further form generation.
88+
89+ :param only:
90+ An optional iterable with the property names that should be included in
91+ the form. Only these properties will have fields.
92+ Fields are always appear in provided order, this allows user to change form
93+ fields ordering, without changing database model.
94+ :param exclude:
95+ An optional iterable with the property names that should be excluded
96+ from the form. All other properties will have fields.
97+ Fields are appears in order, defined in model, excluding provided fields
98+ names. For adjusting fields ordering, use :attr:`only`.
99+ """
100+ field_names = cls ._fields_ordered
101+
102+ if only :
103+ field_names = [field for field in only if field in field_names ]
104+ elif exclude :
105+ field_names = [field for field in field_names if field not in exclude ]
106+
107+ return field_names
108+
109+ @classmethod
110+ @wtf_required
111+ def to_wtf_form (
112+ cls : Union ["WtfFormMixin" , mongoengine .document .BaseDocument ],
113+ base_class : Type [ModelForm ] = ModelForm ,
114+ only : Optional [List [str ]] = None ,
115+ exclude : Optional [List [str ]] = None ,
116+ field_args = None ,
117+ ) -> Type [ModelForm ]:
118+ """
119+ Generate WTForm from Document model.
120+
121+ :param base_class:
122+ Base form class to extend from. Must be a :class:`.ModelForm` subclass.
123+ :param only:
124+ An optional iterable with the property names that should be included in
125+ the form. Only these properties will have fields.
126+ Fields are always appear in provided order, this allows user to change form
127+ fields ordering, without changing database model.
128+ :param exclude:
129+ An optional iterable with the property names that should be excluded
130+ from the form. All other properties will have fields.
131+ Fields are appears in order, defined in model, excluding provided fields
132+ names. For adjusting fields ordering, use :attr:`only`.
133+ :param field_args:
134+ An optional dictionary of field names mapping to keyword arguments used
135+ to construct each field object.
136+ """
137+ form_fields_dict = {}
138+ fields_names = cls ._get_fields_names (only , exclude )
139+
140+ for field_name in fields_names :
141+ # noinspection PyUnresolvedReferences
142+ field_class = cls ._fields [field_name ]
143+ try :
144+ form_fields_dict [field_name ] = field_class .to_wtf_field (
145+ field_args .get (field_name )
146+ )
147+ except AttributeError :
148+ logger .warning (
149+ f"Field { field_name } ignored, field type does not have "
150+ f".to_wtf_field() method."
151+ )
152+
153+ form_fields_dict ["model_class" ] = cls
154+ # noinspection PyTypeChecker
155+ return type (f"{ cls .__name__ } Form" , (base_class ,), form_fields_dict )
156+
157+
158+ class Document (WtfFormMixin , mongoengine .Document ):
159+ """Abstract Document with QuerySet and WTForms extra helpers."""
69160
70161 meta = {"abstract" : True , "queryset_class" : BaseQuerySet }
71162
@@ -79,7 +170,19 @@ def paginate_field(self, field_name, page, per_page, total=None):
79170 )
80171
81172
82- class DynamicDocument (mongoengine .DynamicDocument ):
83- """Abstract Dynamic document with extra helpers in the queryset class """
173+ class DynamicDocument (WtfFormMixin , mongoengine .DynamicDocument ):
174+ """Abstract DynamicDocument with QuerySet and WTForms extra helpers. """
84175
85176 meta = {"abstract" : True , "queryset_class" : BaseQuerySet }
177+
178+
179+ class EmbeddedDocument (WtfFormMixin , mongoengine .EmbeddedDocument ):
180+ """Abstract EmbeddedDocument document with extra WTForms helpers."""
181+
182+ meta = {"abstract" : True }
183+
184+
185+ class DynamicEmbeddedDocument (WtfFormMixin , mongoengine .DynamicEmbeddedDocument ):
186+ """Abstract DynamicEmbeddedDocument document with extra WTForms helpers."""
187+
188+ meta = {"abstract" : True }
0 commit comments