Edgewall Software

Changes between Version 12 and Version 13 of GenshiTutorial


Ignore:
Timestamp:
Aug 29, 2007, 2:01:59 PM (17 years ago)
Author:
cmlenz
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • GenshiTutorial

    v12 v13  
    452452
    453453Now, all entered values are preserved when validation errors occur. Note that the form is populated as the template is being generated, there is no reparsing and reserialization of the output.
     454
     455== Factoring out the Templating ==
     456
     457By now, we already have some repetitive code when it comes to rendering templates: both the `Root.index()` and the `Root.submit()` methods look very similar in that regard: they load a specific template, call its `generate()` method passing it some data, and then call the `render()` method of the resulting stream. As we're going to be adding more controller methods, let's factor out those things into a library module.
     458
     459There's a special challenge here, though: we still want to be able to add the `HTMLFormFiller` or other stream filters to the template output stream, which needs to be done before that output stream is serialized. We'll use a combination of a decorator and a regular function to achieve that, which collaborate using the !CherryPy thread-local context.
     460
     461Create a directory called `lib` inside the `geddit` directory, and inside the `lib` directory create two files, named `__init__.py` and `template.py`, respectively. Leave the first one empty, and in the second one, insert the following code:
     462
     463{{{
     464#!python
     465import os
     466
     467import cherrypy
     468from genshi.core import Stream
     469from genshi.output import encode, get_serializer
     470from genshi.template import TemplateLoader
     471
     472loader = TemplateLoader(
     473    os.path.join(os.path.dirname(__file__), '..', 'templates'),
     474    auto_reload=True
     475)
     476
     477def output(filename, method=None, encoding='utf-8', **options):
     478    """Decorator for exposed methods to specify what template the should use
     479    for rendering, and which serialization method and options should be
     480    applied.
     481    """
     482    def decorate(func):
     483        def wrapper(*args, **kwargs):
     484            cherrypy.thread_data.template = loader.load(filename)
     485            serializer = get_serializer(method, **options)
     486            stream = func(*args, **kwargs)
     487            if not isinstance(stream, Stream):
     488                return stream
     489            return encode(serializer(stream), method=serializer,
     490                          encoding=encoding)
     491        return wrapper
     492    return decorate
     493
     494def render(*args, **kwargs):
     495    """Function to render the given data to the template specified via the
     496    ``@output`` decorator.
     497    """
     498    if args:
     499        assert len(args) == 1, \
     500            'Expected exactly one argument, but got %r' % (args,)
     501        template = loader.load(args[0])
     502    else:
     503        template = cherrypy.thread_data.template
     504    return template.generate(**kwargs)
     505}}}
     506
     507In the `genshi/controller.py` file, you can now remove the `from genshi.template import TemplateLoader` line, and also the instantiation of the `TemplateLoader`, as that is now done in our new library module. Of course, you'll have to import that library module instead:
     508
     509{{{
     510#!python
     511from geddit.lib import template
     512}}}
     513
     514Now, we can change the `Root` class to match the following:
     515
     516{{{
     517#!python
     518class Root(object):
     519
     520    def __init__(self, data):
     521        self.data = data
     522
     523    @cherrypy.expose
     524    @template.output('index.html', method='html', doctype='html')
     525    def index(self):
     526        return template.render(submissions=self.data)
     527
     528    @cherrypy.expose
     529    @template.output('submit.html', method='html', doctype='html')
     530    def submit(self, cancel=False, **data):
     531        if cherrypy.request.method == 'POST':
     532            if cancel:
     533                raise cherrypy.HTTPRedirect('/')
     534            form = SubmissionForm()
     535            try:
     536                data = form.to_python(data)
     537                submission = Submission(**data)
     538                self.data.append(submission)
     539                raise cherrypy.HTTPRedirect('/')
     540            except Invalid, e:
     541                errors = e.unpack_errors()
     542        else:
     543            errors = {}
     544
     545        return template.render(errors=errors) | HTMLFormFiller(data=data)
     546}}}
     547
     548There's still some duplication of code in the decorators: the need to specify the serialization method and the doctype over and over again is suboptimal, and should ideally be made configurable in a central location.