Code

Ticket #2926: django_forms_attributes.diff

File django_forms_attributes.diff, 24.6 KB (added by Winston Lee <lee.winston@…>, 8 years ago)

Patch for 0.95 to include custom attributes. Warning, not properly tested so you'll probably need to submit further patches.

Line 
1Index: __init__.py
2===================================================================
3--- __init__.py (revision 3874)
4+++ __init__.py (working copy)
5@@ -280,6 +280,8 @@
6             this field must pass in order to be added or changed.
7         is_required
8             A Boolean. Is it a required field?
9+        attribute_dict
10+            A dictionary of property/value attributes for the form field.
11     Subclasses should also implement a render(data) method, which is responsible
12     for rending the form field in XHTML.
13     """
14@@ -360,6 +362,12 @@
15     def get_id(self):
16         "Returns the HTML 'id' attribute for this form field."
17         return FORM_FIELD_ID_PREFIX + self.field_name
18+       
19+    def get_attribute_string(self):
20+        attributes = ''
21+        if self.attribute_dict:
22+            attributes = " ".join(["%s=\"%s\"" % (p, v) for p, v in self.attribute_dict.items()])
23+        return attributes
24 
25 ####################
26 # GENERIC WIDGETS  #
27@@ -367,12 +375,14 @@
28 
29 class TextField(FormField):
30     input_type = "text"
31-    def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None, member_name=None):
32+    def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None, member_name=None, attribute_dict=None):
33         if validator_list is None: validator_list = []
34+        if attribute_dict is None: attribute_dict = {}
35         self.field_name = field_name
36         self.length, self.maxlength = length, maxlength
37         self.is_required = is_required
38         self.validator_list = [self.isValidLength, self.hasNoNewlines] + validator_list
39+        self.attribute_dict = attribute_dict
40         if member_name != None:
41             self.member_name = member_name
42 
43@@ -393,9 +403,9 @@
44             maxlength = 'maxlength="%s" ' % self.maxlength
45         if isinstance(data, unicode):
46             data = data.encode(settings.DEFAULT_CHARSET)
47-        return '<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
48+        return '<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s %s />' % \
49             (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
50-            self.field_name, self.length, escape(data), maxlength)
51+            self.field_name, self.length, escape(data), maxlength, self.get_attribute_string())
52 
53     def html2python(data):
54         return data
55@@ -405,11 +415,13 @@
56     input_type = "password"
57 
58 class LargeTextField(TextField):
59-    def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=None, maxlength=None):
60+    def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=None, maxlength=None, attribute_dict=None):
61         if validator_list is None: validator_list = []
62+        if attribute_dict is None: attribute_dict = {}
63         self.field_name = field_name
64         self.rows, self.cols, self.is_required = rows, cols, is_required
65         self.validator_list = validator_list[:]
66+        self.attribute_dict = attribute_dict
67         if maxlength:
68             self.validator_list.append(self.isValidLength)
69             self.maxlength = maxlength
70@@ -419,35 +431,39 @@
71             data = ''
72         if isinstance(data, unicode):
73             data = data.encode(settings.DEFAULT_CHARSET)
74-        return '<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
75+        return '<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s" %s>%s</textarea>' % \
76             (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
77-            self.field_name, self.rows, self.cols, escape(data))
78+            self.field_name, self.rows, self.cols, self.get_attribute_string(), escape(data))
79 
80 class HiddenField(FormField):
81-    def __init__(self, field_name, is_required=False, validator_list=None):
82+    def __init__(self, field_name, is_required=False, validator_list=None, attribute_dict=None):
83         if validator_list is None: validator_list = []
84+        if attribute_dict is None: attribute_dict = {}
85         self.field_name, self.is_required = field_name, is_required
86         self.validator_list = validator_list[:]
87+        self.attribute_dict = attribute_dict
88 
89     def render(self, data):
90-        return '<input type="hidden" id="%s" name="%s" value="%s" />' % \
91-            (self.get_id(), self.field_name, escape(data))
92+        return '<input type="hidden" id="%s" name="%s" value="%s" %s />' % \
93+            (self.get_id(), self.field_name, escape(data), self.get_attribute_string())
94 
95 class CheckboxField(FormField):
96-    def __init__(self, field_name, checked_by_default=False, validator_list=None):
97+    def __init__(self, field_name, checked_by_default=False, validator_list=None, attribute_dict=None):
98         if validator_list is None: validator_list = []
99+        if attribute_dict is None: attribute_dict = {}
100         self.field_name = field_name
101         self.checked_by_default = checked_by_default
102         self.is_required = False # because the validator looks for these
103         self.validator_list = validator_list[:]
104+        self.attribute_dict = attribute_dict
105 
106     def render(self, data):
107         checked_html = ''
108         if data or (data is '' and self.checked_by_default):
109             checked_html = ' checked="checked"'
110-        return '<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
111+        return '<input type="checkbox" id="%s" class="v%s" name="%s"%s %s />' % \
112             (self.get_id(), self.__class__.__name__,
113-            self.field_name, checked_html)
114+            self.field_name, checked_html, self.get_attribute_string())
115 
116     def html2python(data):
117         "Convert value from browser ('on' or '') to a Python boolean"
118@@ -457,20 +473,22 @@
119     html2python = staticmethod(html2python)
120 
121 class SelectField(FormField):
122-    def __init__(self, field_name, choices=None, size=1, is_required=False, validator_list=None, member_name=None):
123+    def __init__(self, field_name, choices=None, size=1, is_required=False, validator_list=None, member_name=None, attribute_dict=None):
124         if validator_list is None: validator_list = []
125+        if attribute_dict is None: attribute_dict = {}
126         if choices is None: choices = []
127         self.field_name = field_name
128         # choices is a list of (value, human-readable key) tuples because order matters
129         self.choices, self.size, self.is_required = choices, size, is_required
130         self.validator_list = [self.isValidChoice] + validator_list
131+        self.attribute_dict = attribute_dict
132         if member_name != None:
133             self.member_name = member_name
134 
135     def render(self, data):
136-        output = ['<select id="%s" class="v%s%s" name="%s" size="%s">' % \
137+        output = ['<select id="%s" class="v%s%s" name="%s" size="%s" %s>' % \
138             (self.get_id(), self.__class__.__name__,
139-             self.is_required and ' required' or '', self.field_name, self.size)]
140+             self.is_required and ' required' or '', self.field_name, self.size, self.get_attribute_string())]
141         str_data = str(data) # normalize to string
142         for value, display_name in self.choices:
143             selected_html = ''
144@@ -495,14 +513,16 @@
145     html2python = staticmethod(html2python)
146 
147 class RadioSelectField(FormField):
148-    def __init__(self, field_name, choices=None, ul_class='', is_required=False, validator_list=None, member_name=None):
149+    def __init__(self, field_name, choices=None, ul_class='', is_required=False, validator_list=None, member_name=None, attribute_dict=None):
150         if validator_list is None: validator_list = []
151+        if attribute_dict is None: attribute_dict = {}
152         if choices is None: choices = []
153         self.field_name = field_name
154         # choices is a list of (value, human-readable key) tuples because order matters
155         self.choices, self.is_required = choices, is_required
156         self.validator_list = [self.isValidChoice] + validator_list
157         self.ul_class = ul_class
158+        self.attribute_dict = attribute_dict
159         if member_name != None:
160             self.member_name = member_name
161 
162@@ -546,8 +566,8 @@
163             datalist.append({
164                 'value': value,
165                 'name': display_name,
166-                'field': '<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
167-                    (self.get_id() + '_' + str(i), self.field_name, value, selected_html),
168+                'field': '<input type="radio" id="%s" name="%s" value="%s"%s %s/>' % \
169+                    (self.get_id() + '_' + str(i), self.field_name, value, selected_html, self.get_attribute_string()),
170                 'label': '<label for="%s">%s</label>' % \
171                     (self.get_id() + '_' + str(i), display_name),
172             })
173@@ -561,10 +581,10 @@
174 
175 class NullBooleanField(SelectField):
176     "This SelectField provides 'Yes', 'No' and 'Unknown', mapping results to True, False or None"
177-    def __init__(self, field_name, is_required=False, validator_list=None):
178+    def __init__(self, field_name, is_required=False, validator_list=None, attribute_dict=None):
179         if validator_list is None: validator_list = []
180         SelectField.__init__(self, field_name, choices=[('1', 'Unknown'), ('2', 'Yes'), ('3', 'No')],
181-            is_required=is_required, validator_list=validator_list)
182+            is_required=is_required, validator_list=validator_list, attribute_dict=attribute_dict)
183 
184     def render(self, data):
185         if data is None: data = '1'
186@@ -579,9 +599,9 @@
187 class SelectMultipleField(SelectField):
188     requires_data_list = True
189     def render(self, data):
190-        output = ['<select id="%s" class="v%s%s" name="%s" size="%s" multiple="multiple">' % \
191+        output = ['<select id="%s" class="v%s%s" name="%s" size="%s" multiple="multiple" %s>' % \
192             (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
193-            self.field_name, self.size)]
194+            self.field_name, self.size, self.get_attribute_string())]
195         str_data_list = map(str, data) # normalize to strings
196         for value, choice in self.choices:
197             selected_html = ''
198@@ -615,11 +635,11 @@
199     back into the single list that validators, renderers and save() expect.
200     """
201     requires_data_list = True
202-    def __init__(self, field_name, choices=None, ul_class='', validator_list=None):
203+    def __init__(self, field_name, choices=None, ul_class='', validator_list=None, attribute_dict=None):
204         if validator_list is None: validator_list = []
205         if choices is None: choices = []
206         self.ul_class = ul_class
207-        SelectMultipleField.__init__(self, field_name, choices, size=1, is_required=False, validator_list=validator_list)
208+        SelectMultipleField.__init__(self, field_name, choices, size=1, is_required=False, validator_list=validator_list, attribute_dict=attribute_dict)
209 
210     def prepare(self, new_data):
211         # new_data has "split" this field into several fields, so flatten it
212@@ -638,9 +658,9 @@
213             if str(value) in str_data_list:
214                 checked_html = ' checked="checked"'
215             field_name = '%s%s' % (self.field_name, value)
216-            output.append('<li><input type="checkbox" id="%s" class="v%s" name="%s"%s /> <label for="%s">%s</label></li>' % \
217-                (self.get_id() + value , self.__class__.__name__, field_name, checked_html,
218-                self.get_id() + value, choice))
219+            output.append('<li><input type="checkbox" id="%s" class="v%s" name="%s"%s %s /> <label for="%s">%s</label></li>' % \
220+                (self.get_id() + value , self.__class__.__name__, field_name, checked_html,
221+                self.get_attribute_string(), self.get_id() + value, choice))
222         output.append('</ul>')
223         return '\n'.join(output)
224 
225@@ -649,9 +669,11 @@
226 ####################
227 
228 class FileUploadField(FormField):
229-    def __init__(self, field_name, is_required=False, validator_list=None):
230+    def __init__(self, field_name, is_required=False, validator_list=None, attribute_dict=None):
231         if validator_list is None: validator_list = []
232+        if attribute_dict is None: attribute_dict = {}
233         self.field_name, self.is_required = field_name, is_required
234+        self.attribute_dict = attribute_dict
235         self.validator_list = [self.isNonEmptyFile] + validator_list
236 
237     def isNonEmptyFile(self, field_data, all_data):
238@@ -663,8 +685,8 @@
239             raise validators.CriticalValidationError, gettext("The submitted file is empty.")
240 
241     def render(self, data):
242-        return '<input type="file" id="%s" class="v%s" name="%s" />' % \
243-            (self.get_id(), self.__class__.__name__, self.field_name)
244+        return '<input type="file" id="%s" class="v%s" name="%s" %s />' % \
245+            (self.get_id(), self.__class__.__name__, self.field_name, self.get_attribute_string())
246 
247     def html2python(data):
248         if data is None:
249@@ -689,12 +711,12 @@
250 ####################
251 
252 class IntegerField(TextField):
253-    def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None, member_name=None):
254+    def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None, member_name=None, attribute_dict=None):
255         if validator_list is None: validator_list = []
256         validator_list = [self.isInteger] + validator_list
257         if member_name is not None:
258             self.member_name = member_name
259-        TextField.__init__(self, field_name, length, maxlength, is_required, validator_list)
260+        TextField.__init__(self, field_name, length, maxlength, is_required, validator_list, attribute_dict=attribute_dict)
261 
262     def isInteger(self, field_data, all_data):
263         try:
264@@ -709,41 +731,41 @@
265     html2python = staticmethod(html2python)
266 
267 class SmallIntegerField(IntegerField):
268-    def __init__(self, field_name, length=5, maxlength=5, is_required=False, validator_list=None):
269+    def __init__(self, field_name, length=5, maxlength=5, is_required=False, validator_list=None, attribute_dict=None):
270         if validator_list is None: validator_list = []
271         validator_list = [self.isSmallInteger] + validator_list
272-        IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
273+        IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list, attribute_dict=attribute_dict)
274 
275     def isSmallInteger(self, field_data, all_data):
276         if not -32768 <= int(field_data) <= 32767:
277             raise validators.CriticalValidationError, gettext("Enter a whole number between -32,768 and 32,767.")
278 
279 class PositiveIntegerField(IntegerField):
280-    def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None):
281+    def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None, attribute_dict=None):
282         if validator_list is None: validator_list = []
283         validator_list = [self.isPositive] + validator_list
284-        IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
285+        IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list, attribute_dict=attribute_dict)
286 
287     def isPositive(self, field_data, all_data):
288         if int(field_data) < 0:
289             raise validators.CriticalValidationError, gettext("Enter a positive number.")
290 
291 class PositiveSmallIntegerField(IntegerField):
292-    def __init__(self, field_name, length=5, maxlength=None, is_required=False, validator_list=None):
293+    def __init__(self, field_name, length=5, maxlength=None, is_required=False, validator_list=None, attribute_dict=None):
294         if validator_list is None: validator_list = []
295         validator_list = [self.isPositiveSmall] + validator_list
296-        IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
297+        IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list, attribute_dict=attribute_dict)
298 
299     def isPositiveSmall(self, field_data, all_data):
300         if not 0 <= int(field_data) <= 32767:
301             raise validators.CriticalValidationError, gettext("Enter a whole number between 0 and 32,767.")
302 
303 class FloatField(TextField):
304-    def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=None):
305+    def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=None, attribute_dict=None):
306         if validator_list is None: validator_list = []
307         self.max_digits, self.decimal_places = max_digits, decimal_places
308         validator_list = [self.isValidFloat] + validator_list
309-        TextField.__init__(self, field_name, max_digits+1, max_digits+1, is_required, validator_list)
310+        TextField.__init__(self, field_name, max_digits+1, max_digits+1, is_required, validator_list, attribute_dict=attribute_dict)
311 
312     def isValidFloat(self, field_data, all_data):
313         v = validators.IsValidFloat(self.max_digits, self.decimal_places)
314@@ -765,12 +787,14 @@
315 class DatetimeField(TextField):
316     """A FormField that automatically converts its data to a datetime.datetime object.
317     The data should be in the format YYYY-MM-DD HH:MM:SS."""
318-    def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None):
319+    def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None, attribute_dict=None):
320         if validator_list is None: validator_list = []
321+        if attribute_dict is None: attribute_dict = {}
322         self.field_name = field_name
323         self.length, self.maxlength = length, maxlength
324         self.is_required = is_required
325         self.validator_list = [validators.isValidANSIDatetime] + validator_list
326+        self.attribute_dict = attribute_dict
327 
328     def html2python(data):
329         "Converts the field into a datetime.datetime object"
330@@ -792,11 +816,11 @@
331 class DateField(TextField):
332     """A FormField that automatically converts its data to a datetime.date object.
333     The data should be in the format YYYY-MM-DD."""
334-    def __init__(self, field_name, is_required=False, validator_list=None):
335+    def __init__(self, field_name, is_required=False, validator_list=None, attribute_dict=None):
336         if validator_list is None: validator_list = []
337         validator_list = [self.isValidDate] + validator_list
338         TextField.__init__(self, field_name, length=10, maxlength=10,
339-            is_required=is_required, validator_list=validator_list)
340+            is_required=is_required, validator_list=validator_list, attribute_dict=attribute_dict)
341 
342     def isValidDate(self, field_data, all_data):
343         try:
344@@ -817,11 +841,11 @@
345 class TimeField(TextField):
346     """A FormField that automatically converts its data to a datetime.time object.
347     The data should be in the format HH:MM:SS or HH:MM:SS.mmmmmm."""
348-    def __init__(self, field_name, is_required=False, validator_list=None):
349+    def __init__(self, field_name, is_required=False, validator_list=None, attribute_dict=None):
350         if validator_list is None: validator_list = []
351         validator_list = [self.isValidTime] + validator_list
352         TextField.__init__(self, field_name, length=8, maxlength=8,
353-            is_required=is_required, validator_list=validator_list)
354+            is_required=is_required, validator_list=validator_list, attribute_dict=None)
355 
356     def isValidTime(self, field_data, all_data):
357         try:
358@@ -852,11 +876,11 @@
359 
360 class EmailField(TextField):
361     "A convenience FormField for validating e-mail addresses"
362-    def __init__(self, field_name, length=50, maxlength=75, is_required=False, validator_list=None):
363+    def __init__(self, field_name, length=50, maxlength=75, is_required=False, validator_list=None, attribute_dict=None):
364         if validator_list is None: validator_list = []
365         validator_list = [self.isValidEmail] + validator_list
366         TextField.__init__(self, field_name, length, maxlength=maxlength,
367-            is_required=is_required, validator_list=validator_list)
368+            is_required=is_required, validator_list=validator_list, attribute_dict=attribute_dict)
369 
370     def isValidEmail(self, field_data, all_data):
371         try:
372@@ -866,11 +890,11 @@
373 
374 class URLField(TextField):
375     "A convenience FormField for validating URLs"
376-    def __init__(self, field_name, length=50, maxlength=200, is_required=False, validator_list=None):
377+    def __init__(self, field_name, length=50, maxlength=200, is_required=False, validator_list=None, attribute_dict=None):
378         if validator_list is None: validator_list = []
379         validator_list = [self.isValidURL] + validator_list
380         TextField.__init__(self, field_name, length=length, maxlength=maxlength,
381-            is_required=is_required, validator_list=validator_list)
382+            is_required=is_required, validator_list=validator_list, attribute_dict=attribute_dict)
383 
384     def isValidURL(self, field_data, all_data):
385         try:
386@@ -879,11 +903,11 @@
387             raise validators.CriticalValidationError, e.messages
388 
389 class IPAddressField(TextField):
390-    def __init__(self, field_name, length=15, maxlength=15, is_required=False, validator_list=None):
391+    def __init__(self, field_name, length=15, maxlength=15, is_required=False, validator_list=None, attribute_dict=None):
392         if validator_list is None: validator_list = []
393         validator_list = [self.isValidIPAddress] + validator_list
394         TextField.__init__(self, field_name, length=length, maxlength=maxlength,
395-            is_required=is_required, validator_list=validator_list)
396+            is_required=is_required, validator_list=validator_list, attribute_dict=attribute_dict)
397 
398     def isValidIPAddress(self, field_data, all_data):
399         try:
400@@ -901,7 +925,7 @@
401 
402 class FilePathField(SelectField):
403     "A SelectField whose choices are the files in a given directory."
404-    def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=None):
405+    def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=None, attribute_dict=None):
406         import os
407         from django.db.models import BLANK_CHOICE_DASH
408         if match is not None:
409@@ -921,15 +945,15 @@
410                         choices.append((full_file, f))
411             except OSError:
412                 pass
413-        SelectField.__init__(self, field_name, choices, 1, is_required, validator_list)
414+        SelectField.__init__(self, field_name, choices, 1, is_required, validator_list, attribute_dict=attribute_dict)
415 
416 class PhoneNumberField(TextField):
417     "A convenience FormField for validating phone numbers (e.g. '630-555-1234')"
418-    def __init__(self, field_name, is_required=False, validator_list=None):
419+    def __init__(self, field_name, is_required=False, validator_list=None, attribute_dict=None):
420         if validator_list is None: validator_list = []
421         validator_list = [self.isValidPhone] + validator_list
422         TextField.__init__(self, field_name, length=12, maxlength=12,
423-            is_required=is_required, validator_list=validator_list)
424+            is_required=is_required, validator_list=validator_list, attribute_dict=attribute_dict)
425 
426     def isValidPhone(self, field_data, all_data):
427         try:
428@@ -939,11 +963,11 @@
429 
430 class USStateField(TextField):
431     "A convenience FormField for validating U.S. states (e.g. 'IL')"
432-    def __init__(self, field_name, is_required=False, validator_list=None):
433+    def __init__(self, field_name, is_required=False, validator_list=None, attribute_dict=None):
434         if validator_list is None: validator_list = []
435         validator_list = [self.isValidUSState] + validator_list
436         TextField.__init__(self, field_name, length=2, maxlength=2,
437-            is_required=is_required, validator_list=validator_list)
438+            is_required=is_required, validator_list=validator_list, attribute_dict=attribute_dict)
439 
440     def isValidUSState(self, field_data, all_data):
441         try:
442@@ -960,11 +984,11 @@
443 
444 class CommaSeparatedIntegerField(TextField):
445     "A convenience FormField for validating comma-separated integer fields"
446-    def __init__(self, field_name, maxlength=None, is_required=False, validator_list=None):
447+    def __init__(self, field_name, maxlength=None, is_required=False, validator_list=None, attribute_dict=None):
448         if validator_list is None: validator_list = []
449         validator_list = [self.isCommaSeparatedIntegerList] + validator_list
450         TextField.__init__(self, field_name, length=20, maxlength=maxlength,
451-            is_required=is_required, validator_list=validator_list)
452+            is_required=is_required, validator_list=validator_list, attribute_dict=attribute_dict)
453 
454     def isCommaSeparatedIntegerList(self, field_data, all_data):
455         try: