-
Notifications
You must be signed in to change notification settings - Fork 3
/
admin_forms.py
196 lines (157 loc) · 6.53 KB
/
admin_forms.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
from google.appengine.ext import db
from . import fields, wtforms
from wtforms.ext.appengine.db import ModelConverter, model_form
class WTForm(wtforms.Form):
def __init__(self, formdata=None, obj=None, prefix='', handler=None, **kwargs):
if self.pre_init:
obj = self.pre_init(self, obj, formdata)
self.instance = obj
self.handler = handler
super(WTForm, self).__init__(formdata=formdata, obj=obj, prefix=prefix, **kwargs)
if self.post_init:
obj = self.post_init(self, obj, formdata, handler)
def validate(self):
"""
Validates the form by calling `validate` on each field, passing any
extra `Form.validate_<fieldname>` validators to the field validator.
Override wtforms.Form behavior to add extra validators.
"""
field_names_to_skip = []
for name, field in self._fields.items():
if hasattr(field, 'readonly'):
field_names_to_skip.append(name)
for field_name in field_names_to_skip:
delattr(self, field_name)
self._valid = super(wtforms.Form, self).validate(self.field_validators)
return self._valid
def save(self, put=True):
if not hasattr(self, '_valid'):
raise Exception('self.validate() not called before saving.')
data = self.data
model_properties = {}
dynamic_properties = {}
properties = self.model.properties().keys()
for name, value in data.items():
if name in properties:
model_properties[name] = value
else:
dynamic_properties[name] = value
instance = self.instance
if instance:
for name, value in model_properties.items():
setattr(instance, name, value)
else:
instance = self.model(**model_properties)
for name, value in dynamic_properties.items():
setattr(instance, name, value)
if self.pre_save:
instance = self.pre_save(self, instance, self.handler)
if put:
instance_or_result = instance.put()
if isinstance(instance_or_result, self.model):
instance = instance_or_result
elif isinstance(instance_or_result, db.Key):
instance = db.get(instance_or_result)
if self.post_save:
return self.post_save(self, instance, self.handler)
return instance
def convert_DateTimeProperty(model, prop, kwargs):
"""Returns a form field for a ``db.DateTimeProperty``."""
if prop.auto_now or prop.auto_now_add:
return None
return fields.DateTimeField(format='%Y-%m-%d %H:%M:%S', **kwargs)
def convert_DateProperty(model, prop, kwargs):
"""Returns a form field for a ``db.DateProperty``."""
if prop.auto_now or prop.auto_now_add:
return None
return fields.DateField(format='%Y-%m-%d', **kwargs)
def coerce_boolean(value):
if value is None or isinstance(value, bool):
return value
if value == 'None':
return None
if value == 'True':
return True
return False
def convert_BooleanProperty(model, prop, kwargs):
"""Returns a form field for a ``db.BooleanProperty``."""
kwargs.setdefault('coerce', coerce_boolean)
choices = [
(False, 'False'),
(True, 'True'),
]
if not prop.required:
choices.append((None, 'None'))
kwargs.setdefault('choices', choices)
return fields.BooleanField(**kwargs)
def convert_DecimalProperty(model, prop, kwargs):
"""Returns a form field for a ``db.DecimalProperty``."""
return fields.DecimalField(**kwargs)
def convert_IntegerProperty(model, prop, kwargs):
"""Returns a form field for a ``db.DecimalProperty``."""
return fields.IntegerField(**kwargs)
def convert_ListProperty(model, prop, kwargs):
"""Returns a form field for a ``db.ListProperty``."""
if prop.item_type == db.Key:
return fields.AjaxKeyField(multiple=True,
object_classes=getattr(prop, 'object_classes', None),
**kwargs)
# TODO fall back to textarea for int
# And list of text areas for list of strings
return None
def convert_ReferenceProperty(model, prop, kwargs):
"""Returns a form field for a ``db.ReferenceProperty``."""
kwargs.setdefault('required', prop.required)
return fields.AjaxKeyField(multiple=False,
object_classes=getattr(prop, 'object_classes', [prop.reference_class]),
**kwargs)
class AdminConverter(ModelConverter):
def __init__(self, model, *args, **kwargs):
self.default_converters.update({
'DateTimeProperty': convert_DateTimeProperty,
'DateProperty': convert_DateProperty,
'DecimalProperty': convert_DecimalProperty,
'IntegerProperty': convert_IntegerProperty,
'ListProperty': convert_ListProperty,
'ReferenceProperty': convert_ReferenceProperty,
'BooleanProperty': convert_BooleanProperty,
})
for prop in model.properties().values():
if hasattr(prop, 'wtforms_convert'):
self.default_converters[type(prop).__name__] = getattr(prop, 'wtforms_convert')
super(AdminConverter, self).__init__(*args, **kwargs)
def create(model, only=None, exclude=None, base_class=WTForm, converter=None,
pre_init=None, post_init=None, pre_save=None, post_save=None,
field_validators=None):
'''Factory for admin forms.
Input:
* model - class to be used for wtforms creation
* only - tuple of field names that should be exposed in the form
* exclude - marked separately as read-only when editing,
but still editable for new instances
All the following receive the form and the object as parameters:
(Note that the object may be None for new model instances)
* pre_init - hook called before initializing the form, for a chance to
modify the instance before editing
* post_init - hook called immediately after initializing the form, can be useful
to modify the form before display
* pre_save, post_save - hooks called before/after saving an item
All the following receive the form and the field as parameters:
* field_validators - a dict of field -> callback function for validating
individual properties
'''
converter = converter or AdminConverter(model)
form = model_form(
model=model, base_class=base_class, only=only, exclude=exclude, converter=converter)
form.model = model
form.pre_init = pre_init
form.post_init = post_init
form.pre_save = pre_save
form.post_save = post_save
if field_validators:
for field_name, validators in field_validators.items():
if not isinstance(validators, (list, tuple)):
field_validators[field_name] = [validators]
form.field_validators = field_validators
form.converter = converter
return form