forked from sshwsfc/xadmin
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwizard.py
More file actions
328 lines (276 loc) · 12.1 KB
/
wizard.py
File metadata and controls
328 lines (276 loc) · 12.1 KB
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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
import re
from collections import OrderedDict
from django import forms
from django.db import models
from django.template import loader
try:
from formtools.wizard.storage import get_storage
from formtools.wizard.forms import ManagementForm
from formtools.wizard.views import StepsHelper
except:
##work for django<1.8
from django.contrib.formtools.wizard.storage import get_storage
from django.contrib.formtools.wizard.forms import ManagementForm
from django.contrib.formtools.wizard.views import StepsHelper
from django.utils.module_loading import import_string
from django.forms import ValidationError
from django.forms.models import modelform_factory
from xadmin.sites import site
from xadmin.views import BaseAdminPlugin, ModelFormAdminView
def normalize_name(name):
new = re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', '_\\1', name)
return new.lower().strip('_')
class WizardFormPlugin(BaseAdminPlugin):
wizard_form_list = None
wizard_for_update = False
storage_name = 'formtools.wizard.storage.session.SessionStorage'
form_list = None
initial_dict = None
instance_dict = None
condition_dict = None
file_storage = None
def _get_form_prefix(self, step=None):
if step is None:
step = self.steps.current
return 'step_%d' % self.get_form_list().keys().index(step)
def get_form_list(self):
if not hasattr(self, '_form_list'):
init_form_list = OrderedDict()
assert len(
self.wizard_form_list) > 0, 'at least one form is needed'
for i, form in enumerate(self.wizard_form_list):
init_form_list[unicode(form[0])] = form[1]
self._form_list = init_form_list
return self._form_list
# Plugin replace methods
def init_request(self, *args, **kwargs):
if self.request.is_ajax() or ("_ajax" in self.request.GET) or not hasattr(self.request, 'session') or (args and not self.wizard_for_update):
#update view
return False
return bool(self.wizard_form_list)
def prepare_form(self, __):
# init storage and step helper
self.prefix = normalize_name(self.__class__.__name__)
self.storage = get_storage(
self.storage_name, self.prefix, self.request,
getattr(self, 'file_storage', None))
self.steps = StepsHelper(self)
self.wizard_goto_step = False
if self.request.method == 'GET':
self.storage.reset()
self.storage.current_step = self.steps.first
self.admin_view.model_form = self.get_step_form()
else:
# Look for a wizard_goto_step element in the posted data which
# contains a valid step name. If one was found, render the requested
# form. (This makes stepping back a lot easier).
wizard_goto_step = self.request.POST.get('wizard_goto_step', None)
if wizard_goto_step and int(wizard_goto_step) < len(self.get_form_list()):
self.storage.current_step = self.get_form_list(
).keys()[int(wizard_goto_step)]
self.admin_view.model_form = self.get_step_form()
self.wizard_goto_step = True
return
# Check if form was refreshed
management_form = ManagementForm(
self.request.POST, prefix=self.prefix)
if not management_form.is_valid():
raise ValidationError(
'ManagementForm data is missing or has been tampered.')
form_current_step = management_form.cleaned_data['current_step']
if (form_current_step != self.steps.current and
self.storage.current_step is not None):
# form refreshed, change current step
self.storage.current_step = form_current_step
# get the form for the current step
self.admin_view.model_form = self.get_step_form()
def get_form_layout(self, __):
attrs = self.get_form_list()[self.steps.current]
if type(attrs) is dict and 'layout' in attrs:
self.admin_view.form_layout = attrs['layout']
else:
self.admin_view.form_layout = None
return __()
def get_step_form(self, step=None):
if step is None:
step = self.steps.current
attrs = self.get_form_list()[step]
if type(attrs) in (list, tuple):
return modelform_factory(self.model, form=forms.ModelForm,
fields=attrs, formfield_callback=self.admin_view.formfield_for_dbfield)
elif type(attrs) is dict:
if attrs.get('fields', None):
return modelform_factory(self.model, form=forms.ModelForm,
fields=attrs['fields'], formfield_callback=self.admin_view.formfield_for_dbfield)
if attrs.get('callback', None):
callback = attrs['callback']
if callable(callback):
return callback(self)
elif hasattr(self.admin_view, str(callback)):
return getattr(self.admin_view, str(callback))(self)
elif issubclass(attrs, forms.BaseForm):
return attrs
return None
def get_step_form_obj(self, step=None):
if step is None:
step = self.steps.current
form = self.get_step_form(step)
return form(prefix=self._get_form_prefix(step),
data=self.storage.get_step_data(step),
files=self.storage.get_step_files(step))
def get_form_datas(self, datas):
datas['prefix'] = self._get_form_prefix()
if self.request.method == 'POST' and self.wizard_goto_step:
datas.update({
'data': self.storage.get_step_data(self.steps.current),
'files': self.storage.get_step_files(self.steps.current)
})
return datas
def valid_forms(self, __):
if self.wizard_goto_step:
# goto get_response directly
return False
return __()
def _done(self):
cleaned_data = self.get_all_cleaned_data()
exclude = self.admin_view.exclude
opts = self.admin_view.opts
instance = self.admin_view.org_obj or self.admin_view.model()
file_field_list = []
for f in opts.fields:
if not f.editable or isinstance(f, models.AutoField) \
or not f.name in cleaned_data:
continue
if exclude and f.name in exclude:
continue
# Defer saving file-type fields until after the other fields, so a
# callable upload_to can use the values from other fields.
if isinstance(f, models.FileField):
file_field_list.append(f)
else:
f.save_form_data(instance, cleaned_data[f.name])
for f in file_field_list:
f.save_form_data(instance, cleaned_data[f.name])
instance.save()
for f in opts.many_to_many:
if f.name in cleaned_data:
f.save_form_data(instance, cleaned_data[f.name])
self.admin_view.new_obj = instance
def save_forms(self, __):
# if the form is valid, store the cleaned data and files.
form_obj = self.admin_view.form_obj
self.storage.set_step_data(self.steps.current, form_obj.data)
self.storage.set_step_files(self.steps.current, form_obj.files)
# check if the current step is the last step
if self.steps.current == self.steps.last:
# no more steps, render done view
return self._done()
def save_models(self, __):
pass
def save_related(self, __):
pass
def get_context(self, context):
context.update({
"show_save": False,
"show_save_as_new": False,
"show_save_and_add_another": False,
"show_save_and_continue": False,
})
return context
def get_response(self, response):
self.storage.update_response(response)
return response
def post_response(self, __):
if self.steps.current == self.steps.last:
self.storage.reset()
return __()
# change the stored current step
self.storage.current_step = self.steps.next
self.admin_view.form_obj = self.get_step_form_obj()
self.admin_view.setup_forms()
return self.admin_view.get_response()
def get_all_cleaned_data(self):
"""
Returns a merged dictionary of all step cleaned_data dictionaries.
If a step contains a `FormSet`, the key will be prefixed with formset
and contain a list of the formset cleaned_data dictionaries.
"""
cleaned_data = {}
for form_key, attrs in self.get_form_list().items():
form_obj = self.get_step_form_obj(form_key)
if form_obj.is_valid():
if type(attrs) is dict and 'convert' in attrs:
callback = attrs['convert']
if callable(callback):
callback(self, cleaned_data, form_obj)
elif hasattr(self.admin_view, str(callback)):
getattr(self.admin_view,
str(callback))(self, cleaned_data, form_obj)
elif isinstance(form_obj.cleaned_data, (tuple, list)):
cleaned_data.update({
'formset-%s' % form_key: form_obj.cleaned_data
})
else:
cleaned_data.update(form_obj.cleaned_data)
return cleaned_data
def get_cleaned_data_for_step(self, step):
"""
Returns the cleaned data for a given `step`. Before returning the
cleaned data, the stored values are being revalidated through the
form. If the data doesn't validate, None will be returned.
"""
if step in self.get_form_list():
form_obj = self.get_step_form_obj(step)
if form_obj.is_valid():
return form_obj.cleaned_data
return None
def get_next_step(self, step=None):
"""
Returns the next step after the given `step`. If no more steps are
available, None will be returned. If the `step` argument is None, the
current step will be determined automatically.
"""
if step is None:
step = self.steps.current
form_list = self.get_form_list()
key = form_list.keys().index(step) + 1
if len(form_list.keys()) > key:
return form_list.keys()[key]
return None
def get_prev_step(self, step=None):
"""
Returns the previous step before the given `step`. If there are no
steps available, None will be returned. If the `step` argument is
None, the current step will be determined automatically.
"""
if step is None:
step = self.steps.current
form_list = self.get_form_list()
key = form_list.keys().index(step) - 1
if key >= 0:
return form_list.keys()[key]
return None
def get_step_index(self, step=None):
"""
Returns the index for the given `step` name. If no step is given,
the current step will be used to get the index.
"""
if step is None:
step = self.steps.current
return self.get_form_list().keys().index(step)
def block_before_fieldsets(self, context, nodes):
context.update(dict(self.storage.extra_data))
context['wizard'] = {
'steps': self.steps,
'management_form': ManagementForm(prefix=self.prefix, initial={
'current_step': self.steps.current,
}),
}
nodes.append(loader.render_to_string('xadmin/blocks/model_form.before_fieldsets.wizard.html',context))
def block_submit_line(self, context, nodes):
context.update(dict(self.storage.extra_data))
context['wizard'] = {
'steps': self.steps
}
nodes.append(loader.render_to_string('xadmin/blocks/model_form.submit_line.wizard.html',context))
site.register_plugin(WizardFormPlugin, ModelFormAdminView)