One of the constant annoyances to me in web applications is the self-proclaimed need for those applications to know about everything and do everything, and only spotty ad hoc techniques for including things from other applications.
An example might be blog navigation or search, where you can only include data from the application itself. Or "Recent Posts" which can only show locally-produce posts. What if I post something elsewhere? I have to create some shoddy placeholder post to refer to it. Bah! Underlying this the data is usually structured in a specific way, with the HTML being a sort of artifact of the database, the markup transient and a slave to the database’s structure.
An example of this might be a recent post listing like:
<ul>
for post in recent_posts:
<li>
<a href="/post/{{post.year}}/{{post.month}}/{{post.slug}}">
{{post.title}}</a>
</li>
</ul>
There’s clearly no room for exceptions in this code. I am thus proposing that any system like this should have the notion of a "link" as a first-class object. The code should look like this:
<ul>
for post in recent_posts:
<li>
{{post.link()}}
</li>
</ul>
Just like with changing IDs to links in service documents, the template doesn’t actually look any more complicated than it did before (simpler, even). But now we can use simple object-oriented techniques to create first-class links. The code might look like:
class Post(SomeORM):
def url(self):
if self.type == 'link':
return self.body
else:
base = get_request().application_url
return '%s/%s/%s/%s' % (
base, self.year, self.month, self.slug)
def link(self):
return html('<a href="%s">%s</a>') % (
self.url(), self.title)
The addition of the .url() method has the obvious effect of making these offsite links work. Using a .link() method has the added advantage of allowing things like HTML snippets to be inserted into the system (even though that is not implemented here). By allowing arbitrary HTML in certain places you make it possible for people to extend the site in little ways — possibly adding markup to a title, or allowing an item in the list that actually contains two URLs (e.g., <a href="url1">Some Item</a> (<a href="url2">via</a>)).
In the context of Python I recommend making these into methods, not properties, because it allows you to later add keyword arguments to specialize the markup (like post.link(abbreviated=True)).
One negative aspect of this is that you cannot affect all the markup through the template alone, you may have to go into the Python code to change things. Anyone have ideas for handling this problem?
Automatically generated list of related posts:
- Atompub as an alternative to WebDAV I’ve been thinking about an import/export API for PickyWiki; I...
- Hypertext-driven URLs Roy T. Fielding, author of the REST thesis wrote an...
- HTML Accessibility So I gave a presentation at PyCon about HTML, which...
- lxml: an underappreciated web scraping library When people think about web scraping in Python, they usually...
Interesting post! If you’re interested in this concept, Adam Gomaa has taken this basic idea, but in a different direction by (ab?)using Properties. It’s interesting to look at both similar approaches, I think. http://adam.gomaa.us/blog/the-python-property-builtin/
I prefer to configure my template engine to render a link to an object simply by naming it; in Mako for example, this can be done by specifying a filter which sniffs the class of whatever object is being rendered, and converts it to a properly formatted string. This approach scales beyond simply linking to domain objects — one might apply to every
int
orfloat
aspan
of classnumber
, or something like that. It also keeps such rendering logic cleanly factored out of the domain model.How about keeping the HTML in the template with the same (or even more) flexibility:
<a href="{{post.url()}}" rel="nofollow">{{post.title()}}</a>
.Hi Ian,
I agree with the initial 40% of your idea, the rest, I think, pushes too much the View layer into the Control layer —
post.link()
will likely output links with classes and/or ids (even when assuming inline JavaScript is strictly forbidden), which introduces a high dependency/risk of keeping the Control code in sync with the View whenever the web team refactors/updates the design.I believe a balance can be achieved when
post.link_href()
outputs the content of href and nothing else, whilepost.link_text()
returns the a link text.Jerry
P.S. How to disable/escape underscore Markdown formating? [for cases like these using backticks renders it as code and no markup is not interpreted inside the backticks -- Ian]
The URL approach seems sound, with the primary issue being that web frameworks like to abstract the URL away from the object itself via routing. Most web frameworks would rather see you grab the url via a “reverse” method, rather than hard-coded into the Model itself:
URI-aware objects provide consistency and extensibility.
Yeah, I agree with Rudolph — why not give the model objects a
url()
method that simply outputs the URL itself? This lets the designer/template define custom classes for the link.Regarding Marcus’ point about relying on a framework’s reversing logic — IMHO, that’s going a bit too far, and I never use
reverse()
stuff in model URL methods. I tend to see this as an acceptable breach of the DRY principle, because it’s a lot more efficient just to hard-code the URL-generating logic directly in theurl()
method (as opposed to relying on the reverse logic).It’s sort of like denormalizing a database for performance. If you’re outputting a list of links and each one is calling
url()
, which in turn callsreverse()
…whew! That’s a lot of unnecessary overhead.Sure, the
.link()
method (in my example) isn’t entirely necessary, far more important is the.url()
method. I wish, though, that there was a way of doing object-oriented display code that is also designer-friendly. For example, if you do allow for something like a compound link (as with the via example), coding that into every template where you display a link is entirely infeasible.One way of handling this would be to use template functions or blocks. Then you can centralize the logic of link display (making it easier to extend that logic) while still putting it somewhere a designer can find. Backtracking rendered code to these template functions isn’t that easy, but that’s something that should be fixed — it should be much easier to see a bit of rendered code and backtrack to where that code was defined.
In Grok/Zope-land, I just create as many IAbsoluteURL adapters as I have object classes that I want to assign URLs to, and the component framework swings into action and invokes the right one when a view asks for some object’s URL. That way my models do not accumulate interface-specific information about where they live, which would be deeply troubling, and yet I have a way to maintain the URL information alongside my models in a way that I find scalable.
See slide 147 of [my NOLA Grok talk](http://rhodesmill.org/brandon/2008/nola-plone-symposium-talk/) for an example of how a URL factory looks when you build it for one of your own types. I should nudge the Grok folks to make that take less lines and look less grungy. Like maybe three lines. Yeah, three lines.
In this case using interfaces is similar to generic functions (and has relatively little to do with an “interface”). Using PyProtocols it might look like:
Of course in Zope you’d probably have separate class types; outside of Zope it’s more common to use a single class that’s more connected to the storage type (e.g., a one-to-one mapping of class to database table).
Generally, I’m not sure the complexity of the indirection pays off much. In theory there’s some important separation going on there, but in practice I’ve never seen it. Given a RESTful approach, there actually isn’t a distinction at all — an object is at its URL, this is not just an implementation detail. I don’t think of the web as an implementation detail, in my mind it is core to the application itself.
That said, creating URLs without some kind of helper is also a needless waste of time and causes bugs (e.g., not allowing for application path prefixes, not quoting segments properly, or spending an inordinate amount of time formatting query string parameters). And once you have a helper, you’ve got at least some indirection happening.
Not sure what you’re gaining by pushing your view helpers into the model layer. You’re concerned about your posts keeping the same URL/slug, but throwing link helpers into your models is going to hinder more than it will help.
This is how it would be done with Ruby/ERB:
Or perhaps if you find yourself creating links to posts often, use a template helper:
I just build some Mako defs that deal with URLs for certain types of objects. The main point of the “def” in Mako is a place that you can put programmatic view logic.
(something is wrong with your markdown formatting, or I’ve just drank too much tonight)
The filter method someone mentioned above can work with this, though I don’t use filters that way myself that often.
I think that template helpers / template tags and a url method achieves the desired effect. The instance can return its url via a url() method and the template helpers can manage markup.
This keeps the separation of concern between the model and templates. Also designers have specific areas in which they can manage / inspect templates should they want to extend or change markup.
How you handle template helpers is another area for debate – you could use template fragments in separate html files (keeping the designers away from code) or return strings from the helper methods – but then is that as bad as declaring them in the model? Probably not, I’d say its still slightly better but its a matter of preference. I’d choose the latter if my designers had a high level of competence with scripting languages i.e. frontend developers.
My biggest issue in working with links has been how to handle transformations gracefully. For example, I would prefer my links to be absolute at all times. This reduces ambiguity between links generated from content and links within the actual application. Along the same lines, when the application is made of smaller microapps (as you’ve advocated), having an easy way of creating these links effectively can be difficult.
One method I’ve used is the idea of a resolver. The application might keep the current pattern for a link generated from data, but the resolve allows for previous patterns to also be used. It also allows for a catalog of bases to be used (ie XML Catalog). This idea is based entirely on creating a custom URL resolver for XSLT, which most XSLT processing libraries support.
The point here is that I agree links should be first class objects in addition to having a framework or controller in place for managing them that goes beyond simple parsing/routing.