#!/usr/bin/env python2.4 # -*- coding: utf-8 -*- ''' Created on Jan 31, 2010 @author: epeli Kela is simple form system for www-sovellukset courcs ''' import sys import random import inspect import re from datetime import date from xml.dom.minidom import Document class Invalid(Exception): pass def constraint(f): f.is_constraint = True return f def add_class_to_element(class_name, element): class_name = class_name.strip().lower().replace(" ", "_") previous_classes = element.getAttribute("class").strip() if not class_name in previous_classes.split(" "): new_classes = "%s %s" % (previous_classes, class_name) element.setAttribute("class", new_classes.strip() ) class _CommonWidget(object): ok = True _form = None _doc = None def _get_constraints(self): return [attr for name, attr in inspect.getmembers(self) if getattr(attr, 'is_constraint', False)] def validate(self): try: for constraint in self._get_constraints(): constraint() except Invalid, e: self.add_error(e.args[0]) return self.ok def error_str(self): self._error.firstChild.toxml() def add_error(self, msg): self._error.appendChild(self._doc.createTextNode(msg)) self.ok = False def error(self): if not self.ok: return self._error.toxml() return "" class Widget(_CommonWidget): _tag_name = "input" _type="text" _input_id = 0 def __init__(self, name, value="", label_text=None, required=False, min_length=0): self.min_length = min_length self.required = required self.name = name self._value = value if not label_text: label_text = name self.label_text = label_text @constraint def not_empty(self): if self.required and self.is_empty(): raise Invalid("Required") @constraint def require_length(self): if len(self.get_value()) < self.min_length: raise Invalid("Too short! Min length is %s characters." % self.min_length) def _init_form(self, form): self._form = form self._doc = form._doc self._input = self._doc.createElement(self._tag_name) self._input.setAttribute("value", self._value) self._input.setAttribute("type", self._type) input_id = self._next_id() self._input.setAttribute("name", self.name) add_class_to_element(self.name, self._input) self._input.setAttribute("id", input_id) self._label = self._doc.createElement("label") self._label.setAttribute("for", input_id) self._label.appendChild(self._doc.createTextNode(self.label_text)) self._error = self._doc.createElement("span") add_class_to_element("error", self._error) def add_error(self, msg): self._error.appendChild(self._doc.createTextNode(msg)) add_class_to_element("error", self._input) self.ok = False def input(self): return self._input.toxml() def label(self): return self._label.toxml() def set_attr(self, key, value): self._input.setAttribute(key, value) def remove_attr(self, key): self._input.removeAttribute(key) def _next_id(self): next = Widget._input_id Widget._input_id += 1 return self.name + str(next) def set_value(self, value): self._input.setAttribute("value", str(value).strip()) self._value = value def get_value(self): return self._input.getAttribute("value") def get_bool(self): return bool(self._input.getAttribute("value")) def is_empty(self): return str(self.get_value()).strip() == "" def is_int(self): try: int(self.get_value()) return True except ValueError: return False def get_int(self): try: return int(self.get_value()) except ValueError: return 0 def __str__(self): return self.input() def __repr__(self): return "<%s %s>" % (self.__class__.__name__, self.get_value()) class Text(Widget): pass class Password(Widget): _type="password" class Hidden(Widget): _type="hidden" class Checkbox(Widget): _type="checkbox" checked=False def set_value(self, value): if str(value).strip(): self.checked = True self.set_attr("checked", "checked") else: self.checked = False self.remove_attr("checked") Widget.set_value(self, value) def get_bool(self): return self.checked class Date(Widget): _tag_name = "input" date_str = ( r"([0-9]{1,2})\." r"([0-9]{1,2})\." r"([0-9]{4})$") date_pattern = re.compile(date_str) @constraint def require_date_format(self): if not self.date_pattern.match(self.get_value()): raise Invalid("Invalid date format. Must match %s" % self.date_str) def get_date(self): match = self.date_pattern.match(self.get_value()) if not match: raise ValueError("Malformatted date %s" % self.get_value()) day, month, year = match.groups() return date(int(year), int(month), int(day)) class WidgetDict(dict): def add(self, widget): """Add widget to form""" try: widget_list = self[widget.name] if widget not in widget_list: widget_list.append(widget) except KeyError: self[widget.name] = [widget] return widget def as_list(self): return sum(self.values(), []) def getfirst(self, key): return self.widgets[key][0] class WidgetGroup(_CommonWidget): _form = None def __init__(self, name): self.name = name self.widgets = WidgetDict() def as_list(self): return self.widgets.as_list() def add(self, widget): self.widgets.add(widget) if self._form: self._form.add(widget) return widget def _init_form(self, form): self._form = form self._doc = form._doc self._error = self._doc.createElement("span") add_class_to_element("error", self._error) class Form(object): ok = True def __init__(self, name): self.name = name self._doc = Document() self.hidden_widgets = [] self.widgets = WidgetDict() self.groups = {} def _init_widget(self, widget): widget._init_form(self) def add(self, widget): if not widget._form: widget._init_form(self) self.widgets.add(widget) return widget def add_group(self, group): """Add widget group to form""" if not group._form: group._init_form(self) self.groups[group.name] = group for widget in group.as_list(): self.add(widget) return group def populate(self, valuedict): for name, value_list in valuedict.items(): try: widget = self.widgets[name][0] if isinstance(value_list, str): widget.set_value(value_list) else: widget.set_value(value_list[0]) except KeyError: pass # try: # widget_list = self.widgets[name] # except KeyError: # # Fail point, mutta ei haittaa tässä tehtävässä . # self.save_as_hidden_widget(name, value_list[0]) # else: # for widget, value in zip(widget_list, value_list): # widget.set_value(value) # def save_as_hidden_widget(self, name, value): self.add_hidden_widget(name, value) def add_hidden_widget(self, key, value): hidden = Hidden(key, value) self.hidden_widgets.append(hidden) self.add(hidden) def hidden_widgets_as_xml(self): return "\n".join(widget.input() for widget in self.hidden_widgets) def validate(self): for widget in sum(self.widgets.values(), []): if not widget.validate(): self.ok = False for group in self.groups.values(): if not group.validate(): self.ok = False return self.ok def getfirst(self, key): return self.widgets[key][0] def __getitem__(self, key): return self.widgets[key][0] def __getattr__(self, name): """ Called when an attribute lookup has not found the attribute in the usual places. """ try: return self.widgets[name] except KeyError, e: raise AttributeError(*e.args) class RequireOne(WidgetGroup): @constraint def require_one_person(self): for widget in sum(self.widgets.values(), []): if widget.get_bool(): return raise Invalid("Please select at least one") if __name__ == '__main__': f = Form("Testi") group = f.add_group(RequireOne("test")) group.add(Checkbox("Eka", "")) print f.validate()