bureaucrat 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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