Django

Code

root/django/branches/gis/django/forms/fields.py

Revision 8215, 32.7 kB (checked in by jbronn, 4 months ago)

gis: Merged revisions 7981-8001,8003-8011,8013-8033,8035-8036,8038-8039,8041-8063,8065-8076,8078-8139,8141-8154,8156-8214 via svnmerge from trunk.

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