"""
a set of classes that represent an HTML form, including its field names,
the values of those fields corresponding to their application setting
as well as the value pulled from an HTTP request, and validation rules
for the fields. "hierarchical forms" can be created as well, allowing a form
to be grouped into "subforms", which may live on the same HTML page or across
several HTML pages.
"""
from myghty.util import *
import inspect, datetime, types
class FormElement:
def __init__(self, name, **params):
self.name = name
self.description = ''
def set_form(self, form):pass
def set_request(self, req, validate = True):pass
def is_valid(self):return None
def unvalidate(self):pass
def get_valid_message(self):return ''
class Form(FormElement):
def __init__(self, name, elements, **params):
FormElement.__init__(self, name)
self.isvalid = None
self.elements = OrderedDict()
for elem in elements:
self.elements[elem.name] = elem
elem.set_form(self)
def set_form(self, form):pass
def show_default(self):[elem.show_default() for elem in self.elements.values()]
def show_value(self):[elem.show_value() for elem in self.elements.values()]
def show_request_value(self):[elem.show_request_value() for elem in self.elements.values()]
def is_valid(self):
return self.isvalid
def get_valid_message(self):
if self.isvalid:
return "Form is valid"
elif self.isvalid is False:
return "Some fields could not be validated"
def unvalidate(self):
self.isvalid = None
for elem in self.elements.values(): elem.unvalidate()
def get_field(self, name):
try:
return self.elements[name]
except KeyError:
return None
def set_request(self, req, validate = True):
self.isvalid = True
for elem in self.elements.values():
elem.set_request(req, validate)
if not elem.is_valid():
self.isvalid = False
def _fieldname(self, formfield):
return formfield.name
def reflect_from(self, object):
for elem in self.elements.values():
elem.reflect_from(object)
def reflect_to(self, object):
for elem in self.elements.values():
elem.reflect_to(object)
class SubForm(Form):
def _fieldname(self, formfield):
return self.name + "_" + formfield.name
class FormField(FormElement):
def __init__(self, name, description = None, required = False, default = None, textsize = None, options = None, **params):
FormElement.__init__(self, name)
if description is None:
self.description = name
else:
self.description = description
self.required = required
self.options = options
self.textsize = textsize
# default value (any type)
self.default = default
# programmatically set value (any type)
self.value = None
# value taken from the request (any type)
self.request_value = None
# current used value (any type)
self.currentvalue = default
# string display value
if self.currentvalue is None:
self.displayvalue = ''
else:
self.displayvalue = str(self.currentvalue)
self.displayname = None
self.valid_message = ''
self.isvalid = None
def set_form(self, form):
self.displayname = form._fieldname(self)
def set_value(self, value):
self.value = value
self.currentvalue = value
if self.currentvalue is None:
self.displayvalue = ''
else:
self.displayvalue = str(self.currentvalue)
def show_default(self):self.displayvalue = self.default
def show_value(self):self.displayvalue = self.value
def show_request_value(self):self.displayvalue = self.request_value
def reflect_to(self, object):
if hasattr(object, self.name):
setattr(object, self.name, self.displayvalue)
def reflect_from(self, object):
if hasattr(object, self.name):
self.set_value(getattr(object, self.name))
def is_valid(self):
"""returns whether or not this field is valid. note that the
third state of None indicates this field has not been validated."""
return self.isvalid
def get_valid_message(self):
return self.valid_message
def set_request(self, request, validate = True):
"""sets the request for this form. if validate is True, also
validates the value."""
try:
self.request_value = request[self.displayname]
except KeyError:
self.request_value = None
if validate:
if self.required and not self.request_value:
self.isvalid = False
self.valid_message = 'required field "%s" missing' % self.description
else:
self.isvalid = True
if self.request_value is None:
self.displayvalue = ''
else:
self.displayvalue = str(self.request_value)
self.currentvalue = self.request_value
def unvalidate(self):
"""resets the isvalid state of this form to None"""
self.isvalid = None
self.valid_message = ''
class IntFormField(FormField):
def __init__(self, *args, **params):
FormField.__init__(self, *args, **params)
self.currentvalue = None
def set_request(self, request, validate = True):
FormField.set_request(self, request, validate)
try:
if self.currentvalue == '':
self.currentvalue = None
if self.currentvalue is not None:
self.currentvalue = int(self.currentvalue)
except ValueError:
if self.isvalid and validate:
self.valid_message = 'field "%s" must be an integer number' % self.description
self.isvalid = False
self.currentvalue = None
class CompoundFormField(SubForm):
"""
a SubForm that acts like a single formfield in that it contains a single value,
but also contains subfields that comprise elements of that value.
examples: a date with year, month, day fields, corresponding to a date object
more exotic examples: a credit card field with ccnumber, ccexpiration fields corresponding to a
CreditCard object, an address field with multiple subfields corresopnding to an Address object, etc.
"""
def __init__(self, name, elements, description = None, **params):
SubForm.__init__(self, name, elements, **params)
self.value = None
self.request_value = None
self.displayvalue = None
if description is None:
self.description = name
else:
self.description = description
def set_value(self, value):
self.value = value
self.currentvalue = value
self.displayvalue = str(value)
self.set_compound_values(value)
def reflect_to(self, object):
if hasattr(object, self.name):
setattr(object, self.name, self.currentvalue)
def reflect_from(self, object):
if hasattr(object, self.name):
self.set_value(getattr(object, self.name))
def show_default(self):
self.displayvalue = self.default
[elem.show_default() for elem in self.elements.values()]
def show_value(self):
self.displayvalue = self.value
[elem.show_value() for elem in self.elements.values()]
def show_request_value(self):
self.displayvalue = self.request_value
[elem.show_request_value() for elem in self.elements.values()]
class CCFormField(FormField):
def set_request(self, request, validate = True):
FormField.set_request(self, request, validate)
if (
self.currentvalue and
self.isvalid and
validate and
not self.luhn_mod_ten(self.currentvalue)):
self.isvalid = False
self.valid_message = 'invalid credit card number'
def luhn_mod_ten(self, ccnumber):
""" checks to make sure that the card passes a luhn mod-10 checksum.
courtesy: http://aspn.activestate.com
"""
sum = 0
num_digits = len(ccnumber)
oddeven = num_digits & 1
for count in range(0, num_digits):
digit = int(ccnumber[count])
if not (( count & 1 ) ^ oddeven ):
digit = digit * 2
if digit > 9:
digit = digit - 9
sum = sum + digit
return ( (sum % 10) == 0 )
class DateFormField(CompoundFormField):
def __init__(self, name, fields, yeardeltas = range(-5, 5), required = False, *args, **params):
elements = {}
for field in fields:
if field == 'ampm':
elements[field] = FormField(field, required = required)
else:
elements[field] = IntFormField(field, required = required)
for key in ['year', 'month', 'day']:
if elements.has_key(key):
self.hasdate = True
break
else:
self.hasdate = False
for key in ['hour', 'minute', 'second']:
if elements.has_key(key):
self.hastime = True
break
else:
self.hastime = False
assert (self.hasdate or self.hastime)
CompoundFormField.__init__(self, name, elements.values(), **params)
self.valid_message = ""
self.required = required
if self.hasdate:
today = datetime.datetime.today()
year = today.year
self.yearrange = [year + i for i in yeardeltas]
def set_compound_values(self, value):
if self.hasdate:
self.elements['year'].set_value(value.year)
self.elements['month'].set_value(value.month)
self.elements['day'].set_value(value.day)
if self.hastime:
if self.elements.has_key('ampm'):
v = value.hour % 12
if v == 0: v = 12
self.elements['hour'].set_value(v)
else:
self.elements['hour'].set_value(value.hour)
self.elements['minute'].set_value(value.minute)
self.elements['second'].set_value(value.second)
if self.elements.has_key('ampm'):
self.elements['ampm'].set_value(value.hour > 12 and 'pm' or 'am')
def get_valid_message(self):
return self.valid_message
def set_request(self, request, validate = True):
CompoundFormField.set_request(self, request, validate)
if validate:
for elem in self.elements.values():
if elem.is_valid() is False:
self.valid_message = 'field "%s": %s' % (self.description, elem.get_valid_message())
return
args = {}
has_value = False
if self.hasdate:
dummy = datetime.date.min
for key in ['year', 'month', 'day']:
if self.elements.has_key(key):
args[key] = self.elements[key].currentvalue
if args[key] is not None:
has_value = True
else:
args[key] = getattr(dummy, key)
if self.hastime:
dummy = datetime.time.min
for key in ['hour', 'minute', 'second']:
if self.elements.has_key(key):
args[key] = self.elements[key].currentvalue
if args[key] is not None:
has_value = True
else:
args[key] = getattr(dummy, key)
if self.elements.has_key('ampm'):
if self.elements['ampm'] == 'pm':
args['hour'] += 12
elif args['hour'] == 12:
args['hour'] = 0
if not has_value:
self.request_value = None
return
try:
if self.hasdate and self.hastime:
value = datetime.datetime(**args)
elif self.hasdate:
value = datetime.date(**args)
else:
value = datetime.time(**args)
self.request_value = value
self.currentvalue = value
self.isvalid = True
except TypeError, e:
self.isvalid = False
self.currentvalue = None
self.valid_message = 'field "%s" does not contain a valid date/time (%s)' % (self.description, str(e))
except ValueError, e:
self.isvalid = False
self.currentvalue = None
self.valid_message = 'field "%s" does not contain a valid date/time (%s)' % (self.description, str(e))
|