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

295 lines
9.4 KiB
Python

from wtforms import fields
from peewee import (CharField, DateTimeField, DateField, TimeField,
PrimaryKeyField, ForeignKeyField)
try:
from peewee import BaseModel
except ImportError:
from peewee import ModelBase as BaseModel
from wtfpeewee.orm import ModelConverter, model_form
from flask_admin import form
from flask_admin._compat import iteritems, itervalues
from flask_admin.model.form import InlineFormAdmin, InlineModelConverterBase
from flask_admin.model.fields import InlineModelFormField, InlineFieldList, AjaxSelectField
from .tools import get_primary_key, get_meta_fields
from .ajax import create_ajax_loader
try:
from playhouse.postgres_ext import JSONField, BinaryJSONField
pg_ext = True
except:
pg_ext = False
class InlineModelFormList(InlineFieldList):
"""
Customized inline model form list field.
"""
form_field_type = InlineModelFormField
"""
Form field type. Override to use custom field for each inline form
"""
def __init__(self, form, model, prop, inline_view, **kwargs):
self.form = form
self.model = model
self.prop = prop
self.inline_view = inline_view
self._pk = get_primary_key(model)
super(InlineModelFormList, self).__init__(self.form_field_type(form, self._pk), **kwargs)
def display_row_controls(self, field):
return field.get_pk() is not None
""" bryhoyt removed def process() entirely, because I believe it was buggy
(but worked because another part of the code had a complimentary bug)
and I'm not sure why it was necessary anyway.
If we want it back in, we need to fix the following bogus query:
self.model.select().where(attr == data).execute()
`data` is not an ID, and only happened to be so because we patched it
in in .contribute() below
For reference, .process() introduced in:
https://github.com/flask-admin/flask-admin/commit/2845e4b28cb40b25e2bf544b327f6202dc7e5709
Fixed, brokenly I think, in:
https://github.com/flask-admin/flask-admin/commit/4383eef3ce7eb01878f086928f8773adb9de79f8#diff-f87e7cd76fb9bc48c8681b24f238fb13R30
"""
def populate_obj(self, obj, name):
pass
def save_related(self, obj):
model_id = getattr(obj, self._pk)
attr = getattr(self.model, self.prop)
values = self.model.select().where(attr == model_id).execute()
pk_map = dict((str(getattr(v, self._pk)), v) for v in values)
# Handle request data
for field in self.entries:
field_id = field.get_pk()
is_created = field_id not in pk_map
if not is_created:
model = pk_map[field_id]
if self.should_delete(field):
model.delete_instance(recursive=True)
continue
else:
model = self.model()
field.populate_obj(model, None)
# Force relation
setattr(model, self.prop, model_id)
self.inline_view._on_model_change(field, model, is_created)
model.save()
# Recurse, to save multi-level nested inlines
for f in itervalues(field.form._fields):
if f.type == 'InlineModelFormList':
f.save_related(model)
class CustomModelConverter(ModelConverter):
def __init__(self, view, additional=None):
super(CustomModelConverter, self).__init__(additional)
self.view = view
# @todo: This really should be done within wtfpeewee
self.defaults[CharField] = fields.StringField
self.converters[PrimaryKeyField] = self.handle_pk
self.converters[DateTimeField] = self.handle_datetime
self.converters[DateField] = self.handle_date
self.converters[TimeField] = self.handle_time
if pg_ext:
self.converters[JSONField] = self.handle_json
self.converters[BinaryJSONField] = self.handle_json
self.overrides = getattr(self.view, 'form_overrides', None) or {}
def handle_foreign_key(self, model, field, **kwargs):
loader = getattr(self.view, '_form_ajax_refs', {}).get(field.name)
if loader:
if field.null:
kwargs['allow_blank'] = True
return field.name, AjaxSelectField(loader, **kwargs)
return super(CustomModelConverter, self).handle_foreign_key(model, field, **kwargs)
def handle_pk(self, model, field, **kwargs):
kwargs['validators'] = []
return field.name, fields.HiddenField(**kwargs)
def handle_date(self, model, field, **kwargs):
kwargs['widget'] = form.DatePickerWidget()
return field.name, fields.DateField(**kwargs)
def handle_datetime(self, model, field, **kwargs):
kwargs['widget'] = form.DateTimePickerWidget()
return field.name, fields.DateTimeField(**kwargs)
def handle_time(self, model, field, **kwargs):
return field.name, form.TimeField(**kwargs)
def handle_json(self, model, field, **kwargs):
return field.name, form.JSONField(**kwargs)
def get_form(model, converter,
base_class=form.BaseForm,
only=None,
exclude=None,
field_args=None,
allow_pk=False,
extra_fields=None):
"""
Create form from peewee model and contribute extra fields, if necessary
"""
result = model_form(model,
base_class=base_class,
only=only,
exclude=exclude,
field_args=field_args,
allow_pk=allow_pk,
converter=converter)
if extra_fields:
for name, field in iteritems(extra_fields):
setattr(result, name, form.recreate_field(field))
return result
class InlineModelConverter(InlineModelConverterBase):
"""
Inline model form helper.
"""
inline_field_list_type = InlineModelFormList
"""
Used field list type.
If you want to do some custom rendering of inline field lists,
you can create your own wtforms field and use it instead
"""
def get_info(self, p):
info = super(InlineModelConverter, self).get_info(p)
if info is None:
if isinstance(p, BaseModel):
info = InlineFormAdmin(p)
else:
model = getattr(p, 'model', None)
if model is None:
raise Exception('Unknown inline model admin: %s' % repr(p))
attrs = dict()
for attr in dir(p):
if not attr.startswith('_') and attr != 'model':
attrs[attr] = getattr(p, attr)
info = InlineFormAdmin(model, **attrs)
# Resolve AJAX FKs
info._form_ajax_refs = self.process_ajax_refs(info)
return info
def process_ajax_refs(self, info):
refs = getattr(info, 'form_ajax_refs', None)
result = {}
if refs:
for name, opts in iteritems(refs):
new_name = '%s.%s' % (info.model.__name__.lower(), name)
loader = None
if isinstance(opts, (list, tuple)):
loader = create_ajax_loader(info.model, new_name, name, opts)
else:
loader = opts
result[name] = loader
self.view._form_ajax_refs[new_name] = loader
return result
def contribute(self, converter, model, form_class, inline_model):
# Find property from target model to current model
reverse_field = None
info = self.get_info(inline_model)
for field in get_meta_fields(info.model):
field_type = type(field)
if field_type == ForeignKeyField:
if field.rel_model == model:
reverse_field = field
break
else:
raise Exception('Cannot find reverse relation for model %s' % info.model)
# Remove reverse property from the list
ignore = [reverse_field.name]
if info.form_excluded_columns:
exclude = ignore + info.form_excluded_columns
else:
exclude = ignore
# Create field
child_form = info.get_form()
if child_form is None:
child_form = model_form(info.model,
base_class=form.BaseForm,
only=info.form_columns,
exclude=exclude,
field_args=info.form_args,
allow_pk=True,
converter=converter)
try:
prop_name = reverse_field.related_name
except AttributeError:
prop_name = reverse_field.backref
label = self.get_label(info, prop_name)
setattr(form_class,
prop_name,
self.inline_field_list_type(child_form,
info.model,
reverse_field.name,
info,
label=label or info.model.__name__))
return form_class
def save_inline(form, model):
for f in itervalues(form._fields):
if f.type == 'InlineModelFormList':
f.save_related(model)