bureaucrat 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +22 -0
- data/README.md +124 -0
- data/lib/bureaucrat/fields.rb +461 -0
- data/lib/bureaucrat/forms.rb +346 -0
- data/lib/bureaucrat/formsets.rb +256 -0
- data/lib/bureaucrat/quickfields.rb +59 -0
- data/lib/bureaucrat/utils.rb +84 -0
- data/lib/bureaucrat/validation.rb +130 -0
- data/lib/bureaucrat/validation_old.rb +148 -0
- data/lib/bureaucrat/widgets.rb +397 -0
- data/lib/bureaucrat/wizard.rb +220 -0
- data/lib/bureaucrat.rb +10 -0
- data/test/fields_test.rb +577 -0
- data/test/forms_test.rb +131 -0
- data/test/formsets_test.rb +51 -0
- data/test/test_helper.rb +22 -0
- data/test/widgets_test.rb +328 -0
- metadata +71 -0
@@ -0,0 +1,397 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
require 'bureaucrat/utils'
|
4
|
+
|
5
|
+
module Bureaucrat
|
6
|
+
module Widgets
|
7
|
+
class Media
|
8
|
+
include Utils
|
9
|
+
|
10
|
+
MEDIA_TYPES = [:css, :js]
|
11
|
+
DEFAULT_MEDIA_PATH = 'http://localhost'
|
12
|
+
|
13
|
+
attr_accessor :media_path
|
14
|
+
|
15
|
+
def initialize(media_attrs={})
|
16
|
+
self.media_path = media_attrs.delete(:media_path) || DEFAULT_MEDIA_PATH
|
17
|
+
media_attrs = media_attrs.to_hash if media_attrs.is_a?(Media)
|
18
|
+
@css = {}
|
19
|
+
@js = []
|
20
|
+
|
21
|
+
MEDIA_TYPES.each do |name|
|
22
|
+
data = media_attrs[name]
|
23
|
+
add_type(name, data) if data
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
render
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_hash
|
32
|
+
hash = {}
|
33
|
+
MEDIA_TYPES.each {|name| hash[name] = instance_variable_get("@#{name}")}
|
34
|
+
hash
|
35
|
+
end
|
36
|
+
|
37
|
+
def render
|
38
|
+
mark_safe(MEDIA_TYPES.map do |name|
|
39
|
+
render_type(name)
|
40
|
+
end.inject(&:+).join("\n"))
|
41
|
+
end
|
42
|
+
|
43
|
+
def render_type(type)
|
44
|
+
send("render_#{type}")
|
45
|
+
end
|
46
|
+
|
47
|
+
def render_js
|
48
|
+
@js.map do |path|
|
49
|
+
"<script type=\"text/javascript\" src=\"#{absolute_path(path)}\"></script>"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def render_css
|
54
|
+
fragments = @css.keys.sort.map do |medium|
|
55
|
+
@css[medium].map do |path|
|
56
|
+
"<link href=\"#{absolute_path(path)}\" type=\"text/css\" media=\"#{medium}\" rel=\"stylesheet\" />"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
fragments.empty? ? fragments : fragments.inject(&:+)
|
60
|
+
end
|
61
|
+
|
62
|
+
def absolute_path(path)
|
63
|
+
path =~ /^(\/|https?:\/\/)/ ? path : URI.join(media_path, path)
|
64
|
+
end
|
65
|
+
|
66
|
+
def [](name)
|
67
|
+
raise IndexError("Unknown media type '#{name}'") unless
|
68
|
+
MEDIA_TYPES.include?(name)
|
69
|
+
Media.new(name => instance_variable_get("@{name}"))
|
70
|
+
end
|
71
|
+
|
72
|
+
def add_type(type, data)
|
73
|
+
send("add_#{type}", data)
|
74
|
+
end
|
75
|
+
|
76
|
+
def add_js(data)
|
77
|
+
@js += data.select {|path| !@js.include?(path)}
|
78
|
+
end
|
79
|
+
|
80
|
+
def add_css(data)
|
81
|
+
data.each do |medium, paths|
|
82
|
+
@css[medium] ||= []
|
83
|
+
css = @css[medium]
|
84
|
+
css.concat(paths.select {|path| !css.include?(path)})
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def +(other)
|
89
|
+
combined = Media.new
|
90
|
+
MEDIA_TYPES.each do |name|
|
91
|
+
combined.add_type(name, instance_variable_get("@#{name}"))
|
92
|
+
combined.add_type(name, other.instance_variable_get("@#{name}"))
|
93
|
+
end
|
94
|
+
combined
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class Widget
|
99
|
+
include Utils
|
100
|
+
|
101
|
+
class << self
|
102
|
+
attr_accessor :needs_multipart_form, :is_hidden
|
103
|
+
|
104
|
+
def inherited(c)
|
105
|
+
super(c)
|
106
|
+
c.is_hidden = is_hidden
|
107
|
+
c.needs_multipart_form = needs_multipart_form
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
self.needs_multipart_form = false
|
112
|
+
self.is_hidden = false
|
113
|
+
|
114
|
+
attr_reader :attrs
|
115
|
+
|
116
|
+
def initialize(attrs=nil)
|
117
|
+
@attrs = attrs.nil? ? {} : attrs.dup
|
118
|
+
end
|
119
|
+
|
120
|
+
def initialize_copy(original)
|
121
|
+
super(original)
|
122
|
+
@attrs = original.attrs.dup
|
123
|
+
end
|
124
|
+
|
125
|
+
def render(name, value, attrs=nil)
|
126
|
+
raise NotImplementedError
|
127
|
+
end
|
128
|
+
|
129
|
+
def build_attrs(extra_attrs=nil, options={})
|
130
|
+
attrs = @attrs.merge(options)
|
131
|
+
attrs.update(extra_attrs) if extra_attrs
|
132
|
+
attrs
|
133
|
+
end
|
134
|
+
|
135
|
+
def value_from_datahash(data, files, name)
|
136
|
+
return data[name]
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.id_for_label(id_)
|
140
|
+
id_
|
141
|
+
end
|
142
|
+
|
143
|
+
def has_changed?(initial, data)
|
144
|
+
data_value = data || ''
|
145
|
+
initial_value = initial || ''
|
146
|
+
initial_value != data_value
|
147
|
+
end
|
148
|
+
|
149
|
+
def hidden?
|
150
|
+
self.class.is_hidden
|
151
|
+
end
|
152
|
+
|
153
|
+
def media
|
154
|
+
Media.new
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
class Input < Widget
|
159
|
+
class << self
|
160
|
+
attr_accessor :input_type
|
161
|
+
|
162
|
+
# Copy data to the child class
|
163
|
+
def inherited(c)
|
164
|
+
super(c)
|
165
|
+
c.input_type = input_type.dup if input_type
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
self.is_hidden = false
|
170
|
+
self.input_type = nil
|
171
|
+
|
172
|
+
def render(name, value, attrs=nil)
|
173
|
+
value ||= ''
|
174
|
+
final_attrs = build_attrs(attrs,
|
175
|
+
:type => self.class.input_type.to_s,
|
176
|
+
:name => name.to_s)
|
177
|
+
final_attrs[:value] = value.to_s unless value == ''
|
178
|
+
mark_safe("<input#{flatatt(final_attrs)} />")
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
class TextInput < Input
|
183
|
+
self.input_type = 'text'
|
184
|
+
end
|
185
|
+
|
186
|
+
class PasswordInput < Input
|
187
|
+
self.input_type = 'password'
|
188
|
+
|
189
|
+
def initialize(attrs=nil, render_value=true)
|
190
|
+
super(attrs)
|
191
|
+
@render_value = render_value
|
192
|
+
end
|
193
|
+
|
194
|
+
def render(name, value, attrs=nil)
|
195
|
+
value = nil unless @render_value
|
196
|
+
super(name, value, attrs)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
class HiddenInput < Input
|
201
|
+
self.input_type = 'hidden'
|
202
|
+
self.is_hidden = true
|
203
|
+
end
|
204
|
+
|
205
|
+
class MultipleHiddenInput < HiddenInput
|
206
|
+
# Used by choice fields
|
207
|
+
attr_accessor :choices
|
208
|
+
|
209
|
+
def initialize(attrs=nil, choices=[])
|
210
|
+
super(attrs)
|
211
|
+
# choices can be any enumerable
|
212
|
+
@choices = choices
|
213
|
+
end
|
214
|
+
|
215
|
+
def render(name, value, attrs=nil, choices=[])
|
216
|
+
value ||= []
|
217
|
+
final_attrs = build_attrs(attrs, :type => self.class.input_type,
|
218
|
+
:name => name)
|
219
|
+
mark_safe(value.map do |v|
|
220
|
+
rattrs = {:value => v.to_s}.merge(final_attrs)
|
221
|
+
"<input#{flatatt(rattrs)} />"
|
222
|
+
end.join("\n"))
|
223
|
+
end
|
224
|
+
|
225
|
+
def value_from_datahash(data, files, name)
|
226
|
+
#if data.is_a?(MultiValueDict) || data.is_a?(MergeDict)
|
227
|
+
# data.getlist(name)
|
228
|
+
#else
|
229
|
+
# data[name]
|
230
|
+
#end
|
231
|
+
data[name]
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
class FileInput < Input
|
236
|
+
self.input_type = 'file'
|
237
|
+
self.needs_multipart_form = true
|
238
|
+
|
239
|
+
def render(name, value, attrs=nil)
|
240
|
+
super(name, nil, attrs)
|
241
|
+
end
|
242
|
+
|
243
|
+
def value_from_datahash(data, files, name)
|
244
|
+
files.fetch(name, nil)
|
245
|
+
end
|
246
|
+
|
247
|
+
def has_changed?(initial, data)
|
248
|
+
data.nil?
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
class Textarea < Widget
|
253
|
+
def initialize(attrs=nil)
|
254
|
+
# The 'rows' and 'cols' attributes are required for HTML correctness.
|
255
|
+
@attrs = {:cols => '40', :rows => '10'}
|
256
|
+
@attrs.merge!(attrs) if attrs
|
257
|
+
end
|
258
|
+
|
259
|
+
def render(name, value, attrs=nil)
|
260
|
+
value ||= ''
|
261
|
+
final_attrs = build_attrs(attrs, :name => name)
|
262
|
+
mark_safe("<textarea#{flatatt(final_attrs)}>#{conditional_escape(value.to_s)}</textarea>")
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# DateInput
|
267
|
+
# DateTimeInput
|
268
|
+
# TimeInput
|
269
|
+
|
270
|
+
class CheckboxInput < Widget
|
271
|
+
def initialize(attrs=nil, check_test=nil)
|
272
|
+
super(attrs)
|
273
|
+
@check_test = check_test || lambda {|v| make_bool(v)}
|
274
|
+
end
|
275
|
+
|
276
|
+
def render(name, value, attrs=nil)
|
277
|
+
final_attrs = build_attrs(attrs, :type => 'checkbox', :name => name.to_s)
|
278
|
+
result = self.check_test(value) rescue false
|
279
|
+
final_attrs[:checked] = 'checked' if result
|
280
|
+
final_attrs[:value] = value.to_s unless
|
281
|
+
['', true, false, nil].include?(value)
|
282
|
+
mark_safe("<input#{flatatt(final_attrs)} />")
|
283
|
+
end
|
284
|
+
|
285
|
+
def value_from_datahash(data, files, name)
|
286
|
+
data.include?(name) ? super(data, files, name) : false
|
287
|
+
end
|
288
|
+
|
289
|
+
def has_changed(initial, data)
|
290
|
+
make_bool(initial) != make_bool(data)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
class Select < Widget
|
295
|
+
attr_accessor :choices
|
296
|
+
|
297
|
+
def initialize(attrs=nil, choices=[])
|
298
|
+
super(attrs)
|
299
|
+
@choices = choices.collect
|
300
|
+
end
|
301
|
+
|
302
|
+
def render(name, value, attrs=nil, choices=[])
|
303
|
+
value = '' if value.nil?
|
304
|
+
final_attrs = build_attrs(attrs, :name => name)
|
305
|
+
output = ["<select#{flatatt(final_attrs)}>"]
|
306
|
+
options = render_options(choices, [value])
|
307
|
+
output << options if options && !options.empty?
|
308
|
+
output << '</select>'
|
309
|
+
mark_safe(output.join("\n"))
|
310
|
+
end
|
311
|
+
|
312
|
+
def render_options(choices, selected_choices)
|
313
|
+
selected_choices = selected_choices.map(&:to_s).uniq
|
314
|
+
output = []
|
315
|
+
(@choices + choices).each do |option_value, option_label|
|
316
|
+
if option_label.is_a?(Array)
|
317
|
+
output << '<optgroup label="%s">' % escape(option_value.to_s)
|
318
|
+
option_label.each do |option|
|
319
|
+
val, label = option
|
320
|
+
output << render_option(val, label, selected_choices)
|
321
|
+
end
|
322
|
+
output << '</optgroup>'
|
323
|
+
else
|
324
|
+
output << render_option(option_value, option_label,
|
325
|
+
selected_choices)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
output.join("\n")
|
329
|
+
end
|
330
|
+
|
331
|
+
def render_option(option_value, option_label, selected_choices)
|
332
|
+
option_value = option_value.to_s
|
333
|
+
selected_html = selected_choices.include?(option_value) ? ' selected="selected"' : ''
|
334
|
+
"<option value=\"#{escape(option_value)}\"#{selected_html}>#{conditional_escape(option_label.to_s)}</option>"
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
class NullBooleanSelect < Select
|
339
|
+
def initialize(attrs=nil)
|
340
|
+
choices = [['1', 'Unknown'], ['2', 'Yes'], ['3', 'No']]
|
341
|
+
super(attrs, choices)
|
342
|
+
end
|
343
|
+
|
344
|
+
def render(name, value, attrs=nil, choices=[])
|
345
|
+
value = case value
|
346
|
+
when true, '2' then '2'
|
347
|
+
when false, '3' then '3'
|
348
|
+
else '1'
|
349
|
+
end
|
350
|
+
super(name, value, attrs, choices)
|
351
|
+
end
|
352
|
+
|
353
|
+
def value_from_datahash(data, files, name)
|
354
|
+
value = data[name]
|
355
|
+
case value
|
356
|
+
when '2', true then true
|
357
|
+
when '3', false then false
|
358
|
+
else nil
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
def has_changed?(initial, data)
|
363
|
+
make_bool(initial) != make_bool(data)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
class SelectMultiple < Select
|
368
|
+
def render(name, value, attrs=nil, choices=[])
|
369
|
+
value = [] if value.nil?
|
370
|
+
final_attrs = build_attrs(attrs, :name => name)
|
371
|
+
output = ["<select multiple=\"multiple\"#{flatatt(final_attrs)}>"]
|
372
|
+
options = render_options(choices, value)
|
373
|
+
output << options if options && !options.empty?
|
374
|
+
output << '</select>'
|
375
|
+
mark_safe(output.join("\n"))
|
376
|
+
end
|
377
|
+
|
378
|
+
def value_from_datahash(data, files, name)
|
379
|
+
#if data.is_a?(MultiValueDict) || data.is_a?(MergeDict)
|
380
|
+
# data.getlist(name)
|
381
|
+
#else
|
382
|
+
# data[name]
|
383
|
+
#end
|
384
|
+
data[name]
|
385
|
+
end
|
386
|
+
|
387
|
+
def has_changed?(initial, data)
|
388
|
+
initial = [] if initial.nil?
|
389
|
+
data = [] if data.nil?
|
390
|
+
return true if initial.length != data.length
|
391
|
+
initial.zip(data).each do |value1, value2|
|
392
|
+
return true if value1.to_s != value2.to_s
|
393
|
+
end
|
394
|
+
false
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end; end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
require 'bureaucrat/forms'
|
4
|
+
# import cPickle as pickle
|
5
|
+
|
6
|
+
# from django.conf import settings
|
7
|
+
# from django.http import Http404
|
8
|
+
# from django.shortcuts import render_to_response
|
9
|
+
# from django.template.context import RequestContext
|
10
|
+
# from django.utils.hashcompat import md5_constructor
|
11
|
+
# from django.utils.translation import ugettext_lazy as _
|
12
|
+
|
13
|
+
module Bureaucrat; Wizard
|
14
|
+
module Utils
|
15
|
+
def security_hash(request, form, *args)
|
16
|
+
data = []
|
17
|
+
form.each do |bf|
|
18
|
+
value = if form.empty_permitted? && ! form.changed? then bf.data
|
19
|
+
else bf.field.clean(bf.data)
|
20
|
+
end
|
21
|
+
value ||= ''
|
22
|
+
value = value.strip if value.respond_to? :strip
|
23
|
+
data.append([bf.name, value])
|
24
|
+
end
|
25
|
+
|
26
|
+
data += args
|
27
|
+
data << settings.SECRET_KEY # FIXME
|
28
|
+
|
29
|
+
Digest::MD5.hexdigest(Marshal.dump(data))
|
30
|
+
end
|
31
|
+
|
32
|
+
module_function :security_hash
|
33
|
+
end
|
34
|
+
|
35
|
+
class FormWizard
|
36
|
+
# FIXME
|
37
|
+
# The HTML (and POST data) field name for the "step" variable.
|
38
|
+
|
39
|
+
class << self
|
40
|
+
attr_accessor :step_field_name
|
41
|
+
end
|
42
|
+
|
43
|
+
self.step_field_name = "wizard_step"
|
44
|
+
|
45
|
+
def initialize(form_list, initial=Hash.new)
|
46
|
+
@form_list = form_list.dup
|
47
|
+
@initial = initial
|
48
|
+
@step = 0
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_form(step, data=nil)
|
52
|
+
@form_list[step].new(data,
|
53
|
+
:prefix => prefix_for_step(step),
|
54
|
+
:initial => @initial.fetch(step, nil))
|
55
|
+
end
|
56
|
+
|
57
|
+
def num_steps
|
58
|
+
@form_list.length
|
59
|
+
end
|
60
|
+
|
61
|
+
def new(request, *args)
|
62
|
+
current_step = determine_step(request, *args)
|
63
|
+
parse_params(request, *args)
|
64
|
+
|
65
|
+
raise ArgumentError("Step #{current_step} does not exist") if
|
66
|
+
current_step >= num_steps
|
67
|
+
|
68
|
+
(0...current_step).each do |i|
|
69
|
+
form = get_form(i, request.POST)
|
70
|
+
|
71
|
+
return render_hash_failure(request, i) if
|
72
|
+
request.POST.fetch("hash_#{i}", '') != security_hash(request, form)
|
73
|
+
|
74
|
+
process_step(request, form, i)
|
75
|
+
end
|
76
|
+
|
77
|
+
form = get_form(current_step, request.post? ? request.POST : nil)
|
78
|
+
|
79
|
+
if form.valid?
|
80
|
+
process_step(request, form, current_step)
|
81
|
+
next_step = current_step + 1
|
82
|
+
|
83
|
+
if next_step == num_steps
|
84
|
+
final_form_list = (0...num_steps).map {|i| get_form(i, request.POST)}
|
85
|
+
|
86
|
+
final_form_list.each_with_index do |f, i|
|
87
|
+
return render_revalidation_failure(request, i, f) unless f.valid?
|
88
|
+
end
|
89
|
+
|
90
|
+
return done(request, final_form_list)
|
91
|
+
else
|
92
|
+
form = get_form(next_step)
|
93
|
+
@step = current_step = next_step
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
render(form, request, current_step)
|
98
|
+
end
|
99
|
+
|
100
|
+
def render(form, request, step, context=nil)
|
101
|
+
"Renders the given Form object, returning an HttpResponse." # FIXME
|
102
|
+
old_data = request.POST
|
103
|
+
prev_fields = []
|
104
|
+
|
105
|
+
if old_data
|
106
|
+
hidden = Widgets::HiddenInput.new
|
107
|
+
# Collect all data from previous steps and render it as HTML hidden fields.
|
108
|
+
(0...step).each do |i|
|
109
|
+
old_form = get_form(i, old_data)
|
110
|
+
hash_name = "hash_#{i}"
|
111
|
+
hash = old_data[hash_name] || security_hash(request, old_form)
|
112
|
+
prev_fields += old_form.map {|bf| bf.as_hidden}
|
113
|
+
prev_fields << hidden.render(hash_name, hash)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
render_template(request, form, prev_fields.join(''), step, context)
|
118
|
+
end
|
119
|
+
|
120
|
+
# METHODS SUBCLASSES MIGHT OVERRIDE IF APPROPRIATE
|
121
|
+
|
122
|
+
def prefix_for_step(step)
|
123
|
+
step.to_s
|
124
|
+
end
|
125
|
+
|
126
|
+
def render_hash_failure(request, step)
|
127
|
+
# Hook for rendering a template if a hash check failed.
|
128
|
+
|
129
|
+
# step is the step that failed. Any previous step is guaranteed to be
|
130
|
+
# valid.
|
131
|
+
|
132
|
+
# This default implementation simply renders the form for the given step,
|
133
|
+
# but subclasses may want to display an error message, etc.
|
134
|
+
render(get_form(step), request, step, :context => {:wizard_error => _('We apologize, but your form has expired. Please continue filling out the form from this page.')})
|
135
|
+
end
|
136
|
+
|
137
|
+
def render_revalidation_failure(request, step, form)
|
138
|
+
# Hook for rendering a template if final revalidation failed.
|
139
|
+
|
140
|
+
# It is highly unlikely that this point would ever be reached, but See
|
141
|
+
# the comment in __call__() for an explanation.
|
142
|
+
render(form, request, step)
|
143
|
+
end
|
144
|
+
|
145
|
+
def security_hash(request, form)
|
146
|
+
Utils::security_hash(request, form)
|
147
|
+
end
|
148
|
+
|
149
|
+
def determine_step(request, *args)
|
150
|
+
if request.post?
|
151
|
+
begin
|
152
|
+
Integer(request.POST.fetch(step_field_name, 0))
|
153
|
+
rescue ArgumentError
|
154
|
+
0
|
155
|
+
end
|
156
|
+
else
|
157
|
+
0
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def parse_params(request, *args)
|
162
|
+
end
|
163
|
+
|
164
|
+
def get_template(step)
|
165
|
+
'forms/wizard.erb'
|
166
|
+
end
|
167
|
+
|
168
|
+
def render_template(request, form, previous_fields, step, context=nil)
|
169
|
+
|
170
|
+
# Renders the template for the given step, returning an HttpResponse object.
|
171
|
+
|
172
|
+
# Override this method if you want to add a custom context, return a
|
173
|
+
# different MIME type, etc. If you only need to override the template
|
174
|
+
# name, use get_template() instead.
|
175
|
+
|
176
|
+
# The template will be rendered with the following context:
|
177
|
+
# step_field -- The name of the hidden field containing the step.
|
178
|
+
# step0 -- The current step (zero-based).
|
179
|
+
# step -- The current step (one-based).
|
180
|
+
# step_count -- The total number of steps.
|
181
|
+
# form -- The Form instance for the current step (either empty
|
182
|
+
# or with errors).
|
183
|
+
# previous_fields -- A string representing every previous data field,
|
184
|
+
# plus hashes for completed forms, all in the form of
|
185
|
+
# hidden fields. Note that you'll need to run this
|
186
|
+
# through the 'safe' template filter, to prevent
|
187
|
+
# auto-escaping, because it's raw HTML.
|
188
|
+
|
189
|
+
context ||= {}
|
190
|
+
# FIXME
|
191
|
+
render_to_response(get_template(step),
|
192
|
+
context.merge(:step_field => step_field_name,
|
193
|
+
:step0 => step,
|
194
|
+
:step => step + 1,
|
195
|
+
:step_count => num_steps,
|
196
|
+
:form => form,
|
197
|
+
:previous_fields => previous_fields),
|
198
|
+
:context_instance => RequestContext(request))
|
199
|
+
end
|
200
|
+
|
201
|
+
def process_step(request, form, step)
|
202
|
+
# Hook for modifying the FormWizard's internal state, given a fully
|
203
|
+
# validated Form object. The Form is guaranteed to have clean, valid
|
204
|
+
# data.
|
205
|
+
|
206
|
+
# This method should *not* modify any of that data. Rather, it might want
|
207
|
+
# to set self.extra_context or dynamically alter self.form_list, based on
|
208
|
+
# previously submitted forms.
|
209
|
+
|
210
|
+
# Note that this method is called every time a page is rendered for *all*
|
211
|
+
# submitted steps.
|
212
|
+
end
|
213
|
+
|
214
|
+
# METHODS SUBCLASSES MUST OVERRIDE ########################################
|
215
|
+
|
216
|
+
def done(request, form_list)
|
217
|
+
raise NotImplementedError.new("#{self.class.name} doesn't define the required 'done' method.")
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
data/lib/bureaucrat.rb
ADDED