Picture-Puzzle-website/venv/Lib/site-packages/flask_admin/contrib/mongoengine/form.py

260 lines
8.8 KiB
Python

from mongoengine import ReferenceField, ListField
from mongoengine.base import BaseDocument, DocumentMetaclass, get_document
from wtforms import fields, validators
from flask_mongoengine.wtf import orm, fields as mongo_fields
from flask_admin import form
from flask_admin.model.form import FieldPlaceholder
from flask_admin.model.fields import InlineFieldList, AjaxSelectField, AjaxSelectMultipleField
from flask_admin.form.validators import FieldListInputRequired
from flask_admin._compat import iteritems
from .fields import ModelFormField, MongoFileField, MongoImageField
from .subdoc import EmbeddedForm
class CustomModelConverter(orm.ModelConverter):
"""
Customized MongoEngine form conversion class.
Injects various Flask-Admin widgets and handles lists with
customized InlineFieldList field.
"""
def __init__(self, view):
super(CustomModelConverter, self).__init__()
self.view = view
def _get_field_override(self, name):
form_overrides = getattr(self.view, 'form_overrides', None)
if form_overrides:
return form_overrides.get(name)
return None
def _get_subdocument_config(self, name):
config = getattr(self.view, '_form_subdocuments', {})
p = config.get(name)
if not p:
return EmbeddedForm()
return p
def _convert_choices(self, choices):
for c in choices:
if isinstance(c, tuple):
yield c
else:
yield (c, c)
def clone_converter(self, view):
return self.__class__(view)
def convert(self, model, field, field_args):
# Check if it is overridden field
if isinstance(field, FieldPlaceholder):
return form.recreate_field(field.field)
kwargs = {
'label': getattr(field, 'verbose_name', None),
'description': getattr(field, 'help_text', ''),
'validators': [],
'filters': [],
'default': field.default
}
if field_args:
kwargs.update(field_args)
if kwargs['validators']:
# Create a copy of the list since we will be modifying it.
kwargs['validators'] = list(kwargs['validators'])
if field.required:
if isinstance(field, ListField):
kwargs['validators'].append(FieldListInputRequired())
else:
kwargs['validators'].append(validators.InputRequired())
elif not isinstance(field, ListField):
kwargs['validators'].append(validators.Optional())
ftype = type(field).__name__
if field.choices:
kwargs['choices'] = list(self._convert_choices(field.choices))
if ftype in self.converters:
kwargs["coerce"] = self.coerce(ftype)
if kwargs.pop('multiple', False):
return fields.SelectMultipleField(**kwargs)
return fields.SelectField(**kwargs)
ftype = type(field).__name__
if hasattr(field, 'to_form_field'):
return field.to_form_field(model, kwargs)
override = self._get_field_override(field.name)
if override:
return override(**kwargs)
if ftype in self.converters:
return self.converters[ftype](model, field, kwargs)
@orm.converts('DateTimeField')
def conv_DateTime(self, model, field, kwargs):
kwargs['widget'] = form.DateTimePickerWidget()
return orm.ModelConverter.conv_DateTime(self, model, field, kwargs)
@orm.converts('ListField')
def conv_List(self, model, field, kwargs):
if field.field is None:
raise ValueError('ListField "%s" must have field specified for model %s' % (field.name, model))
if isinstance(field.field, ReferenceField):
loader = getattr(self.view, '_form_ajax_refs', {}).get(field.name)
if loader:
return AjaxSelectMultipleField(loader, **kwargs)
kwargs['widget'] = form.Select2Widget(multiple=True)
kwargs.setdefault('validators', []).append(validators.Optional())
# TODO: Support AJAX multi-select
doc_type = field.field.document_type
return mongo_fields.ModelSelectMultipleField(model=doc_type, **kwargs)
# Create converter
view = self._get_subdocument_config(field.name)
converter = self.clone_converter(view)
if field.field.choices:
kwargs['multiple'] = True
return converter.convert(model, field.field, kwargs)
unbound_field = converter.convert(model, field.field, {})
return InlineFieldList(unbound_field, min_entries=0, **kwargs)
@orm.converts('EmbeddedDocumentField')
def conv_EmbeddedDocument(self, model, field, kwargs):
# FormField does not support validators
kwargs['validators'] = []
view = self._get_subdocument_config(field.name)
form_opts = form.FormOpts(widget_args=getattr(view, 'form_widget_args', None),
form_rules=view._form_rules)
form_class = view.get_form()
if form_class is None:
converter = self.clone_converter(view)
form_class = get_form(field.document_type_obj, converter,
base_class=view.form_base_class or form.BaseForm,
only=view.form_columns,
exclude=view.form_excluded_columns,
field_args=view.form_args,
extra_fields=view.form_extra_fields)
form_class = view.postprocess_form(form_class)
return ModelFormField(field.document_type_obj, view, form_class, form_opts=form_opts, **kwargs)
@orm.converts('ReferenceField')
def conv_Reference(self, model, field, kwargs):
kwargs['allow_blank'] = not field.required
loader = getattr(self.view, '_form_ajax_refs', {}).get(field.name)
if loader:
return AjaxSelectField(loader, **kwargs)
kwargs['widget'] = form.Select2Widget()
return orm.ModelConverter.conv_Reference(self, model, field, kwargs)
@orm.converts('FileField')
def conv_File(self, model, field, kwargs):
return MongoFileField(**kwargs)
@orm.converts('ImageField')
def conv_image(self, model, field, kwargs):
return MongoImageField(**kwargs)
def get_form(model, converter,
base_class=form.BaseForm,
only=None,
exclude=None,
field_args=None,
extra_fields=None):
"""
Create a wtforms Form for a given mongoengine Document schema::
from flask_mongoengine.wtf import model_form
from myproject.myapp.schemas import Article
ArticleForm = model_form(Article)
:param model:
A mongoengine Document schema class
:param base_class:
Base form class to extend from. Must be a ``wtforms.Form`` subclass.
:param only:
An optional iterable with the property names that should be included in
the form. Only these properties will have fields.
:param exclude:
An optional iterable with the property names that should be excluded
from the form. All other properties will have fields.
:param field_args:
An optional dictionary of field names mapping to keyword arguments used
to construct each field object.
:param converter:
A converter to generate the fields based on the model properties. If
not set, ``ModelConverter`` is used.
"""
if isinstance(model, str):
model = get_document(model)
if not isinstance(model, (BaseDocument, DocumentMetaclass)):
raise TypeError('Model must be a mongoengine Document schema')
field_args = field_args or {}
# Find properties
properties = sorted(((k, v) for k, v in iteritems(model._fields)),
key=lambda v: v[1].creation_counter)
if only:
props = dict(properties)
def find(name):
if extra_fields and name in extra_fields:
return FieldPlaceholder(extra_fields[name])
p = props.get(name)
if p is not None:
return p
raise ValueError('Invalid model property name %s.%s' % (model, name))
properties = ((p, find(p)) for p in only)
elif exclude:
properties = (p for p in properties if p[0] not in exclude)
# Create fields
field_dict = {}
for name, p in properties:
field = converter.convert(model, p, field_args.get(name))
if field is not None:
field_dict[name] = field
# Contribute extra fields
if not only and extra_fields:
for name, field in iteritems(extra_fields):
field_dict[name] = form.recreate_field(field)
field_dict['model_class'] = model
return type(model.__name__ + 'Form', (base_class,), field_dict)