# coding: utf-8
"""
Generic controller base classes.
respond(Request) -> Response
"""
import werkzeug
from werkzeug import Response
from svv import html, pdf
from svv.html import tags
class AppHandler (object):
"""
Per-request handler type, containing the request context and implementing the response.
Works on a generic level, i.e. WSGI/Werkzeug URL-mapped request in, response out.
"""
def __init__ (self, app, request, urlmap) :
"""
Initialize for processing the given Request, to prepare for action-method later on
app - the global Application state
request - the request state
urlmap - the URL map we were matched through
"""
self.app = app
self.request = request
self.urlmap = urlmap
def url_for (self, endpoint, **values) :
"""
Return an URL for the given endpoint, applying the given URL-embedded values
"""
return self.urlmap.build(endpoint, values)
def redirect_for (self, endpoint, **values) :
"""
Return a Response that will redirect the client to the given endpoint.
"""
return werkzeug.redirect(self.url_for(endpoint, **values))
@property
def POST (self) :
"""
Access request's POST data
"""
# MultiDict from werkzeug.Request
return self.request.form
def respond (self, **url_values) :
"""
Handle request that was mapped to ourselves via the URL routing, using given dict of values from URL.
"""
# process e.g. POST data for e.g. redirect
response = self.process(**url_values)
if not response :
# assume superclass does something else if process didn't handle it
pass
return response
def process (self, **args) :
"""
Process incoming POST data, optionally returning a redirect response.
"""
# default to ignore
pass
class PageHandler (AppHandler) :
"""
Specialized AppHandler for normal HTML page views.
Renders the layout template and HTML.
"""
def render_content (self, **args) :
"""
Render and return HTML for page content.
XXX: must be HTML object, support just text?
"""
raise NotImplementedError()
def render_layout (self, content) :
"""
Render the page layout for the given content block.
"""
# XXX: layout template, should be somewhere far away
title = "Index"
css = [
"/static/layout.css",
"/static/style.css",
"/static/forms.css",
"/static/tables.css",
"/static/treelist.css",
"/static/jquery-ui.theme/jquery-ui.css",
]
scripts = [
"/static/js/jquery.js",
"/static/js/jquery-ui.min.js", # fairly big
"/static/js/jquery-ui-timepicker.js",
"/static/js/jquery-ui-datepicker-fi.js",
"/static/js/forms.js",
#"/static/js/prototype.js",
#"/static/js/scriptaculous/scriptaculous.js",
#"/static/js/treelist.js"
]
head = (
tags.title(title),
(tags.link(rel='Stylesheet', type="text/css", href=src) for src in css),
# XXX: script can't self-close >_<
(tags.script(src=src)(" ") for src in scripts),
)
# XXX: silly circular-import hack for urls
# it's not sustainable for a base class to referr to its subclasses at define time
from svv import urls
header = ("Foo List")
nav = [tags.ul(tags.li(tags.a(href='#')(name)) for name in ['Nav A', 'Nav B', 'Nav C'])]
menu = [tags.ul(tags.li(tags.a(href=url)(name)) for name, url in [
("Etusivu", self.url_for(urls.Index)),
("Uusi tilaus", self.url_for(urls.NewOrderView)),
("Tilaukset", self.url_for(urls.OrdersView)),
("Tilaajat", self.url_for(urls.CustomersView)),
])]
footer = ("Copyright?")
layout = (
tags.div(id='header')(header),
tags.div(id='container')(
tags.div(id='menu')(menu),
# tags.div(id='nav')(nav),
tags.div(id='content')(content),
),
tags.div(id='footer')(footer),
)
# perform the actual rendering (run generators etc.)
return unicode(html.document(head, layout))
def render (self, **url_values) :
"""
Render full page HTML
"""
# render page content
content = self.render_content(**url_values)
# render into layout
response = self.render_layout(content)
# ok
return response
def respond (self, **url_values) :
"""
Build and return a response from the following steps:
* process()
* render() -> render_content() as HTML
"""
# optional processing
response = super(PageHandler, self).respond(**url_values)
if not response :
# render page HTML
html = self.render(**url_values)
# response object
# XXX: unicode?
return Response(html, mimetype='text/html')
# ok
return response
class DocumentHandler (AppHandler) :
"""
PDF generation/export
"""
def respond (self, **url_values) :
"""
Generate the document, and return it as a .pdf file, with the filename generated from the document's title.
"""
# optional processing
response = super(DocumentHandler, self).respond(**url_values)
if not response :
pdf_file = self.generate(**url_values)
# file wrapper
# XXX: is this any use at all for StringIO?
pdf_file = werkzeug.wrap_file(self.request.environ, pdf_file)
# respond with file wrapper
response = Response(pdf_file, mimetype='application/pdf', direct_passthrough=True)
# ok
return response
def generate (self, **url_values) :
"""
Generate the PDF document as a file-like object.
"""
document, elements = self.generate_document(**url_values)
# render and return StringIO
return document.render_buf(elements)
def generate_document (self, **url_values) :
"""
Return the
(DocumentTemplate, elements)
to generate the PDF document from.
"""
raise NotImplementedError()
### XXX: random test controllers
class Index (PageHandler) :
DATA = (
(100, "Top A", []),
(200, "Top B", [
(210, "Mid BA", [
(211, "Sub BAA", []),
(212, "Sub BAB", []),
]),
(220, "Mid BB", []),
]),
(300, "Top C", [
(310, "Mid CA", []),
(320, "Mid CB", []),
]),
)
def render_content (self) :
# build data set
data = self.DATA
def render_item (id, text, children) :
return tags.li(
tags.div(class_='item')(
# item text
tags.a(tags.input(type='checkbox', name_=id, checked=(
'checked' if self.request.form.get(str(id)) else None
)), text, class_='text'),
),
# children
tags.ul(render_item(*item) for item in children if item) if children else None,
#
class_=('more open' if children else None),
)
# XXX: nothing much to see here
return tags.h1("Mui.")
# render it
return (
tags.h3("Item list"),
tags.form(action='.', method='post')(
tags.ul(class_='treelist')(render_item(*item) for item in data),
tags.input(type='submit'),
),
)