Django

Code

root/django/branches/newforms-admin/django/newforms/fields.py

Revision 7960, 32.0 kB (checked in by brosner, 5 months ago)

newforms-admin: Merged from trunk up to [7956].

  • Property svn:eol-style set to native
Line 
1 """
2 Field classes.
3 """
4
5 import copy
6 import datetime
7 import os
8 import re
9 import time
10 try:
11     from cStringIO import StringIO
12 except ImportError:
13     from StringIO import StringIO
14
15 # Python 2.3 fallbacks
16 try:
17     from decimal import Decimal, DecimalException
18 except ImportError:
19     from django.utils._decimal import Decimal, DecimalException
20 try:
21     set
22 except NameError:
23     from sets import Set as set
24
25 from django.utils.translation import ugettext_lazy as _
26 from django.utils.encoding import StrAndUnicode, smart_unicode, smart_str
27
28 from util import ErrorList, ValidationError
29 from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput
30 from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile
31
32 __all__ = (
33     'Field', 'CharField', 'IntegerField',
34     'DEFAULT_DATE_INPUT_FORMATS', 'DateField',
35     'DEFAULT_TIME_INPUT_FORMATS', 'TimeField',
36     'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField',
37     'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
38     'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
39     'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
40     'SplitDateTimeField', 'IPAddressField', 'FilePathField',
41 )
42
43 # These values, if given to to_python(), will trigger the self.required check.
44 EMPTY_VALUES = (None, '')
45
46
47 class Field(object):
48     widget = TextInput # Default widget to use when rendering this type of Field.
49     hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden".
50     default_error_messages = {
51         'required': _(u'This field is required.'),
52         'invalid': _(u'Enter a valid value.'),
53     }
54
55     # Tracks each time a Field instance is created. Used to retain order.
56     creation_counter = 0
57
58     def __init__(self, required=True, widget=None, label=None, initial=None,
59                  help_text=None, error_messages=None):
60         # required -- Boolean that specifies whether the field is required.
61         #             True by default.
62         # widget -- A Widget class, or instance of a Widget class, that should
63         #           be used for this Field when displaying it. Each Field has a
64         #           default Widget that it'll use if you don't specify this. In
65         #           most cases, the default widget is TextInput.
66         # label -- A verbose name for this field, for use in displaying this
67         #          field in a form. By default, Django will use a "pretty"
68         #          version of the form field name, if the Field is part of a
69         #          Form.
70         # initial -- A value to use in this Field's initial display. This value
71         #            is *not* used as a fallback if data isn't given.
72         # help_text -- An optional string to use as "help text" for this Field.
73         if label is not None:
74             label = smart_unicode(label)
75         self.required, self.label, self.initial = required, label, initial
76         self.help_text = smart_unicode(help_text or '')
77         widget = widget or self.widget
78         if isinstance(widget, type):
79             widget = widget()
80
81         # Hook into self.widget_attrs() for any Field-specific HTML attributes.
82         extra_attrs = self.widget_attrs(widget)
83         if extra_attrs:
84             widget.attrs.update(extra_attrs)
85
86         self.widget = widget
87
88         # Increase the creation counter, and save our local copy.
89         self.creation_counter = Field.creation_counter
90         Field.creation_counter += 1
91
92         def set_class_error_messages(messages, klass):
93             for base_class in klass.__bases__:
94                 set_class_error_messages(messages, base_class)
95             messages.update(getattr(klass, 'default_error_messages', {}))
96
97         messages = {}
98         set_class_error_messages(messages, self.__class__)
99         messages.update(error_messages or {})
100         self.error_messages = messages
101
102     def clean(self, value):
103         """
104         Validates the given value and returns its "cleaned" value as an
105         appropriate Python object.
106
107         Raises ValidationError for any errors.
108         """
109         if self.required and value in EMPTY_VALUES:
110             raise ValidationError(self.error_messages['required'])
111         return value
112
113     def widget_attrs(self, widget):
114         """
115         Given a Widget instance (*not* a Widget class), returns a dictionary of
116         any HTML attributes that should be added to the Widget, based on this
117         Field.
118         """
119         return {}
120
121     def __deepcopy__(self, memo):
122         result = copy.copy(self)
123         memo[id(self)] = result
124         result.widget = copy.deepcopy(self.widget, memo)
125         return result
126
127 class CharField(Field):
128     default_error_messages = {
129         'max_length': _(u'Ensure this value has at most %(max)d characters (it has %(length)d).'),
130         'min_length': _(u'Ensure this value has at least %(min)d characters (it has %(length)d).'),
131     }
132
133     def __init__(self, max_length=None, min_length=None, *args, **kwargs):
134         self.max_length, self.min_length = max_length, min_length
135         super(CharField, self).__init__(*args, **kwargs)
136
137     def clean(self, value):
138         "Validates max_length and min_length. Returns a Unicode object."
139         super(CharField, self).clean(value)
140         if value in EMPTY_VALUES:
141             return u''
142         value = smart_unicode(value)
143         value_length = len(value)
144         if self.max_length is not None and value_length > self.max_length:
145             raise ValidationError(self.error_messages['max_length'] % {'max': self.max_length, 'length': value_length})
146         if self.min_length is not None and value_length < self.min_length:
147             raise ValidationError(self.error_messages['min_length'] % {'min': self.min_length, 'length': value_length})
148         return value
149
150     def widget_attrs(self, widget):
151         if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)):
152             # The HTML attribute is maxlength, not max_length.
153             return {'maxlength': str(self.max_length)}
154
155 class IntegerField(Field):
156     default_error_messages = {
157         'invalid': _(u'Enter a whole number.'),
158         'max_value': _(u'Ensure this value is less than or equal to %s.'),
159         'min_value': _(u'Ensure this value is greater than or equal to %s.'),
160     }
161
162     def __init__(self, max_value=None, min_value=None, *args, **kwargs):
163         self.max_value, self.min_value = max_value, min_value
164         super(IntegerField, self).__init__(*args, **kwargs)
165
166     def clean(self, value):
167         """
168         Validates that int() can be called on the input. Returns the result
169         of int(). Returns None for empty values.
170         """
171         super(IntegerField, self).clean(value)
172         if value in EMPTY_VALUES:
173             return None
174         try:
175             value = int(str(value))
176         except (ValueError, TypeError):
177             raise ValidationError(self.error_messages['invalid'])
178         if self.max_value is not None and value > self.max_value:
179             raise ValidationError(self.error_messages['max_value'] % self.max_value)
180         if self.min_value is not None and value < self.min_value:
181             raise ValidationError(self.error_messages['min_value'] % self.min_value)
182         return value
183
184 class FloatField(Field):
185     default_error_messages = {
186         'invalid': _(u'Enter a number.'),
187         'max_value': _(u'Ensure this value is less than or equal to %s.'),
188         'min_value': _(u'Ensure this value is greater than or equal to %s.'),
189     }
190
191     def __init__(self, max_value=None, min_value=None, *args, **kwargs):
192         self.max_value, self.min_value = max_value, min_value
193         Field.__init__(self, *args, **kwargs)
194
195     def clean(self, value):
196         """
197         Validates that float() can be called on the input. Returns a float.
198         Returns None for empty values.
199         """
200         super(FloatField, self).clean(value)
201         if not self.required and value in EMPTY_VALUES:
202             return None
203         try:
204             value = float(value)
205         except (ValueError, TypeError):
206             raise ValidationError(self.error_messages['invalid'])
207         if self.max_value is not None and value > self.max_value:
208             raise ValidationError(self.error_messages['max_value'] % self.max_value)
209         if self.min_value is not None and value < self.min_value:
210             raise ValidationError(self.error_messages['min_value'] % self.min_value)
211         return value
212
213 class DecimalField(Field):
214     default_error_messages = {
215         'invalid': _(u'Enter a number.'),
216         'max_value': _(u'Ensure this value is less than or equal to %s.'),
217         'min_value': _(u'Ensure this value is greater than or equal to %s.'),
218         'max_digits': _('Ensure that there are no more than %s digits in total.'),
219         'max_decimal_places': _('Ensure that there are no more than %s decimal places.'),
220         'max_whole_digits': _('Ensure that there are no more than %s digits before the decimal point.')
221     }
222
223     def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs):
224         self.max_value, self.min_value = max_value, min_value
225         self.max_digits, self.decimal_places = max_digits, decimal_places
226         Field.__init__(self, *args, **kwargs)
227
228     def clean(self, value):
229         """
230         Validates that the input is a decimal number. Returns a Decimal
231         instance. Returns None for empty values. Ensures that there are no more
232         than max_digits in the number, and no more than decimal_places digits
233         after the decimal point.
234         """
235         super(DecimalField, self).clean(value)
236         if not self.required and value in EMPTY_VALUES:
237             return None
238         value = smart_str(value).strip()
239         try:
240             value = Decimal(value)
241         except DecimalException:
242             raise ValidationError(self.error_messages['invalid'])
243         pieces = str(value).lstrip("-").split('.')
244         decimals = (len(pieces) == 2) and len(pieces[1]) or 0
245         digits = len(pieces[0])
246         if self.max_value is not None and value > self.max_value:
247             raise ValidationError(self.error_messages['max_value'] % self.max_value)
248         if self.min_value is not None and value < self.min_value:
249             raise ValidationError(self.error_messages['min_value'] % self.min_value)
250         if self.max_digits is not None and (digits + decimals) > self.max_digits:
251             raise ValidationError(self.error_messages['max_digits'] % self.max_digits)
252         if self.decimal_places is not None and decimals > self.decimal_places:
253             raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places)
254         if self.max_digits is not None and self.decimal_places is not None and digits > (self.max_digits - self.decimal_places):
255             raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places))
256         return value
257
258 DEFAULT_DATE_INPUT_FORMATS = (
259     '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
260     '%b %d %Y', '%b %d, %Y',            # 'Oct 25 2006', 'Oct 25, 2006'
261     '%d %b %Y', '%d %b, %Y',            # '25 Oct 2006', '25 Oct, 2006'
262     '%B %d %Y', '%B %d, %Y',            # 'October 25 2006', 'October 25, 2006'
263     '%d %B %Y', '%d %B, %Y',            # '25 October 2006', '25 October, 2006'
264 )
265
266 class DateField(Field):
267     default_error_messages = {
268         'invalid': _(u'Enter a valid date.'),
269     }
270
271     def __init__(self, input_formats=None, *args, **kwargs):
272         super(DateField, self).__init__(*args, **kwargs)
273         self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
274
275     def clean(self, value):
276         """
277         Validates that the input can be converted to a date. Returns a Python
278         datetime.date object.
279         """
280         super(DateField, self).clean(value)
281         if value in EMPTY_VALUES:
282             return None
283         if isinstance(value, datetime.datetime):
284             return value.date()
285         if isinstance(value, datetime.date):
286             return value
287         for format in self.input_formats:
288             try:
289                 return datetime.date(*time.strptime(value, format)[:3])
290             except ValueError:
291                 continue
292         raise ValidationError(self.error_messages['invalid'])
293
294 DEFAULT_TIME_INPUT_FORMATS = (
295     '%H:%M:%S',     # '14:30:59'
296     '%H:%M',        # '14:30'
297 )
298
299 class TimeField(Field):
300     default_error_messages = {
301         'invalid': _(u'Enter a valid time.')
302     }
303
304     def __init__(self, input_formats=None, *args, **kwargs):
305         super(TimeField, self).__init__(*args, **kwargs)
306         self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS
307
308     def clean(self, value):
309         """
310         Validates that the input can be converted to a time. Returns a Python
311         datetime.time object.
312         """
313         super(TimeField, self).clean(value)
314         if value in EMPTY_VALUES:
315             return None
316         if isinstance(value, datetime.time):
317             return value
318         for format in self.input_formats:
319             try:
320                 return datetime.time(*time.strptime(value, format)[3:6])
321             except ValueError:
322                 continue
323         raise ValidationError(self.error_messages['invalid'])
324
325 DEFAULT_DATETIME_INPUT_FORMATS = (
326     '%Y-%m-%d %H:%M:%S',     # '2006-10-25 14:30:59'
327     '%Y-%m-%d %H:%M',        # '2006-10-25 14:30'
328     '%Y-%m-%d',              # '2006-10-25'
329     '%m/%d/%Y %H:%M:%S',     # '10/25/2006 14:30:59'
330     '%m/%d/%Y %H:%M',        # '10/25/2006 14:30'
331     '%m/%d/%Y',              # '10/25/2006'
332     '%m/%d/%y %H:%M:%S',     # '10/25/06 14:30:59'
333     '%m/%d/%y %H:%M',        # '10/25/06 14:30'
334     '%m/%d/%y',              # '10/25/06'
335 )
336
337 class DateTimeField(Field):
338     widget = DateTimeInput
339     default_error_messages = {
340         'invalid': _(u'Enter a valid date/time.'),
341     }
342
343     def __init__(self, input_formats=None, *args, **kwargs):
344         super(DateTimeField, self).__init__(*args, **kwargs)
345         self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
346
347     def clean(self, value):
348         """
349         Validates that the input can be converted to a datetime. Returns a
350         Python datetime.datetime object.
351         """
352         super(DateTimeField, self).clean(value)
353         if value in EMPTY_VALUES:
354             return None
355         if isinstance(value, datetime.datetime):
356             return value
357         if isinstance(value, datetime.date):
358             return datetime.datetime(value.year, value.month, value.day)
359         if isinstance(value, list):
360             # Input comes from a SplitDateTimeWidget, for example. So, it's two
361             # components: date and time.
362             if len(value) != 2:
363                 raise ValidationError(self.error_messages['invalid'])
364             value = '%s %s' % tuple(value)
365         for format in self.input_formats:
366             try:
367                 return datetime.datetime(*time.strptime(value, format)[:6])
368             except ValueError:
369                 continue
370         raise ValidationError(self.error_messages['invalid'])
371
372 class RegexField(CharField):
373     def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs):
374         """
375         regex can be either a string or a compiled regular expression object.
376         error_message is an optional error message to use, if
377         'Enter a valid value' is too generic for you.
378         """
379         # error_message is just kept for backwards compatibility:
380         if error_message:
381             error_messages = kwargs.get('error_messages') or {}
382             error_messages['invalid'] = error_message
383             kwargs['error_messages'] = error_messages
384         super(RegexField, self).__init__(max_length, min_length, *args, **kwargs)
385         if isinstance(regex, basestring):
386             regex = re.compile(regex)
387         self.regex = regex
388
389     def clean(self, value):
390         """
391         Validates that the input matches the regular expression. Returns a
392         Unicode object.
393         """
394         value = super(RegexField, self).clean(value)
395         if value == u'':
396             return value
397         if not self.regex.search(value):
398             raise ValidationError(self.error_messages['invalid'])
399         return value
400
401 email_re = re.compile(
402     r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom
403     r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string
404     r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE)  # domain
405
406 class EmailField(RegexField):
407     default_error_messages = {
408         'invalid': _(u'Enter a valid e-mail address.'),
409     }
410
411     def __init__(self, max_length=None, min_length=None, *args, **kwargs):
412         RegexField.__init__(self, email_re, max_length, min_length, *args,
413                             **kwargs)
414
415 try:
416     from django.conf import settings
417     URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT
418 except ImportError:
419     # It's OK if Django settings aren't configured.
420     URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
421
422
423 class FileField(Field):
424     widget = FileInput
425     default_error_messages = {
426         'invalid': _(u"No file was submitted. Check the encoding type on the form."),
427         'missing': _(u"No file was submitted."),
428         'empty': _(u"The submitted file is empty."),
429     }
430
431     def __init__(self, *args, **kwargs):
432         super(FileField, self).__init__(*args, **kwargs)
433
434     def clean(self, data, initial=None):
435         super(FileField, self).clean(initial or data)
436         if not self.required and data in EMPTY_VALUES:
437             return None
438         elif not data and initial:
439             return initial
440
441         if isinstance(data, dict):
442             # We warn once, then support both ways below.
443             import warnings
444             warnings.warn(
445                 message = "Representing uploaded files as dictionaries is deprecated. Use django.core.files.uploadedfile.SimpleUploadedFile instead.",
446                 category = DeprecationWarning,
447                 stacklevel = 2
448             )
449             data = UploadedFile(data['filename'], data['content'])
450
451         try:
452             file_name = data.name
453             file_size = data.size
454         except AttributeError:
455             raise ValidationError(self.error_messages['invalid'])
456
457         if not file_name:
458             raise ValidationError(self.error_messages['invalid'])
459         if not file_size:
460             raise ValidationError(self.error_messages['empty'])
461
462         return data
463
464 class ImageField(FileField):
465     default_error_messages = {
466         'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."),
467     }
468
469     def clean(self, data, initial=None):
470         """
471         Checks that the file-upload field data contains a valid image (GIF, JPG,
472         PNG, possibly others -- whatever the Python Imaging Library supports).
473         """
474         f = super(ImageField, self).clean(data, initial)
475         if f is None:
476             return None
477         elif not data and initial:
478             return initial
479         from PIL import Image
480
481         # We need to get a file object for PIL. We might have a path or we might
482         # have to read the data into memory.
483         if hasattr(data, 'temporary_file_path'):
484             file = data.temporary_file_path()
485         else:
486             if hasattr(data, 'read'):
487                 file = StringIO(data.read())
488             else:
489                 file = StringIO(data['content'])
490
491         try:
492             # load() is the only method that can spot a truncated JPEG,
493             #  but it cannot be called sanely after verify()
494             trial_image = Image.open(file)
495