~ubuntu-branches/ubuntu/jaunty/python-django/jaunty

« back to all changes in this revision

Viewing changes to django/forms/fields.py

  • Committer: Bazaar Package Importer
  • Author(s): Scott James Remnant, Eddy Mulyono
  • Date: 2008-09-16 12:18:47 UTC
  • mfrom: (1.1.5 upstream) (4.1.1 lenny)
  • Revision ID: james.westby@ubuntu.com-20080916121847-mg225rg5mnsdqzr0
Tags: 1.0-1ubuntu1
* Merge from Debian (LP: #264191), remaining changes:
  - Run test suite on build.

[Eddy Mulyono]
* Update patch to workaround network test case failures.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
import django.core.exceptions
 
27
from django.utils.translation import ugettext_lazy as _
 
28
from django.utils.encoding import smart_unicode, smart_str
 
29
 
 
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
 
33
 
 
34
__all__ = (
 
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',
 
43
    'TypedChoiceField'
 
44
)
 
45
 
 
46
# These values, if given to to_python(), will trigger the self.required check.
 
47
EMPTY_VALUES = (None, '')
 
48
 
 
49
 
 
50
class Field(object):
 
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.'),
 
56
    }
 
57
 
 
58
    # Tracks each time a Field instance is created. Used to retain order.
 
59
    creation_counter = 0
 
60
 
 
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.
 
64
        #             True by default.
 
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
 
72
        #          Form.
 
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.
 
78
        if label is not None:
 
79
            label = smart_unicode(label)
 
80
        self.required, self.label, self.initial = required, label, initial
 
81
        self.show_hidden_initial = show_hidden_initial
 
82
        if help_text is None:
 
83
            self.help_text = u''
 
84
        else:
 
85
            self.help_text = smart_unicode(help_text)
 
86
        widget = widget or self.widget
 
87
        if isinstance(widget, type):
 
88
            widget = widget()
 
89
 
 
90
        # Hook into self.widget_attrs() for any Field-specific HTML attributes.
 
91
        extra_attrs = self.widget_attrs(widget)
 
92
        if extra_attrs:
 
93
            widget.attrs.update(extra_attrs)
 
94
 
 
95
        self.widget = widget
 
96
 
 
97
        # Increase the creation counter, and save our local copy.
 
98
        self.creation_counter = Field.creation_counter
 
99
        Field.creation_counter += 1
 
100
 
 
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', {}))
 
105
 
 
106
        messages = {}
 
107
        set_class_error_messages(messages, self.__class__)
 
108
        messages.update(error_messages or {})
 
109
        self.error_messages = messages
 
110
 
 
111
    def clean(self, value):
 
112
        """
 
113
        Validates the given value and returns its "cleaned" value as an
 
114
        appropriate Python object.
 
115
 
 
116
        Raises ValidationError for any errors.
 
117
        """
 
118
        if self.required and value in EMPTY_VALUES:
 
119
            raise ValidationError(self.error_messages['required'])
 
120
        return value
 
121
 
 
122
    def widget_attrs(self, widget):
 
123
        """
 
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
 
126
        Field.
 
127
        """
 
128
        return {}
 
129
 
 
130
    def __deepcopy__(self, memo):
 
131
        result = copy.copy(self)
 
132
        memo[id(self)] = result
 
133
        result.widget = copy.deepcopy(self.widget, memo)
 
134
        return result
 
135
 
 
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).'),
 
140
    }
 
141
 
 
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)
 
145
 
 
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:
 
150
            return u''
 
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})
 
157
        return value
 
158
 
 
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)}
 
163
 
 
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.'),
 
169
    }
 
170
 
 
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)
 
174
 
 
175
    def clean(self, value):
 
176
        """
 
177
        Validates that int() can be called on the input. Returns the result
 
178
        of int(). Returns None for empty values.
 
179
        """
 
180
        super(IntegerField, self).clean(value)
 
181
        if value in EMPTY_VALUES:
 
182
            return None
 
183
        try:
 
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)
 
191
        return value
 
192
 
 
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.'),
 
198
    }
 
199
 
 
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)
 
203
 
 
204
    def clean(self, value):
 
205
        """
 
206
        Validates that float() can be called on the input. Returns a float.
 
207
        Returns None for empty values.
 
208
        """
 
209
        super(FloatField, self).clean(value)
 
210
        if not self.required and value in EMPTY_VALUES:
 
211
            return None
 
212
        try:
 
213
            value = float(value)
 
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)
 
220
        return value
 
221
 
 
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.')
 
230
    }
 
231
 
 
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)
 
236
 
 
237
    def clean(self, value):
 
238
        """
 
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.
 
243
        """
 
244
        super(DecimalField, self).clean(value)
 
245
        if not self.required and value in EMPTY_VALUES:
 
246
            return None
 
247
        value = smart_str(value).strip()
 
248
        try:
 
249
            value = Decimal(value)
 
250
        except DecimalException:
 
251
            raise ValidationError(self.error_messages['invalid'])
 
252
 
 
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
 
264
 
 
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))
 
275
        return value
 
276
 
 
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'
 
283
)
 
284
 
 
285
class DateField(Field):
 
286
    default_error_messages = {
 
287
        'invalid': _(u'Enter a valid date.'),
 
288
    }
 
289
 
 
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
 
293
 
 
294
    def clean(self, value):
 
295
        """
 
296
        Validates that the input can be converted to a date. Returns a Python
 
297
        datetime.date object.
 
298
        """
 
299
        super(DateField, self).clean(value)
 
300
        if value in EMPTY_VALUES:
 
301
            return None
 
302
        if isinstance(value, datetime.datetime):
 
303
            return value.date()
 
304
        if isinstance(value, datetime.date):
 
305
            return value
 
306
        for format in self.input_formats:
 
307
            try:
 
308
                return datetime.date(*time.strptime(value, format)[:3])
 
309
            except ValueError:
 
310
                continue
 
311
        raise ValidationError(self.error_messages['invalid'])
 
312
 
 
313
DEFAULT_TIME_INPUT_FORMATS = (
 
314
    '%H:%M:%S',     # '14:30:59'
 
315
    '%H:%M',        # '14:30'
 
316
)
 
317
 
 
318
class TimeField(Field):
 
319
    widget = TimeInput
 
320
    default_error_messages = {
 
321
        'invalid': _(u'Enter a valid time.')
 
322
    }
 
323
 
 
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
 
327
 
 
328
    def clean(self, value):
 
329
        """
 
330
        Validates that the input can be converted to a time. Returns a Python
 
331
        datetime.time object.
 
332
        """
 
333
        super(TimeField, self).clean(value)
 
334
        if value in EMPTY_VALUES:
 
335
            return None
 
336
        if isinstance(value, datetime.time):
 
337
            return value
 
338
        for format in self.input_formats:
 
339
            try:
 
340
                return datetime.time(*time.strptime(value, format)[3:6])
 
341
            except ValueError:
 
342
                continue
 
343
        raise ValidationError(self.error_messages['invalid'])
 
344
 
 
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'
 
355
)
 
356
 
 
357
class DateTimeField(Field):
 
358
    widget = DateTimeInput
 
359
    default_error_messages = {
 
360
        'invalid': _(u'Enter a valid date/time.'),
 
361
    }
 
362
 
 
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
 
366
 
 
367
    def clean(self, value):
 
368
        """
 
369
        Validates that the input can be converted to a datetime. Returns a
 
370
        Python datetime.datetime object.
 
371
        """
 
372
        super(DateTimeField, self).clean(value)
 
373
        if value in EMPTY_VALUES:
 
374
            return None
 
375
        if isinstance(value, datetime.datetime):
 
376
            return value
 
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.
 
382
            if len(value) != 2:
 
383
                raise ValidationError(self.error_messages['invalid'])
 
384
            value = '%s %s' % tuple(value)
 
385
        for format in self.input_formats:
 
386
            try:
 
387
                return datetime.datetime(*time.strptime(value, format)[:6])
 
388
            except ValueError:
 
389
                continue
 
390
        raise ValidationError(self.error_messages['invalid'])
 
391
 
 
392
class RegexField(CharField):
 
393
    def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs):
 
394
        """
 
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.
 
398
        """
 
399
        # error_message is just kept for backwards compatibility:
 
400
        if error_message:
 
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)
 
407
        self.regex = regex
 
408
 
 
409
    def clean(self, value):
 
410
        """
 
411
        Validates that the input matches the regular expression. Returns a
 
412
        Unicode object.
 
413
        """
 
414
        value = super(RegexField, self).clean(value)
 
415
        if value == u'':
 
416
            return value
 
417
        if not self.regex.search(value):
 
418
            raise ValidationError(self.error_messages['invalid'])
 
419
        return value
 
420
 
 
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
 
425
 
 
426
class EmailField(RegexField):
 
427
    default_error_messages = {
 
428
        'invalid': _(u'Enter a valid e-mail address.'),
 
429
    }
 
430
 
 
431
    def __init__(self, max_length=None, min_length=None, *args, **kwargs):
 
432
        RegexField.__init__(self, email_re, max_length, min_length, *args,
 
433
                            **kwargs)
 
434
 
 
435
try:
 
436
    from django.conf import settings
 
437
    URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT
 
438
except ImportError:
 
439
    # It's OK if Django settings aren't configured.
 
440
    URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
 
441
 
 
442
 
 
443
class FileField(Field):
 
444
    widget = FileInput
 
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."),
 
449
    }
 
450
 
 
451
    def __init__(self, *args, **kwargs):
 
452
        super(FileField, self).__init__(*args, **kwargs)
 
453
 
 
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:
 
457
            return None
 
458
        elif not data and initial:
 
459
            return initial
 
460
 
 
461
        # UploadedFile objects should have name and size attributes.
 
462
        try:
 
463
            file_name = data.name
 
464
            file_size = data.size
 
465
        except AttributeError:
 
466
            raise ValidationError(self.error_messages['invalid'])
 
467
 
 
468
        if not file_name:
 
469
            raise ValidationError(self.error_messages['invalid'])
 
470
        if not file_size:
 
471
            raise ValidationError(self.error_messages['empty'])
 
472
 
 
473
        return data
 
474
 
 
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."),
 
478
    }
 
479
 
 
480
    def clean(self, data, initial=None):
 
481
        """
 
482
        Checks that the file-upload field data contains a valid image (GIF, JPG,
 
483
        PNG, possibly others -- whatever the Python Imaging Library supports).
 
484
        """
 
485
        f = super(ImageField, self).clean(data, initial)
 
486
        if f is None:
 
487
            return None
 
488
        elif not data and initial:
 
489
            return initial
 
490
        from PIL import Image
 
491
 
 
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()
 
496
        else:
 
497
            if hasattr(data, 'read'):
 
498
                file = StringIO(data.read())
 
499
            else:
 
500
                file = StringIO(data['content'])
 
501
 
 
502
        try:
 
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)
 
506
            trial_image.load()
 
507
 
 
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'):
 
511
                file.reset()
 
512
 
 
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)
 
516
            trial_image.verify()
 
517
        except ImportError:
 
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.
 
521
            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):
 
525
            f.seek(0)
 
526
        return f
 
527
 
 
528
url_re = re.compile(
 
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)
 
535
 
 
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.'),
 
540
    }
 
541
 
 
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,
 
545
                                       **kwargs)
 
546
        self.verify_exists = verify_exists
 
547
        self.user_agent = validator_user_agent
 
548
 
 
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]:
 
555
            value += '/'
 
556
        value = super(URLField, self).clean(value)
 
557
        if value == u'':
 
558
            return value
 
559
        if self.verify_exists:
 
560
            import urllib2
 
561
            headers = {
 
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,
 
567
            }
 
568
            try:
 
569
                req = urllib2.Request(value, None, headers)
 
570
                u = urllib2.urlopen(req)
 
571
            except ValueError:
 
572
                raise ValidationError(self.error_messages['invalid'])
 
573
            except: # urllib2.URLError, httplib.InvalidURL, etc.
 
574
                raise ValidationError(self.error_messages['invalid_link'])
 
575
        return value
 
576
 
 
577
class BooleanField(Field):
 
578
    widget = CheckboxInput
 
579
 
 
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.
 
585
        if value == 'False':
 
586
            value = False
 
587
        else:
 
588
            value = bool(value)
 
589
        super(BooleanField, self).clean(value)
 
590
        if not value and self.required:
 
591
            raise ValidationError(self.error_messages['required'])
 
592
        return value
 
593
 
 
594
class NullBooleanField(BooleanField):
 
595
    """
 
596
    A field whose valid values are None, True and False. Invalid values are
 
597
    cleaned to None.
 
598
    """
 
599
    widget = NullBooleanSelect
 
600
 
 
601
    def clean(self, value):
 
602
        """
 
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
 
606
        the bool() function
 
607
        """
 
608
        if value in (True, 'True'):
 
609
            return True
 
610
        elif value in (False, 'False'):
 
611
            return False
 
612
        else:
 
613
            return None
 
614
 
 
615
class ChoiceField(Field):
 
616
    widget = Select
 
617
    default_error_messages = {
 
618
        'invalid_choice': _(u'Select a valid choice. %(value)s is not one of the available choices.'),
 
619
    }
 
620
 
 
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
 
626
 
 
627
    def _get_choices(self):
 
628
        return self._choices
 
629
 
 
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)
 
635
 
 
636
    choices = property(_get_choices, _set_choices)
 
637
 
 
638
    def clean(self, value):
 
639
        """
 
640
        Validates that the input is in self.choices.
 
641
        """
 
642
        value = super(ChoiceField, self).clean(value)
 
643
        if value in EMPTY_VALUES:
 
644
            value = u''
 
645
        value = smart_unicode(value)
 
646
        if value == u'':
 
647
            return value
 
648
        if not self.valid_value(value):
 
649
            raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
 
650
        return value
 
651
 
 
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
 
657
                for k2, v2 in v:
 
658
                    if value == smart_unicode(k2):
 
659
                        return True
 
660
            else:
 
661
                if value == smart_unicode(k):
 
662
                    return True
 
663
        return False
 
664
 
 
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)
 
670
        
 
671
    def clean(self, value):
 
672
        """
 
673
        Validate that the value is in self.choices and can be coerced to the
 
674
        right type.
 
675
        """
 
676
        value = super(TypedChoiceField, self).clean(value)
 
677
        if value == self.empty_value or value in EMPTY_VALUES:
 
678
            return self.empty_value
 
679
        
 
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
 
685
        # both.
 
686
        try:
 
687
            value = self.coerce(value)
 
688
        except (ValueError, TypeError, django.core.exceptions.ValidationError):
 
689
            raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
 
690
        return value
 
691
 
 
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.'),
 
698
    }
 
699
 
 
700
    def clean(self, value):
 
701
        """
 
702
        Validates that the input is a list or tuple.
 
703
        """
 
704
        if self.required and not value:
 
705
            raise ValidationError(self.error_messages['required'])
 
706
        elif not self.required and not value:
 
707
            return []
 
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})
 
715
        return new_value
 
716
 
 
717
class ComboField(Field):
 
718
    """
 
719
    A Field whose clean() method calls multiple Field clean() methods.
 
720
    """
 
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
 
725
        # individual fields.
 
726
        for f in fields:
 
727
            f.required = False
 
728
        self.fields = fields
 
729
 
 
730
    def clean(self, value):
 
731
        """
 
732
        Validates the given value against all of self.fields, which is a
 
733
        list of Field instances.
 
734
        """
 
735
        super(ComboField, self).clean(value)
 
736
        for field in self.fields:
 
737
            value = field.clean(value)
 
738
        return value
 
739
 
 
740
class MultiValueField(Field):
 
741
    """
 
742
    A Field that aggregates the logic of multiple Fields.
 
743
 
 
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.
 
750
 
 
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.
 
754
 
 
755
    You'll probably want to use this with MultiWidget.
 
756
    """
 
757
    default_error_messages = {
 
758
        'invalid': _(u'Enter a list of values.'),
 
759
    }
 
760
 
 
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
 
765
        # individual fields.
 
766
        for f in fields:
 
767
            f.required = False
 
768
        self.fields = fields
 
769
 
 
770
    def clean(self, value):
 
771
        """
 
772
        Validates every value in the given list. A value is validated against
 
773
        the corresponding Field in self.fields.
 
774
 
 
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]).
 
778
        """
 
779
        clean_data = []
 
780
        errors = ErrorList()
 
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]:
 
783
                if self.required:
 
784
                    raise ValidationError(self.error_messages['required'])
 
785
                else:
 
786
                    return self.compress([])
 
787
        else:
 
788
            raise ValidationError(self.error_messages['invalid'])
 
789
        for i, field in enumerate(self.fields):
 
790
            try:
 
791
                field_value = value[i]
 
792
            except IndexError:
 
793
                field_value = None
 
794
            if self.required and field_value in EMPTY_VALUES:
 
795
                raise ValidationError(self.error_messages['required'])
 
796
            try:
 
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)
 
803
        if errors:
 
804
            raise ValidationError(errors)
 
805
        return self.compress(clean_data)
 
806
 
 
807
    def compress(self, data_list):
 
808
        """
 
809
        Returns a single value for the given list of values. The values can be
 
810
        assumed to be valid.
 
811
 
 
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.
 
815
        """
 
816
        raise NotImplementedError('Subclasses must implement this method.')
 
817
 
 
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,
 
821
                 *args, **kwargs):
 
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,
 
825
            *args, **kwargs)
 
826
        self.choices = []
 
827
        if self.match is not None:
 
828
            self.match_re = re.compile(self.match)
 
829
        if recursive:
 
830
            for root, dirs, files in os.walk(self.path):
 
831
                for f in files:
 
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)))
 
835
        else:
 
836
            try:
 
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))
 
841
            except OSError:
 
842
                pass
 
843
        self.widget.choices = self.choices
 
844
 
 
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.'),
 
850
    }
 
851
 
 
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'])
 
856
        fields = (
 
857
            DateField(error_messages={'invalid': errors['invalid_date']}),
 
858
            TimeField(error_messages={'invalid': errors['invalid_time']}),
 
859
        )
 
860
        super(SplitDateTimeField, self).__init__(fields, *args, **kwargs)
 
861
 
 
862
    def compress(self, data_list):
 
863
        if 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)
 
871
        return None
 
872
 
 
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}$')
 
874
 
 
875
class IPAddressField(RegexField):
 
876
    default_error_messages = {
 
877
        'invalid': _(u'Enter a valid IPv4 address.'),
 
878
    }
 
879
 
 
880
    def __init__(self, *args, **kwargs):
 
881
        super(IPAddressField, self).__init__(ipv4_re, *args, **kwargs)
 
882
 
 
883
slug_re = re.compile(r'^[-\w]+$')
 
884
 
 
885
class SlugField(RegexField):
 
886
    default_error_messages = {
 
887
        'invalid': _(u"Enter a valid 'slug' consisting of letters, numbers,"
 
888
                     u" underscores or hyphens."),
 
889
    }
 
890
 
 
891
    def __init__(self, *args, **kwargs):
 
892
        super(SlugField, self).__init__(slug_re, *args, **kwargs)