12 from cStringIO
import StringIO
14 from StringIO
import StringIO
16 # Python 2.3 fallbacks
18 from decimal
import Decimal
, DecimalException
20 from django
.utils
._decimal
import Decimal
, DecimalException
24 from sets
import Set
as set
26 from django
.utils
.translation
import ugettext_lazy
as _
27 from django
.utils
.encoding
import smart_unicode
, smart_str
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
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',
44 # These values, if given to to_python(), will trigger the self.required check.
45 EMPTY_VALUES
= (None, '')
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.'),
56 # Tracks each time a Field instance is created. Used to retain order.
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.
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
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.
75 label
= smart_unicode(label
)
76 self
.required
, self
.label
, self
.initial
= required
, label
, initial
77 self
.help_text
= smart_unicode(help_text
or '')
78 widget
= widget
or self
.widget
79 if isinstance(widget
, type):
82 # Hook into self.widget_attrs() for any Field-specific HTML attributes.
83 extra_attrs
= self
.widget_attrs(widget
)
85 widget
.attrs
.update(extra_attrs
)
89 # Increase the creation counter, and save our local copy.
90 self
.creation_counter
= Field
.creation_counter
91 Field
.creation_counter
+= 1
93 def set_class_error_messages(messages
, klass
):
94 for base_class
in klass
.__bases
__:
95 set_class_error_messages(messages
, base_class
)
96 messages
.update(getattr(klass
, 'default_error_messages', {}))
99 set_class_error_messages(messages
, self
.__class
__)
100 messages
.update(error_messages
or {})
101 self
.error_messages
= messages
103 def clean(self
, value
):
105 Validates the given value and returns its "cleaned" value as an
106 appropriate Python object.
108 Raises ValidationError for any errors.
110 if self
.required
and value
in EMPTY_VALUES
:
111 raise ValidationError(self
.error_messages
['required'])
114 def widget_attrs(self
, widget
):
116 Given a Widget instance (*not* a Widget class), returns a dictionary of
117 any HTML attributes that should be added to the Widget, based on this
122 def __deepcopy__(self
, memo
):
123 result
= copy
.copy(self
)
124 memo
[id(self
)] = result
125 result
.widget
= copy
.deepcopy(self
.widget
, memo
)
128 class CharField(Field
):
129 default_error_messages
= {
130 'max_length': _(u
'Ensure this value has at most %(max)d characters (it has %(length)d).'),
131 'min_length': _(u
'Ensure this value has at least %(min)d characters (it has %(length)d).'),
134 def __init__(self
, max_length
=None, min_length
=None, *args
, **kwargs
):
135 self
.max_length
, self
.min_length
= max_length
, min_length
136 super(CharField
, self
).__init
__(*args
, **kwargs
)
138 def clean(self
, value
):
139 "Validates max_length and min_length. Returns a Unicode object."
140 super(CharField
, self
).clean(value
)
141 if value
in EMPTY_VALUES
:
143 value
= smart_unicode(value
)
144 value_length
= len(value
)
145 if self
.max_length
is not None and value_length
> self
.max_length
:
146 raise ValidationError(self
.error_messages
['max_length'] % {'max': self
.max_length
, 'length': value_length
})
147 if self
.min_length
is not None and value_length
< self
.min_length
:
148 raise ValidationError(self
.error_messages
['min_length'] % {'min': self
.min_length
, 'length': value_length
})
151 def widget_attrs(self
, widget
):
152 if self
.max_length
is not None and isinstance(widget
, (TextInput
, PasswordInput
)):
153 # The HTML attribute is maxlength, not max_length.
154 return {'maxlength': str(self
.max_length
)}
156 class IntegerField(Field
):
157 default_error_messages
= {
158 'invalid': _(u
'Enter a whole number.'),
159 'max_value': _(u
'Ensure this value is less than or equal to %s.'),
160 'min_value': _(u
'Ensure this value is greater than or equal to %s.'),
163 def __init__(self
, max_value
=None, min_value
=None, *args
, **kwargs
):
164 self
.max_value
, self
.min_value
= max_value
, min_value
165 super(IntegerField
, self
).__init
__(*args
, **kwargs
)
167 def clean(self
, value
):
169 Validates that int() can be called on the input. Returns the result
170 of int(). Returns None for empty values.
172 super(IntegerField
, self
).clean(value
)
173 if value
in EMPTY_VALUES
:
176 value
= int(str(value
))
177 except (ValueError, TypeError):
178 raise ValidationError(self
.error_messages
['invalid'])
179 if self
.max_value
is not None and value
> self
.max_value
:
180 raise ValidationError(self
.error_messages
['max_value'] % self
.max_value
)
181 if self
.min_value
is not None and value
< self
.min_value
:
182 raise ValidationError(self
.error_messages
['min_value'] % self
.min_value
)
185 class FloatField(Field
):
186 default_error_messages
= {
187 'invalid': _(u
'Enter a number.'),
188 'max_value': _(u
'Ensure this value is less than or equal to %s.'),
189 'min_value': _(u
'Ensure this value is greater than or equal to %s.'),
192 def __init__(self
, max_value
=None, min_value
=None, *args
, **kwargs
):
193 self
.max_value
, self
.min_value
= max_value
, min_value
194 Field
.__init
__(self
, *args
, **kwargs
)
196 def clean(self
, value
):
198 Validates that float() can be called on the input. Returns a float.
199 Returns None for empty values.
201 super(FloatField
, self
).clean(value
)
202 if not self
.required
and value
in EMPTY_VALUES
:
206 except (ValueError, TypeError):
207 raise ValidationError(self
.error_messages
['invalid'])
208 if self
.max_value
is not None and value
> self
.max_value
:
209 raise ValidationError(self
.error_messages
['max_value'] % self
.max_value
)
210 if self
.min_value
is not None and value
< self
.min_value
:
211 raise ValidationError(self
.error_messages
['min_value'] % self
.min_value
)
214 class DecimalField(Field
):
215 default_error_messages
= {
216 'invalid': _(u
'Enter a number.'),
217 'max_value': _(u
'Ensure this value is less than or equal to %s.'),
218 'min_value': _(u
'Ensure this value is greater than or equal to %s.'),
219 'max_digits': _('Ensure that there are no more than %s digits in total.'),
220 'max_decimal_places': _('Ensure that there are no more than %s decimal places.'),
221 'max_whole_digits': _('Ensure that there are no more than %s digits before the decimal point.')
224 def __init__(self
, max_value
=None, min_value
=None, max_digits
=None, decimal_places
=None, *args
, **kwargs
):
225 self
.max_value
, self
.min_value
= max_value
, min_value
226 self
.max_digits
, self
.decimal_places
= max_digits
, decimal_places
227 Field
.__init
__(self
, *args
, **kwargs
)
229 def clean(self
, value
):
231 Validates that the input is a decimal number. Returns a Decimal
232 instance. Returns None for empty values. Ensures that there are no more
233 than max_digits in the number, and no more than decimal_places digits
234 after the decimal point.
236 super(DecimalField
, self
).clean(value
)
237 if not self
.required
and value
in EMPTY_VALUES
:
239 value
= smart_str(value
).strip()
241 value
= Decimal(value
)
242 except DecimalException
:
243 raise ValidationError(self
.error_messages
['invalid'])
244 pieces
= str(value
).lstrip("-").split('.')
245 decimals
= (len(pieces
) == 2) and len(pieces
[1]) or 0
246 digits
= len(pieces
[0])
247 if self
.max_value
is not None and value
> self
.max_value
:
248 raise ValidationError(self
.error_messages
['max_value'] % self
.max_value
)
249 if self
.min_value
is not None and value
< self
.min_value
:
250 raise ValidationError(self
.error_messages
['min_value'] % self
.min_value
)
251 if self
.max_digits
is not None and (digits
+ decimals
) > self
.max_digits
:
252 raise ValidationError(self
.error_messages
['max_digits'] % self
.max_digits
)
253 if self
.decimal_places
is not None and decimals
> self
.decimal_places
:
254 raise ValidationError(self
.error_messages
['max_decimal_places'] % self
.decimal_places
)
255 if self
.max_digits
is not None and self
.decimal_places
is not None and digits
> (self
.max_digits
- self
.decimal_places
):
256 raise ValidationError(self
.error_messages
['max_whole_digits'] % (self
.max_digits
- self
.decimal_places
))
259 DEFAULT_DATE_INPUT_FORMATS
= (
260 '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
261 '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006'
262 '%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006'
263 '%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006'
264 '%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006'
267 class DateField(Field
):
268 default_error_messages
= {
269 'invalid': _(u
'Enter a valid date.'),
272 def __init__(self
, input_formats
=None, *args
, **kwargs
):
273 super(DateField
, self
).__init
__(*args
, **kwargs
)
274 self
.input_formats
= input_formats
or DEFAULT_DATE_INPUT_FORMATS
276 def clean(self
, value
):
278 Validates that the input can be converted to a date. Returns a Python
279 datetime.date object.
281 super(DateField
, self
).clean(value
)
282 if value
in EMPTY_VALUES
:
284 if isinstance(value
, datetime
.datetime
):
286 if isinstance(value
, datetime
.date
):
288 for format
in self
.input_formats
:
290 return datetime
.date(*time
.strptime(value
, format
)[:3])
293 raise ValidationError(self
.error_messages
['invalid'])
295 DEFAULT_TIME_INPUT_FORMATS
= (
296 '%H:%M:%S', # '14:30:59'
300 class TimeField(Field
):
301 default_error_messages
= {
302 'invalid': _(u
'Enter a valid time.')
305 def __init__(self
, input_formats
=None, *args
, **kwargs
):
306 super(TimeField
, self
).__init
__(*args
, **kwargs
)
307 self
.input_formats
= input_formats
or DEFAULT_TIME_INPUT_FORMATS
309 def clean(self
, value
):
311 Validates that the input can be converted to a time. Returns a Python
312 datetime.time object.
314 super(TimeField
, self
).clean(value
)
315 if value
in EMPTY_VALUES
:
317 if isinstance(value
, datetime
.time
):
319 for format
in self
.input_formats
:
321 return datetime
.time(*time
.strptime(value
, format
)[3:6])
324 raise ValidationError(self
.error_messages
['invalid'])
326 DEFAULT_DATETIME_INPUT_FORMATS
= (
327 '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
328 '%Y-%m-%d %H:%M', # '2006-10-25 14:30'
329 '%Y-%m-%d', # '2006-10-25'
330 '%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59'
331 '%m/%d/%Y %H:%M', # '10/25/2006 14:30'
332 '%m/%d/%Y', # '10/25/2006'
333 '%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59'
334 '%m/%d/%y %H:%M', # '10/25/06 14:30'
335 '%m/%d/%y', # '10/25/06'
338 class DateTimeField(Field
):
339 widget
= DateTimeInput
340 default_error_messages
= {
341 'invalid': _(u
'Enter a valid date/time.'),
344 def __init__(self
, input_formats
=None, *args
, **kwargs
):
345 super(DateTimeField
, self
).__init
__(*args
, **kwargs
)
346 self
.input_formats
= input_formats
or DEFAULT_DATETIME_INPUT_FORMATS
348 def clean(self
, value
):
350 Validates that the input can be converted to a datetime. Returns a
351 Python datetime.datetime object.
353 super(DateTimeField
, self
).clean(value
)
354 if value
in EMPTY_VALUES
:
356 if isinstance(value
, datetime
.datetime
):
358 if isinstance(value
, datetime
.date
):
359 return datetime
.datetime(value
.year
, value
.month
, value
.day
)
360 if isinstance(value
, list):
361 # Input comes from a SplitDateTimeWidget, for example. So, it's two
362 # components: date and time.
364 raise ValidationError(self
.error_messages
['invalid'])
365 value
= '%s %s' % tuple(value
)
366 for format
in self
.input_formats
:
368 return datetime
.datetime(*time
.strptime(value
, format
)[:6])
371 raise ValidationError(self
.error_messages
['invalid'])
373 class RegexField(CharField
):
374 def __init__(self
, regex
, max_length
=None, min_length
=None, error_message
=None, *args
, **kwargs
):
376 regex can be either a string or a compiled regular expression object.
377 error_message is an optional error message to use, if
378 'Enter a valid value' is too generic for you.
380 # error_message is just kept for backwards compatibility:
382 error_messages
= kwargs
.get('error_messages') or {}
383 error_messages
['invalid'] = error_message
384 kwargs
['error_messages'] = error_messages
385 super(RegexField
, self
).__init
__(max_length
, min_length
, *args
, **kwargs
)
386 if isinstance(regex
, basestring
):
387 regex
= re
.compile(regex
)
390 def clean(self
, value
):
392 Validates that the input matches the regular expression. Returns a
395 value
= super(RegexField
, self
).clean(value
)
398 if not self
.regex
.search(value
):
399 raise ValidationError(self
.error_messages
['invalid'])
402 email_re
= re
.compile(
403 r
"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom
404 r
'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string
405 r
')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re
.IGNORECASE
) # domain
407 class EmailField(RegexField
):
408 default_error_messages
= {
409 'invalid': _(u
'Enter a valid e-mail address.'),
412 def __init__(self
, max_length
=None, min_length
=None, *args
, **kwargs
):
413 RegexField
.__init
__(self
, email_re
, max_length
, min_length
, *args
,
417 from django
.conf
import settings
418 URL_VALIDATOR_USER_AGENT
= settings
.URL_VALIDATOR_USER_AGENT
420 # It's OK if Django settings aren't configured.
421 URL_VALIDATOR_USER_AGENT
= 'Django (http://www.djangoproject.com/)'
424 class FileField(Field
):
426 default_error_messages
= {
427 'invalid': _(u
"No file was submitted. Check the encoding type on the form."),
428 'missing': _(u
"No file was submitted."),
429 'empty': _(u
"The submitted file is empty."),
432 def __init__(self
, *args
, **kwargs
):
433 super(FileField
, self
).__init
__(*args
, **kwargs
)
435 def clean(self
, data
, initial
=None):
436 super(FileField
, self
).clean(initial
or data
)
437 if not self
.required
and data
in EMPTY_VALUES
:
439 elif not data
and initial
:
442 if isinstance(data
, dict):
443 # We warn once, then support both ways below.
446 message
= "Representing uploaded files as dictionaries is deprecated. Use django.core.files.uploadedfile.SimpleUploadedFile instead.",
447 category
= DeprecationWarning,
450 data
= UploadedFile(data
['filename'], data
['content'])
453 file_name
= data
.name
454 file_size
= data
.size
455 except AttributeError:
456 raise ValidationError(self
.error_messages
['invalid'])
459 raise ValidationError(self
.error_messages
['invalid'])
461 raise ValidationError(self
.error_messages
['empty'])
465 class ImageField(FileField
):
466 default_error_messages
= {
467 'invalid_image': _(u
"Upload a valid image. The file you uploaded was either not an image or a corrupted image."),
470 def clean(self
, data
, initial
=None):
472 Checks that the file-upload field data contains a valid image (GIF, JPG,
473 PNG, possibly others -- whatever the Python Imaging Library supports).
475 f
= super(ImageField
, self
).clean(data
, initial
)
478 elif not data
and initial
:
480 from PIL
import Image
482 # We need to get a file object for PIL. We might have a path or we might
483 # have to read the data into memory.
484 if hasattr(data
, 'temporary_file_path'):
485 file = data
.temporary_file_path()
487 if hasattr(data
, 'read'):
488 file = StringIO(data
.read())
490 file = StringIO(data
['content'])
493 # load() is the only method that can spot a truncated JPEG,
494 # but it cannot be called sanely after verify()
495 trial_image
= Image
.open(file)
498 # Since we're about to use the file again we have to reset the
499 # file object if possible.
500 if hasattr(file, 'reset'):
503 # verify() is the only method that can spot a corrupt PNG,
504 # but it must be called immediately after the constructor
505 trial_image
= Image
.open(file)
508 # Under PyPy, it is possible to import PIL. However, the underlying
509 # _imaging C module isn't available, so an ImportError will be
510 # raised. Catch and re-raise.
512 except Exception: # Python Imaging Library doesn't recognize it as an image
513 raise ValidationError(self
.error_messages
['invalid_image'])
514 if hasattr(f
, 'seek') and callable(f
.seek
):
519 r
'^https?://' # http:// or https://
520 r
'(?:(?:[A-Z0-9-]+\.)+[A-Z]{2,6}|' #domain...
521 r
'localhost|' #localhost...
522 r
'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
523 r
'(?::\d+)?' # optional port
524 r
'(?:/?|/\S+)$', re
.IGNORECASE
)
526 class URLField(RegexField
):
527 default_error_messages
= {
528 'invalid': _(u
'Enter a valid URL.'),
529 'invalid_link': _(u
'This URL appears to be a broken link.'),
532 def __init__(self
, max_length
=None, min_length
=None, verify_exists
=False,
533 validator_user_agent
=URL_VALIDATOR_USER_AGENT
, *args
, **kwargs
):
534 super(URLField
, self
).__init
__(url_re
, max_length
, min_length
, *args
,
536 self
.verify_exists
= verify_exists
537 self
.user_agent
= validator_user_agent
539 def clean(self
, value
):
540 # If no URL scheme given, assume http://
541 if value
and '://' not in value
:
542 value
= u
'http://%s' % value
543 # If no URL path given, assume /
544 if value
and not urlparse
.urlsplit(value
).path
:
546 value
= super(URLField
, self
).clean(value
)
549 if self
.verify_exists
:
552 "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
553 "Accept-Language": "en-us,en;q=0.5",
554 "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
555 "Connection": "close",
556 "User-Agent": self
.user_agent
,
559 req
= urllib2
.Request(value
, None, headers
)
560 u
= urllib2
.urlopen(req
)
562 raise ValidationError(self
.error_messages
['invalid'])
563 except: # urllib2.URLError, httplib.InvalidURL, etc.
564 raise ValidationError(self
.error_messages
['invalid_link'])
567 class BooleanField(Field
):
568 widget
= CheckboxInput
570 def clean(self
, value
):
571 """Returns a Python boolean object."""
572 # Explicitly check for the string 'False', which is what a hidden field
573 # will submit for False. Because bool("True") == True, we don't need to
574 # handle that explicitly.
579 super(BooleanField
, self
).clean(value
)
580 if not value
and self
.required
:
581 raise ValidationError(self
.error_messages
['required'])
584 class NullBooleanField(BooleanField
):
586 A field whose valid values are None, True and False. Invalid values are
589 widget
= NullBooleanSelect
591 def clean(self
, value
):
592 return {True: True, False: False}.get(value
, None)
594 class ChoiceField(Field
):
596 default_error_messages
= {
597 'invalid_choice': _(u
'Select a valid choice. %(value)s is not one of the available choices.'),
600 def __init__(self
, choices
=(), required
=True, widget
=None, label
=None,
601 initial
=None, help_text
=None, *args
, **kwargs
):
602 super(ChoiceField
, self
).__init
__(required
, widget
, label
, initial
,
603 help_text
, *args
, **kwargs
)
604 self
.choices
= choices
606 def _get_choices(self
):
609 def _set_choices(self
, value
):
610 # Setting choices also sets the choices on the widget.
611 # choices can be any iterable, but we call list() on it because
612 # it will be consumed more than once.
613 self
._choices
= self
.widget
.choices
= list(value
)
615 choices
= property(_get_choices
, _set_choices
)
617 def clean(self
, value
):
619 Validates that the input is in self.choices.
621 value
= super(ChoiceField
, self
).clean(value
)
622 if value
in EMPTY_VALUES
:
624 value
= smart_unicode(value
)
627 if not self
.valid_value(value
):
628 raise ValidationError(self
.error_messages
['invalid_choice'] % {'value': value
})
631 def valid_value(self
, value
):
632 "Check to see if the provided value is a valid choice"
633 for k
, v
in self
.choices
:
634 if type(v
) in (tuple, list):
635 # This is an optgroup, so look inside the group for options
637 if value
== smart_unicode(k2
):
640 if value
== smart_unicode(k
):
644 class MultipleChoiceField(ChoiceField
):
645 hidden_widget
= MultipleHiddenInput
646 widget
= SelectMultiple
647 default_error_messages
= {
648 'invalid_choice': _(u
'Select a valid choice. %(value)s is not one of the available choices.'),
649 'invalid_list': _(u
'Enter a list of values.'),
652 def clean(self
, value
):
654 Validates that the input is a list or tuple.
656 if self
.required
and not value
:
657 raise ValidationError(self
.error_messages
['required'])
658 elif not self
.required
and not value
:
660 if not isinstance(value
, (list, tuple)):
661 raise ValidationError(self
.error_messages
['invalid_list'])
662 new_value
= [smart_unicode(val
) for val
in value
]
663 # Validate that each value in the value list is in self.choices.
664 for val
in new_value
:
665 if not self
.valid_value(val
):
666 raise ValidationError(self
.error_messages
['invalid_choice'] % {'value': val
})
669 class ComboField(Field
):
671 A Field whose clean() method calls multiple Field clean() methods.
673 def __init__(self
, fields
=(), *args
, **kwargs
):
674 super(ComboField
, self
).__init
__(*args
, **kwargs
)
675 # Set 'required' to False on the individual fields, because the
676 # required validation will be handled by ComboField, not by those
682 def clean(self
, value
):
684 Validates the given value against all of self.fields, which is a
685 list of Field instances.
687 super(ComboField
, self
).clean(value
)
688 for field
in self
.fields
:
689 value
= field
.clean(value
)
692 class MultiValueField(Field
):
694 A Field that aggregates the logic of multiple Fields.
696 Its clean() method takes a "decompressed" list of values, which are then
697 cleaned into a single value according to self.fields. Each value in
698 this list is cleaned by the corresponding field -- the first value is
699 cleaned by the first field, the second value is cleaned by the second
700 field, etc. Once all fields are cleaned, the list of clean values is
701 "compressed" into a single value.
703 Subclasses should not have to implement clean(). Instead, they must
704 implement compress(), which takes a list of valid values and returns a
705 "compressed" version of those values -- a single value.
707 You'll probably want to use this with MultiWidget.
709 default_error_messages
= {
710 'invalid': _(u
'Enter a list of values.'),
713 def __init__(self
, fields
=(), *args
, **kwargs
):
714 super(MultiValueField
, self
).__init
__(*args
, **kwargs
)
715 # Set 'required' to False on the individual fields, because the
716 # required validation will be handled by MultiValueField, not by those
722 def clean(self
, value
):
724 Validates every value in the given list. A value is validated against
725 the corresponding Field in self.fields.
727 For example, if this MultiValueField was instantiated with
728 fields=(DateField(), TimeField()), clean() would call
729 DateField.clean(value[0]) and TimeField.clean(value[1]).
733 if not value
or isinstance(value
, (list, tuple)):
734 if not value
or not [v
for v
in value
if v
not in EMPTY_VALUES
]:
736 raise ValidationError(self
.error_messages
['required'])
738 return self
.compress([])
740 raise ValidationError(self
.error_messages
['invalid'])
741 for i
, field
in enumerate(self
.fields
):
743 field_value
= value
[i
]
746 if self
.required
and field_value
in EMPTY_VALUES
:
747 raise ValidationError(self
.error_messages
['required'])
749 clean_data
.append(field
.clean(field_value
))
750 except ValidationError
, e
:
751 # Collect all validation errors in a single list, which we'll
752 # raise at the end of clean(), rather than raising a single
753 # exception for the first error we encounter.
754 errors
.extend(e
.messages
)
756 raise ValidationError(errors
)
757 return self
.compress(clean_data
)
759 def compress(self
, data_list
):
761 Returns a single value for the given list of values. The values can be
764 For example, if this MultiValueField was instantiated with
765 fields=(DateField(), TimeField()), this might return a datetime
766 object created by combining the date and time in data_list.
768 raise NotImplementedError('Subclasses must implement this method.')
770 class FilePathField(ChoiceField
):
771 def __init__(self
, path
, match
=None, recursive
=False, required
=True,
772 widget
=Select
, label
=None, initial
=None, help_text
=None,
774 self
.path
, self
.match
, self
.recursive
= path
, match
, recursive
775 super(FilePathField
, self
).__init
__(choices
=(), required
=required
,
776 widget
=widget
, label
=label
, initial
=initial
, help_text
=help_text
,
779 if self
.match
is not None:
780 self
.match_re
= re
.compile(self
.match
)
782 for root
, dirs
, files
in os
.walk(self
.path
):
784 if self
.match
is None or self
.match_re
.search(f
):
785 f
= os
.path
.join(root
, f
)
786 self
.choices
.append((f
, f
.replace(path
, "", 1)))
789 for f
in os
.listdir(self
.path
):
790 full_file
= os
.path
.join(self
.path
, f
)
791 if os
.path
.isfile(full_file
) and (self
.match
is None or self
.match_re
.search(f
)):
792 self
.choices
.append((full_file
, f
))
795 self
.widget
.choices
= self
.choices
797 class SplitDateTimeField(MultiValueField
):
798 default_error_messages
= {
799 'invalid_date': _(u
'Enter a valid date.'),
800 'invalid_time': _(u
'Enter a valid time.'),
803 def __init__(self
, *args
, **kwargs
):
804 errors
= self
.default_error_messages
.copy()
805 if 'error_messages' in kwargs
:
806 errors
.update(kwargs
['error_messages'])
808 DateField(error_messages
={'invalid': errors
['invalid_date']}),
809 TimeField(error_messages
={'invalid': errors
['invalid_time']}),
811 super(SplitDateTimeField
, self
).__init
__(fields
, *args
, **kwargs
)
813 def compress(self
, data_list
):
815 # Raise a validation error if time or date is empty
816 # (possible if SplitDateTimeField has required=False).
817 if data_list
[0] in EMPTY_VALUES
:
818 raise ValidationError(self
.error_messages
['invalid_date'])
819 if data_list
[1] in EMPTY_VALUES
:
820 raise ValidationError(self
.error_messages
['invalid_time'])
821 return datetime
.datetime
.combine(*data_list
)
824 ipv4_re
= re
.compile(r
'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
826 class IPAddressField(RegexField
):
827 default_error_messages
= {
828 'invalid': _(u
'Enter a valid IPv4 address.'),
831 def __init__(self
, *args
, **kwargs
):
832 super(IPAddressField
, self
).__init
__(ipv4_re
, *args
, **kwargs
)