Edgewall Software

Changes between Version 12 and Version 13 of GenshiTutorial

Aug 29, 2007 2:01:59 PM (11 years ago)



  • GenshiTutorial

    v12 v13  
    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.
     455== Factoring out the Templating ==
     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.
     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.
     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:
     465import os
     467import cherrypy
     468from genshi.core import Stream
     469from genshi.output import encode, get_serializer
     470from genshi.template import TemplateLoader
     472loader = TemplateLoader(
     473    os.path.join(os.path.dirname(__file__), '..', 'templates'),
     474    auto_reload=True
     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
     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)
     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:
     511from geddit.lib import template
     514Now, we can change the `Root` class to match the following:
     518class Root(object):
     520    def __init__(self, data):
     521        self.data = data
     523    @cherrypy.expose
     524    @template.output('index.html', method='html', doctype='html')
     525    def index(self):
     526        return template.render(submissions=self.data)
     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 = {}
     545        return template.render(errors=errors) | HTMLFormFiller(data=data)
     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.