from __future__ import nested_scopes
from twisted.python import formmethod, failure
from twisted.python.components import registerAdapter, getAdapter
from twisted.web import domhelpers, resource, util
from twisted.internet import defer
from twisted.web.woven import model, view, controller, widgets, input, interfaces
from twisted.web.microdom import parseString, lmx, Element
import math
_renderers = {}
def registerRenderer(argumentClass, renderer):
"""Register a renderer for a given argument class.
The renderer function should act in the same way
as the 'input_XXX' methods of C{FormFillerWidget}.
"""
assert callable(renderer)
global _renderers
_renderers[argumentClass] = renderer
class FormFillerWidget(widgets.Widget):
SPANNING_TYPES = ["hidden", "submit"]
def getValue(self, request, argument):
"""Return value for form input."""
if not self.model.alwaysDefault:
values = request.args.get(argument.name, None)
if values:
try:
return argument.coerce(values[0])
except formmethod.InputError:
return values[0]
return argument.default
def getValues(self, request, argument):
"""Return values for form input."""
if not self.model.alwaysDefault:
values = request.args.get(argument.name, None)
if values:
try:
return argument.coerce(values)
except formmethod.InputError:
return values
return argument.default
def createShell(self, request, node, data):
"""Create a `shell' node that will hold the additional form
elements, if one is required.
"""
return lmx(node).table(border="0")
def input_single(self, request, content, model, templateAttributes={}):
"""
Returns a text input node built based upon the node model.
Optionally takes an already-coded DOM node merges that
information with the model's information. Returns a new (??)
lmx node.
"""
attribs = ['type', 'name', 'value', 'size', 'maxlength',
'readonly']
arguments = {}
for attrib in attribs:
val = model.getHint(attrib, templateAttributes.get(attrib, None))
if val:
arguments[attrib] = str(val)
value = self.getValue(request, model)
if value:
arguments["value"] = str(value)
arguments["type"] = "text" arguments["name"] = model.name
return content.input(**arguments)
def input_string(self, request, content, model, templateAttributes={}):
if not templateAttributes.has_key("size"):
templateAttributes["size"] = '60'
return self.input_single(request, content, model, templateAttributes)
input_integer = input_single
input_integerrange = input_single
input_float = input_single
def input_text(self, request, content, model, templateAttributes={}):
r = content.textarea(
cols=str(model.getHint('cols',
templateAttributes.get('cols', '60'))),
rows=str(model.getHint('rows',
templateAttributes.get('rows', '10'))),
name=model.name,
wrap=str(model.getHint('wrap',
templateAttributes.get('wrap', "virtual"))))
r.text(str(self.getValue(request, model)))
return r
def input_hidden(self, request, content, model, templateAttributes={}):
return content.input(type="hidden",
name=model.name,
value=str(self.getValue(request, model)))
def input_submit(self, request, content, model, templateAttributes={}):
arguments = {}
val = model.getHint("onClick", templateAttributes.get("onClick", None))
if val:
arguments["onClick"] = val
arguments["type"] = "submit"
arguments["name"] = model.name
div = content.div()
for tag, value, desc in model.choices:
args = arguments.copy()
args["value"] = tag
div.input(**args)
div.text(" ")
if model.reset:
div.input(type="reset")
return div
def input_choice(self, request, content, model, templateAttributes={}):
arguments = {}
val = model.getHint("onChange", templateAttributes.get("onChange", None))
if val:
arguments["onChange"] = val
arguments["name"] = model.name
s = content.select(**arguments)
default = self.getValues(request, model)
for tag, value, desc in model.choices:
kw = {}
if value in default:
kw = {'selected' : '1'}
s.option(value=tag, **kw).text(desc)
return s
def input_group(self, request, content, model, groupValues, inputType,
templateAttributes={}):
"""
Base code for a group of objects. Checkgroup will use this, as
well as radiogroup. In the attributes, rows means how many rows
the group should be arranged into, cols means how many cols the
group should be arranged into. Columns take precedence over
rows: if both are specified, the output will always generate the
correct number of columns. However, if the number of elements
in the group exceed (or is smaller than) rows*cols, then the
number of rows will be off. A cols attribute of 1 will mean that
all the elements will be listed one underneath another. The
default is a rows attribute of 1: everything listed next to each
other.
"""
rows = model.getHint('rows', templateAttributes.get('rows', None))
cols = model.getHint('cols', templateAttributes.get('cols', None))
if rows:
rows = int(rows)
if cols:
cols = int(cols)
defaults = self.getValues(request, model)
if (rows and rows>1) or (cols and cols>1): s = content.table(border="0")
if cols:
breakat = cols
else:
breakat = math.ceil(float(len(groupValues))/rows)
for i in range(0, len(groupValues), breakat):
tr = s.tr()
for j in range(0, breakat):
if i+j >= len(groupValues):
break
tag, value, desc = groupValues[i+j]
kw = {}
if value in defaults:
kw = {'checked' : '1'}
tr.td().input(type=inputType, name=model.name,
value=tag, **kw).text(desc)
else:
s = content.div()
for tag, value, desc in groupValues:
kw = {}
if value in defaults:
kw = {'checked' : '1'}
s.input(type=inputType, name=model.name,
value=tag, **kw).text(desc)
if cols:
s.br()
return s
def input_checkgroup(self, request, content, model, templateAttributes={}):
return self.input_group(request, content, model, model.flags,
"checkbox", templateAttributes)
def input_radiogroup(self, request, content, model, templateAttributes={}):
return self.input_group(request, content, model, model.choices,
"radio", templateAttributes)
input_flags = input_checkgroup
def input_boolean(self, request, content, model, templateAttributes={}):
kw = {}
if self.getValue(request, model):
kw = {'checked' : '1'}
return content.input(type="checkbox", name=model.name, **kw)
def input_file(self, request, content, model, templateAttributes={}):
kw = {}
for attrib in ['size', 'accept']:
val = model.getHint(attrib, templateAttributes.get(attrib, None))
if val:
kw[attrib] = str(val)
return content.input(type="file", name=model.name, **kw)
def input_date(self, request, content, model, templateAttributes={}):
breakLines = model.getHint('breaklines', 1)
date = self.getValues(request, model)
if date == None:
year, month, day = "", "", ""
else:
year, month, day = date
div = content.div()
div.text("Year: ")
div.input(type="text", size="4", maxlength="4", name=model.name, value=str(year))
if breakLines:
div.br()
div.text("Month: ")
div.input(type="text", size="2", maxlength="2", name=model.name, value=str(month))
if breakLines:
div.br()
div.text("Day: ")
div.input(type="text", size="2", maxlength="2", name=model.name, value=str(day))
return div
def input_password(self, request, content, model, templateAttributes={}):
return content.input(
type="password",
size=str(templateAttributes.get('size', "60")),
name=model.name)
def input_verifiedpassword(self, request, content, model, templateAttributes={}):
breakLines = model.getHint('breaklines', 1)
values = self.getValues(request, model)
if isinstance(values, (str, unicode)):
values = (values, values)
if not values:
p1, p2 = "", ""
elif len(values) == 1:
p1, p2 = values, ""
elif len(values) == 2:
p1, p2 = values
else:
p1, p2 = "", ""
div = content.div()
div.text("Password: ")
div.input(type="password", size="20", name=model.name, value=str(p1))
if breakLines:
div.br()
div.text("Verify: ")
div.input(type="password", size="20", name=model.name, value=str(p2))
return div
def convergeInput(self, request, content, model, templateNode):
name = model.__class__.__name__.lower()
if _renderers.has_key(model.__class__):
imeth = _renderers[model.__class__]
else:
imeth = getattr(self,"input_"+name)
return imeth(request, content, model, templateNode.attributes).node
def createInput(self, request, shell, model, templateAttributes={}):
name = model.__class__.__name__.lower()
if _renderers.has_key(model.__class__):
imeth = _renderers[model.__class__]
else:
imeth = getattr(self,"input_"+name)
if name in self.SPANNING_TYPES:
td = shell.tr().td(valign="top", colspan="2")
return (imeth(request, td, model).node, shell.tr().td(colspan="2").node)
else:
if model.allowNone:
required = ""
else:
required = " *"
tr = shell.tr()
tr.td(align="right", valign="top").text(model.getShortDescription()+":"+required)
content = tr.td(valign="top")
return (imeth(request, content, model).node,
content.div(_class="formDescription"). text(model.getLongDescription()).node)
def setUp(self, request, node, data):
lmn = lmx(node)
if not node.hasAttribute('action'):
lmn['action'] = (request.prepath+request.postpath)[-1]
if not node.hasAttribute("method"):
lmn['method'] = 'post'
lmn['enctype'] = 'multipart/form-data'
self.errorNodes = errorNodes = {} self.inputNodes = inputNodes = {}
for errorNode in domhelpers.findElementsWithAttribute(node, 'errorFor'):
errorNodes[errorNode.getAttribute('errorFor')] = errorNode
argz={}
hasSubmit = 0
argList = self.model.fmethod.getArgs()
for arg in argList:
if isinstance(arg, formmethod.Submit):
hasSubmit = 1
argz[arg.name] = arg
inNodes = domhelpers.findElements(
node,
lambda n: n.tagName.lower() in ('textarea', 'select', 'input',
'div'))
for inNode in inNodes:
t = inNode.getAttribute("type")
if t and t.lower() == "submit":
hasSubmit = 1
if not inNode.hasAttribute("name"):
continue
nName = inNode.getAttribute("name")
if argz.has_key(nName):
inputNodes[nName] = self.convergeInput(request, lmx(),
argz[nName], inNode)
inNode.parentNode.replaceChild(inputNodes[nName], inNode)
del argz[nName]
if argz:
shell = self.createShell(request, node, data)
for remArg in [arg for arg in argList if argz.has_key(arg.name)]:
inputNode, errorNode = self.createInput(request, shell, remArg)
errorNodes[remArg.name] = errorNode
inputNodes[remArg.name] = inputNode
if not hasSubmit:
lmn.input(type="submit")
class FormErrorWidget(FormFillerWidget):
def setUp(self, request, node, data):
FormFillerWidget.setUp(self, request, node, data)
for k, f in self.model.err.items():
en = self.errorNodes[k]
tn = self.inputNodes[k]
en.setAttribute('class', 'formError')
tn.setAttribute('class', 'formInputError')
en.childNodes[:]=[] if isinstance(f, failure.Failure):
f = f.getErrorMessage()
lmx(en).text(str(f))
class FormDisplayModel(model.MethodModel):
def initialize(self, fmethod, alwaysDefault=False):
self.fmethod = fmethod
self.alwaysDefault = alwaysDefault
class FormErrorModel(FormDisplayModel):
def initialize(self, fmethod, args, err):
FormDisplayModel.initialize(self, fmethod)
self.args = args
if isinstance(err, failure.Failure):
err = err.value
if isinstance(err, Exception):
self.err = getattr(err, "descriptions", {})
self.desc = err
else:
self.err = err
self.desc = "Please try again"
def wmfactory_description(self, request):
return str(self.desc)
class _RequestHack(model.MethodModel):
def wmfactory_hack(self, request):
rv = [[str(a), repr(b)] for (a, b)
in request._outDict.items()]
return rv
class FormProcessor(resource.Resource):
def __init__(self, formMethod, callback=None, errback=None):
resource.Resource.__init__(self)
self.formMethod = formMethod
if callback is None:
callback = self.viewFactory
self.callback = callback
if errback is None:
errback = self.errorViewFactory
self.errback = errback
def getArgs(self, request):
"""Return the formmethod.Arguments.
Overridable hook to allow pre-processing, e.g. if we want to enable
on them depending on one of the inputs.
"""
return self.formMethod.getArgs()
def render(self, request):
outDict = {}
errDict = {}
for methodArg in self.getArgs(request):
valmethod = getattr(self,"mangle_"+
(methodArg.__class__.__name__.lower()), None)
tmpval = request.args.get(methodArg.name)
if valmethod:
tmpval = valmethod(tmpval)
try:
cv = methodArg.coerce(tmpval)
outDict[methodArg.name] = cv
except:
errDict[methodArg.name] = failure.Failure()
if errDict:
return self.errback(self.errorModelFactory(
request.args, outDict, errDict)).render(request)
else:
try:
if self.formMethod.takesRequest:
outObj = self.formMethod.call(request=request, **outDict)
else:
outObj = self.formMethod.call(**outDict)
except formmethod.FormException, e:
err = request.errorInfo = self.errorModelFactory(
request.args, outDict, e)
return self.errback(err).render(request)
else:
request._outDict = outDict if isinstance(outObj, defer.Deferred):
def _ebModel(err):
if err.trap(formmethod.FormException):
mf = self.errorModelFactory(request.args, outDict,
err.value)
return self.errback(mf)
raise err
(outObj
.addCallback(self.modelFactory)
.addCallback(self.callback)
.addErrback(_ebModel))
return util.DeferredResource(outObj).render(request)
else:
return self.callback(self.modelFactory(outObj)).render(
request)
def errorModelFactory(self, args, out, err):
return FormErrorModel(self.formMethod, args, err)
def errorViewFactory(self, m):
v = view.View(m)
v.template = '''
<html>
<head>
<title> Form Error View </title>
<style>
.formDescription {color: green}
.formError {color: red; font-weight: bold}
.formInputError {color: #900}
</style>
</head>
<body>
Error: <span model="description" />
<form model=".">
</form>
</body>
</html>
'''
return v
def modelFactory(self, outObj):
adapt = getAdapter(outObj, interfaces.IModel, outObj)
return adapt
def viewFactory(self, model):
if model is None:
bodyStr = '''
<table model="hack" style="background-color: #99f">
<tr pattern="listItem" view="Widget">
<td model="0" style="font-weight: bold">
</td>
<td model="1">
</td>
</tr>
</table>
'''
model = _RequestHack()
else:
bodyStr = '<div model="." />'
v = view.View(model)
v.template = '''
<html>
<head>
<title> Thank You </title>
</head>
<body>
<h1>Thank You for Using Woven</h1>
%s
</body>
</html>
''' % bodyStr
return v
def mangle_single(self, args):
if args:
return args[0]
else:
return ''
mangle_string = mangle_single
mangle_text = mangle_single
mangle_integer = mangle_single
mangle_password = mangle_single
mangle_integerrange = mangle_single
mangle_float = mangle_single
mangle_choice = mangle_single
mangle_boolean = mangle_single
mangle_hidden = mangle_single
mangle_submit = mangle_single
mangle_file = mangle_single
mangle_radiogroup = mangle_single
def mangle_multi(self, args):
if args is None:
return []
return args
mangle_checkgroup = mangle_multi
mangle_flags = mangle_multi
from twisted.python.formmethod import FormMethod
view.registerViewForModel(FormFillerWidget, FormDisplayModel)
view.registerViewForModel(FormErrorWidget, FormErrorModel)
registerAdapter(FormDisplayModel, FormMethod, interfaces.IModel)