Fixed #7345 -- When normalising the URLField form field, attach a trailing
[django.git] / django / forms / fields.py
blob6b834af0c46f4ce81201e2872402b71f41c7eddf
1 """
2 Field classes.
3 """
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
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
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
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',
44 # These values, if given to to_python(), will trigger the self.required check.
45 EMPTY_VALUES = (None, '')
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.'),
56 # Tracks each time a Field instance is created. Used to retain order.
57 creation_counter = 0
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 self.help_text = smart_unicode(help_text or '')
78 widget = widget or self.widget
79 if isinstance(widget, type):
80 widget = widget()
82 # Hook into self.widget_attrs() for any Field-specific HTML attributes.
83 extra_attrs = self.widget_attrs(widget)
84 if extra_attrs:
85 widget.attrs.update(extra_attrs)
87 self.widget = widget
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', {}))
98 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'])
112 return value
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
118 Field.
120 return {}
122 def __deepcopy__(self, memo):
123 result = copy.copy(self)
124 memo[id(self)] = result
125 result.widget = copy.deepcopy(self.widget, memo)
126 return result
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:
142 return u''
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})
149 return value
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:
174 return None
175 try:
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)
183 return 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:
203 return None
204 try:
205 value = float(value)
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)
212 return 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:
238 return None
239 value = smart_str(value).strip()
240 try:
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))
257 return value
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:
283 return None
284 if isinstance(value, datetime.datetime):
285 return value.date()
286 if isinstance(value, datetime.date):
287 return value
288 for format in self.input_formats:
289 try:
290 return datetime.date(*time.strptime(value, format)[:3])
291 except ValueError:
292 continue
293 raise ValidationError(self.error_messages['invalid'])
295 DEFAULT_TIME_INPUT_FORMATS = (
296 '%H:%M:%S', # '14:30:59'
297 '%H:%M', # '14:30'
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:
316 return None
317 if isinstance(value, datetime.time):
318 return value
319 for format in self.input_formats:
320 try:
321 return datetime.time(*time.strptime(value, format)[3:6])
322 except ValueError:
323 continue
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:
355 return None
356 if isinstance(value, datetime.datetime):
357 return value
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.
363 if len(value) != 2:
364 raise ValidationError(self.error_messages['invalid'])
365 value = '%s %s' % tuple(value)
366 for format in self.input_formats:
367 try:
368 return datetime.datetime(*time.strptime(value, format)[:6])
369 except ValueError:
370 continue
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:
381 if error_message:
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)
388 self.regex = regex
390 def clean(self, value):
392 Validates that the input matches the regular expression. Returns a
393 Unicode object.
395 value = super(RegexField, self).clean(value)
396 if value == u'':
397 return value
398 if not self.regex.search(value):
399 raise ValidationError(self.error_messages['invalid'])
400 return value
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,
414 **kwargs)
416 try:
417 from django.conf import settings
418 URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT
419 except ImportError:
420 # It's OK if Django settings aren't configured.
421 URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
424 class FileField(Field):
425 widget = FileInput
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:
438 return None
439 elif not data and initial:
440 return initial
442 if isinstance(data, dict):
443 # We warn once, then support both ways below.
444 import warnings
445 warnings.warn(
446 message = "Representing uploaded files as dictionaries is deprecated. Use django.core.files.uploadedfile.SimpleUploadedFile instead.",
447 category = DeprecationWarning,
448 stacklevel = 2
450 data = UploadedFile(data['filename'], data['content'])
452 try:
453 file_name = data.name
454 file_size = data.size
455 except AttributeError:
456 raise ValidationError(self.error_messages['invalid'])
458 if not file_name:
459 raise ValidationError(self.error_messages['invalid'])
460 if not file_size:
461 raise ValidationError(self.error_messages['empty'])
463 return data
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)
476 if f is None:
477 return None
478 elif not data and initial:
479 return 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()
486 else:
487 if hasattr(data, 'read'):
488 file = StringIO(data.read())
489 else:
490 file = StringIO(data['content'])
492 try:
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)
496 trial_image.load()
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'):
501 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)
506 trial_image.verify()
507 except ImportError:
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.
511 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):
515 f.seek(0)
516 return f
518 url_re = re.compile(
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,
535 **kwargs)
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:
545 value += '/'
546 value = super(URLField, self).clean(value)
547 if value == u'':
548 return value
549 if self.verify_exists:
550 import urllib2
551 headers = {
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,
558 try:
559 req = urllib2.Request(value, None, headers)
560 u = urllib2.urlopen(req)
561 except ValueError:
562 raise ValidationError(self.error_messages['invalid'])
563 except: # urllib2.URLError, httplib.InvalidURL, etc.
564 raise ValidationError(self.error_messages['invalid_link'])
565 return value
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.
575 if value == 'False':
576 value = False
577 else:
578 value = bool(value)
579 super(BooleanField, self).clean(value)
580 if not value and self.required:
581 raise ValidationError(self.error_messages['required'])
582 return value
584 class NullBooleanField(BooleanField):
586 A field whose valid values are None, True and False. Invalid values are
587 cleaned to None.
589 widget = NullBooleanSelect
591 def clean(self, value):
592 return {True: True, False: False}.get(value, None)
594 class ChoiceField(Field):
595 widget = Select
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):
607 return self._choices
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:
623 value = u''
624 value = smart_unicode(value)
625 if value == u'':
626 return value
627 if not self.valid_value(value):
628 raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
629 return 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
636 for k2, v2 in v:
637 if value == smart_unicode(k2):
638 return True
639 else:
640 if value == smart_unicode(k):
641 return True
642 return False
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:
659 return []
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})
667 return new_value
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
677 # individual fields.
678 for f in fields:
679 f.required = False
680 self.fields = fields
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)
690 return 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
717 # individual fields.
718 for f in fields:
719 f.required = False
720 self.fields = fields
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]).
731 clean_data = []
732 errors = ErrorList()
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]:
735 if self.required:
736 raise ValidationError(self.error_messages['required'])
737 else:
738 return self.compress([])
739 else:
740 raise ValidationError(self.error_messages['invalid'])
741 for i, field in enumerate(self.fields):
742 try:
743 field_value = value[i]
744 except IndexError:
745 field_value = None
746 if self.required and field_value in EMPTY_VALUES:
747 raise ValidationError(self.error_messages['required'])
748 try:
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)
755 if errors:
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
762 assumed to be valid.
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,
773 *args, **kwargs):
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,
777 *args, **kwargs)
778 self.choices = []
779 if self.match is not None:
780 self.match_re = re.compile(self.match)
781 if recursive:
782 for root, dirs, files in os.walk(self.path):
783 for f in files:
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)))
787 else:
788 try:
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))
793 except OSError:
794 pass
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'])
807 fields = (
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):
814 if 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)
822 return None
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)