[Grok-dev] grok and urls

Martijn Faassen faassen at infrae.com
Mon Oct 30 10:05:25 EST 2006


Hey,

So here I'm replying to myself, investigating some ways to make URL 
generation uniform.

Martijn Faassen wrote:
> Meanwhile, grok also needs to generate various kinds of URLs. the 
> grokwiki has a number of places where URLs are involved:
> 
> * the redirect in wiki.py does self.request.response.redirect('home')
>   That's a relative URL that just happens to work. Relative URLs
>   unfortunately don't always happen to work.

Perhaps we need to have a method on the view that helps redirecting, as 
we do it often:

view.redirect(some_url)

this saves us typing the whole '.request.response' bit.

> * in page.Index there's a before method which does:
> 
>   zapi.absoluteURL(self.context.__parent__, self.request)
> 
>   to get the URL to the wiki itself.

It's interesting to note here that we generate a URL to__parent__. I 
suspect this shows up in many applications. Another thing grokwiki 
doesn't do yet but will appear in other applications is to generate a 
URL to the site:

zapi.absoluteURL(getSite(), self.request)

A simple method on grok.View might help again:

view.url(self.context.__parent__)

perhaps automated as:

view.parent_url()

though this encourages URL construction in Page Templates, something we 
may perhaps want to discourage, leading people to a good API..

we may also be interested in:

view.site_url()

I realize by the way that I'm adding all kinds of utility methods to the 
grok.View class here. While we run the risk of name clashes, I think 
it's worth it. Suddenly there's no need to pull up self.request all the 
time anymore and an import goes away.

> * in page.Edit there's another interesting
>   combination:

I'll insert some information here that I forgot in my last mail:

    self.wiki = self.context.__parent__

This is done for reuse later. It's not actually used by anything else 
but the code below that I can find, so there doesn't look like a good 
reason this is an assignment on the view instead of just a local variable.

>   wiki_url = zapi.absoluteURL(self.wiki, self.request)
>   self.request.response.redirect("%s/%s" % (wiki_url, 
> self.context.__name__))
> 
>   This one is particularly horrible as it takes place in a 'before()'
>   method. We have a special class of URL redirects which happen
>   after form submits and we're sort of misusing before() here to make
>   that happen.

I won't think here about making this be outside before(). This is a 
topic for later - the whole view/form redirection issue (we often want 
to carry status messages along too).

In the new spelling this could look like:

   self.redirect(self.parent_url())

or perhaps:

   self.redirect(self.url(self.context.__parent__))

> * then in page_templates/edit.pt, there's a form submit to request/URL
>   We get the URL from the request here.

perhaps this can become:

view/url

which then calls url() without arguments which will deliver the URL for 
the view itself. Then again, URL generation in page templates does open 
a whole separate can of worms..

> * and in page_templates/index.pt there's a tal:attributes="href 
> string:${context/@@absolute_url}/edit
>   We use the absolute_url view that gets registered for lots of things,
>   looking it up by name. Then we tack /edit after it.

This one is interesting, as it generates a URL to a 'sibling view' on 
the current context object. I expect this is something that happens 
quite frequently, so we need a construct for it. In code, it might look 
like this:

self.sibling_url('edit')

or as I explain later, we could overload the behavior of 'url' a little:

self.url('edit')

Unfortunately in a page template we're interested in avoiding Python 
expressions like this:

   tal:attributes="href python:view.url('edit')"

though it isn't too bad compared to the original construct...

Later on I discuss some possible more magical options, but they don't 
satisfy me yet, really.

> * and then in layout.pt
> 
>   tal:define="wiki context/__parent__;
>               wiki_url wiki/@@absolute_url"
> 
>   and later
> 
>    <a tal:attributes="href string:$wiki_url/$page"
>                      tal:content="page"
>                      />
> 
>   And here we tack do some more construction, this time constructing
>   the name from two other names.

The first define would be easy:

wiki_url wiki/parent_url

or alternatively:

wiki_url python:view.url(context.__parent__)

of course this is only done to do URL construction in a page template. I 
think we should see whether we can avoid having to do such a lot in grok.

sibling_url (or .url('sibling') doesn't work, as we're not pointing to 
siblings of the view here, but siblings of the context. Perhaps this 
gives us a clue about sibling_url being misnamed...

Above we construct a URL to the object by knowing about the structure of 
the site. We can do this in a different way though; we already have the 
page object so there's no reason to know about the structure of the site 
to construct a link to it:

tal:attributes="href page/url"

This links to the index view of page. If we want to construct a url to a 
view on the page, we might do this:

tal:attributes="href python:page.url('edit')

But that leads to more Python in a page template. There's another, more 
severe problem: both examples also imply that grok.Model supports a 
url() method, and this may be something harder to defend than adding 
methods to views - the notion that our content base class is very simple 
is one I'd like to keep.

Above we define view.url() as optionally getting an object, so that'd 
help us here:

tal:attributes="href python:view.url(page)"

what if we want to construct URLs to edit pages, though?

tal:attributes="href python:view.url(page, 'edit')"

again, somewhat ugly from page templates though as we use Python 
expressions... *Not* uglier than what we replaced, though!

Let's try to sketch out a minimal API:

class IGrokView(Interface):

    def redirect(url):
        """Redirect to given URL"""

    def url(obj=None, view=''):
        """Construct URL.

        If no arguments given, construct URL to view itself.

        If only obj argument is given, construct URL to obj.

        If only view is given (or string as first argument, a bit of
        magic there), construct view URL to view with that name on our
        context.

        If both object and view arguments are supplied, construct
        view URL to view on that object.
        """

Usages:

   >>> view.SomeView()
   >>> view.url()
   http://localhost/foo/bar/edit
   >>> view.url('display')
   http://localhost/foo/bar/display
   >>> view.url(view.context)
   http://localhost/foo/bar
   >>> view.url(view.context, 'display')
   http://localhost/foo/bar/display
   >>> parent = view.context.__parent__
   >>> view.url(parent)
   http://localhost/foo
   >>> view.url(parent, 'display')
   http://localhost/foo/display

this is the minimal thing I can think of that helps us in Python code. 
In page templates it's going to be trickier, if we want to avoid python 
there.

Could we make pass in a magic 'url', along the lines of 'static', that 
helps us here?

  >>> zpt('<a tal:attributes="href url">Link</a>')
  '<a href="http://localhost/foo/bar/edit">'
  >>> zpt('<a tal:attributes="href url/display">Link</a>')
  '<a href="http://localhost/foo/bar/display">'

So far so good, but it breaks down when we want to link to objects:

   >>> zpt('<a tal:attributes="href url/view/context">') # XXX makes no 
sense

Ideas?

Regards,

Martijn





More information about the Grok-dev mailing list