#Validator #============ # #This module is not aimed to replace the newforms, but I would like manually write html code, #and just need a pure validate module, so I write this, and many things may be similar with #newforms. So if you like me would only need a pure validator module, you can use it. # #And it has some different features from newforms: # #1. Support validator_list parameter, so you could use it just like the old manipuator class #2. Supply easy method, such as `validate_and_save()`, so you can pass a request object, and # get a tuple result `(flag, obj_or_error)`, if the `flag` is `True`, then the next value # is an object; and if the `flag` is `False`, then the next value is error message. #3. Each field has a `validate_and_get` method, and it'll validate first and then return the # result, maybe an object or error message. Just like above. #4. SplitDateTimeField is somewhat different from the newforms. For example:: # # c = SplitDateTimeField('date', 'time') # print c.validate_and_get({'date':'2006/11/30', 'time':'12:13'}) # # So the first parameter is DateField's field_name, and the second parameter is TimeField's # field_name. #5. Add yyyy/mm/dd date format support #6. Support default value of a field. You can add a default value for a field, if this field # is not required, and the value is *empty*, Validator will return the default value. # #This module is new, so many things could be changed. # #Version: 0.2 #Author: limodou #Update: # * 2007/02/17 0.1 # * 2007/02/26 0.2 Fixed add_validator bug import datetime import time import re import copy from django.utils.datastructures import MultiValueDict from django.utils.datastructures import SortedDict __all__ = ( 'Field', 'CharField', 'IntegerField', 'DEFAULT_DATE_INPUT_FORMATS', 'DateField', 'DEFAULT_TIME_INPUT_FORMATS', 'TimeField', 'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField', 'RegexField', 'EmailField', 'URLField', 'BooleanField', 'ComboField', 'SplitDateTimeField', 'ValidationError', 'Validator', 'isChoices', 'isMultipleChoices', ) try: set except: from sets import Set as set #from django.utils.translation import gettext as _ def _(v): return v class SortedDictFromList(SortedDict): "A dictionary that keeps its keys in the order in which they're inserted." # This is different than django.utils.datastructures.SortedDict, because # this takes a list/tuple as the argument to __init__(). def __init__(self, data=None): if data is None: data = [] self.keyOrder = [d[0] for d in data] dict.__init__(self, dict(data)) def copy(self): l = [] for k, v in self.items(): l.append((k, copy.copy(v))) v.validator_list = copy.copy(v.validator_list) return SortedDictFromList(l) class ValidationError(Exception): def __init__(self, message): self.message = message def __str__(self): return str(self.message) class Field(object): creation_counter = 0 def __init__(self, field_name=None, required=True, multi_value=False, default=None, validator_list=None): self.field_name = field_name self.required = required self.default = default self.multi_value = multi_value self.default_validator_list = [] if validator_list: self.validator_list = validator_list else: self.validator_list = [] self.creation_counter = Field.creation_counter Field.creation_counter += 1 def get_data_from_datadict(self, all_data): if not self.multi_value or not isinstance(all_data, MultiValueDict): return all_data.get(self.field_name, None) else: return all_data.getlist(self.field_name) def validate_and_get(self, data, all_data=None): if not data: if not self.required: if self.default is not None: return True, self.default else: return True, None else: return False, _(u'This field is required.') try: if isinstance(data, list): v = [] for i in data: v.append(self.convert(i, all_data)) data = v else: data = self.convert(data, all_data) except ValidationError, e: return False, e.message except: return False, _(u'Convert data error.') try: for v in self.default_validator_list + self.validator_list: v(data, all_data) except ValidationError, e: return False, e.message return True, data def convert(self, data, all_data=None): raise Exception, 'Not implement yet!' def add_validator(self, validator): self.validator_list.append(validator) class ValidatorMetaclass(type): """ Metaclass that converts Field attributes to a dictionary called """ def __new__(cls, name, bases, attrs): fields = [] for field_name, obj in attrs.items(): if isinstance(obj, Field): obj = attrs.pop(field_name) obj.field_name = field_name fields.append((field_name, obj)) fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter)) # If this class is subclassing another Form, add that Form's fields. # Note that we loop over the bases in *reverse*. This is necessary in # order to preserve the correct order of fields. for base in bases[::-1]: if hasattr(base, 'base_fields'): fields = base.base_fields.items() + fields attrs['base_fields'] = SortedDictFromList(fields) old_init = attrs.get('__init__', None) def _f(self, *args, **kwargs): self.fields = self.base_fields.copy() if old_init: old_init(self, *args, **kwargs) attrs['__init__'] = _f return type.__new__(cls, name, bases, attrs) class Validator(object): __metaclass__ = ValidatorMetaclass def validate(self, request_or_data): if hasattr(request_or_data, 'POST'): all_data = request_or_data.POST.copy() all_data.update(request_or_data.FILES) else: all_data = request_or_data if all_data: errors = {} new_data = {} #gather all fields for field_name, field in self.fields.items(): new_data[field_name] = field.get_data_from_datadict(all_data) #validate and gather the result result = {} for field_name, field in self.fields.items(): flag, value = field.validate_and_get(new_data[field_name], new_data) if not flag: if isinstance(value, dict): errors.update(value) else: errors[field_name] = value else: result[field_name] = value if flag: #validate global try: self.full_validate(result, new_data) except ValidationError, e: errors['_'] = e.message #if there is errors, then result (False, error_messages) if errors or not flag: return False, errors return True, result else: return False, {'_':_(u'There is not data posted.')} def validate_and_save(self, request): flag, result = self.validate(request) if flag: #then try do the save try: obj = self.save(result) except: import traceback traceback.print_exc() return False, {'_':_(u'Saving object error.')} return True, obj else: return flag, result def save(self, data): pass def full_validate(self, new_data, all_data): pass class CharField(Field): def __init__(self, max_length=None, min_length=None, *args, **kwargs): super(CharField, self).__init__(*args, **kwargs) self.max_length, self.min_length = max_length, min_length self.default_validator_list.append(self.validate_length) def validate_length(self, data, all_data=None): if self.max_length is not None and len(data) > self.max_length: raise ValidationError, _(u'Ensure this value has at most %d characters.') % self.max_length if self.min_length is not None and len(data) < self.min_length: raise ValidationError, _(u'Ensure this value has at least %d characters.') % self.min_length def convert(self, data, all_data=None): return str(data) class IntegerField(Field): def __init__(self, max_value=None, min_value=None, *args, **kwargs): super(IntegerField, self).__init__(*args, **kwargs) self.max_value, self.min_value = max_value, min_value self.default_validator_list.append(self.validate_value) def validate_value(self, data, all_data=None): if self.max_value is not None and data > self.max_value: raise ValidationError, _(u'Ensure this value is less than or equal to %s.') % self.max_value if self.min_value is not None and data < self.min_value: raise ValidationError, _(u'Ensure this value is greater than or equal to %s.') % self.min_value def convert(self, data, all_data=None): try: return int(data) except (ValueError, TypeError): raise ValidationError, _(u'Enter a whole number.') DEFAULT_DATE_INPUT_FORMATS = ( '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', '%Y/%m/%d', # '2006-10-25', '10/25/2006', '10/25/06' '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006' '%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006' '%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006' '%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006' ) class DateField(Field): def __init__(self, input_formats=None, *args, **kwargs): super(DateField, self).__init__(*args, **kwargs) self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS def convert(self, data, all_data=None): for format in self.input_formats: try: return datetime.date(*time.strptime(data, format)[:3]) except ValueError: continue raise ValidationError, _(u'Date format is not right.') DEFAULT_TIME_INPUT_FORMATS = ( '%H:%M:%S', # '14:30:59' '%H:%M', # '14:30' ) class TimeField(Field): def __init__(self, input_formats=None, *args, **kwargs): super(TimeField, self).__init__(*args, **kwargs) self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS def convert(self, data, all_data=None): for format in self.input_formats: try: return datetime.time(*time.strptime(data, format)[3:6]) except ValueError: continue raise ValidationError, _(u'Time format is not right.') DEFAULT_DATETIME_INPUT_FORMATS = ( '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' '%Y-%m-%d %H:%M', # '2006-10-25 14:30' '%Y-%m-%d', # '2006-10-25' '%Y/%m/%d %H:%M:%S', # '2006/10/25 14:30:59' '%Y/%m/%d %H:%M', # '2006/10/25 14:30' '%Y/%m/%d ', # '2006/10/25 ' '%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59' '%m/%d/%Y %H:%M', # '10/25/2006 14:30' '%m/%d/%Y', # '10/25/2006' '%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59' '%m/%d/%y %H:%M', # '10/25/06 14:30' '%m/%d/%y', # '10/25/06' ) class DateTimeField(Field): def __init__(self, input_formats=None, *args, **kwargs): super(DateTimeField, self).__init__(*args, **kwargs) self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS def convert(self, data, all_data=None): for format in self.input_formats: try: return datetime.datetime(*time.strptime(data, format)[:6]) except ValueError: continue raise ValidationError, _(u'Time format is not right.') class RegexField(CharField): def __init__(self, regex, error_message=None, *args, **kwargs): super(RegexField, self).__init__(*args, **kwargs) if isinstance(regex, basestring): regex = re.compile(regex) self.regex = regex self.default_validator_list.append(self.validate_string) self.error_message = error_message or _(u'Enter a valid value.') def validate_string(self, data, all_data=None): if not self.regex.match(data): raise ValidationError, self.error_message email_re = re.compile( r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain class EmailField(RegexField): def __init__(self, *args, **kwargs): super(EmailField, self).__init__(email_re, _(u'Enter a valid e-mail address.'), *args, **kwargs) url_re = re.compile( r'^https?://' # http:// or https:// r'(?:[A-Z0-9-]+\.)+[A-Z]{2,6}' # domain r'(?::\d+)?' # optional port r'(?:/?|/\S+)$', re.IGNORECASE) class URLField(RegexField): def __init__(self, verify_exists=False, validator_user_agent='User Agent', *args, **kwargs): super(URLField, self).__init__(url_re, _(u'Enter a valid URL.'), *args, **kwargs) self.verify_exists = verify_exists self.user_agent = validator_user_agent self.default_validator_list.append(self.validate_url) def validate_url(self, data, all_data=None): if self.verify_exists: import urllib2 headers = { "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", "Accept-Language": "en-us,en;q=0.5", "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7", "Connection": "close", "User-Agent": self.user_agent, } try: req = urllib2.Request(data, None, headers) u = urllib2.urlopen(req) except ValueError: raise ValidationError, _(u'Enter a valid URL.') except: # urllib2.URLError, httplib.InvalidURL, etc. raise ValidationError, _(u'This URL appears to be a broken link.') return data class BooleanField(Field): def __init__(self): super(BooleanField, self).__init__() def convert(self, data, all_data=None): if isinstance(data, basestring): if data.lower() in ('on', 'true', 'yes', 'ok'): return True elif data.lower() in ('off', 'false', 'no', 'cancel'): return False else: raise ValidationError, _(u'Need a boolean value.') else: try: return bool(data) except: raise ValidationError, _(u'Need a boolean value.') class ComboField(Field): def __init__(self, fields=()): super(ComboField, self).__init__() self.fields = fields def get_data_from_datadict(self, all_data): data = {} for field in self.fields: data[field.field_name] = field.get_data_from_datadict(all_data) return data def validate_and_get(self, data, all_data=None): errors = {} result = {} for field in self.fields: flag, value = field.validate_and_get(data[field.field_name], data) if not flag: if isinstance(value, dict): errors.update(value) else: errors[field.field_name] = value else: result[field.field_name] = value if not errors or not flag: try: data = self.convert(result, all_data) except ValidationError, e: return False, e.message except: return False, _(u'Convert data error.') return True, data def convert(self, data, all_data=None): raise Exception, 'Not implement yet!' class SplitDateTimeField(ComboField): def __init__(self, date_fieldname, time_fieldname): self.date_fieldname = date_fieldname self.time_fieldname = time_fieldname fields = [DateField(field_name=date_fieldname), TimeField(field_name=time_fieldname)] super(SplitDateTimeField, self).__init__(fields) def convert(self, data, all_data=None): try: return datetime.datetime.combine(data[self.date_fieldname], data[self.time_fieldname]) except: raise ValidationError, _(u'Time format is not right.') def _get_choices_keys(choices): if isinstance(choices, dict): keys = set(choices.keys()) elif isinstance(choices, (list, tuple)): keys = set([]) for v in choices: if isinstance(v, (list, tuple)): keys.add(v[0]) else: keys.add(v) else: raise ValidationError, _(u'Choices need a dict, tuple or list data.') return keys def isChoices(choices): ''' choices should be a list or a tuple, e.g. [1,2,3] ''' def _f(data, all_data=None): if data not in _get_choices_keys(choices): raise ValidationError, _(u'Select a valid choice. That choice is not one of the available choices.') return _f def isMultipleChoices(choices): ''' choices should be a list or a tuple, e.g. [1,2,3] ''' def _f(data, all_data=None): data = set(data) if data - _get_choices_keys(choices): raise ValidationError, _(u'Select a valid choice. That choice is not one of the available choices.') return _f if __name__ == '__main__': c = CharField() print c.validate_and_get('abc') print c.validate_and_get('') print c.validate_and_get(None) c = CharField(max_length=10) print c.validate_and_get('abcdefghijklmn') c = CharField(required=False) print c.validate_and_get('') print c.validate_and_get('abc') c = IntegerField() print c.validate_and_get('abc') print c.validate_and_get('123') print c.validate_and_get(123) c = DateField() print c.validate_and_get('abc') print c.validate_and_get('2007/02/16') c = TimeField() print c.validate_and_get('abc') print c.validate_and_get('01:22:34') print c.validate_and_get('01:22') c = DateTimeField() print c.validate_and_get('abc') print c.validate_and_get('2007/02/13 01:22:34') print c.validate_and_get('2006-01-01 01:22') c = RegexField('\d+') print c.validate_and_get('abc') print c.validate_and_get('123') c = EmailField() print c.validate_and_get('abc') print c.validate_and_get('abc@gmail.com') c = URLField() print c.validate_and_get('abc') print c.validate_and_get('http://sina.com.cn') c = BooleanField() print c.validate_and_get('abc') print c.validate_and_get('True') c = IntegerField(validator_list=[isChoices([1,2,3])]) print c.validate_and_get('a') print c.validate_and_get('1') print c.validate_and_get('4') c = IntegerField(validator_list=[isMultipleChoices([1,2,3])]) print c.validate_and_get(['a']) print c.validate_and_get(['1', '2']) print c.validate_and_get(['1', '4']) c = SplitDateTimeField('date', 'time') print c.validate_and_get({'date':'2006/11/30', 'time':'12:13'}) data = {'username':'limodou', 'email':'abc@gmail.com', 'age':'123', 'password':'limodou'} def validate_username(data, all_data): print 'validate_username', data if data != 'limodou': raise ValidationError, 'Username must be "limodou"' class TV(Validator): username = CharField(validator_list=[validate_username]) email = EmailField() age = IntegerField() password = CharField() def __init__(self): self.fields['username'].add_validator(self.AnotherValidator) def full_validate(self, data, all_data): if not data.has_key('password'): raise ValidationError, _(u'Need password') def save(self, data): print 'ok' def AnotherValidator(self, data, all_data): print 'AnotherValidator', data return t = TV() print t.validate_and_save(data) t = TV() print t.validate_and_save(data)