""" 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(object): def __init__(self, name, **params): self.name = name self.description = '' self.errors = [] self.isvalid = None def set_form(self, form):pass def set_request(self, req, validate = True):pass def append_error(self, error): self.errors.append(error) self.isvalid=False def clear(self):pass def is_valid(self):return self.isvalid def unvalidate(self):pass class Form(FormElement): def __init__(self, name, elements, **params): FormElement.__init__(self, name) self.success = [] self.elements = OrderedDict() for elem in elements: self.elements[elem.name] = elem elem.set_form(self) def append_success(self, success): self.success.append(success) def set_form(self, form):pass def append(self, element): self.elements[element.name] = element element.set_form(self) def __iter__(self): return iter(self.elements.values()) def get(self, key, *args, **kwargs): return self.elements.get(key, *args, **kwargs) def __getitem__(self, key): return self.elements[key] def __setitem__(self, key, elem): self.elements[elem.name] = elem elem.set_form(self) def unvalidate(self): self.isvalid = None for elem in self.elements.values(): elem.unvalidate() def set_request(self, req, validate = True): self.isvalid = True for elem in self.elements.values(): if elem is None: continue 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) def clear(self): for elem in self.elements.values(): elem.clear() 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, data=None, attribute=False, setattribute=False, getattribute=False, disabled=False): FormElement.__init__(self, name) if description is None: self.description = name else: self.description = description self.required = required if setattribute is not False: self.setattribute = setattribute elif attribute is not False: self.setattribute = attribute else: self.setattribute = name if getattribute is not False: self.getattribute = getattribute elif attribute is not False: self.getattribute = attribute else: self.getattribute = name self.default = default self.value = default self.displayname = None self.data = data self.disabled = disabled def clear(self): self.value = self.default def set_form(self, form): self.displayname = form._fieldname(self) def _set_value(self, value): self._value = value if value is None: self.display = '' else: self.display = str(value) value = property(lambda s: s._value, lambda s, v: s._set_value(v)) def reflect_to(self, object): if self.disabled or self.setattribute is None: return elif callable(self.setattribute): self.setattribute(self, object) else: attrs = self.setattribute.split('.') o = object for attr in attrs[0:-2]: o = getattr(o, attr) setattr(o, attrs[-1], self.value) def reflect_from(self, object): if self.getattribute is None: return elif callable(self.getattribute): self.value = self.getattribute(object) else: for attr in self.getattribute.split('.'): try: object = getattr(object, attr) except AttributeError: return self.value = object def set_request(self, request, validate = True): """sets the request for this form. if validate is True, also validates the value.""" if request.has_key(self.displayname): self.display = request[self.displayname] if validate: if self.required and (not request.has_key(self.displayname) or not self.display): self.isvalid = False self.append_error('required field "%s" missing' % self.description) else: self.isvalid = True self.value = self.display def unvalidate(self): """resets the isvalid state of this form to None""" self.isvalid = None self.errors = [] class IntFormField(FormField): def _set_value(self, value): if not value: self._value = None self.display = '' else: try: self._value = int(value) except ValueError: self.append_error('field "%s" must be an integer number' % self.description) self.isvalid = False self.display = str(value) 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 _set_value(self, value): SubForm._set_value(self, value) self.set_compound_values(value) def set_compound_values(self, value): pass class CCFormField(FormField): def _set_value(self, value): if not self.luhn_mod_ten(value): self.isvalid = False self.append_error('invalid credit card number') else: self._value = value 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): # TODO: fixy 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.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'].value = value.year self.elements['month'].value = value.month self.elements['day'].value = value.day if self.hastime: if self.elements.has_key('ampm'): v = value.hour % 12 if v == 0: v = 12 self.elements['hour'].value = v else: self.elements['hour'].value = value.hour self.elements['minute'].value = value.minute self.elements['second'].value = value.second if self.elements.has_key('ampm'): self.elements['ampm'].value = (value.hour > 12 and 'pm' or 'am') 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.append_error('field "%s": %s' % (self.description, string.join([elem.errors]))) 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.append_error('field "%s" does not contain a valid date/time (%s)' % (self.description, str(e))) except ValueError, e: self.isvalid = False self.currentvalue = None self.append_error('field "%s" does not contain a valid date/time (%s)' % (self.description, str(e)))