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