bureaucrat 0.0.3 → 0.10.0
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/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
|