A little while ago I asked a question on #pocoo:
"When using a tree of Jinja loaders such as PrefixLoader, ChoiceLoader
it is sometimes useful to know which leaf-level loader (e.g.
FileSystemLoader) actually provides the template. What's the best way
of doing this?"
I explained that I needed this for a component-based project, and some
IRC discussion ensured wherein Armin pointed me to URL generation in
Solace. I've looked at it in a bit more detail and of course there are
many ways to achieve a specific effect, but here's a scenario which
encapsulates my problem. It doesn't occur for monolitically developed
applications - but in my case, I'm building a component-based
application. The requirement is to have a setup which allows
independently developed components (sometimes developed by third
parties, and where the source code of the component can't be tweaked)
to be plugged together using a site configuration component and
configuration data.
By "component-based", I mean that the system is driven by components,
i.e. classes, which if designed correctly offer more flexible options
for reuse than just using functions and modules. (See how Django's
function-based views aren't always ideal, leading to development of
newer ideas such as class-based views.) Say I have three components in
my web site, one to hold the site configuration logic itself, and then
two others, "public" and "faq", which manage their own functionality
through instances of classes Public and Faq. This functionality
includes handling requests, which in turn involves rendering
templates. Each component manages endpoints: public manages "index",
"about" and "contact". Faq manages "faq" and "news".
My URL map looks like this:
Map([[<Rule '/' -> public/index>,
<Rule '/about/' -> public/about>,
<Rule '/contact/' -> public/contact>,
<Rule '/static/public/<file>' -> public/static>,
<Rule '/faq/' -> faq/faq>,
<Rule '/news/' -> faq/news>]])
As you can see, I'm using EndpointPrefix in the site configuration to
distinguish the endpoints managed by each component.
During deployment, a single Jinja2 environment (owned by the "site"
component) is set up with a PrefixLoader where "public" maps to the
Public instance's loader, "faq" to the Faq instance's loader. There's
a specific template in the Public component called "public/
layout.html" which contains a "{{ url_for('static',
file='style.css') }}" fragment and a placeholder block named
"content". The Public component also contains a template "public/
about.html" which extends "public/layout.html" and defines a "content"
block containing some "About this site" content.
The Faq component contains a template "faq/faq.html" which extends
"public/layout.html" and defines some FAQs in block "content", which
contains a fragment with "{{ url_for('news') }}".
Notice that the only dependency between "faq" and "public" consists of
the 'extends "public/layout.html"' in the "faq/faq.html" template, and
that each component is self-consistent - Public implements an endpoint
"static" and Faq implements an endpoint "news".
My problem comes in implementing url_for in a generic way, i.e. one
which does not depend (in a "hard-coded" kind of way) on the specifics
of how components are wired together in a specific site. Perhaps wiser
heads on this list can help with this. A simple approach would be
based on knowing which component is handling a particular request.
When we dispatch the request to a view, we can record the component
which implements the view in a thread-local. So when dispatching a
request for "/about/", we see that the matching endpoint is "public/
about", determine that the component to handle it is the Public
instance, strip the prefix, get the handler for "about" from the
instance and call it. We record this instance as the handling
component in a thread-local, then call the view. When url_for is
subsequently called while rendering "public/about.html", it gets the
endpoint prefix of the component stored in thread-local, prefixes that
to the passed-in endpoint and then does an adapter.build
(endpoint, ...). So when url_for("static") is implemented, the
endpoint gets converted to "public/static" before the build() call,
which results in a correct resolution to /static/public/style.css.
However, this breaks down when handling a request to "/faq/" - we
establish that the component is the Faq instance, set it in the thread-
local, and call the view. When "faq/faq.html" is rendered, it invokes
"public/layout.html", which invokes url_for("static"), which
(according to the algorithm above) tries adapter.build("faq/
static", ...) which of course fails, because Faq doesn't manage any
endpoint "static".
Solace's solution, which is to iterate over available endpoints,
doesn't necessarily work in the general case. For example, if Faq were
to manage a new endpoint "static" and its content block were to invoke
an "{{ url_for('static', file='style.css') }}" one might reasonably
expect this to resolve to faq's static media (maybe it's just the
additional styles needed to present FAQs). So ... I'd want to have
url_for("static") in "public/layout.html" to call adapter.build
("public/static"), url_for("static") in "faq/faq.html" to call
adapter.build("faq/static").
I've considered the obvious alternative of using the template prefix
(rather than the handler's endpoint prefix) in implementing url_for
(thinking about this was what prompted my original question on
#pocoo). However, AFAIK url_for has no way of knowing from which
template it's being invoked, and as template invocations via include/
extend occur internally to Jinja, there's no easy way to get at which
endpoint to use. For example, in a rendering of "faq/faq.html",
1. The "extends" tag causes rendering of "public/layout.html". Any
url_for('x') at this point should lead to adapter.build('public/x').
2. At some point, rendering moves to "faq/faq.html". In here, any
url_for('x') should lead to adapter.build('faq/x').
3. Later, rendering moves back to "public/layout.html". Any url_for
('x') should lead to adapter.build('public/x').
4. If at some point another template "foo/bar.html" is included by
"public/layout.html", within that, url_for('x') should lead to
adapter.build('foo/x')
Is there a simple solution for my problem which I'm missing?
Thanks for your patience,
Vinay Sajip
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"pocoo-libs" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/pocoo-libs?hl=en
-~----------~----~----~----~------~----~------~--~---