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,346 @@
|
|
1
|
+
require 'bureaucrat/utils'
|
2
|
+
require 'bureaucrat/validation'
|
3
|
+
require 'bureaucrat/widgets'
|
4
|
+
require 'bureaucrat/fields'
|
5
|
+
|
6
|
+
module Bureaucrat; module Forms
|
7
|
+
class BoundField
|
8
|
+
include Utils
|
9
|
+
|
10
|
+
attr_accessor :label
|
11
|
+
|
12
|
+
def initialize(form, field, name)
|
13
|
+
@form = form
|
14
|
+
@field = field
|
15
|
+
@name = name
|
16
|
+
@html_name = form.add_prefix(name).to_sym
|
17
|
+
@html_initial_name = form.add_initial_prefix(name).to_sym
|
18
|
+
@label = @field.label || pretty_name(name)
|
19
|
+
@help_text = @field.help_text || ''
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
@field.show_hidden_initial ? as_widget + as_hidden(nil, true) : as_widget
|
24
|
+
end
|
25
|
+
|
26
|
+
def errors
|
27
|
+
@form.errors.fetch(@name, @form.error_class.new)
|
28
|
+
end
|
29
|
+
|
30
|
+
def as_widget(widget=nil, attrs=nil, only_initial=false)
|
31
|
+
widget ||= @field.widget
|
32
|
+
attrs ||= {}
|
33
|
+
auto_id = self.auto_id
|
34
|
+
attrs[:id] ||= auto_id if auto_id && !widget.attrs.key?(:id)
|
35
|
+
|
36
|
+
if !@form.bound?
|
37
|
+
data = @form.initial.fetch(@name, @field.initial)
|
38
|
+
data = data.call if data.respond_to?(:call)
|
39
|
+
else
|
40
|
+
if @field.is_a?(Fields::FileField) && @data.nil?
|
41
|
+
data = @form.initial.fetch(@name, @field.initial)
|
42
|
+
else
|
43
|
+
data = self.data
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
name = only_initial ? @html_initial_name : @html_name
|
48
|
+
|
49
|
+
widget.render(name.to_s, data, attrs)
|
50
|
+
end
|
51
|
+
|
52
|
+
def as_text(attrs=nil, only_initial=false)
|
53
|
+
as_widget(Widgets::TextInput.new, attrs, only_initial)
|
54
|
+
end
|
55
|
+
|
56
|
+
def as_textarea(attrs=nil, only_initial=false)
|
57
|
+
as_widget(Widgets::Textarea.new, attrs, only_initial)
|
58
|
+
end
|
59
|
+
|
60
|
+
def as_hidden(attrs=nil, only_initial=false)
|
61
|
+
as_widget(@field.hidden_widget, attrs, only_initial)
|
62
|
+
end
|
63
|
+
|
64
|
+
def data
|
65
|
+
@field.widget.value_from_datahash(@form.data, @form.files, @html_name)
|
66
|
+
end
|
67
|
+
|
68
|
+
def label_tag(contents=nil, attrs=nil)
|
69
|
+
contents ||= conditional_escape(@label)
|
70
|
+
widget = @field.widget
|
71
|
+
id_ = widget.attrs[:id] || self.auto_id
|
72
|
+
|
73
|
+
if id_
|
74
|
+
attrs = attrs ? flattatt(attrs) : ''
|
75
|
+
contents = "<label for=\"#{Widgets::Widget.id_for_label(id_)}\"#{attrs}>#{contents}</label>"
|
76
|
+
end
|
77
|
+
|
78
|
+
mark_safe(contents)
|
79
|
+
end
|
80
|
+
|
81
|
+
def hidden?
|
82
|
+
@field.widget.hidden?
|
83
|
+
end
|
84
|
+
|
85
|
+
def auto_id
|
86
|
+
fauto_id = @form.auto_id
|
87
|
+
fauto_id ? fauto_id % @html_name : ''
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class Form
|
92
|
+
include Utils
|
93
|
+
include Validation
|
94
|
+
|
95
|
+
class << self
|
96
|
+
attr_writer :base_fields
|
97
|
+
|
98
|
+
def base_fields
|
99
|
+
@base_fields ||= Utils::OrderedHash.new
|
100
|
+
end
|
101
|
+
|
102
|
+
def field(name, field_obj)
|
103
|
+
base_fields[name] = field_obj
|
104
|
+
end
|
105
|
+
|
106
|
+
# Copy data to the child class
|
107
|
+
def inherited(c)
|
108
|
+
super(c)
|
109
|
+
c.base_fields = base_fields.dup
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
attr_accessor :error_class, :auto_id, :initial, :data, :files, :cleaned_data
|
114
|
+
|
115
|
+
def bound? ; @is_bound; end
|
116
|
+
|
117
|
+
def initialize(data=nil, options={})
|
118
|
+
@is_bound = !data.nil?
|
119
|
+
@data = data ? data.dup : {}
|
120
|
+
@data.each {|k, v| @data[k.to_sym] ||= v}
|
121
|
+
@files = options.fetch(:files, {})
|
122
|
+
@auto_id = options.fetch(:auto_id, 'id_%s')
|
123
|
+
@prefix = options[:prefix]
|
124
|
+
@initial = options.fetch(:initial, {})
|
125
|
+
@error_class = options.fetch(:error_class, Fields::ErrorList)
|
126
|
+
@label_suffix = options.fetch(:label_suffix, ':')
|
127
|
+
@empty_permitted = options.fetch(:empty_permitted, false)
|
128
|
+
@errors = nil
|
129
|
+
@changed_data = nil
|
130
|
+
|
131
|
+
@fields = self.class.base_fields.dup
|
132
|
+
@fields.each { |key, value| @fields[key] = value.dup }
|
133
|
+
end
|
134
|
+
|
135
|
+
def to_s
|
136
|
+
as_table
|
137
|
+
end
|
138
|
+
|
139
|
+
def [](name)
|
140
|
+
field = @fields[name] or return nil
|
141
|
+
BoundField.new(self, field, name)
|
142
|
+
end
|
143
|
+
|
144
|
+
def errors
|
145
|
+
full_clean if @errors.nil?
|
146
|
+
@errors
|
147
|
+
end
|
148
|
+
|
149
|
+
def valid?
|
150
|
+
@is_bound && (errors.nil? || errors.empty?)
|
151
|
+
end
|
152
|
+
|
153
|
+
def add_prefix(field_name)
|
154
|
+
@prefix ? :"#{@prefix}-#{field_name}" : field_name
|
155
|
+
end
|
156
|
+
|
157
|
+
def add_initial_prefix(field_name)
|
158
|
+
"initial-#{add_prefix(field_name)}"
|
159
|
+
end
|
160
|
+
|
161
|
+
def empty_permitted?
|
162
|
+
@empty_permitted
|
163
|
+
end
|
164
|
+
|
165
|
+
def as_table
|
166
|
+
html_output('<tr><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>',
|
167
|
+
'<tr><td colspan="2">%s</td></tr>', '</td></tr>',
|
168
|
+
'<br />%s', false)
|
169
|
+
end
|
170
|
+
|
171
|
+
def as_ul
|
172
|
+
html_output('<li>%(errors)s%(label)s %(field)s%(help_text)s</li>',
|
173
|
+
'<li>%s</li>', '</li>', ' %s', false)
|
174
|
+
end
|
175
|
+
|
176
|
+
def as_p
|
177
|
+
html_output('<p>%(label)s %(field)s%(help_text)s</p>',
|
178
|
+
'%s', '</p>', ' %s', true)
|
179
|
+
end
|
180
|
+
|
181
|
+
def non_field_errors
|
182
|
+
errors.fetch(:__NON_FIELD_ERRORS, @error_class.new)
|
183
|
+
end
|
184
|
+
|
185
|
+
def full_clean
|
186
|
+
@errors = Fields::ErrorHash.new
|
187
|
+
|
188
|
+
return unless bound?
|
189
|
+
|
190
|
+
@cleaned_data = {}
|
191
|
+
|
192
|
+
return if empty_permitted? && !changed?
|
193
|
+
|
194
|
+
@fields.each do |name, field|
|
195
|
+
value = field.widget.value_from_datahash(@data, @files,
|
196
|
+
add_prefix(name))
|
197
|
+
|
198
|
+
begin
|
199
|
+
if field.is_a?(Fields::FileField)
|
200
|
+
initial = @initial.fetch(name, field.initial)
|
201
|
+
@cleaned_data[name] = field.clean(value, initial)
|
202
|
+
else
|
203
|
+
@cleaned_data[name] = field.clean(value)
|
204
|
+
end
|
205
|
+
|
206
|
+
clean_method = 'clean_%s' % name
|
207
|
+
@cleaned_data[name] = send(clean_method) if respond_to?(clean_method)
|
208
|
+
rescue Fields::FieldValidationError => e
|
209
|
+
@errors[name] = e.messages
|
210
|
+
@cleaned_data.clear
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
begin
|
215
|
+
@cleaned_data = clean
|
216
|
+
rescue Fields::FieldValidationError => e
|
217
|
+
@errors[:__NON_FIELD_ERRORS] = e.messages
|
218
|
+
end
|
219
|
+
@cleaned_data = nil if @errors && !@errors.empty?
|
220
|
+
end
|
221
|
+
|
222
|
+
def clean
|
223
|
+
@cleaned_data
|
224
|
+
end
|
225
|
+
|
226
|
+
def changed?
|
227
|
+
changed_data && !changed_data.empty?
|
228
|
+
end
|
229
|
+
|
230
|
+
def changed_data
|
231
|
+
if @changed_data.nil?
|
232
|
+
@changed_data = []
|
233
|
+
|
234
|
+
@fields.each do |name, field|
|
235
|
+
prefixed_name = add_prefix(name)
|
236
|
+
data_value = field.widget.value_from_datahash(@data, @files,
|
237
|
+
prefixed_name)
|
238
|
+
if !field.show_hidden_initial
|
239
|
+
initial_value = @initial.fetch(name, field.initial)
|
240
|
+
else
|
241
|
+
initial_prefixed_name = add_initial_prefix(name)
|
242
|
+
hidden_widget = field.hidden_widget.new
|
243
|
+
initial_value = hidden_widget.value_from_datahash(@data, @files,
|
244
|
+
initial_prefixed_name)
|
245
|
+
end
|
246
|
+
|
247
|
+
@changed_data << name if
|
248
|
+
field.widget.has_changed?(initial_value, data_value)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
@changed_data
|
253
|
+
end
|
254
|
+
|
255
|
+
def media
|
256
|
+
@fields.values.inject(Widgets::Media.new) do |media, field|
|
257
|
+
media + field.widget.media
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def multipart?
|
262
|
+
@fields.any? {|f| f.widgetneeds_multipart_form?}
|
263
|
+
end
|
264
|
+
|
265
|
+
def hidden_fields
|
266
|
+
@fields.select {|f| f.hidden?}
|
267
|
+
end
|
268
|
+
|
269
|
+
def visible_fields
|
270
|
+
@fields.select {|f| !f.hidden?}
|
271
|
+
end
|
272
|
+
|
273
|
+
private
|
274
|
+
def html_output(normal_row, error_row, row_ender, help_text_html,
|
275
|
+
errors_on_separate_row)
|
276
|
+
top_errors = non_field_errors
|
277
|
+
output, hidden_fields = [], []
|
278
|
+
|
279
|
+
add_fields_output(output, hidden_fields, normal_row, error_row,
|
280
|
+
help_text_html, errors_on_separate_row, top_errors)
|
281
|
+
output = [error_row % top_errors] + output unless top_errors.empty?
|
282
|
+
add_hidden_fields_output(output, hidden_fields, row_ender)
|
283
|
+
|
284
|
+
mark_safe(output.join("\n"))
|
285
|
+
end
|
286
|
+
|
287
|
+
def add_fields_output(output, hidden_fields, normal_row, error_row,
|
288
|
+
help_text_html, errors_on_separate_row,
|
289
|
+
top_errors)
|
290
|
+
@fields.each do |name, field|
|
291
|
+
bf = BoundField.new(self, field, name)
|
292
|
+
bf_errors = @error_class.new(bf.errors.map {|e| conditional_escape(e)})
|
293
|
+
if bf.hidden?
|
294
|
+
top_errors += bf_errors.map do |e|
|
295
|
+
"(Hidden field #{name}) #{e.to_s}"
|
296
|
+
end unless bf_errors.empty?
|
297
|
+
hidden_fields << bf.to_s
|
298
|
+
else
|
299
|
+
output << error_row % bf_errors if
|
300
|
+
errors_on_separate_row && !bf_errors.empty?
|
301
|
+
|
302
|
+
label = ''
|
303
|
+
unless bf.label.nil? || bf.label.empty?
|
304
|
+
label = conditional_escape(bf.label)
|
305
|
+
label += @label_suffix if @label_suffix && label[-1,1] !~ /[:?.!]/
|
306
|
+
label = bf.label_tag(label)
|
307
|
+
end
|
308
|
+
|
309
|
+
help_text = field.help_text.empty? ? '' : help_text_html % field.help_text
|
310
|
+
vars = {
|
311
|
+
:errors => bf_errors, :label => label,
|
312
|
+
:field => bf, :help_text => help_text
|
313
|
+
}
|
314
|
+
output << format_string(normal_row, vars)
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def add_hidden_fields_output(output, hidden_fields, row_ender)
|
320
|
+
unless hidden_fields.empty?
|
321
|
+
str_hidden = hidden_fields.join('')
|
322
|
+
|
323
|
+
unless output.empty?
|
324
|
+
last_row = output[-1]
|
325
|
+
if last_row !~ /#{row_ender}$/
|
326
|
+
vars = {
|
327
|
+
:errors => '', :label => '', :field => '', :help_text => ''
|
328
|
+
}
|
329
|
+
last_row = format_string(normal_row, vars)
|
330
|
+
output << last_row
|
331
|
+
end
|
332
|
+
output[-1] = last_row[0...-row_ender.length] + str_hidden + row_ender
|
333
|
+
else
|
334
|
+
output << str_hidden
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
def raw_value(fieldname)
|
340
|
+
field = @fields.fetch(fieldname)
|
341
|
+
prefix = add_prefix(fieldname)
|
342
|
+
field.widget.value_from_datahash(@data, @files, prefix)
|
343
|
+
end
|
344
|
+
|
345
|
+
end
|
346
|
+
end; end
|
@@ -0,0 +1,256 @@
|
|
1
|
+
require 'bureaucrat/utils'
|
2
|
+
require 'bureaucrat/validation'
|
3
|
+
require 'bureaucrat/widgets'
|
4
|
+
require 'bureaucrat/forms'
|
5
|
+
|
6
|
+
# TODO: needs more testing
|
7
|
+
module Bureaucrat
|
8
|
+
module Formsets
|
9
|
+
TOTAL_FORM_COUNT = :'TOTAL_FORMS'
|
10
|
+
INITIAL_FORM_COUNT = :'INITIAL_FORMS'
|
11
|
+
ORDERING_FIELD_NAME = :'ORDER'
|
12
|
+
DELETION_FIELD_NAME = :'DELETE'
|
13
|
+
|
14
|
+
class ManagementForm < Forms::Form
|
15
|
+
include Fields
|
16
|
+
include Widgets
|
17
|
+
|
18
|
+
field TOTAL_FORM_COUNT, IntegerField.new(:widget => HiddenInput)
|
19
|
+
field INITIAL_FORM_COUNT, IntegerField.new(:widget => HiddenInput)
|
20
|
+
end
|
21
|
+
|
22
|
+
class BaseFormSet
|
23
|
+
include Utils
|
24
|
+
include Validation
|
25
|
+
include Fields
|
26
|
+
include Forms
|
27
|
+
|
28
|
+
def self.default_prefix
|
29
|
+
'form'
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_accessor :form, :extra, :can_order, :can_delete, :max_num
|
33
|
+
|
34
|
+
def initialize(data=nil, options={})
|
35
|
+
@is_bound = !data.nil?
|
36
|
+
@prefix = options.fetch(:prefix, self.class.default_prefix)
|
37
|
+
@auto_id = options.fetch(:auto_id, 'id_%s')
|
38
|
+
@data = data
|
39
|
+
@files = options[:files]
|
40
|
+
@initial = options[:initial]
|
41
|
+
@error_class = options.fetch(:error_class, ErrorList)
|
42
|
+
@errors = nil
|
43
|
+
@non_form_errors = nil
|
44
|
+
|
45
|
+
construct_forms
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
as_table
|
50
|
+
end
|
51
|
+
|
52
|
+
def management_form
|
53
|
+
if @data || @files
|
54
|
+
form = ManagementForm.new(@data, :auto_id => @auto_id,
|
55
|
+
:prefix => @prefix)
|
56
|
+
raise ValidationError.new('ManagementForm data is missing or has been tampered with') unless
|
57
|
+
form.valid?
|
58
|
+
else
|
59
|
+
form = ManagementForm.new(nil, :auto_id => @auto_id,
|
60
|
+
:prefix => @prefix,
|
61
|
+
:initial => {
|
62
|
+
TOTAL_FORM_COUNT => total_form_count,
|
63
|
+
INITIAL_FORM_COUNT => initial_form_count
|
64
|
+
})
|
65
|
+
end
|
66
|
+
form
|
67
|
+
end
|
68
|
+
|
69
|
+
def total_form_count
|
70
|
+
if @data || @files
|
71
|
+
management_form.cleaned_data[TOTAL_FORM_COUNT]
|
72
|
+
else
|
73
|
+
n = initial_form_count + self.extra
|
74
|
+
(n > self.max_num && self.max_num > 0) ? self.max_num : n
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def initial_form_count
|
79
|
+
if @data || @files
|
80
|
+
management_form.cleaned_data[INITIAL_FORM_COUNT]
|
81
|
+
else
|
82
|
+
n = @initial ? @initial.length : 0
|
83
|
+
(n > self.max_num && self.max_num > 0) ? self.max_num : n
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def construct_forms
|
88
|
+
@forms = (0...total_form_count).map { |i| construct_form(i) }
|
89
|
+
end
|
90
|
+
|
91
|
+
def construct_form(i, options={})
|
92
|
+
defaults = {:auto_id => @auto_id, :prefix => add_prefix(i)}
|
93
|
+
defaults[:files] = @files if @files
|
94
|
+
defaults[:initial] = @initial[i] if @initial && @initial[i]
|
95
|
+
|
96
|
+
# Allow extra forms to be empty.
|
97
|
+
defaults[:empty_permitted] = true if i >= initial_form_count
|
98
|
+
defaults.merge!(options)
|
99
|
+
form = self.form.new(@data, defaults)
|
100
|
+
add_fields(form, i)
|
101
|
+
form
|
102
|
+
end
|
103
|
+
|
104
|
+
def initial_forms
|
105
|
+
@forms[0, initial_form_count]
|
106
|
+
end
|
107
|
+
|
108
|
+
def extra_forms
|
109
|
+
n = initial_form_count
|
110
|
+
@forms[n, @forms.length - n]
|
111
|
+
end
|
112
|
+
|
113
|
+
# Maybe this should just go away?
|
114
|
+
def cleaned_data
|
115
|
+
unless valid?
|
116
|
+
raise NoMethodError.new("'#{self.class.name}' object has no method 'cleaned_data'")
|
117
|
+
end
|
118
|
+
@forms.collect(&:cleaned_data)
|
119
|
+
end
|
120
|
+
|
121
|
+
def deleted_forms
|
122
|
+
unless valid? && self.can_delete
|
123
|
+
raise NoMethodError.new("'#{self.class.name}' object has no method 'deleted_forms'")
|
124
|
+
end
|
125
|
+
|
126
|
+
if @deleted_form_indexes.nil?
|
127
|
+
@deleted_form_indexes = (0...total_form_count).select do |i|
|
128
|
+
form = @forms[i]
|
129
|
+
(i < initial_form_count || form.changed?) && form.cleaned_data[DELETION_FIELD_NAME]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
@deleted_form_indexes.map {|i| @forms[i]}
|
133
|
+
end
|
134
|
+
|
135
|
+
def ordered_forms
|
136
|
+
unless valid? && self.can_order
|
137
|
+
raise NoMethodError.new("'#{self.class.name}' object has no method 'ordered_forms'")
|
138
|
+
end
|
139
|
+
|
140
|
+
if @ordering.nil?
|
141
|
+
@ordering = (0...total_form_count).map do |i|
|
142
|
+
form = @forms[i]
|
143
|
+
next if i >= initial_form_count && !form.changed?
|
144
|
+
next if self.can_delete && form.cleaned_data[DELETION_FIELD_NAME]
|
145
|
+
[i, form.cleaned_data[ORDERING_FIELD_NAME]]
|
146
|
+
end.compact
|
147
|
+
@ordering.sort! do |a, b|
|
148
|
+
if x[1].nil? then 1
|
149
|
+
elsif y[1].nil? then -1
|
150
|
+
else x[1] - y[1]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
@ordering.map {|i| @forms[i.first]}
|
156
|
+
end
|
157
|
+
|
158
|
+
def non_form_errors
|
159
|
+
@non_form_errors || @error_class.new
|
160
|
+
end
|
161
|
+
|
162
|
+
def errors
|
163
|
+
full_clean if @errors.nil?
|
164
|
+
@errors
|
165
|
+
end
|
166
|
+
|
167
|
+
def valid?
|
168
|
+
return false unless @is_bound
|
169
|
+
forms_valid = true
|
170
|
+
(0...total_form_count).each do |i|
|
171
|
+
form = @forms[i]
|
172
|
+
if self.can_delete
|
173
|
+
field = form.fields[DELETION_FIELD_NAME]
|
174
|
+
raw_value = form.send(:raw_value, DELETION_FIELD_NAME)
|
175
|
+
should_delete = field.clean(raw_value)
|
176
|
+
next if should_delete
|
177
|
+
end
|
178
|
+
forms_valid = false unless errors[i].empty?
|
179
|
+
end
|
180
|
+
forms_valid && non_form_errors.empty?
|
181
|
+
end
|
182
|
+
|
183
|
+
def full_clean
|
184
|
+
if @is_bound
|
185
|
+
@errors = @forms.collect(&:errors)
|
186
|
+
|
187
|
+
begin
|
188
|
+
self.clean
|
189
|
+
rescue ValidationError => e
|
190
|
+
@non_form_errors = e.messages
|
191
|
+
end
|
192
|
+
else
|
193
|
+
@errors = []
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def clean
|
198
|
+
end
|
199
|
+
|
200
|
+
def add_fields(form, index)
|
201
|
+
if self.can_order
|
202
|
+
attrs = {:label => 'Order', :required => false}
|
203
|
+
attrs[:initial] = index + 1 if index < initial_form_count
|
204
|
+
form.fields[ORDERING_FIELD_NAME] = IntegerField.new(attrs)
|
205
|
+
end
|
206
|
+
if self.can_delete
|
207
|
+
field = BooleanField.new(:label => 'Delete', :required => false)
|
208
|
+
form.fields[DELETION_FIELD_NAME] = field
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def add_prefix(index)
|
213
|
+
'%s-%s' % [@prefix, index]
|
214
|
+
end
|
215
|
+
|
216
|
+
def multipart?
|
217
|
+
@forms && @forms.first.multipart?
|
218
|
+
end
|
219
|
+
|
220
|
+
def media
|
221
|
+
@forms ? @forms.first.media : Media.new
|
222
|
+
end
|
223
|
+
|
224
|
+
def as_table
|
225
|
+
forms = @forms.map(&:as_table).join(' ')
|
226
|
+
mark_safe([management_form.to_s, forms].join("\n"))
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
module_function
|
231
|
+
|
232
|
+
def make_formset_class(form, options={})
|
233
|
+
formset = options.fetch(:formset, BaseFormSet)
|
234
|
+
attrs = {
|
235
|
+
:form => form,
|
236
|
+
:extra => options.fetch(:extra, 1),
|
237
|
+
:can_order => options.fetch(:can_order, false),
|
238
|
+
:can_delete => options.fetch(:can_delete, false),
|
239
|
+
:max_num => options.fetch(:max_num, 0)
|
240
|
+
}
|
241
|
+
Class.new(formset) do
|
242
|
+
attrs.each do |name, value|
|
243
|
+
define_method(name) { value }
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def all_valid?(formsets)
|
249
|
+
valid = true
|
250
|
+
formsets.each do |formset|
|
251
|
+
valid = false unless formset.valid?
|
252
|
+
end
|
253
|
+
valid
|
254
|
+
end
|
255
|
+
|
256
|
+
end; end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'bureaucrat/fields'
|
2
|
+
|
3
|
+
module Bureaucrat
|
4
|
+
module Quickfields
|
5
|
+
include Fields
|
6
|
+
|
7
|
+
def hide(name)
|
8
|
+
base_fields[name].widget = Widgets::HiddenInput.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def string(name, options={})
|
12
|
+
field name, CharField.new(options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def password(name, options={})
|
16
|
+
field name, CharField.new(options.merge(:widget => Widgets::PasswordInput.new))
|
17
|
+
end
|
18
|
+
|
19
|
+
def integer(name, options={})
|
20
|
+
field name, IntegerField.new(options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def decimal(name, options={})
|
24
|
+
field name, BigDecimalField.new(options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def regex(name, regexp, options={})
|
28
|
+
field name, RegexField.new(regexp, options)
|
29
|
+
end
|
30
|
+
|
31
|
+
def email(name, options={})
|
32
|
+
field name, EmailField.new(options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def file(name, options={})
|
36
|
+
field name, FileField.new(options)
|
37
|
+
end
|
38
|
+
|
39
|
+
def boolean(name, options={})
|
40
|
+
field name, BooleanField.new(options)
|
41
|
+
end
|
42
|
+
|
43
|
+
def null_boolean(name, options={})
|
44
|
+
field name, NullBooleanField.new(options)
|
45
|
+
end
|
46
|
+
|
47
|
+
def choice(name, choices=[], options={})
|
48
|
+
field name, ChoiceField.new(choices, options)
|
49
|
+
end
|
50
|
+
|
51
|
+
def typed_choice(name, choices=[], options={})
|
52
|
+
field name, TypedChoiceField.new(choices, options)
|
53
|
+
end
|
54
|
+
|
55
|
+
def multiple_choice(name, choices=[], options={})
|
56
|
+
field name, MultipleChoiceField.new(choices, options)
|
57
|
+
end
|
58
|
+
|
59
|
+
end; end
|