-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
form.py
253 lines (192 loc) · 7.05 KB
/
form.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
import inspect
import warnings
from flask_admin.form import BaseForm, rules
from flask_admin._compat import iteritems
from wtforms.fields import HiddenField
from wtforms.fields.core import UnboundField
from wtforms.validators import InputRequired
from .widgets import XEditableWidget
def converts(*args):
def _inner(func):
func._converter_for = frozenset(args)
return func
return _inner
def create_editable_list_form(form_base_class, form_class, widget=None):
"""
Create a form class with all the fields wrapped in a FieldList.
Wrapping each field in FieldList allows submitting POST requests
in this format: ('<field_name>-<primary_key>', '<value>')
Used in the editable list view.
:param form_base_class:
WTForms form class, by default `form_base_class` from base.
:param form_class:
WTForms form class generated by `form.get_form`.
:param widget:
WTForms widget class. Defaults to `XEditableWidget`.
"""
if widget is None:
widget = XEditableWidget()
class ListForm(form_base_class):
list_form_pk = HiddenField(validators=[InputRequired()])
# iterate FormMeta to get unbound fields, replace widget, copy to ListForm
for name, obj in iteritems(form_class.__dict__):
if isinstance(obj, UnboundField):
obj.kwargs['widget'] = widget
setattr(ListForm, name, obj)
if name == "list_form_pk":
raise Exception('Form already has a list_form_pk column.')
return ListForm
class InlineBaseFormAdmin(object):
"""
Settings for inline form administration.
You can use this class to customize displayed form.
For example::
class MyUserInfoForm(InlineBaseFormAdmin):
form_columns = ('name', 'email')
"""
_defaults = ['form_base_class', 'form_columns', 'form_excluded_columns', 'form_args', 'form_extra_fields']
def __init__(self, **kwargs):
"""
Constructor
:param kwargs:
Additional options
"""
for k in self._defaults:
if not hasattr(self, k):
setattr(self, k, None)
for k, v in iteritems(kwargs):
setattr(self, k, v)
# Convert form rules
form_rules = getattr(self, 'form_rules', None)
if form_rules:
self._form_rules = rules.RuleSet(self, form_rules)
else:
self._form_rules = None
def get_form(self):
"""
If you want to use completely custom form for inline field, you can override
Flask-Admin form generation logic by overriding this method and returning your form.
"""
return None
def postprocess_form(self, form_class):
"""
Post process form. Use this to contribute fields.
For example::
class MyInlineForm(InlineFormAdmin):
def postprocess_form(self, form):
form.value = StringField('value')
return form
class MyAdmin(ModelView):
inline_models = (MyInlineForm(ValueModel),)
"""
return form_class
def on_model_change(self, form, model, is_created):
"""
Called when inline model is about to be saved.
:param form:
Inline form
:param model:
Model
:param is_created:
Will be set to True if the model is being created, False if edited
"""
pass
def _on_model_change(self, form, model, is_created):
"""
Compatibility helper.
"""
try:
self.on_model_change(form, model, is_created)
except TypeError:
msg = ('%s.on_model_change() now accepts third ' +
'parameter is_created. Please update your code') % self.model
warnings.warn(msg)
self.on_model_change(form, model)
class InlineFormAdmin(InlineBaseFormAdmin):
"""
Settings for inline form administration. Used by relational backends (SQLAlchemy, Peewee), where model
class can not be inherited from the parent model definition.
"""
def __init__(self, model, **kwargs):
"""
Constructor
:param model:
Model class
"""
self.model = model
super(InlineFormAdmin, self).__init__(**kwargs)
class ModelConverterBase(object):
def __init__(self, converters=None, use_mro=True):
self.use_mro = use_mro
if not converters:
converters = {}
for name in dir(self):
obj = getattr(self, name)
if hasattr(obj, '_converter_for'):
for classname in obj._converter_for:
converters[classname] = obj
self.converters = converters
def get_converter(self, column):
if self.use_mro:
types = inspect.getmro(type(column.type))
else:
types = [type(column.type)]
# Search by module + name
for col_type in types:
type_string = '%s.%s' % (col_type.__module__, col_type.__name__)
if type_string in self.converters:
return self.converters[type_string]
# Search by name
for col_type in types:
if col_type.__name__ in self.converters:
return self.converters[col_type.__name__]
return None
def get_form(self, model, base_class=BaseForm,
only=None, exclude=None,
field_args=None):
raise NotImplementedError()
class InlineModelConverterBase(object):
form_admin_class = InlineFormAdmin
def __init__(self, view):
"""
Base constructor
:param view:
View class
"""
self.view = view
def get_label(self, info, name):
"""
Get inline model field label
:param info:
Inline model info
:param name:
Field name
"""
form_name = getattr(info, 'form_label', None)
if form_name:
return form_name
column_labels = getattr(self.view, 'column_labels', None)
if column_labels and name in column_labels:
return column_labels[name]
return None
def get_info(self, p):
"""
Figure out InlineFormAdmin information.
:param p:
Inline model. Can be one of:
- ``tuple``, first value is related model instance,
second is dictionary with options
- ``InlineFormAdmin`` instance
- Model class
"""
if isinstance(p, tuple):
return self.form_admin_class(p[0], **p[1])
elif isinstance(p, self.form_admin_class):
return p
return None
class FieldPlaceholder(object):
"""
Field placeholder for model convertors.
"""
def __init__(self, field):
self.field = field