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
import django.core.exceptions
27
from django.utils.translation import ugettext_lazy as _
28
from django.utils.encoding import smart_unicode, smart_str
30
from util import ErrorList, ValidationError
31
from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput, TimeInput, SplitHiddenDateTimeWidget
32
from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile
35
'Field', 'CharField', 'IntegerField',
36
'DEFAULT_DATE_INPUT_FORMATS', 'DateField',
37
'DEFAULT_TIME_INPUT_FORMATS', 'TimeField',
38
'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField', 'TimeField',
39
'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
40
'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
41
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
42
'SplitDateTimeField', 'IPAddressField', 'FilePathField', 'SlugField',
46
# These values, if given to to_python(), will trigger the self.required check.
47
EMPTY_VALUES = (None, '')
51
widget = TextInput # Default widget to use when rendering this type of Field.
52
hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden".
53
default_error_messages = {
54
'required': _(u'This field is required.'),
55
'invalid': _(u'Enter a valid value.'),
58
# Tracks each time a Field instance is created. Used to retain order.
61
def __init__(self, required=True, widget=None, label=None, initial=None,
62
help_text=None, error_messages=None, show_hidden_initial=False):
63
# required -- Boolean that specifies whether the field is required.
65
# widget -- A Widget class, or instance of a Widget class, that should
66
# be used for this Field when displaying it. Each Field has a
67
# default Widget that it'll use if you don't specify this. In
68
# most cases, the default widget is TextInput.
69
# label -- A verbose name for this field, for use in displaying this
70
# field in a form. By default, Django will use a "pretty"
71
# version of the form field name, if the Field is part of a
73
# initial -- A value to use in this Field's initial display. This value
74
# is *not* used as a fallback if data isn't given.
75
# help_text -- An optional string to use as "help text" for this Field.
76
# show_hidden_initial -- Boolean that specifies if it is needed to render a
77
# hidden widget with initial value after widget.
79
label = smart_unicode(label)
80
self.required, self.label, self.initial = required, label, initial
81
self.show_hidden_initial = show_hidden_initial
85
self.help_text = smart_unicode(help_text)
86
widget = widget or self.widget
87
if isinstance(widget, type):
90
# Hook into self.widget_attrs() for any Field-specific HTML attributes.
91
extra_attrs = self.widget_attrs(widget)
93
widget.attrs.update(extra_attrs)
97
# Increase the creation counter, and save our local copy.
98
self.creation_counter = Field.creation_counter
99
Field.creation_counter += 1
101
def set_class_error_messages(messages, klass):
102
for base_class in klass.__bases__:
103
set_class_error_messages(messages, base_class)
104
messages.update(getattr(klass, 'default_error_messages', {}))
107
set_class_error_messages(messages, self.__class__)
108
messages.update(error_messages or {})
109
self.error_messages = messages
111
def clean(self, value):
113
Validates the given value and returns its "cleaned" value as an
114
appropriate Python object.
116
Raises ValidationError for any errors.
118
if self.required and value in EMPTY_VALUES:
119
raise ValidationError(self.error_messages['required'])
122
def widget_attrs(self, widget):
124
Given a Widget instance (*not* a Widget class), returns a dictionary of
125
any HTML attributes that should be added to the Widget, based on this
130
def __deepcopy__(self, memo):
131
result = copy.copy(self)
132
memo[id(self)] = result
133
result.widget = copy.deepcopy(self.widget, memo)
136
class CharField(Field):
137
default_error_messages = {
138
'max_length': _(u'Ensure this value has at most %(max)d characters (it has %(length)d).'),
139
'min_length': _(u'Ensure this value has at least %(min)d characters (it has %(length)d).'),
142
def __init__(self, max_length=None, min_length=None, *args, **kwargs):
143
self.max_length, self.min_length = max_length, min_length
144
super(CharField, self).__init__(*args, **kwargs)
146
def clean(self, value):
147
"Validates max_length and min_length. Returns a Unicode object."
148
super(CharField, self).clean(value)
149
if value in EMPTY_VALUES:
151
value = smart_unicode(value)
152
value_length = len(value)
153
if self.max_length is not None and value_length > self.max_length:
154
raise ValidationError(self.error_messages['max_length'] % {'max': self.max_length, 'length': value_length})
155
if self.min_length is not None and value_length < self.min_length:
156
raise ValidationError(self.error_messages['min_length'] % {'min': self.min_length, 'length': value_length})
159
def widget_attrs(self, widget):
160
if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)):
161
# The HTML attribute is maxlength, not max_length.
162
return {'maxlength': str(self.max_length)}
164
class IntegerField(Field):
165
default_error_messages = {
166
'invalid': _(u'Enter a whole number.'),
167
'max_value': _(u'Ensure this value is less than or equal to %s.'),
168
'min_value': _(u'Ensure this value is greater than or equal to %s.'),
171
def __init__(self, max_value=None, min_value=None, *args, **kwargs):
172
self.max_value, self.min_value = max_value, min_value
173
super(IntegerField, self).__init__(*args, **kwargs)
175
def clean(self, value):
177
Validates that int() can be called on the input. Returns the result
178
of int(). Returns None for empty values.
180
super(IntegerField, self).clean(value)
181
if value in EMPTY_VALUES:
184
value = int(str(value))
185
except (ValueError, TypeError):
186
raise ValidationError(self.error_messages['invalid'])
187
if self.max_value is not None and value > self.max_value:
188
raise ValidationError(self.error_messages['max_value'] % self.max_value)
189
if self.min_value is not None and value < self.min_value:
190
raise ValidationError(self.error_messages['min_value'] % self.min_value)
193
class FloatField(Field):
194
default_error_messages = {
195
'invalid': _(u'Enter a number.'),
196
'max_value': _(u'Ensure this value is less than or equal to %s.'),
197
'min_value': _(u'Ensure this value is greater than or equal to %s.'),
200
def __init__(self, max_value=None, min_value=None, *args, **kwargs):
201
self.max_value, self.min_value = max_value, min_value
202
Field.__init__(self, *args, **kwargs)
204
def clean(self, value):
206
Validates that float() can be called on the input. Returns a float.
207
Returns None for empty values.
209
super(FloatField, self).clean(value)
210
if not self.required and value in EMPTY_VALUES:
214
except (ValueError, TypeError):
215
raise ValidationError(self.error_messages['invalid'])
216
if self.max_value is not None and value > self.max_value:
217
raise ValidationError(self.error_messages['max_value'] % self.max_value)
218
if self.min_value is not None and value < self.min_value:
219
raise ValidationError(self.error_messages['min_value'] % self.min_value)
222
class DecimalField(Field):
223
default_error_messages = {
224
'invalid': _(u'Enter a number.'),
225
'max_value': _(u'Ensure this value is less than or equal to %s.'),
226
'min_value': _(u'Ensure this value is greater than or equal to %s.'),
227
'max_digits': _('Ensure that there are no more than %s digits in total.'),
228
'max_decimal_places': _('Ensure that there are no more than %s decimal places.'),
229
'max_whole_digits': _('Ensure that there are no more than %s digits before the decimal point.')
232
def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs):
233
self.max_value, self.min_value = max_value, min_value
234
self.max_digits, self.decimal_places = max_digits, decimal_places
235
Field.__init__(self, *args, **kwargs)
237
def clean(self, value):
239
Validates that the input is a decimal number. Returns a Decimal
240
instance. Returns None for empty values. Ensures that there are no more
241
than max_digits in the number, and no more than decimal_places digits
242
after the decimal point.
244
super(DecimalField, self).clean(value)
245
if not self.required and value in EMPTY_VALUES:
247
value = smart_str(value).strip()
249
value = Decimal(value)
250
except DecimalException:
251
raise ValidationError(self.error_messages['invalid'])
253
sign, digittuple, exponent = value.as_tuple()
254
decimals = abs(exponent)
255
# digittuple doesn't include any leading zeros.
256
digits = len(digittuple)
257
if decimals >= digits:
258
# We have leading zeros up to or past the decimal point. Count
259
# everything past the decimal point as a digit. We also add one
260
# for leading zeros before the decimal point (any number of leading
261
# whole zeros collapse to one digit).
262
digits = decimals + 1
263
whole_digits = digits - decimals
265
if self.max_value is not None and value > self.max_value:
266
raise ValidationError(self.error_messages['max_value'] % self.max_value)
267
if self.min_value is not None and value < self.min_value:
268
raise ValidationError(self.error_messages['min_value'] % self.min_value)
269
if self.max_digits is not None and digits > self.max_digits:
270
raise ValidationError(self.error_messages['max_digits'] % self.max_digits)
271
if self.decimal_places is not None and decimals > self.decimal_places:
272
raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places)
273
if self.max_digits is not None and self.decimal_places is not None and whole_digits > (self.max_digits - self.decimal_places):
274
raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places))
277
DEFAULT_DATE_INPUT_FORMATS = (
278
'%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
279
'%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006'
280
'%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006'
281
'%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006'
282
'%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006'
285
class DateField(Field):
286
default_error_messages = {
287
'invalid': _(u'Enter a valid date.'),
290
def __init__(self, input_formats=None, *args, **kwargs):
291
super(DateField, self).__init__(*args, **kwargs)
292
self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
294
def clean(self, value):
296
Validates that the input can be converted to a date. Returns a Python
297
datetime.date object.
299
super(DateField, self).clean(value)
300
if value in EMPTY_VALUES:
302
if isinstance(value, datetime.datetime):
304
if isinstance(value, datetime.date):
306
for format in self.input_formats:
308
return datetime.date(*time.strptime(value, format)[:3])
311
raise ValidationError(self.error_messages['invalid'])
313
DEFAULT_TIME_INPUT_FORMATS = (
314
'%H:%M:%S', # '14:30:59'
318
class TimeField(Field):
320
default_error_messages = {
321
'invalid': _(u'Enter a valid time.')
324
def __init__(self, input_formats=None, *args, **kwargs):
325
super(TimeField, self).__init__(*args, **kwargs)
326
self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS
328
def clean(self, value):
330
Validates that the input can be converted to a time. Returns a Python
331
datetime.time object.
333
super(TimeField, self).clean(value)
334
if value in EMPTY_VALUES:
336
if isinstance(value, datetime.time):
338
for format in self.input_formats:
340
return datetime.time(*time.strptime(value, format)[3:6])
343
raise ValidationError(self.error_messages['invalid'])
345
DEFAULT_DATETIME_INPUT_FORMATS = (
346
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
347
'%Y-%m-%d %H:%M', # '2006-10-25 14:30'
348
'%Y-%m-%d', # '2006-10-25'
349
'%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59'
350
'%m/%d/%Y %H:%M', # '10/25/2006 14:30'
351
'%m/%d/%Y', # '10/25/2006'
352
'%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59'
353
'%m/%d/%y %H:%M', # '10/25/06 14:30'
354
'%m/%d/%y', # '10/25/06'
357
class DateTimeField(Field):
358
widget = DateTimeInput
359
default_error_messages = {
360
'invalid': _(u'Enter a valid date/time.'),
363
def __init__(self, input_formats=None, *args, **kwargs):
364
super(DateTimeField, self).__init__(*args, **kwargs)
365
self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
367
def clean(self, value):
369
Validates that the input can be converted to a datetime. Returns a
370
Python datetime.datetime object.
372
super(DateTimeField, self).clean(value)
373
if value in EMPTY_VALUES:
375
if isinstance(value, datetime.datetime):
377
if isinstance(value, datetime.date):
378
return datetime.datetime(value.year, value.month, value.day)
379
if isinstance(value, list):
380
# Input comes from a SplitDateTimeWidget, for example. So, it's two
381
# components: date and time.
383
raise ValidationError(self.error_messages['invalid'])
384
value = '%s %s' % tuple(value)
385
for format in self.input_formats:
387
return datetime.datetime(*time.strptime(value, format)[:6])
390
raise ValidationError(self.error_messages['invalid'])
392
class RegexField(CharField):
393
def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs):
395
regex can be either a string or a compiled regular expression object.
396
error_message is an optional error message to use, if
397
'Enter a valid value' is too generic for you.
399
# error_message is just kept for backwards compatibility:
401
error_messages = kwargs.get('error_messages') or {}
402
error_messages['invalid'] = error_message
403
kwargs['error_messages'] = error_messages
404
super(RegexField, self).__init__(max_length, min_length, *args, **kwargs)
405
if isinstance(regex, basestring):
406
regex = re.compile(regex)
409
def clean(self, value):
411
Validates that the input matches the regular expression. Returns a
414
value = super(RegexField, self).clean(value)
417
if not self.regex.search(value):
418
raise ValidationError(self.error_messages['invalid'])
421
email_re = re.compile(
422
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom
423
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string
424
r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain
426
class EmailField(RegexField):
427
default_error_messages = {
428
'invalid': _(u'Enter a valid e-mail address.'),
431
def __init__(self, max_length=None, min_length=None, *args, **kwargs):
432
RegexField.__init__(self, email_re, max_length, min_length, *args,
436
from django.conf import settings
437
URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT
439
# It's OK if Django settings aren't configured.
440
URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
443
class FileField(Field):
445
default_error_messages = {
446
'invalid': _(u"No file was submitted. Check the encoding type on the form."),
447
'missing': _(u"No file was submitted."),
448
'empty': _(u"The submitted file is empty."),
451
def __init__(self, *args, **kwargs):
452
super(FileField, self).__init__(*args, **kwargs)
454
def clean(self, data, initial=None):
455
super(FileField, self).clean(initial or data)
456
if not self.required and data in EMPTY_VALUES:
458
elif not data and initial:
461
# UploadedFile objects should have name and size attributes.
463
file_name = data.name
464
file_size = data.size
465
except AttributeError:
466
raise ValidationError(self.error_messages['invalid'])
469
raise ValidationError(self.error_messages['invalid'])
471
raise ValidationError(self.error_messages['empty'])
475
class ImageField(FileField):
476
default_error_messages = {
477
'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."),
480
def clean(self, data, initial=None):
482
Checks that the file-upload field data contains a valid image (GIF, JPG,
483
PNG, possibly others -- whatever the Python Imaging Library supports).
485
f = super(ImageField, self).clean(data, initial)
488
elif not data and initial:
490
from PIL import Image
492
# We need to get a file object for PIL. We might have a path or we might
493
# have to read the data into memory.
494
if hasattr(data, 'temporary_file_path'):
495
file = data.temporary_file_path()
497
if hasattr(data, 'read'):
498
file = StringIO(data.read())
500
file = StringIO(data['content'])
503
# load() is the only method that can spot a truncated JPEG,
504
# but it cannot be called sanely after verify()
505
trial_image = Image.open(file)
508
# Since we're about to use the file again we have to reset the
509
# file object if possible.
510
if hasattr(file, 'reset'):
513
# verify() is the only method that can spot a corrupt PNG,
514
# but it must be called immediately after the constructor
515
trial_image = Image.open(file)
518
# Under PyPy, it is possible to import PIL. However, the underlying
519
# _imaging C module isn't available, so an ImportError will be
520
# raised. Catch and re-raise.
522
except Exception: # Python Imaging Library doesn't recognize it as an image
523
raise ValidationError(self.error_messages['invalid_image'])
524
if hasattr(f, 'seek') and callable(f.seek):
529
r'^https?://' # http:// or https://
530
r'(?:(?:[A-Z0-9-]+\.)+[A-Z]{2,6}|' #domain...
531
r'localhost|' #localhost...
532
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
533
r'(?::\d+)?' # optional port
534
r'(?:/?|/\S+)$', re.IGNORECASE)
536
class URLField(RegexField):
537
default_error_messages = {
538
'invalid': _(u'Enter a valid URL.'),
539
'invalid_link': _(u'This URL appears to be a broken link.'),
542
def __init__(self, max_length=None, min_length=None, verify_exists=False,
543
validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs):
544
super(URLField, self).__init__(url_re, max_length, min_length, *args,
546
self.verify_exists = verify_exists
547
self.user_agent = validator_user_agent
549
def clean(self, value):
550
# If no URL scheme given, assume http://
551
if value and '://' not in value:
552
value = u'http://%s' % value
553
# If no URL path given, assume /
554
if value and not urlparse.urlsplit(value)[2]:
556
value = super(URLField, self).clean(value)
559
if self.verify_exists:
562
"Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
563
"Accept-Language": "en-us,en;q=0.5",
564
"Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
565
"Connection": "close",
566
"User-Agent": self.user_agent,
569
req = urllib2.Request(value, None, headers)
570
u = urllib2.urlopen(req)
572
raise ValidationError(self.error_messages['invalid'])
573
except: # urllib2.URLError, httplib.InvalidURL, etc.
574
raise ValidationError(self.error_messages['invalid_link'])
577
class BooleanField(Field):
578
widget = CheckboxInput
580
def clean(self, value):
581
"""Returns a Python boolean object."""
582
# Explicitly check for the string 'False', which is what a hidden field
583
# will submit for False. Because bool("True") == True, we don't need to
584
# handle that explicitly.
589
super(BooleanField, self).clean(value)
590
if not value and self.required:
591
raise ValidationError(self.error_messages['required'])
594
class NullBooleanField(BooleanField):
596
A field whose valid values are None, True and False. Invalid values are
599
widget = NullBooleanSelect
601
def clean(self, value):
603
Explicitly checks for the string 'True' and 'False', which is what a
604
hidden field will submit for True and False. Unlike the
605
Booleanfield we also need to check for True, because we are not using
608
if value in (True, 'True'):
610
elif value in (False, 'False'):
615
class ChoiceField(Field):
617
default_error_messages = {
618
'invalid_choice': _(u'Select a valid choice. %(value)s is not one of the available choices.'),
621
def __init__(self, choices=(), required=True, widget=None, label=None,
622
initial=None, help_text=None, *args, **kwargs):
623
super(ChoiceField, self).__init__(required, widget, label, initial,
624
help_text, *args, **kwargs)
625
self.choices = choices
627
def _get_choices(self):
630
def _set_choices(self, value):
631
# Setting choices also sets the choices on the widget.
632
# choices can be any iterable, but we call list() on it because
633
# it will be consumed more than once.
634
self._choices = self.widget.choices = list(value)
636
choices = property(_get_choices, _set_choices)
638
def clean(self, value):
640
Validates that the input is in self.choices.
642
value = super(ChoiceField, self).clean(value)
643
if value in EMPTY_VALUES:
645
value = smart_unicode(value)
648
if not self.valid_value(value):
649
raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
652
def valid_value(self, value):
653
"Check to see if the provided value is a valid choice"
654
for k, v in self.choices:
655
if type(v) in (tuple, list):
656
# This is an optgroup, so look inside the group for options
658
if value == smart_unicode(k2):
661
if value == smart_unicode(k):
665
class TypedChoiceField(ChoiceField):
666
def __init__(self, *args, **kwargs):
667
self.coerce = kwargs.pop('coerce', lambda val: val)
668
self.empty_value = kwargs.pop('empty_value', '')
669
super(TypedChoiceField, self).__init__(*args, **kwargs)
671
def clean(self, value):
673
Validate that the value is in self.choices and can be coerced to the
676
value = super(TypedChoiceField, self).clean(value)
677
if value == self.empty_value or value in EMPTY_VALUES:
678
return self.empty_value
680
# Hack alert: This field is purpose-made to use with Field.to_python as
681
# a coercion function so that ModelForms with choices work. However,
682
# Django's Field.to_python raises django.core.exceptions.ValidationError,
683
# which is a *different* exception than
684
# django.forms.utils.ValidationError. So unfortunatly we need to catch
687
value = self.coerce(value)
688
except (ValueError, TypeError, django.core.exceptions.ValidationError):
689
raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
692
class MultipleChoiceField(ChoiceField):
693
hidden_widget = MultipleHiddenInput
694
widget = SelectMultiple
695
default_error_messages = {
696
'invalid_choice': _(u'Select a valid choice. %(value)s is not one of the available choices.'),
697
'invalid_list': _(u'Enter a list of values.'),
700
def clean(self, value):
702
Validates that the input is a list or tuple.
704
if self.required and not value:
705
raise ValidationError(self.error_messages['required'])
706
elif not self.required and not value:
708
if not isinstance(value, (list, tuple)):
709
raise ValidationError(self.error_messages['invalid_list'])
710
new_value = [smart_unicode(val) for val in value]
711
# Validate that each value in the value list is in self.choices.
712
for val in new_value:
713
if not self.valid_value(val):
714
raise ValidationError(self.error_messages['invalid_choice'] % {'value': val})
717
class ComboField(Field):
719
A Field whose clean() method calls multiple Field clean() methods.
721
def __init__(self, fields=(), *args, **kwargs):
722
super(ComboField, self).__init__(*args, **kwargs)
723
# Set 'required' to False on the individual fields, because the
724
# required validation will be handled by ComboField, not by those
730
def clean(self, value):
732
Validates the given value against all of self.fields, which is a
733
list of Field instances.
735
super(ComboField, self).clean(value)
736
for field in self.fields:
737
value = field.clean(value)
740
class MultiValueField(Field):
742
A Field that aggregates the logic of multiple Fields.
744
Its clean() method takes a "decompressed" list of values, which are then
745
cleaned into a single value according to self.fields. Each value in
746
this list is cleaned by the corresponding field -- the first value is
747
cleaned by the first field, the second value is cleaned by the second
748
field, etc. Once all fields are cleaned, the list of clean values is
749
"compressed" into a single value.
751
Subclasses should not have to implement clean(). Instead, they must
752
implement compress(), which takes a list of valid values and returns a
753
"compressed" version of those values -- a single value.
755
You'll probably want to use this with MultiWidget.
757
default_error_messages = {
758
'invalid': _(u'Enter a list of values.'),
761
def __init__(self, fields=(), *args, **kwargs):
762
super(MultiValueField, self).__init__(*args, **kwargs)
763
# Set 'required' to False on the individual fields, because the
764
# required validation will be handled by MultiValueField, not by those
770
def clean(self, value):
772
Validates every value in the given list. A value is validated against
773
the corresponding Field in self.fields.
775
For example, if this MultiValueField was instantiated with
776
fields=(DateField(), TimeField()), clean() would call
777
DateField.clean(value[0]) and TimeField.clean(value[1]).
781
if not value or isinstance(value, (list, tuple)):
782
if not value or not [v for v in value if v not in EMPTY_VALUES]:
784
raise ValidationError(self.error_messages['required'])
786
return self.compress([])
788
raise ValidationError(self.error_messages['invalid'])
789
for i, field in enumerate(self.fields):
791
field_value = value[i]
794
if self.required and field_value in EMPTY_VALUES:
795
raise ValidationError(self.error_messages['required'])
797
clean_data.append(field.clean(field_value))
798
except ValidationError, e:
799
# Collect all validation errors in a single list, which we'll
800
# raise at the end of clean(), rather than raising a single
801
# exception for the first error we encounter.
802
errors.extend(e.messages)
804
raise ValidationError(errors)
805
return self.compress(clean_data)
807
def compress(self, data_list):
809
Returns a single value for the given list of values. The values can be
812
For example, if this MultiValueField was instantiated with
813
fields=(DateField(), TimeField()), this might return a datetime
814
object created by combining the date and time in data_list.
816
raise NotImplementedError('Subclasses must implement this method.')
818
class FilePathField(ChoiceField):
819
def __init__(self, path, match=None, recursive=False, required=True,
820
widget=None, label=None, initial=None, help_text=None,
822
self.path, self.match, self.recursive = path, match, recursive
823
super(FilePathField, self).__init__(choices=(), required=required,
824
widget=widget, label=label, initial=initial, help_text=help_text,
827
if self.match is not None:
828
self.match_re = re.compile(self.match)
830
for root, dirs, files in os.walk(self.path):
832
if self.match is None or self.match_re.search(f):
833
f = os.path.join(root, f)
834
self.choices.append((f, f.replace(path, "", 1)))
837
for f in os.listdir(self.path):
838
full_file = os.path.join(self.path, f)
839
if os.path.isfile(full_file) and (self.match is None or self.match_re.search(f)):
840
self.choices.append((full_file, f))
843
self.widget.choices = self.choices
845
class SplitDateTimeField(MultiValueField):
846
hidden_widget = SplitHiddenDateTimeWidget
847
default_error_messages = {
848
'invalid_date': _(u'Enter a valid date.'),
849
'invalid_time': _(u'Enter a valid time.'),
852
def __init__(self, *args, **kwargs):
853
errors = self.default_error_messages.copy()
854
if 'error_messages' in kwargs:
855
errors.update(kwargs['error_messages'])
857
DateField(error_messages={'invalid': errors['invalid_date']}),
858
TimeField(error_messages={'invalid': errors['invalid_time']}),
860
super(SplitDateTimeField, self).__init__(fields, *args, **kwargs)
862
def compress(self, data_list):
864
# Raise a validation error if time or date is empty
865
# (possible if SplitDateTimeField has required=False).
866
if data_list[0] in EMPTY_VALUES:
867
raise ValidationError(self.error_messages['invalid_date'])
868
if data_list[1] in EMPTY_VALUES:
869
raise ValidationError(self.error_messages['invalid_time'])
870
return datetime.datetime.combine(*data_list)
873
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}$')
875
class IPAddressField(RegexField):
876
default_error_messages = {
877
'invalid': _(u'Enter a valid IPv4 address.'),
880
def __init__(self, *args, **kwargs):
881
super(IPAddressField, self).__init__(ipv4_re, *args, **kwargs)
883
slug_re = re.compile(r'^[-\w]+$')
885
class SlugField(RegexField):
886
default_error_messages = {
887
'invalid': _(u"Enter a valid 'slug' consisting of letters, numbers,"
888
u" underscores or hyphens."),
891
def __init__(self, *args, **kwargs):
892
super(SlugField, self).__init__(slug_re, *args, **kwargs)