pvl/web/application.py
author Tero Marttila <terom@paivola.fi>
Tue, 14 Jan 2014 23:14:53 +0200
changeset 374 d2426cebb46a
parent 368 be42e2d38c77
permissions -rw-r--r--
pvl.web.Application: render_html(body=..., extrahead=...)
"""
    WSGI Application with pvl.web.urls-mapped Handlers building pvl.web.html.
"""

import werkzeug
from werkzeug.wrappers import Request, Response
from werkzeug.exceptions import HTTPException

import pvl.web.response

import logging; log = logging.getLogger('pvl.web.application')

class Application (object) :
    """
        Main wsgi entry point state.
    """

    URLS = None

    # TODO: charset

    def __init__ (self, urls=None, layout=None) :
        """
                urls        - werkzeug.routing.Map -> Handler
                layout      - template with {TITLE} and {HEAD} and {BODY}
        """

        if not urls :
            urls = self.URLS

        if not urls :
            raise ValueError("No URLS/urls=... given")
        
        self.layout = layout
        self.urls = urls

    def respond (self, request) :
        """
            Lookup Request -> web.Handler, params
        """
        
        # bind to request
        urls = self.urls.bind_to_environ(request)
        
        # lookup
        handler, params = urls.match()

        # handler instance
        handler = handler(self, request, urls, params)

        try :
            # apply
            return handler.respond()

        finally :
            handler.cleanup()

    @Request.application
    def __call__ (self, request) :
        """
            WSGI entry point, werkzeug Request -> Response
        """

        try :
            return self.respond(request)
        
        except HTTPException as ex :
            return ex

from pvl.invoke import merge # XXX
from pvl.web.html import tags as html

class Handler (object) :
    """
        Per-Request controller/view, containing the request context and generating the response.
    """

    DOCTYPE = 'html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"'
    HTML_XMLNS = 'http://www.w3.org/1999/xhtml'
    HTML_LANG = None

    TITLE = None
    STYLE = None
    SCRIPT = None
    CSS = (
        #"/static/...css", 
    )
    JS = (
        #"/static/....js"
    )

    STATUS = 200

    def __init__ (self, app, request, urls, params) :
        """
            app     - wsgi.Application
            request - werkzeug.Request
            urls    - werkzeug.routing.Map.bind_to_environ()
        """

        self.app = app
        self.request = request
        self.urls = urls
        self.params = params

        self.init()
        
    def init (self) :
        """
            Initialize on request.
        """

    def url (self, handler=None, **params) :
        """
            Return an URL for given endpoint, with parameters,
        """

        if not handler :
            # XXX: just generate a plain-relative '?foo=...' url instead?
            handler = self.__class__
            params = merge(self.params, params)

        return self.urls.build(handler, params)

    def status (self) :
        """
            Return HTTP status code for response.
        """

        return self.STATUS

    def title (self) :
        """
            Render site/page title as text.
        """

        return self.TITLE
 
    def render (self) :
        """
            Render page content (as <body>...</body>).
        """

        raise NotImplementedError()

    def render_html (self, body=None, extrahead=None) :
        """
            Render page layout (as <html>).
        """

        title = self.title()
        head = html(
                (
                    html.link(rel='Stylesheet', type="text/css", href=src) for src in self.CSS
                ), 
                (
                    html.script(src=src, type='text/javascript', _selfclosing=False) for src in self.JS
                ),
                html.style(type='text/css')(self.STYLE) if self.STYLE else None,
                html.script(type='text/javascript')(self.SCRIPT) if self.SCRIPT else None,
                extrahead,
        )
        
        if body is None :
            body = html(self.render())

        if not title :
            raise Exception("%s: no page title!" % (self, ))

        if self.app.layout :
            return self.app.layout.format(
                TITLE   = unicode(title),
                HEAD    = unicode(head),
                BODY    = unicode(body),
            )
        else :
            return html.document(html.html(
                html.head(
                    html.title(title),
                    head,
                ),
                html.body(
                    html.h1(title),
                    body,
                )
            ), doctype=self.DOCTYPE, html_xmlns=self.HTML_XMLNS, html_lang=self.HTML_LANG)

    def response_file (self, file) :
        """
            Wrap a file for returning in a response with direct_passthrough=True.
        """

        return werkzeug.wrap_file(self.request.environ, file)

    def process (self, **params) :
        """
            Process request args to build internal request state.
        """

        pass

    def respond (self) :
        """
            Generate a response, or raise an HTTPException

            Does an HTML layout'd response per default.
        """
        
        # returning e.g. redirect?
        response = self.process(**self.params)

        if response :
            return response
        
        # render as html per default
        # XXX: might also be string?
        text = unicode(self.render_html())

        return pvl.web.response.html(text,
                status  = self.status(),
        )
    
    def cleanup (self) :
        """
            After request processing. Do not fail :)
        """
        
        pass