From 860c36b0f263f1ffc4211eb8a302dbcaf8eb2061 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Sun, 2 Jul 2017 11:32:28 -0500 Subject: [PATCH] app: Support multiple resource representations The `VariedResponse` class essentially turns the website into a REST API. When a controller returns a response object that is an instance of this class, the response representation sent to the user-agent will vary based on the value of the `Accept` request header. Specifically, requests containing `Accept: application/json` will receive a JSON-encoded object, while typical browser requests will get an (X)HTML document. --- src/dcow/app.py | 27 ++++++++++++++++++++++++-- src/dcow/base.py | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 src/dcow/base.py diff --git a/src/dcow/app.py b/src/dcow/app.py index 24f4fbe..1a04d96 100644 --- a/src/dcow/app.py +++ b/src/dcow/app.py @@ -1,7 +1,12 @@ from milla.dispatch import routing -from . import gallery, thumbnails -import os +from . import ( + base, + gallery, + thumbnails, +) +import functools import milla.util +import os DEFAULT_CONFIG = { @@ -30,3 +35,21 @@ class Application(milla.Application): r.add_route('/', gallery.GalleryController()) r.add_route('/thumbnails/{image}', thumbnails.ThumbnailController()) + + def make_request(self, environ): + request = super(Application, self).make_request(environ) + default_format = base.VariedResponse.default_content_type + offers = [ + 'application/json', + 'application/xhtml+xml', + 'text/html', + ] + want_fmt = request.accept.best_match(offers, default_format) + if want_fmt == 'application/json': + request.want = 'json' + elif want_fmt == 'text/html': + request.want = 'html' + else: + request.want = 'xhtml' + request.ResponseClass = functools.partial(base.VariedResponse, request) + return request diff --git a/src/dcow/base.py b/src/dcow/base.py new file mode 100644 index 0000000..4a14905 --- /dev/null +++ b/src/dcow/base.py @@ -0,0 +1,49 @@ +import jinja2 +import json +import milla + + +class Template(object): + + LOADER = jinja2.PackageLoader(__name__.rsplit('.', 1)[0]) + + def __init__(self, name): + self.env = jinja2.Environment(loader=self.LOADER) + self.tmpl = self.env.get_template(name) + + def render(self, context): + return self.tmpl.render(**context) + + +class VariedResponse(milla.Response): + + def __init__(self, request, *args, **kwargs): + super(VariedResponse, self).__init__(*args, **kwargs) + self.request = request + + def set_payload(self, template, context=None): + if not self.vary: + self.vary = ['Accept'] + elif 'accept' not in (v.lower() for v in self.vary): + self.vary = self.vary + ('Accept',) + if context is None: + context = {} + if template is None or self.request.want == 'json': + self.content_type = str('application/json') + json.dump(context, self.body_file) + else: + if self.request.want == 'xhtml': + self.content_type = str('application/xhtml+xml') + self.render(template, context) + + def render(self, template, context=None): + if context is None: + context = {} + t = Template(template) + t.env.globals.update( + url=self.request.create_href, + static=self.request.static_resource, + ) + if hasattr(self.request, 'user'): + t.env.globals['user'] = self.request.user + self.text = t.render(context)