Code

Ticket #1714: readonly-fields.diff

File readonly-fields.diff, 20.4 KB (added by mir@…, 8 years ago)

dummdidumm, here it comes ;-)

Line 
1--- a/django/forms/__init__.py
2+++ b/django/forms/__init__.py
3@@ -361,19 +361,26 @@ class FormField:
4         "Returns the HTML 'id' attribute for this form field."
5         return FORM_FIELD_ID_PREFIX + self.field_name
6 
7+    def css_class(self):
8+        readonly_label = ''
9+        if self.readonly:
10+            readonly_label = "Readonly"
11+        return "v%s%s" % (readonly_label, self.__class__.__name__)
12+
13 ####################
14 # GENERIC WIDGETS  #
15 ####################
16 
17 class TextField(FormField):
18     input_type = "text"
19-    def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=[], member_name=None):
20+    def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=[], member_name=None, readonly=False):
21         self.field_name = field_name
22         self.length, self.maxlength = length, maxlength
23         self.is_required = is_required
24         self.validator_list = [self.isValidLength, self.hasNoNewlines] + validator_list
25         if member_name != None:
26             self.member_name = member_name
27+        self.readonly = readonly
28 
29     def isValidLength(self, data, form):
30         if data and self.maxlength and len(data.decode(settings.DEFAULT_CHARSET)) > self.maxlength:
31@@ -388,13 +395,16 @@ class TextField(FormField):
32         if data is None:
33             data = ''
34         maxlength = ''
35+        readonly = ''
36         if self.maxlength:
37             maxlength = 'maxlength="%s" ' % self.maxlength
38+        if self.readonly:
39+            readonly = 'readonly="readonly"'
40         if isinstance(data, unicode):
41             data = data.encode(settings.DEFAULT_CHARSET)
42-        return '<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
43-            (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
44-            self.field_name, self.length, escape(data), maxlength)
45+        return '<input type="%s" id="%s" class="%s%s" name="%s" size="%s" value="%s" %s %s/>' % \
46+            (self.input_type, self.get_id(), self.css_class(), self.is_required and ' required' or '',
47+            self.field_name, self.length, escape(data), maxlength, readonly)
48 
49     def html2python(data):
50         return data
51@@ -404,22 +414,26 @@ class PasswordField(TextField):
52     input_type = "password"
53 
54 class LargeTextField(TextField):
55-    def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=[], maxlength=None):
56+    def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=[], maxlength=None, readonly=False):
57         self.field_name = field_name
58         self.rows, self.cols, self.is_required = rows, cols, is_required
59         self.validator_list = validator_list[:]
60         if maxlength:
61             self.validator_list.append(self.isValidLength)
62             self.maxlength = maxlength
63+        self.readonly = readonly
64 
65     def render(self, data):
66         if data is None:
67             data = ''
68         if isinstance(data, unicode):
69             data = data.encode(settings.DEFAULT_CHARSET)
70-        return '<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
71-            (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
72-            self.field_name, self.rows, self.cols, escape(data))
73+        readonly = ''
74+        if self.readonly:
75+            readonly = 'readonly="readonly"'
76+        return '<textarea id="%s" class="%s%s" name="%s" rows="%s" cols="%s" %s>%s</textarea>' % \
77+            (self.get_id(), self.css_class(), self.is_required and ' required' or '',
78+            self.field_name, self.rows, self.cols, readonly, escape(data))
79 
80 class HiddenField(FormField):
81     def __init__(self, field_name, is_required=False, validator_list=[]):
82@@ -431,18 +445,22 @@ class HiddenField(FormField):
83             (self.get_id(), self.field_name, escape(data))
84 
85 class CheckboxField(FormField):
86-    def __init__(self, field_name, checked_by_default=False):
87+    def __init__(self, field_name, checked_by_default=False, readonly=False):
88         self.field_name = field_name
89         self.checked_by_default = checked_by_default
90         self.is_required, self.validator_list = False, [] # because the validator looks for these
91+        self.readonly = readonly
92 
93     def render(self, data):
94         checked_html = ''
95+        readonly = ''
96+        if self.readonly:
97+            readonly = ' disabled="disabled"'
98         if data or (data is '' and self.checked_by_default):
99             checked_html = ' checked="checked"'
100-        return '<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
101-            (self.get_id(), self.__class__.__name__,
102-            self.field_name, checked_html)
103+        return '<input type="checkbox" id="%s" class="%s" name="%s"%s%s/>' % \
104+            (self.get_id(), self.css_class(),
105+            self.field_name, checked_html, readonly)
106 
107     def html2python(data):
108         "Convert value from browser ('on' or '') to a Python boolean"
109@@ -452,18 +470,23 @@ class CheckboxField(FormField):
110     html2python = staticmethod(html2python)
111 
112 class SelectField(FormField):
113-    def __init__(self, field_name, choices=[], size=1, is_required=False, validator_list=[], member_name=None):
114+    def __init__(self, field_name, choices=[], size=1, is_required=False, validator_list=[], member_name=None, readonly=False):
115         self.field_name = field_name
116         # choices is a list of (value, human-readable key) tuples because order matters
117         self.choices, self.size, self.is_required = choices, size, is_required
118         self.validator_list = [self.isValidChoice] + validator_list
119+        self.readonly = readonly
120         if member_name != None:
121             self.member_name = member_name
122 
123     def render(self, data):
124-        output = ['<select id="%s" class="v%s%s" name="%s" size="%s">' % \
125-            (self.get_id(), self.__class__.__name__,
126-             self.is_required and ' required' or '', self.field_name, self.size)]
127+        disabled = ''
128+        if self.readonly:
129+            disabled='disabled="disabled"'
130+        output = ['<select id="%s" class="%s%s" name="%s" size="%s" %s>' % \
131+            (self.get_id(), self.css_class(),
132+             self.is_required and ' required' or '', self.field_name, self.size,
133+             disabled)]
134         str_data = str(data) # normalize to string
135         for value, display_name in self.choices:
136             selected_html = ''
137@@ -488,12 +511,13 @@ class NullSelectField(SelectField):
138     html2python = staticmethod(html2python)
139 
140 class RadioSelectField(FormField):
141-    def __init__(self, field_name, choices=[], ul_class='', is_required=False, validator_list=[], member_name=None):
142+    def __init__(self, field_name, choices=[], ul_class='', is_required=False, validator_list=[], member_name=None, readonly=False):
143         self.field_name = field_name
144         # choices is a list of (value, human-readable key) tuples because order matters
145         self.choices, self.is_required = choices, is_required
146         self.validator_list = [self.isValidChoice] + validator_list
147         self.ul_class = ul_class
148+        self.readonly = readonly
149         if member_name != None:
150             self.member_name = member_name
151 
152@@ -532,13 +556,16 @@ class RadioSelectField(FormField):
153         str_data = str(data) # normalize to string
154         for i, (value, display_name) in enumerate(self.choices):
155             selected_html = ''
156+            readonly = ''
157             if str(value) == str_data:
158                 selected_html = ' checked="checked"'
159+            if self.readonly:
160+                readonly = 'readonly="readonly"'
161             datalist.append({
162                 'value': value,
163                 'name': display_name,
164-                'field': '<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
165-                    (self.get_id() + '_' + str(i), self.field_name, value, selected_html),
166+                'field': '<input type="radio" id="%s" name="%s" value="%s"%s %s/>' % \
167+                    (self.get_id() + '_' + str(i), self.field_name, value, selected_html, readonly),
168                 'label': '<label for="%s">%s</label>' % \
169                     (self.get_id() + '_' + str(i), display_name),
170             })
171@@ -552,9 +579,9 @@ class RadioSelectField(FormField):
172 
173 class NullBooleanField(SelectField):
174     "This SelectField provides 'Yes', 'No' and 'Unknown', mapping results to True, False or None"
175-    def __init__(self, field_name, is_required=False, validator_list=[]):
176+    def __init__(self, field_name, is_required=False, validator_list=[], readonly=False):
177         SelectField.__init__(self, field_name, choices=[('1', 'Unknown'), ('2', 'Yes'), ('3', 'No')],
178-            is_required=is_required, validator_list=validator_list)
179+            is_required=is_required, validator_list=validator_list, readonly=readonly)
180 
181     def render(self, data):
182         if data is None: data = '1'
183@@ -605,8 +632,8 @@ class CheckboxSelectMultipleField(Select
184     back into the single list that validators, renderers and save() expect.
185     """
186     requires_data_list = True
187-    def __init__(self, field_name, choices=[], validator_list=[]):
188-        SelectMultipleField.__init__(self, field_name, choices, size=1, is_required=False, validator_list=validator_list)
189+    def __init__(self, field_name, choices=[], validator_list=[], readonly=False):
190+        SelectMultipleField.__init__(self, field_name, choices, size=1, is_required=False, validator_list=validator_list, readonly=readonly)
191 
192     def prepare(self, new_data):
193         # new_data has "split" this field into several fields, so flatten it
194@@ -671,11 +698,11 @@ # INTEGERS/FLOATS  #
195 ####################
196 
197 class IntegerField(TextField):
198-    def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=[], member_name=None):
199+    def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=[], member_name=None, readonly=False):
200         validator_list = [self.isInteger] + validator_list
201         if member_name is not None:
202             self.member_name = member_name
203-        TextField.__init__(self, field_name, length, maxlength, is_required, validator_list)
204+        TextField.__init__(self, field_name, length, maxlength, is_required, validator_list, readonly)
205 
206     def isInteger(self, field_data, all_data):
207         try:
208@@ -690,37 +717,37 @@ class IntegerField(TextField):
209     html2python = staticmethod(html2python)
210 
211 class SmallIntegerField(IntegerField):
212-    def __init__(self, field_name, length=5, maxlength=5, is_required=False, validator_list=[]):
213+    def __init__(self, field_name, length=5, maxlength=5, is_required=False, validator_list=[], readonly=False):
214         validator_list = [self.isSmallInteger] + validator_list
215-        IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
216+        IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list, readonly)
217 
218     def isSmallInteger(self, field_data, all_data):
219         if not -32768 <= int(field_data) <= 32767:
220             raise validators.CriticalValidationError, gettext("Enter a whole number between -32,768 and 32,767.")
221 
222 class PositiveIntegerField(IntegerField):
223-    def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=[]):
224+    def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=[], readonly=False):
225         validator_list = [self.isPositive] + validator_list
226-        IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
227+        IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list, readonly)
228 
229     def isPositive(self, field_data, all_data):
230         if int(field_data) < 0:
231             raise validators.CriticalValidationError, gettext("Enter a positive number.")
232 
233 class PositiveSmallIntegerField(IntegerField):
234-    def __init__(self, field_name, length=5, maxlength=None, is_required=False, validator_list=[]):
235+    def __init__(self, field_name, length=5, maxlength=None, is_required=False, validator_list=[], readonly=False):
236         validator_list = [self.isPositiveSmall] + validator_list
237-        IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
238+        IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list, readonly)
239 
240     def isPositiveSmall(self, field_data, all_data):
241         if not 0 <= int(field_data) <= 32767:
242             raise validators.CriticalValidationError, gettext("Enter a whole number between 0 and 32,767.")
243 
244 class FloatField(TextField):
245-    def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=[]):
246+    def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=[], readonly=False):
247         self.max_digits, self.decimal_places = max_digits, decimal_places
248         validator_list = [self.isValidFloat] + validator_list
249-        TextField.__init__(self, field_name, max_digits+1, max_digits+1, is_required, validator_list)
250+        TextField.__init__(self, field_name, max_digits+1, max_digits+1, is_required, validator_list, readonly)
251 
252     def isValidFloat(self, field_data, all_data):
253         v = validators.IsValidFloat(self.max_digits, self.decimal_places)
254@@ -742,11 +769,12 @@ ####################
255 class DatetimeField(TextField):
256     """A FormField that automatically converts its data to a datetime.datetime object.
257     The data should be in the format YYYY-MM-DD HH:MM:SS."""
258-    def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=[]):
259+    def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=[], readonly=False):
260         self.field_name = field_name
261         self.length, self.maxlength = length, maxlength
262         self.is_required = is_required
263         self.validator_list = [validators.isValidANSIDatetime] + validator_list
264+        self.readonly = readonly
265 
266     def html2python(data):
267         "Converts the field into a datetime.datetime object"
268@@ -765,10 +793,10 @@ class DatetimeField(TextField):
269 class DateField(TextField):
270     """A FormField that automatically converts its data to a datetime.date object.
271     The data should be in the format YYYY-MM-DD."""
272-    def __init__(self, field_name, is_required=False, validator_list=[]):
273+    def __init__(self, field_name, is_required=False, validator_list=[], readonly=False):
274         validator_list = [self.isValidDate] + validator_list
275         TextField.__init__(self, field_name, length=10, maxlength=10,
276-            is_required=is_required, validator_list=validator_list)
277+            is_required=is_required, validator_list=validator_list, readonly=readonly)
278 
279     def isValidDate(self, field_data, all_data):
280         try:
281@@ -789,10 +817,10 @@ class DateField(TextField):
282 class TimeField(TextField):
283     """A FormField that automatically converts its data to a datetime.time object.
284     The data should be in the format HH:MM:SS or HH:MM:SS.mmmmmm."""
285-    def __init__(self, field_name, is_required=False, validator_list=[]):
286+    def __init__(self, field_name, is_required=False, validator_list=[], readonly=False):
287         validator_list = [self.isValidTime] + validator_list
288         TextField.__init__(self, field_name, length=8, maxlength=8,
289-            is_required=is_required, validator_list=validator_list)
290+            is_required=is_required, validator_list=validator_list, readonly=readonly)
291 
292     def isValidTime(self, field_data, all_data):
293         try:
294@@ -823,10 +851,10 @@ ####################
295 
296 class EmailField(TextField):
297     "A convenience FormField for validating e-mail addresses"
298-    def __init__(self, field_name, length=50, maxlength=75, is_required=False, validator_list=[]):
299+    def __init__(self, field_name, length=50, maxlength=75, is_required=False, validator_list=[], readonly=False):
300         validator_list = [self.isValidEmail] + validator_list
301         TextField.__init__(self, field_name, length, maxlength=maxlength,
302-            is_required=is_required, validator_list=validator_list)
303+            is_required=is_required, validator_list=validator_list, readonly=readonly)
304 
305     def isValidEmail(self, field_data, all_data):
306         try:
307@@ -836,10 +864,10 @@ class EmailField(TextField):
308 
309 class URLField(TextField):
310     "A convenience FormField for validating URLs"
311-    def __init__(self, field_name, length=50, is_required=False, validator_list=[]):
312+    def __init__(self, field_name, length=50, is_required=False, validator_list=[], readonly=False):
313         validator_list = [self.isValidURL] + validator_list
314         TextField.__init__(self, field_name, length=length, maxlength=200,
315-            is_required=is_required, validator_list=validator_list)
316+            is_required=is_required, validator_list=validator_list, readonly=readonly)
317 
318     def isValidURL(self, field_data, all_data):
319         try:
320@@ -848,10 +876,10 @@ class URLField(TextField):
321             raise validators.CriticalValidationError, e.messages
322 
323 class IPAddressField(TextField):
324-    def __init__(self, field_name, length=15, maxlength=15, is_required=False, validator_list=[]):
325+    def __init__(self, field_name, length=15, maxlength=15, is_required=False, validator_list=[], readonly=False):
326         validator_list = [self.isValidIPAddress] + validator_list
327         TextField.__init__(self, field_name, length=length, maxlength=maxlength,
328-            is_required=is_required, validator_list=validator_list)
329+            is_required=is_required, validator_list=validator_list, readonly=readonly)
330 
331     def isValidIPAddress(self, field_data, all_data):
332         try:
333@@ -869,7 +897,7 @@ ####################
334 
335 class FilePathField(SelectField):
336     "A SelectField whose choices are the files in a given directory."
337-    def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=[]):
338+    def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=[], readonly=False):
339         import os
340         if match is not None:
341             import re
342@@ -888,14 +916,14 @@ class FilePathField(SelectField):
343                         choices.append((full_file, f))
344             except OSError:
345                 pass
346-        SelectField.__init__(self, field_name, choices, 1, is_required, validator_list)
347+        SelectField.__init__(self, field_name, choices, 1, is_required, validator_list, readonly)
348 
349 class PhoneNumberField(TextField):
350     "A convenience FormField for validating phone numbers (e.g. '630-555-1234')"
351-    def __init__(self, field_name, is_required=False, validator_list=[]):
352+    def __init__(self, field_name, is_required=False, validator_list=[], readonly=False):
353         validator_list = [self.isValidPhone] + validator_list
354         TextField.__init__(self, field_name, length=12, maxlength=12,
355-            is_required=is_required, validator_list=validator_list)
356+            is_required=is_required, validator_list=validator_list, readonly=readonly)
357 
358     def isValidPhone(self, field_data, all_data):
359         try:
360@@ -905,10 +933,10 @@ class PhoneNumberField(TextField):
361 
362 class USStateField(TextField):
363     "A convenience FormField for validating U.S. states (e.g. 'IL')"
364-    def __init__(self, field_name, is_required=False, validator_list=[]):
365+    def __init__(self, field_name, is_required=False, validator_list=[], readonly=False):
366         validator_list = [self.isValidUSState] + validator_list
367         TextField.__init__(self, field_name, length=2, maxlength=2,
368-            is_required=is_required, validator_list=validator_list)
369+            is_required=is_required, validator_list=validator_list, readonly=readonly)
370 
371     def isValidUSState(self, field_data, all_data):
372         try:
373@@ -925,10 +953,10 @@ class USStateField(TextField):
374 
375 class CommaSeparatedIntegerField(TextField):
376     "A convenience FormField for validating comma-separated integer fields"
377-    def __init__(self, field_name, maxlength=None, is_required=False, validator_list=[]):
378+    def __init__(self, field_name, maxlength=None, is_required=False, validator_list=[], readonly=False):
379         validator_list = [self.isCommaSeparatedIntegerList] + validator_list
380         TextField.__init__(self, field_name, length=20, maxlength=maxlength,
381-            is_required=is_required, validator_list=validator_list)
382+            is_required=is_required, validator_list=validator_list, readonly=readonly)
383 
384     def isCommaSeparatedIntegerList(self, field_data, all_data):
385         try: