diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2fbe9d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/.coverage +/build/ +/cover/ +/coverage.xml +/data/ +/dist/ +/*.egg-info +*.pyc diff --git a/CHANGES.rst b/CHANGES.rst new file mode 100644 index 0000000..35a34f3 --- /dev/null +++ b/CHANGES.rst @@ -0,0 +1,4 @@ +0.0 +--- + +- Initial version diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..af00ba4 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include *.txt *.ini *.cfg *.rst +recursive-include formhelpers2 *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..b07c295 --- /dev/null +++ b/README.rst @@ -0,0 +1,8 @@ +============ +formhelpers2 +============ + +formhelpers2 is an attempt to use Mike Bayer's formhelpers_ with Pyramid_. + +.. _formhelpers: http://techspot.zzzeek.org/2008/07/01/better-form-generation-with-mako-and-pylons +.. _Pyramid: http://pyramid.org diff --git a/development.ini b/development.ini new file mode 100644 index 0000000..4d4d831 --- /dev/null +++ b/development.ini @@ -0,0 +1,51 @@ +[app:main] +use = egg:formhelpers2 + +pyramid.reload_templates = true +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = true +pyramid.default_locale_name = en +pyramid.includes = pyramid_debugtoolbar + +mako.preprocessor = formhelpers2:mako.process_tags +mako.directories = formhelpers2:templates +#mako.module_directory = %(here)s/data/templates +mako.strict_undefined = true + +[server:main] +use = egg:Paste#http +host = 0.0.0.0 +port = 6543 + +# Begin logging configuration + +[loggers] +keys = root, formhelpers2 + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[logger_formhelpers2] +level = DEBUG +handlers = +qualname = formhelpers2 + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/formhelpers2/__init__.py b/formhelpers2/__init__.py new file mode 100644 index 0000000..6c07f9e --- /dev/null +++ b/formhelpers2/__init__.py @@ -0,0 +1,17 @@ +from pyramid.config import Configurator +from pyramid.session import UnencryptedCookieSessionFactoryConfig + +def main(global_config, **settings): + """ + This function returns a Pyramid WSGI application. + """ + + my_session_factory = UnencryptedCookieSessionFactoryConfig('formhelpers2') + config = Configurator(settings=settings, + session_factory=my_session_factory) + + config.add_static_view('static', 'formhelpers2:static') + + config.scan() + + return config.make_wsgi_app() diff --git a/formhelpers2/mako.py b/formhelpers2/mako.py new file mode 100644 index 0000000..8b5245c --- /dev/null +++ b/formhelpers2/mako.py @@ -0,0 +1,86 @@ +import re + + +class Form(object): + class FormContext(object): + pass + + def __init__(self, schema, name): + self.schema = schema + self.name = name + + def validate(self, data): + return self.schema.deserialize(data) + + def render(self, data=None, errors=None, context=None, **kw): + if not context: + context = self.FormContext() + if not data: + data = self.schema.serialize() + + setattr(context, self.name, dict(data.iteritems())) + + if errors: + if hasattr(context, 'errors'): + context.errors.update(errors) + else: + context.errors = errors + + for key, val in kw.items(): + setattr(context, key, val) + + return context + + +tag_regexp = re.compile(r'<(\/)?%(\w+):(\w+)\s*(.*?)(\/)?>', re.S) +attr_regexp = re.compile( + r"\s*(\w+)\s*=\s*(?:(? into a <%call> tag. + + This is a quick regexp approach that can be replaced with a full blown + XML parsing approach, if desired. + + """ + def process_exprs(t): + m = re.match(r'^\${(.+?)}$', t) + if m: + return m.group(1) + + att = [] + + def replace_expr(m): + att.append(m.group(1)) + return "%s" + + t = expr_regexp.sub(replace_expr, t) + if att: + return "'%s' %% (%s)" % (t.replace("'", r"\'"), ",".join(att)) + else: + return "'%s'" % t.replace("'", r"\'") + + def cvt(match): + if bool(match.group(1)): + return "" + + ns = match.group(2) + fname = match.group(3) + attrs = match.group(4) + + attrs = dict([(key, process_exprs(val1 or val2)) + for key, val1, val2 in attr_regexp.findall(attrs)]) + args = attrs.pop("args", "") + + attrs = ",".join(["%s=%s" % (key, value) + for key, value in attrs.iteritems()]) + + if bool(match.group(5)): + return """<%%call expr="%s.%s(%s)" args=%s/>""" % ( + ns, fname, attrs, args) + else: + return """<%%call expr="%s.%s(%s)" args=%s>""" % ( + ns, fname, attrs, args) + return tag_regexp.sub(cvt, source) diff --git a/formhelpers2/static/style.css b/formhelpers2/static/style.css new file mode 100644 index 0000000..228919d --- /dev/null +++ b/formhelpers2/static/style.css @@ -0,0 +1,21 @@ +body { background-color: #fff; color: #333; } + +body, p { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 12px; + line-height: 18px; +} +pre { + background-color: #eee; + padding: 10px; + font-size: 11px; + line-height: 13px; +} + +a { color: #000; } +a:visited { color: #666; } +a:hover { color: #fff; background-color:#000; } + +.error-message{ + color:red; +} \ No newline at end of file diff --git a/formhelpers2/subscribers.py b/formhelpers2/subscribers.py new file mode 100644 index 0000000..e36e150 --- /dev/null +++ b/formhelpers2/subscribers.py @@ -0,0 +1,20 @@ +from pyramid.events import BeforeRender +from pyramid.events import NewRequest +from pyramid.events import subscriber +from pyramid.httpexceptions import HTTPForbidden + +from webhelpers.html import tags + + +@subscriber(BeforeRender) +def add_renderer_globals(event): + event['h'] = tags + + +@subscriber(NewRequest) +def csrf_validation(event): + request = event.request + if request.method == 'POST': + token = request.POST.get('_csrf') + if token is None or token != request.session.get_csrf_token(): + raise HTTPForbidden('Cross Site Request Forgery detected') diff --git a/formhelpers2/templates/comment.mako b/formhelpers2/templates/comment.mako new file mode 100644 index 0000000..2d6c4bc --- /dev/null +++ b/formhelpers2/templates/comment.mako @@ -0,0 +1,48 @@ +<%namespace name="form" file="/form_tags.mako"/> + + + + + Mako Form Helpers + + + + +

Using Mako Helpers

+ +<%form:form name="comment_form" controller="comment" action="post"> +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
Submit your Comment
Your Name:<%form:text name="name"/>
How did you hear about this site ? + <%form:select name="heard" options="${heard_choices}"> + <%form:option value="">None + +
Comment:<%form:textarea name="comment"/>
<%form:submit/>
+ + + + diff --git a/formhelpers2/templates/form_tags.mako b/formhelpers2/templates/form_tags.mako new file mode 100644 index 0000000..29d4c12 --- /dev/null +++ b/formhelpers2/templates/form_tags.mako @@ -0,0 +1,180 @@ +<%doc> + + Mako form tag library. + + + +<%def name="errors(name)">\ +<%doc> + Given a field name, produce a stylized error message from the current + form_errors collection, if one is present. Else render nothing. +\ +% if hasattr(forms, 'errors') and name in forms.errors: +
${forms.errors[name]}
\ +% endif + + +<%def name="form(name, url=None, multipart=False, **attrs)"> +<%doc> + Render an HTML
tag - the body contents will be rendered within. + + name - the name of a dictionary placed on 'forms' which contains form values. + url - URL to be POSTed to +<% + form = getattr(forms, name) + if not isinstance(form, dict): + raise Exception("No form dictionary found at forms.%s" % name) + forms._form = form + if not url: + url = request.url +%>\ +${h.form(url, name=name, multipart=coerce_bool(multipart), **attrs)}\ +${caller.body()}\ +${h.end_form()}\ +<% + del forms._form +%> + +<%def name="text(name, **attrs)" decorator="render_error">\ +<%doc> + Render an HTML tag. +\ +${h.text(name, value=form_value(forms, name), **attrs)} + + +<%def name="upload(name, **attrs)" decorator="render_error">\ +<%doc> + Render an HTML tag. +\ +${h.file(name, **attrs)} + + +<%def name="hidden(name, **attrs)">\ +<%doc> + Render an HTML tag. +\ +${h.hidden(name, value=form_value(forms, name), **attrs)}\ + + +<%def name="password(name, **attrs)" decorator="render_error">\ +<%doc> + Render an HTML tag. +\ +${h.password(name, value=form_value(forms, name), **attrs)} + + +<%def name="textarea(name, **attrs)" decorator="render_error">\ +<%doc> + Render an HTML tag pair with embedded content. +\ +${h.textarea(name, content=form_value(forms, name), **attrs)} + + +<%def name="select(name, options=None, **kw)" decorator="render_error">\ +<%doc> + Render an HTML