bureaucrat 0.0.3 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +120 -105
- data/Rakefile +9 -0
- data/bureaucrat.gemspec +32 -0
- data/lib/bureaucrat.rb +26 -8
- data/lib/bureaucrat/fields.rb +572 -336
- data/lib/bureaucrat/forms.rb +296 -257
- data/lib/bureaucrat/formsets.rb +235 -194
- data/lib/bureaucrat/quickfields.rb +90 -62
- data/lib/bureaucrat/temporary_uploaded_file.rb +14 -0
- data/lib/bureaucrat/utils.rb +79 -62
- data/lib/bureaucrat/validators.rb +163 -0
- data/lib/bureaucrat/widgets.rb +459 -303
- data/test/fields_test.rb +519 -380
- data/test/forms_test.rb +78 -58
- data/test/formsets_test.rb +48 -22
- data/test/test_helper.rb +20 -12
- data/test/widgets_test.rb +224 -204
- metadata +27 -36
- data/lib/bureaucrat/validation.rb +0 -130
- data/lib/bureaucrat/validation_old.rb +0 -148
- data/lib/bureaucrat/wizard.rb +0 -220
data/lib/bureaucrat/formsets.rb
CHANGED
@@ -1,256 +1,297 @@
|
|
1
|
-
require 'bureaucrat/utils'
|
2
|
-
require 'bureaucrat/validation'
|
3
|
-
require 'bureaucrat/widgets'
|
4
|
-
require 'bureaucrat/forms'
|
5
|
-
|
6
|
-
# TODO: needs more testing
|
7
1
|
module Bureaucrat
|
8
|
-
module Formsets
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
include Utils
|
24
|
-
include Validation
|
25
|
-
include Fields
|
26
|
-
include Forms
|
27
|
-
|
28
|
-
def self.default_prefix
|
29
|
-
'form'
|
2
|
+
module Formsets
|
3
|
+
TOTAL_FORM_COUNT = :'TOTAL_FORMS'
|
4
|
+
INITIAL_FORM_COUNT = :'INITIAL_FORMS'
|
5
|
+
MAX_NUM_FORM_COUNT = :'MAX_NUM_FORMS'
|
6
|
+
ORDERING_FIELD_NAME = :'ORDER'
|
7
|
+
DELETION_FIELD_NAME = :'DELETE'
|
8
|
+
|
9
|
+
class ManagementForm < Forms::Form
|
10
|
+
include Fields
|
11
|
+
include Widgets
|
12
|
+
|
13
|
+
field TOTAL_FORM_COUNT, IntegerField.new(widget: HiddenInput)
|
14
|
+
field INITIAL_FORM_COUNT, IntegerField.new(widget: HiddenInput)
|
15
|
+
field MAX_NUM_FORM_COUNT, IntegerField.new(widget: HiddenInput,
|
16
|
+
required: false)
|
30
17
|
end
|
31
18
|
|
32
|
-
|
19
|
+
class BaseFormSet
|
20
|
+
include Utils
|
21
|
+
include Fields
|
22
|
+
include Forms
|
33
23
|
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
24
|
+
def self.default_prefix
|
25
|
+
'form'
|
26
|
+
end
|
44
27
|
|
45
|
-
|
46
|
-
|
28
|
+
# All forms in this formset
|
29
|
+
attr_accessor :forms
|
30
|
+
# Form class used for this formset forms
|
31
|
+
attr_accessor :form
|
32
|
+
# Amount of extra (empty) forms on this formset
|
33
|
+
attr_accessor :extra
|
34
|
+
# Boolean that determines is this formset supports reordering
|
35
|
+
attr_accessor :can_order
|
36
|
+
# Boolean that determines is this formset supports deletion
|
37
|
+
attr_accessor :can_delete
|
38
|
+
# Maximum count of forms allowed on this formset
|
39
|
+
attr_accessor :max_num
|
40
|
+
# Prefix that will be used when rendering form fields
|
41
|
+
attr_accessor :prefix
|
42
|
+
# Error object class for this formset
|
43
|
+
attr_accessor :error_class
|
44
|
+
# Initial data for the forms in this formset. Array of Hashes of {field_name => initial_value}
|
45
|
+
attr_accessor :initial
|
46
|
+
# Data associated to this formset. Hash of {prefixed_field_name => value}
|
47
|
+
attr_accessor :data
|
48
|
+
# Format string for field id generator
|
49
|
+
attr_accessor :auto_id
|
50
|
+
|
51
|
+
def initialize(data=nil, options={})
|
52
|
+
set_defaults
|
53
|
+
@is_bound = !data.nil?
|
54
|
+
@prefix = options.fetch(:prefix, self.class.default_prefix)
|
55
|
+
@auto_id = options.fetch(:auto_id, 'id_%s')
|
56
|
+
@data = data || {}
|
57
|
+
@initial = options[:initial]
|
58
|
+
@error_class = options.fetch(:error_class, ErrorList)
|
59
|
+
@errors = nil
|
60
|
+
@non_form_errors = nil
|
61
|
+
|
62
|
+
construct_forms
|
63
|
+
end
|
47
64
|
|
48
|
-
|
49
|
-
|
50
|
-
|
65
|
+
def each(&block)
|
66
|
+
forms.each(&block)
|
67
|
+
end
|
51
68
|
|
52
|
-
|
53
|
-
|
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
|
-
})
|
69
|
+
def [](index)
|
70
|
+
forms[index]
|
65
71
|
end
|
66
|
-
form
|
67
|
-
end
|
68
72
|
|
69
|
-
|
70
|
-
|
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
|
73
|
+
def length
|
74
|
+
forms.length
|
75
75
|
end
|
76
|
-
end
|
77
76
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
77
|
+
def management_form
|
78
|
+
if @is_bound
|
79
|
+
form = ManagementForm.new(@data, auto_id: @auto_id,
|
80
|
+
prefix: @prefix)
|
81
|
+
unless form.valid?
|
82
|
+
msg = 'ManagementForm data is missing or has been tampered with'
|
83
|
+
raise ValidationError.new(msg)
|
84
|
+
end
|
85
|
+
else
|
86
|
+
form = ManagementForm.new(nil, auto_id: @auto_id,
|
87
|
+
prefix: @prefix,
|
88
|
+
initial: {
|
89
|
+
TOTAL_FORM_COUNT => total_form_count,
|
90
|
+
INITIAL_FORM_COUNT => initial_form_count,
|
91
|
+
MAX_NUM_FORM_COUNT => self.max_num
|
92
|
+
})
|
93
|
+
end
|
94
|
+
form
|
84
95
|
end
|
85
|
-
end
|
86
96
|
|
87
|
-
|
88
|
-
|
89
|
-
|
97
|
+
def total_form_count
|
98
|
+
if @is_bound
|
99
|
+
management_form.cleaned_data[TOTAL_FORM_COUNT]
|
100
|
+
else
|
101
|
+
initial_forms = initial_form_count
|
102
|
+
total_forms = initial_form_count + self.extra
|
103
|
+
|
104
|
+
# Allow all existing related objects/inlines to be displayed,
|
105
|
+
# but don't allow extra beyond max_num.
|
106
|
+
if self.max_num > 0 && initial_forms > self.max_num
|
107
|
+
initial_forms
|
108
|
+
elsif self.max_num > 0 && total_forms > self.max_num
|
109
|
+
max_num
|
110
|
+
else
|
111
|
+
total_forms
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
90
115
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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
|
116
|
+
def initial_form_count
|
117
|
+
if @is_bound
|
118
|
+
management_form.cleaned_data[INITIAL_FORM_COUNT]
|
119
|
+
else
|
120
|
+
n = @initial ? @initial.length : 0
|
103
121
|
|
104
|
-
|
105
|
-
|
106
|
-
|
122
|
+
(self.max_num > 0 && n > self.max_num) ? self.max_num : n
|
123
|
+
end
|
124
|
+
end
|
107
125
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
end
|
126
|
+
def construct_forms
|
127
|
+
@forms = (0...total_form_count).map { |i| construct_form(i) }
|
128
|
+
end
|
112
129
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
130
|
+
def construct_form(i, options={})
|
131
|
+
defaults = {auto_id: @auto_id, prefix: add_prefix(i)}
|
132
|
+
defaults[:initial] = @initial[i] if @initial && @initial[i]
|
133
|
+
|
134
|
+
# Allow extra forms to be empty.
|
135
|
+
defaults[:empty_permitted] = true if i >= initial_form_count
|
136
|
+
defaults.merge!(options)
|
137
|
+
form_data = @is_bound ? @data : nil
|
138
|
+
form = self.form.new(form_data, defaults)
|
139
|
+
add_fields(form, i)
|
140
|
+
form
|
141
|
+
end
|
142
|
+
|
143
|
+
def initial_forms
|
144
|
+
@forms[0, initial_form_count]
|
117
145
|
end
|
118
|
-
@forms.collect(&:cleaned_data)
|
119
|
-
end
|
120
146
|
|
121
|
-
|
122
|
-
|
123
|
-
|
147
|
+
def extra_forms
|
148
|
+
n = initial_form_count
|
149
|
+
@forms[n, @forms.length - n]
|
124
150
|
end
|
125
151
|
|
126
|
-
|
127
|
-
|
152
|
+
# Maybe this should just go away?
|
153
|
+
def cleaned_data
|
154
|
+
unless valid?
|
155
|
+
raise NoMethodError.new("'#{self.class.name}' object has no method 'cleaned_data'")
|
156
|
+
end
|
157
|
+
@forms.collect(&:cleaned_data)
|
158
|
+
end
|
159
|
+
|
160
|
+
def deleted_forms
|
161
|
+
unless valid? && self.can_delete
|
162
|
+
raise NoMethodError.new("'#{self.class.name}' object has no method 'deleted_forms'")
|
163
|
+
end
|
164
|
+
|
165
|
+
if @deleted_form_indexes.nil?
|
166
|
+
@deleted_form_indexes = (0...total_form_count).select do |i|
|
128
167
|
form = @forms[i]
|
129
|
-
|
168
|
+
|
169
|
+
if i >= initial_form_count && !form.changed?
|
170
|
+
false
|
171
|
+
else
|
172
|
+
should_delete_form?(form)
|
173
|
+
end
|
130
174
|
end
|
131
|
-
|
132
|
-
@deleted_form_indexes.map {|i| @forms[i]}
|
133
|
-
end
|
175
|
+
end
|
134
176
|
|
135
|
-
|
136
|
-
unless valid? && self.can_order
|
137
|
-
raise NoMethodError.new("'#{self.class.name}' object has no method 'ordered_forms'")
|
177
|
+
@deleted_form_indexes.map {|i| @forms[i]}
|
138
178
|
end
|
139
179
|
|
140
|
-
|
141
|
-
|
180
|
+
def ordered_forms
|
181
|
+
unless valid? && self.can_order
|
182
|
+
raise NoMethodError.new("'#{self.class.name}' object has no method 'ordered_forms'")
|
183
|
+
end
|
184
|
+
|
185
|
+
if @ordering.nil?
|
186
|
+
@ordering = (0...total_form_count).map do |i|
|
142
187
|
form = @forms[i]
|
143
188
|
next if i >= initial_form_count && !form.changed?
|
144
|
-
next if self.can_delete && form
|
189
|
+
next if self.can_delete && should_delete_form?(form)
|
145
190
|
[i, form.cleaned_data[ORDERING_FIELD_NAME]]
|
146
191
|
end.compact
|
147
|
-
|
192
|
+
@ordering.sort! do |a, b|
|
148
193
|
if x[1].nil? then 1
|
149
194
|
elsif y[1].nil? then -1
|
150
195
|
else x[1] - y[1]
|
151
196
|
end
|
152
197
|
end
|
198
|
+
end
|
199
|
+
|
200
|
+
@ordering.map {|i| @forms[i.first]}
|
153
201
|
end
|
154
202
|
|
155
|
-
|
156
|
-
|
203
|
+
def non_form_errors
|
204
|
+
@non_form_errors || @error_class.new
|
205
|
+
end
|
157
206
|
|
158
|
-
|
159
|
-
|
160
|
-
|
207
|
+
def errors
|
208
|
+
full_clean if @errors.nil?
|
209
|
+
@errors
|
210
|
+
end
|
161
211
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
212
|
+
def should_delete_form?(form)
|
213
|
+
field = form.fields[DELETION_FIELD_NAME]
|
214
|
+
raw_value = form.send(:raw_value, DELETION_FIELD_NAME)
|
215
|
+
field.clean(raw_value)
|
216
|
+
end
|
217
|
+
|
218
|
+
def valid?
|
219
|
+
return false unless @is_bound
|
166
220
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
(0...total_form_count).each do |i|
|
221
|
+
forms_valid = true
|
222
|
+
|
223
|
+
(0...total_form_count).each do |i|
|
171
224
|
form = @forms[i]
|
172
|
-
if self.can_delete
|
173
|
-
|
174
|
-
raw_value = form.send(:raw_value, DELETION_FIELD_NAME)
|
175
|
-
should_delete = field.clean(raw_value)
|
176
|
-
next if should_delete
|
177
|
-
end
|
225
|
+
next if self.can_delete && should_delete_form?(form)
|
226
|
+
|
178
227
|
forms_valid = false unless errors[i].empty?
|
179
228
|
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
229
|
|
187
|
-
|
188
|
-
self.clean
|
189
|
-
rescue ValidationError => e
|
190
|
-
@non_form_errors = e.messages
|
191
|
-
end
|
192
|
-
else
|
193
|
-
@errors = []
|
230
|
+
forms_valid && non_form_errors.empty?
|
194
231
|
end
|
195
|
-
end
|
196
232
|
|
197
|
-
|
198
|
-
|
233
|
+
def full_clean
|
234
|
+
if @is_bound
|
235
|
+
@errors = @forms.collect(&:errors)
|
199
236
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
form.fields[DELETION_FIELD_NAME] = field
|
237
|
+
begin
|
238
|
+
self.clean
|
239
|
+
rescue ValidationError => e
|
240
|
+
@non_form_errors = @error_class.new(e.messages)
|
241
|
+
end
|
242
|
+
else
|
243
|
+
@errors = []
|
244
|
+
end
|
209
245
|
end
|
210
|
-
end
|
211
246
|
|
212
|
-
|
213
|
-
|
214
|
-
end
|
247
|
+
def clean
|
248
|
+
end
|
215
249
|
|
216
|
-
|
217
|
-
|
218
|
-
|
250
|
+
def add_fields(form, index)
|
251
|
+
if can_order
|
252
|
+
attrs = {label: 'Order', required: false}
|
253
|
+
attrs[:initial] = index + 1 if index && index < initial_form_count
|
254
|
+
form.fields[ORDERING_FIELD_NAME] = IntegerField.new(attrs)
|
255
|
+
end
|
256
|
+
if can_delete
|
257
|
+
field = BooleanField.new(label: 'Delete', required: false)
|
258
|
+
form.fields[DELETION_FIELD_NAME] = field
|
259
|
+
end
|
260
|
+
end
|
219
261
|
|
220
|
-
|
221
|
-
|
222
|
-
|
262
|
+
def add_prefix(index)
|
263
|
+
'%s-%s' % [@prefix, index]
|
264
|
+
end
|
223
265
|
|
224
|
-
|
225
|
-
|
226
|
-
|
266
|
+
def multipart?
|
267
|
+
@forms && @forms.first.multipart?
|
268
|
+
end
|
227
269
|
end
|
228
|
-
end
|
229
270
|
|
230
271
|
module_function
|
231
272
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
attrs.each do |name, value|
|
243
|
-
define_method(name) { value }
|
273
|
+
def make_formset_class(form, options={})
|
274
|
+
formset = options.fetch(:formset, BaseFormSet)
|
275
|
+
|
276
|
+
Class.new(formset) do
|
277
|
+
define_method :set_defaults do
|
278
|
+
@form = form
|
279
|
+
@extra = options.fetch(:extra, 1)
|
280
|
+
@can_order = options.fetch(:can_order, false)
|
281
|
+
@can_delete = options.fetch(:can_delete, false)
|
282
|
+
@max_num = options.fetch(:max_num, 0)
|
244
283
|
end
|
284
|
+
private :set_defaults
|
245
285
|
end
|
246
|
-
|
286
|
+
end
|
247
287
|
|
248
|
-
|
249
|
-
|
250
|
-
|
288
|
+
def all_valid?(formsets)
|
289
|
+
valid = true
|
290
|
+
formsets.each do |formset|
|
251
291
|
valid = false unless formset.valid?
|
252
292
|
end
|
253
|
-
|
254
|
-
|
293
|
+
valid
|
294
|
+
end
|
255
295
|
|
256
|
-
end
|
296
|
+
end
|
297
|
+
end
|