a triviality: how do you like your URLs?

7 views
Skip to first unread message

Kevin Dangoor

unread,
Dec 5, 2005, 3:42:25 PM12/5/05
to turbo...@googlegroups.com
So, in a generic CRUD feature, what would you want your URLs to look like:

1) http://yoursite/articles/10/edit
2) http://yoursite/articles/edit/10
3) http://yoursite/articles/edit?id=10

the advantage to the first one is that it makes view look like this:
http://yoursite/articles/10

Kevin

--
Kevin Dangoor
Author of the Zesty News RSS newsreader

email: k...@blazingthings.com
company: http://www.BlazingThings.com
blog: http://www.BlueSkyOnMars.com

Elvelind Grandin

unread,
Dec 5, 2005, 3:45:19 PM12/5/05
to turbo...@googlegroups.com
One vote for number one.
--
cheers
elvelind grandin

Michael Schneider

unread,
Dec 5, 2005, 3:50:06 PM12/5/05
to TurboGears
>From a programming standpoint, I tend to like 3

path to object + function + args


class Articles(...):
def edit(id=None):
.......

Jared Kuolt

unread,
Dec 5, 2005, 3:52:12 PM12/5/05
to turbo...@googlegroups.com
I prefer the command before anything, e.g.:

http://yoursite/edit/article/10

That's assuming 'articles' isn't a subdirectory.

Kevin Dangoor

unread,
Dec 5, 2005, 3:53:25 PM12/5/05
to turbo...@googlegroups.com
"articles" is the controller responsible for doing CRUD on the articles.

Kevin Dangoor

unread,
Dec 5, 2005, 3:54:28 PM12/5/05
to turbo...@googlegroups.com
actually, the programming that you would do would be the same in all
three instances.

Jorge Godoy

unread,
Dec 5, 2005, 3:59:28 PM12/5/05
to turbo...@googlegroups.com
Kevin Dangoor <dan...@gmail.com> writes:

> So, in a generic CRUD feature, what would you want your URLs to look like:
>
> 1) http://yoursite/articles/10/edit
> 2) http://yoursite/articles/edit/10
> 3) http://yoursite/articles/edit?id=10
>
> the advantage to the first one is that it makes view look like this:
> http://yoursite/articles/10

I like more the second kind.

The advantage is that I can use classes for "articles", "news", etc. and have
a consistent "look" throughout the website. The "id" is always the last
element. It looks more "natural" to me to have the variant thing in the end
(it also makes it easier to navigate through the URL location bar.

I "hate" more the third kind.



Anyway, some good articles that might help...

http://www.w3.org/Provider/Style/URI
http://www.adaptivepath.com/publications/essays/archives/000058.php
http://www.port80software.com/support/articles/nextgenerationurls


Be seeing you,
--
Jorge Godoy <jgo...@gmail.com>

Jorge Godoy

unread,
Dec 5, 2005, 4:00:50 PM12/5/05
to turbo...@googlegroups.com
Kevin Dangoor <dan...@gmail.com> writes:

> actually, the programming that you would do would be the same in all
> three instances.

Three answer for the three different options... Couldn't it be made generic /
configurable? :-)

--
Jorge Godoy <jgo...@gmail.com>

Ian Bicking

unread,
Dec 5, 2005, 3:58:44 PM12/5/05
to turbo...@googlegroups.com
Kevin Dangoor wrote:
> So, in a generic CRUD feature, what would you want your URLs to look like:
>
> 1) http://yoursite/articles/10/edit
> 2) http://yoursite/articles/edit/10
> 3) http://yoursite/articles/edit?id=10

http://yoursite/article-10?action=edit

... because "10" doesn't mean anything and "articles" isn't a very
useful container, I prefer non-hierarchical URIs like "article-10". You
can usefully use that in several contexts, like /edit?obj=article-10, or
/editor/article-10, or /article-10/edit

I don't think objects like "article 10" are very meaningful containers
for things like an editor, so I find .../edit to be less than meaningful.

But it depends on what you think the pieces mean:

/articles is a class
/articles/10 is an instance
/articles/10/edit is a method

/edit is an function
/articles/10 is an instance
/edit/articles/10 is a generic function applied to that instance

/articles is a module
/articles/edit is a function
/articles/edit/10 is a function invocation

It might be more useful to consider what object creation looks like too
(cloning/edit-as-new is a useful operation as well). Pragmatically I
usually do /articles/edit/10, and /articles/edit/ creates an object.
Deep down I don't personally care that much about the internal URIs of a
web application; once the system becomes interactive, all the URIs are
tightly bound and opaque for most practical purposes.

--
Ian Bicking / ia...@colorstudy.com / http://blog.ianbicking.org

Ray Cote

unread,
Dec 5, 2005, 4:09:23 PM12/5/05
to turbo...@googlegroups.com, Kevin Dangoor
At 3:42 PM -0500 12/5/05, Kevin Dangoor wrote:
>So, in a generic CRUD feature, what would you want your URLs to look like:
>
>1) http://yoursite/articles/10/edit
>2) http://yoursite/articles/edit/10
>3) http://yoursite/articles/edit?id=10
>
>the advantage to the first one is that it makes view look like this:
>http://yoursite/articles/10
>
>Kevin

Forgot one. ::
4) http://yoursite/articles/10?op=edit
For me, it is selecting an item (articles/10) and then performing an
operation on it.
Also has advantage of having the view be like your #1 which I like.
--Ray


--

Raymond Cote
Appropriate Solutions, Inc.
PO Box 458 ~ Peterborough, NH 03458-0458
Phone: 603.924.6079 ~ Fax: 603.924.8668
rgacote(at)AppropriateSolutions.com
www.AppropriateSolutions.com

bruno modulix

unread,
Dec 5, 2005, 4:57:36 PM12/5/05
to turbo...@googlegroups.com
Kevin Dangoor a écrit :
> So, in a generic CRUD feature, what would you want your URLs to look like:
>
> 1) http://yoursite/articles/10/edit
> 2) http://yoursite/articles/edit/10
> 3) http://yoursite/articles/edit?id=10

The first, of course.

> the advantage to the first one is that it makes view look like this:
> http://yoursite/articles/10

<aol />

Michele Cella

unread,
Dec 5, 2005, 4:13:09 PM12/5/05
to TurboGears
Kevin Dangoor wrote:
> So, in a generic CRUD feature, what would you want your URLs to look like:
>
> 1) http://yoursite/articles/10/edit
> 2) http://yoursite/articles/edit/10
> 3) http://yoursite/articles/edit?id=10

I'm unable to make a choice between 1 and 2, definitely not the third
option.

>
> the advantage to the first one is that it makes view look like this:
> http://yoursite/articles/10

for the second it will look like this:

http://yoursite/articles/show/10

right?

For what is worth RoR scaffolding uses the second one (just checked it
from the screencast ;-)).

Ciao
Michele

Ronald Jaramillo

unread,
Dec 5, 2005, 4:13:37 PM12/5/05
to turbo...@googlegroups.com
+1
________________________________
Ronald Jaramillo
mail: ronald AT checkandshare DOT com
blog: http://www.checkandshare.com/blog



Michael Schneider

unread,
Dec 5, 2005, 4:16:45 PM12/5/05
to TurboGears
Kevin,

I am still new to cherrypy.

I was under the impression that you built an object tree, and it
automatically mapped
the URI to the object tree, passing in args to the function

http://yoursite/articles/edit?id=10

wouldn't this map to

class Article(.....):
def edit(self, id=None):
""" function to edit article""""

with cherry py dispatching the uri to

article.edit(id="10")


------------------------------------------------

would you go to partial matches for the other URL (like the CherryPy
docs below),
or is there a trick that directly mapps to a function?

Just trying to learn the options,

Thanks
Mike
--------- From CherryPy Tutorial ---------------------


Partial matches and the default method

Partial matches can happen when a URL contains components that do not
map to the cpg.root object tree. This can happen for a number of
reasons. For example, it may be an error; the user just typed the wrong
URL. But it also can mean that the URL contains extra arguments.

When a partial match happens, CherryPy calls a default method. The
default method is similar to the index method; however, it is only
called as a last resort method, and it's recommended for two
applications:

* Error handling, to be called when the user types the wrong URL;
* Support for positional arguments.

Support for positional arguments when handling partial URL matches is
one of the new features provided by CherryPy. For example, assume that
you have a blog-like application written in CherryPy that takes the
year, month and day as part of the URL:

http://localhost/blog/2005/01/17

This URL can be handled by the following code:

class Blog:
def default(self, year, month, day):
...
default.exposed = True
...
cpg.root.blog = Blog()

So the URL above will be mapped as a call to:

cpg.root.blog.default('2005', '1', '17')

In this case, there is a partial match up to the blog component. The
rest of the URL can't be found in the published object tree. In this
case, the default() method will be called, and the positional
parameters will receive the remaining path components as arguments. The
values are passed as strings; in the above mentioned example, the
arguments would still need to be converted back into numbers, but the
idea is correctly presented.

Michele Cella

unread,
Dec 5, 2005, 4:20:04 PM12/5/05
to TurboGears
Michele Cella wrote:
> I'm unable to make a choice between 1 and 2, definitely not the third
> option.

Ok, the first seems the best.
If I read the second it seems like I'm going to edit (or show) 10
articles not the article number 10.

So +1 for the first.

Ciao
Michele

Michael Schneider

unread,
Dec 5, 2005, 4:25:35 PM12/5/05
to TurboGears
Here a nice overview of the REST approach to URI encoding with some
links

http://en.wikipedia.org/wiki/Representational_State_Transfer

william

unread,
Dec 5, 2005, 4:27:01 PM12/5/05
to TurboGears

Jorge Godoy wrote:
> Kevin Dangoor <dan...@gmail.com> writes:
>
> > actually, the programming that you would do would be the same in all
> > three instances.
>
> Three answer for the three different options... Couldn't it be made generic /
> configurable? :-)
>

Thus I give my 2 cents ;-).

I prefer to use 1).

But you should look at Quixote ExtDirectory:
http://quixote.ca/qx/ExtDirectory
This give full flexibility (like proposed by Jorge).

Does this is implementable into CP2.1 ????
AFAIK, such flexible URL would available in CP2.2.

William

Michele Cella

unread,
Dec 5, 2005, 4:29:20 PM12/5/05
to TurboGears
Michael Schneider wrote:
>
> would you go to partial matches for the other URL (like the CherryPy
> docs below),
> or is there a trick that directly mapps to a function?
>

With CherryPy 2.1 you can also use a PositionalParametersAware class.
I think that probably 0.9 will use positional parameters by default
since they will be the default in CherryPy 2.2 if I'm right, but we
should really ask Kevin about this.

See also ticket 73: http://trac.turbogears.org/turbogears/ticket/73

Ciao
Michele

Sylvain Hellegouarch

unread,
Dec 5, 2005, 4:36:02 PM12/5/05
to turbo...@googlegroups.com
True.

From CherryPy 2.2, all exposed methods will be positional parameters aware by
default except index().

Selon Michele Cella <michel...@gmail.com>:
----------------------------------------------------------------
This message was sent using IMP, the Internet Messaging Program.

Bob Ippolito

unread,
Dec 5, 2005, 4:32:57 PM12/5/05
to turbo...@googlegroups.com

On Dec 5, 2005, at 12:58 PM, Ian Bicking wrote:

>
> Kevin Dangoor wrote:
>> So, in a generic CRUD feature, what would you want your URLs to
>> look like:
>> 1) http://yoursite/articles/10/edit
>> 2) http://yoursite/articles/edit/10
>> 3) http://yoursite/articles/edit?id=10
>
> http://yoursite/article-10?action=edit
>
> ... because "10" doesn't mean anything and "articles" isn't a very
> useful container, I prefer non-hierarchical URIs like
> "article-10". You can usefully use that in several contexts, like /
> edit?obj=article-10, or /editor/article-10, or /article-10/edit

Generally I don't like query arguments... Mostly because they're
kinda ugly and are too closely related to form posting. I'd avoid
them most of the time if it was easy to.

The combined class-identifier scheme looks nice though, in a "flat is
better than nested" kind of way.

-bob

Michele Cella

unread,
Dec 5, 2005, 4:34:42 PM12/5/05
to TurboGears
william wrote:
>
> Does this is implementable into CP2.1 ????
> AFAIK, such flexible URL would available in CP2.2.
>

I think Routes provides the maximum flexibility one could ever need and
it seems that it will be easily integrable with CherryPy 2.2:

http://www.groovie.org/articles/2005/11/21/routes-1-0-released

Ciao
Michele

Sylvain Hellegouarch

unread,
Dec 5, 2005, 4:45:00 PM12/5/05
to turbo...@googlegroups.com
Indeed, IMO the three are equivalent (in fact 2 and 3 are the same). If we refer
to REST we have:

http://www.ics.uci.edu/~fielding/pubs/dissertation/evaluation.htm#sec_6_2_4

Which clearly indicates that the underlying implementation should be seen
through the URI to access a resource. So in the end if we could support the
three different URLs to access a resource it would be the best.

From a personnal POV, I prefer 2.

Selon Ian Bicking <ia...@colorstudy.com>:

Michael Schneider

unread,
Dec 5, 2005, 4:43:21 PM12/5/05
to TurboGears
Does that mean that with all exposed methods the syntax would be


>> 2) http://yoursite/articles/edit/10


class Article(...):
def edit(self, articleId=None):
if(articleId == None):
# redirect to select article to edit page
......


def delete(self, articleId=None):
......

def create(self):
......

Jorge Godoy

unread,
Dec 5, 2005, 4:52:13 PM12/5/05
to turbo...@googlegroups.com
"Michael Schneider" <michaels...@fuse.net> writes:

> Does that mean that with all exposed methods the syntax would be

That's how I code it today.

--
Jorge Godoy <jgo...@gmail.com>

Sylvain Hellegouarch

unread,
Dec 5, 2005, 4:56:46 PM12/5/05
to turbo...@googlegroups.com
This is the behavior of CP 2.2 (current trunk) at least.

Selon Michael Schneider <michaels...@fuse.net>:

Ian Bicking

unread,
Dec 5, 2005, 4:57:08 PM12/5/05
to turbo...@googlegroups.com
Sylvain Hellegouarch wrote:
> Indeed, IMO the three are equivalent (in fact 2 and 3 are the same). If we refer
> to REST we have:
>
> http://www.ics.uci.edu/~fielding/pubs/dissertation/evaluation.htm#sec_6_2_4
>
> Which clearly indicates that the underlying implementation should be seen
> through the URI to access a resource. So in the end if we could support the
> three different URLs to access a resource it would be the best.

*That's* certainly not what I would propose. I'd rather take the
crappiest of the options (um... let's say
/edit?class=myapp.Articles?id=10) than multiple ways to access the
resource with the same meaning.

Reading that particular document, my impression is that he is saying
that URIs don't matter, that they are opaque strings, and their only
meaning is what you can do with them. They don't represent "resources",
only "potential actions", and if you read anything more into the strings
you are overstepping the abstractions of the web.

Some REST principles seem unattainable in this context as well, like the
fact we are strictly limited to the GET and POST verbs. This is why I
think ?action=edit is reasonable, as it is the verb and there's no real
positional location that implies verb. It's an inversion of the typical
URI of edit?id=10, where the path is the verb and the variables are the
noun.

Of course, a real set of actions would be more like:

view: show a representation
editform: show a form to edit the item
save: save changes to an item
delete: delete an item
createform: show a form to create an item
create: actually create the given item
select: show several items (e.g., /articles/ typically means select all).

Several of these should *absolutely not* be in the URI, specifically
"delete", "save", and "create". Now, you could conflate these with
other actions, e.g., editform and save (edit+GET=editform,
edit+POST=save), and createform and create, and some arbitrary action
and delete (maybe select or even view). But I don't think that's very
REST, if we're throwing about terms.

But on the other hand, this is a total Bike Shed moment, so whatev'.

jason....@gmail.com

unread,
Dec 5, 2005, 5:07:43 PM12/5/05
to TurboGears
> I think Routes provides the maximum flexibility one could ever need and
> it seems that it will be easily integrable with CherryPy 2.2:

> http://www.groovie.org/articles/2005/11/21/routes-1-0-released

I like this url (haven't actually *clicked* on it yet) - the format
site/articles/year/month/day/title_of_article is nice and human
readable.

So I guess my vote is for number 1, but I'd probably replace the "id"
number with either an article title (provided it will be unique) or a
date and article title, or even a category and a title (sticking an
'edit' at the end of the url for editing, etc). Since I think the point
Kevin is trying to make is that we have complete flexability with the
URL, I'd prefer to go with "average-joe" readability over
programming-grammer logic.

If I had my way, we'd have the entire article in the URL (no, not
really; that's a joke).

- Jason

Sylvain Hellegouarch

unread,
Dec 5, 2005, 5:14:02 PM12/5/05
to turbo...@googlegroups.com
Hi there Ian,

> *That's* certainly not what I would propose. I'd rather take the
> crappiest of the options (um... let's say
> /edit?class=myapp.Articles?id=10) than multiple ways to access the
> resource with the same meaning.

I didn't mean that would be something we had to do but merely that overall it
was not such a problem as long as we were able to handle those URIs in only
unique way.

>
> Reading that particular document, my impression is that he is saying
> that URIs don't matter, that they are opaque strings, and their only
> meaning is what you can do with them. They don't represent "resources",
> only "potential actions", and if you read anything more into the strings
> you are overstepping the abstractions of the web.

Agreed. That was my statement too. My point was that TG URIs should not show the
underlying bindery TG uses but they still need to make sense regarding their
actual action on the resource.

>
> Some REST principles seem unattainable in this context as well, like the
> fact we are strictly limited to the GET and POST verbs. This is why I
> think ?action=edit is reasonable, as it is the verb and there's no real
> positional location that implies verb. It's an inversion of the typical
> URI of edit?id=10, where the path is the verb and the variables are the
> noun.

Again I agree and that's the way I go on my applications.

>
> Of course, a real set of actions would be more like:
>
> view: show a representation
> editform: show a form to edit the item
> save: save changes to an item
> delete: delete an item
> createform: show a form to create an item
> create: actually create the given item
> select: show several items (e.g., /articles/ typically means select all).
>
> Several of these should *absolutely not* be in the URI, specifically
> "delete", "save", and "create". Now, you could conflate these with
> other actions, e.g., editform and save (edit+GET=editform,
> edit+POST=save), and createform and create, and some arbitrary action
> and delete (maybe select or even view). But I don't think that's very
> REST, if we're throwing about terms.

Well yes and no. The problem here is that we can easily mix th what and how.

edit+GET = editform is perfectly correct IMO from a REST POV since it means
"fetch the resource with id=10" and its representation is then an HTML form.

Again, I'd be pleased if TG could use the power of HTTP to handle its resources
but it would mean for CP to be a bit more ready. It's where we go with 3.0 but
it's not baked yet.

- Sylvain

Chad L.

unread,
Dec 5, 2005, 5:11:46 PM12/5/05
to TurboGears
#2 would get my vote.

Lee McFadden

unread,
Dec 5, 2005, 5:24:07 PM12/5/05
to turbo...@googlegroups.com
Chalk me up for option 1.
--
Lee

Blog: http://www.splee.co.uk

Bob Ippolito

unread,
Dec 5, 2005, 5:24:26 PM12/5/05
to turbo...@googlegroups.com

On Dec 5, 2005, at 2:07 PM, jason....@gmail.com wrote:

>
>> I think Routes provides the maximum flexibility one could ever
>> need and
>> it seems that it will be easily integrable with CherryPy 2.2:
>
>> http://www.groovie.org/articles/2005/11/21/routes-1-0-released
>
> I like this url (haven't actually *clicked* on it yet) - the format
> site/articles/year/month/day/title_of_article is nice and human
> readable.

Well, for a lot of purposes the date it was published is kinda
irrelevant and essentially impossible to remember (as a human). Year
is memorable or at least can be reasonably guessed, but not full on
yyyy/mm/dd. A URL like that might as well be an SHA1 hash, because
I'm going to have to use a search engine to find it anyway.

I'd personally tend towards something simpler, and enforce unique
titles... either automatically by adding predicable garbage to the
end, or by validating edits/creates to make sure they have a unique
title.

-bob

Michele Cella

unread,
Dec 5, 2005, 5:28:28 PM12/5/05
to TurboGears
Well, I've found another opinion (and some comments) regarding 1) vs 2)
from the Rails side:

http://tech.rufy.com/entry/91

(notice the above url :D)

Ciao
Michele

Ian Bicking

unread,
Dec 5, 2005, 5:59:41 PM12/5/05
to turbo...@googlegroups.com
Bob Ippolito wrote:
> I'd personally tend towards something simpler, and enforce unique
> titles... either automatically by adding predicable garbage to the end,
> or by validating edits/creates to make sure they have a unique title.

It would be quite reasonable in the SQLObject trunk to create a column
that worked like:

class Article(SQLObject):
title = StringCol()
linkTitle = LinkTitle('title')

a = Article(title='foo bar')
a.linkTitle == 'foo-bar'
Article.byLinkTitle('foo-bar') == a

Basically you'd just be listening for create events, and generate the
actual link title at that time, making the title safe and then appending
stuff to make it unique (if necessary). I think this would be quite
useful for web apps. This example of a column tracking last-modified is
similar in mechanism:
http://xentac.net/~jchu/blog/technology/sqlobject-event-fu

Joost Moesker

unread,
Dec 5, 2005, 6:43:27 PM12/5/05
to TurboGears
I would vote #1

For an ecommerce project i'm currently using the following URL scheme:

catagoryname/subcatagoryname
catagoryname/subcatagoryname/edit
catagoryname/subcatagoryname/.../productname
catagoryname/subcatagoryname/.../productname/edit

Editing the product and catagory 'pages' directly feels verry natural.

Mike Orr

unread,
Dec 5, 2005, 7:00:17 PM12/5/05
to turbo...@googlegroups.com
On 12/5/05, Kevin Dangoor <dan...@gmail.com> wrote:
>
> So, in a generic CRUD feature, what would you want your URLs to look like:
>
> 1) http://yoursite/articles/10/edit

This is the most natural. "articles" contains all articles. "10" is
an article. "edit" is an operation on 10. "view" is the index method
(/articles/10/).

> 2) http://yoursite/articles/edit/10

Acceptable, but I don't like the 10 jumping levels. "article" as a
collection of articles makes sense. "article" as a collection of
actions doesn't -- people don't go to the site for actions, they go
for articles. They also don't want to type /articles/view/10 into
the location bar; they want a straightforward /articles/10 . The
actions are just a means to facilitate article management, so they go
to the right. An OO application has objects with methods, not methods
with objects.

/articles/10?action=edit

Acceptable, but an action should arguably be part of the URL path.
And it would mean one huge controller method that switched on actions,
which contradicts the CherryPy structural philosophy of one small
controller method per action. That's one of the main things that
draws people to TG (and Quixote, which does the same thing).

/article-10/edit

Ugh, this goes back to the dark ages of parsing data out of strings.
The thing between the "/" is supposed to be an identifier, not an
identifier embedded into an arbitrary string. Numeric identifiers
have many advantages such as sorting. Will article-100 show up after
article-99 or between article-10 and article-11? I understand the
point about treating the article ID as an arbitrary string, but then
why incorporate a number at all? Still, numbers make more sense for
a generic CRUD application because we don't know what all the user
plans to do with the data, and numbers make it easier for them build
any kind of local add-on.

> 3) http://yoursite/articles/edit?id=10

This is frustrating when you're analyzing logs or implementing a
caching system, and some third-party software has dropped the query
string. Anything that's essential to identifying the resource belongs
in the URL path.

--
Mike Orr <slugg...@gmail.com>
(m...@oz.net address is semi-reliable)

Krys Wilken

unread,
Dec 5, 2005, 7:31:27 PM12/5/05
to turbo...@googlegroups.com
Django has a slug field with is similar.

From the site:

SlugField

"Slug" is a newspaper term. A slug is a short label for something,
containing only letters, numbers, underscores or hyphens. They're
generally used in URLs.

(http://www.djangoproject.com/documentation/model_api/)

I believe it has some built in validation to make sure it complies with
that definition.

This might be a useful addition to TurboGears, whether or not it makes
it into SQLObject directly.

Just a thought. Django has some good ideas. We can learn from it.

Cheers,
Krys

Krys Wilken

unread,
Dec 5, 2005, 7:46:54 PM12/5/05
to turbo...@googlegroups.com
+1 on 1) or 2), though I think I like 1) better.

In my view I want to find my object, in this case article 10, which is
contained in a container of articles. Then I want to do something with
it, namely edit it.

This makes it easy for the view action to be the default, and so can be
omitted from the URL, allowing for:

http://yoursite/articles/10

Personally I don't like query parameters. They *are* ugly. I can see
them being useful for optional special conditions (like
turbogearsfmt=json (old example, I know) or for bookmarkable search
results, etc.

I guess I'm more of a "give me the thing and I'll see what I can do with
it", rather than a "I want to do something, give me a thing on which I
can do it" kinda guy. ... Err, if that makes sense.

And I like clean URLs to match my clean (Python) programming language. :-)

Krys

Kevin Dangoor wrote:
> So, in a generic CRUD feature, what would you want your URLs to look like:
>
> 1) http://yoursite/articles/10/edit
> the advantage to the first one is that it makes view look like this:
> http://yoursite/articles/10
>
> Kevin
>
> --
> Kevin Dangoor
> Author of the Zesty News RSS newsreader
>
> email: k...@blazingthings.com
> company: http://www.BlazingThings.com
> blog: http://www.BlueSkyOnMars.com
>

jorge....@gmail.com

unread,
Dec 5, 2005, 9:20:11 PM12/5/05
to TurboGears
either 1 or 2

Michael Schneider

unread,
Dec 5, 2005, 9:20:39 PM12/5/05
to TurboGears
Sorry for the stupid question, but what would the Controller code look
like for option #1? for CRUD


1) http://yoursite/articles/10/edit
2) http://yoursite/articles/10/delete
3) http://yoursite/articles/10/create

The main reason that I am liking 2, then 3 is that I can see how
cherrypy easily maps the urls to functions edit, delete and create.


I am having a mind blank here, how would you code #1 without using
if clauses and one function?

Sorry for my confusion,
Mike

Jeff Watkins

unread,
Dec 5, 2005, 10:43:18 PM12/5/05
to turbo...@googlegroups.com

On 5 Dec, 2005, at 9:20 pm, Michael Schneider wrote:
> Sorry for the stupid question, but what would the Controller code look
> like for option #1? for CRUD
>
> 1) http://yoursite/articles/10/edit
> 2) http://yoursite/articles/edit/10
> 3) http://yoursite/articles/edit?id=10

I'm certain some will suggest this is unpythonic, but here goes:

class CrudController(turbogears.Controller):
def __init__( self, crudClass, templateBase, instance ):
self.template_base= templateBase
self.instance= instance
self.crud_class= crudClass

@turbogears.expose()
def create( self, **kw ):
template= "%s.created" % self.template_base
# Create a new instance and save it to the DB based on the
keywords
return dict( tg_template=template )

@turbogears.expose()
def retrieve( self ):
template= "%s.edit" % self.template_base
return dict( instance= self.instance, tg_template=template )

@turbogears.expose()
def update( self, **kw ):
# update the instance based on the keywords and save to DB
return self.retrieve()

@turbogears.expose()
def delete( self ):
self.instance.destroySelf()
template= "%s.deleted" % self.template_base
return dict( tg_template=template )

@turbogears.expose()
def default( self, *args, **kw ):
# determine method based on HTTP method
methodDict= { 'GET': self.retrieve,
'PUT': self.update,
'DELETE': self.delete
}
method= methodDict[cherrypy.request.method]
return method( *args, **kw )


class CrudClassController(turbogears.Controller):
def __init__( self, crudClass, keyMethod, templateBase ):
self.crud_class= crudClass
self.template_base= templateBase
self.key_method= keyMethod

@turbogears.expose()
def index( self, **kw ):
if 'POST'==cherrypy.request.method:
# need to perform create
return CrudController( self.crud_class,
self.template_base ).create( **kw )
# Otherwise, assume it's a GET request and return all instances
all= self.crud_class.select()
template= "%s.list" % self.template_base
return dict( all=all, tg_template=template )

def __getattr__( self, objectKey ):
'''
Look up the object by key and return a controller for it.
'''
instance= getattr(self.crud_class, self.key_method)
( objectKey )
return CrudController( self.crud_class, self.template_base,
instance )


class Root(turbogears.RootController):
# the CRUD controller for all articles
articles= CrudController( blog.model.Article,
blog.model.Article.bySlug,
"blog.templates.article.crud" )


Completely composed in Mail.app and untested.

--
Jeff Watkins
http://newburyportion.com/

In the USDA study [of the meat packing industry conducted in 1996]
78.6 percent of the ground beef contained microbes that are spread
primarily by fecal material. The medical literature on the causes of
food poisoning is full of euphemisms and dry scientific terms:
coliform levels, aerobic plate counts, sorbitol, MacConkey agar, and
so on. Behind them lies a simple explanation for why eating a
hamburger can now make you seriously ill: There is shit in the meat.
-- Eric Schlosser, Fast Food Nation


Jeff Watkins

unread,
Dec 5, 2005, 10:45:40 PM12/5/05
to turbo...@googlegroups.com
On 5 Dec, 2005, at 10:43 pm, Jeff Watkins wrote:
> class Root(turbogears.RootController):
> # the CRUD controller for all articles
> articles= CrudController( blog.model.Article,
> blog.model.Article.bySlug,
> "blog.templates.article.crud" )

Oops. That should be:

class Root(turbogears.RootController):
# the CRUD controller for all articles
articles= CrudClassController( blog.model.Article,
blog.model.Article.bySlug,
"blog.templates.article.crud" )

--
Jeff Watkins
http://newburyportion.com/

“In science it often happens that scientists say, ‘You know that’s a
really good argument; my position is mistaken,’ and then they
actually change their minds and you never hear that old view from
them again. They really do it. It doesn’t happen as often as it
should, because scientists are human and change is sometimes painful.
But it happens every day. I cannot recall the last time something
like that happened in politics or religion.” Carl Sagan, 1987

Swaroop C H

unread,
Dec 5, 2005, 10:48:43 PM12/5/05
to turbo...@googlegroups.com
On 12/6/05, Jeff Watkins <je...@newburyportion.com> wrote:
I'm certain some will suggest this is unpythonic


Why?

--
Swaroop C H
www.swaroopch.info

Jeff Watkins

unread,
Dec 5, 2005, 10:54:19 PM12/5/05
to turbo...@googlegroups.com
Well, I've been getting that a lot lately. Seems my non-python experience is coming out.

On 5 Dec, 2005, at 10:48 pm, Swaroop C H wrote:

On 12/6/05, Jeff Watkins <je...@newburyportion.com> wrote:
I'm certain some will suggest this is unpythonic

Why?

--

Jeff Watkins

http://newburyportion.com/


Getting an education was a bit like a communicable sexual disease. It made you unsuitable for a lot of jobs and then you had the urge to pass it on. 

-- (Terry Pratchett, Hogfather)



Jonathan LaCour

unread,
Dec 5, 2005, 11:13:17 PM12/5/05
to turbo...@googlegroups.com
> So, in a generic CRUD feature, what would you want your URLs to
> look like:
>
> 1) http://yoursite/articles/10/edit

+1 for #1. It is far and away the best option. It just seems to
match the way I think of HTTP as messages being sent to objects. A
while back, I read an article[1] about HTTP by Dan Connolly, who said:

HTTP was design as a distributed realization of the Objective C
(originally Smalltalk) message passing infrastructure: the first
few bytes of every HTTP message are a method name: GET or POST.
Uniform Resource Locator is just the result of squeezing the
term object reference through the IETF standardization process.

I may be making a stretch asserting that this article should
influence the decision for #1, but I think it makes sense. You are
sending an edit message to an article that is uniquely identified by
the number 10. To me,

http://yoursite/articles/10/edit

is a left-to-right logical expansion of the lookup, followed by the
message send:

[[Article fromID: 10] edit];

Just my two cents.

[1] http://www.w3.org/People/Connolly/9703-web-apps-essay.html

--
Jonathan LaCour
http://cleverdevil.org

Levi

unread,
Dec 5, 2005, 11:24:44 PM12/5/05
to TurboGears

Swaroop C H

unread,
Dec 5, 2005, 11:26:47 PM12/5/05
to turbo...@googlegroups.com
+1

Dave Warnock

unread,
Dec 5, 2005, 11:41:14 PM12/5/05
to TurboGears

anders pearson

unread,
Dec 5, 2005, 11:46:21 PM12/5/05
to turbo...@googlegroups.com
Jeff Watkins <je...@newburyportion.com> writes:

> I'm certain some will suggest this is unpythonic, but here goes:
>
> class CrudController(turbogears.Controller):
> [... snip a bunch of code ...]

actually, this is pretty similar to what i've been using for building
REST apps (in plain cherrypy; no turbogears yet):

http://tasty.python-hosting.com/file/trunk/tasty/controller/rest_resource.py

it's a bit idiosyncratic to what i've been building lately and i'm sure
it could be made prettier, but it's been working nicely for me.

--
anders pearson : http://www.columbia.edu/~anders/
C C N M T L : http://www.ccnmtl.columbia.edu/
weblog : http://thraxil.org/

Andy-Kim Möller

unread,
Dec 6, 2005, 12:08:05 AM12/6/05
to turbo...@googlegroups.com
For me the natural one is the 3 one. Could be that it is because i was programming
over 8 years php, but for the first 2 possibillities how would you build a form like this:

<form action="http://yoursite/articles/edit" method="GET">
 <input type="text" name="id" value="">
 <input type="submit" name="edit">
</form>

Also for the first two it is not really clear how the application is styled. As example:
articles/10/edit could be:
object.object.object.index or
objext.object.method or
object.method.parameter or
method.parameter.parameter or
index.parameter.parameter.parameter

Kim

2005/12/6, Andy-Kim Möller <ank...@googlemail.com>:
For me the natural one is the 3 one. Could be that it is because i was programming
over 8 years php, but for the first 2 possibillities how would you build a form like this:

<form action="http://yoursite/articles/edit" method="GET">
 <input type="text" name="id" value="">
 <input type="submit" name="edit">
</form>

Also for the first two it is not really clear how the application is styled. As example:
articles/10/edit could be:
object.object.object.index or
objext.object.method or
object.method.parameter or
method.parameter.parameter

Kim


2005/12/5, Kevin Dangoor <dan...@gmail.com>:

So, in a generic CRUD feature, what would you want your URLs to look like:

1) http://yoursite/articles/10/edit

Matt Dimmic

unread,
Dec 6, 2005, 12:45:48 AM12/6/05
to TurboGears
Ian's right, this is a Bike Shed moment. But since I care what color
the bike shed is, +1 for #1. It feels the most pythonic to me:

x = Article(id="10")
x.edit("...")

But my chief hope is that TurboGears' BDFL makes some decision before
we get into an argument about what pythonic means. :)

Claudio Torcato

unread,
Dec 6, 2005, 12:49:20 AM12/6/05
to TurboGears
+1 for #1. for you logic.

Mike Orr

unread,
Dec 6, 2005, 1:21:27 AM12/6/05
to turbo...@googlegroups.com
What's a Bike Shed?

Janzert

unread,
Dec 6, 2005, 3:29:23 AM12/6/05
to turbo...@googlegroups.com
+1 on 1.

Explanation and origin of bike sheds. ;)
http://www.freebsd.org/doc/en_US.ISO8859-1/books/faq/misc.html#BIKESHED-PAINTING

Janzert

paron

unread,
Dec 6, 2005, 7:11:09 AM12/6/05
to TurboGears
Well, thanks for putting it out there -- I really prefer #1.

As far as "/articles/10" not being meaningful -- I don't think that's a
fault in the structure of the URI of the resource, it's a problem in
the representation of the resource. "/articles/My_new_car" or
"/articles/10/10/2005/The_next_trivial_thing" would fit URI #1, and
they are meaningful.

I like to use query strings for "flavors" -- user options that change
the representation, like "?_detail=_verbose&_dataformat=_json." I.e.,
the query string is for user preferences about the representation that
they receive of the resource with URI
"http://yoursite/articles/My_new_car."

I think that's RESTful, but that's just me. How about asking at the
REST Yahoo group? Kind of a second opinion? Some of the heavyweights
that don't hang around here, hang around there.

Ron

Kevin Dangoor

unread,
Dec 6, 2005, 7:46:42 AM12/6/05
to turbo...@googlegroups.com
On 12/5/05, Krys Wilken <krysw...@gmail.com> wrote:
>
> Django has a slug field with is similar.
>
> From the site:
>
> SlugField
>
> "Slug" is a newspaper term. A slug is a short label for something,
> containing only letters, numbers, underscores or hyphens. They're
> generally used in URLs.
>
> (http://www.djangoproject.com/documentation/model_api/)
>
> I believe it has some built in validation to make sure it complies with
> that definition.
>
> This might be a useful addition to TurboGears, whether or not it makes
> it into SQLObject directly.
>
> Just a thought. Django has some good ideas. We can learn from it.

Absolutely. I'm not sure how easy this would be to do with SQLObject
0.7 (or when Ian plans 0.8), but this does seem like a generally
useful construct.

Kevin

Kevin Dangoor

unread,
Dec 6, 2005, 11:54:03 AM12/6/05
to turbo...@googlegroups.com
On 12/6/05, paron <rphi...@engineer.co.summit.oh.us> wrote:
> I think that's RESTful, but that's just me. How about asking at the
> REST Yahoo group? Kind of a second opinion? Some of the heavyweights
> that don't hang around here, hang around there.

I've heard about that group, and I'm afraid we just don't have that
kind of time to wait for a conclusive answer :)

Enough of this thread seems to be in favor of choice #1, so that's
what I'm going to do for starters.

Ian Bicking

unread,
Dec 6, 2005, 1:21:26 PM12/6/05
to turbo...@googlegroups.com

Django's slugs are basically implemented on the client side via the
admin screen (from what I can tell), they aren't particularly tied to
the model. They are mostly like alternateID=true. So there's nothing
special SQLObject has to do to support that. But CatWalk would have to
pay attention to populate_from.

For slugs to be generated automatically when an instance was created
requires the events (or hardcoding slug functionality, but that'd be silly).

Krys Wilken

unread,
Dec 7, 2005, 1:27:00 AM12/7/05
to turbo...@googlegroups.com
Even if TG just provided a slug validator, that would help.

As you say, Ian, can just use alternateId = True from the model perspective.

What makes a slug field interesting is the validation/auto text processing.

Hmm... Now that I am thinking of it, a slug could be viewed simply as a
regexp text processing of another given field. As such, a generic
function could slugify any given text. (Perhaps chopping it off after a
certain upper limit.)

That function is something that TG could provide and it would be generic
enough to be applied to any StringCol field in any model.

e.g.:

my_model_instance.my_field =
turbogears.util.slugify(some_other_text_field_data)

... or something like that, anyway.


A validator could even be provided that would validate a given field to
ensure that it is slugifiable, though ideally, any text should be
slugifiable.

Anyway, I'm just typing as I'm thinking. I don't have the time to
implement this idea, so I am throwing it out there for anyone to run with.

Krys

Jorge Godoy

unread,
Dec 7, 2005, 6:36:02 AM12/7/05
to turbo...@googlegroups.com
Krys Wilken <krysw...@gmail.com> writes:

> What makes a slug field interesting is the validation/auto text processing.

I didn't get why you want to validate this.

> Hmm... Now that I am thinking of it, a slug could be viewed simply as a
> regexp text processing of another given field. As such, a generic
> function could slugify any given text. (Perhaps chopping it off after a
> certain upper limit.)

How about an MD5 or SHA1 or some other hash from any one or two fields (one
can be a date, for example)?

> That function is something that TG could provide and it would be generic
> enough to be applied to any StringCol field in any model.

Just StringCols? Why not UnicodeCol, IntCol, etc.?

> Anyway, I'm just typing as I'm thinking. I don't have the time to
> implement this idea, so I am throwing it out there for anyone to run with.

myinstance.mySlug = md5.md5(myinstance.myCol).hexdigest() ?

>>> import md5
>>> slug = md5.md5('this is a test').hexdigest()
>>> slug
'54b0c58c7ce9f2a8b551351102ee0938'
>>> slug = md5.md5('1').hexdigest()
>>> slug
'c4ca4238a0b923820dcc509a6f75849b'
>>> slug = md5.md5('áéíóúçã').hexdigest()
>>> slug
'a88a39ed784a18cccc9d87fd0e7f48d9'
>>> import datetime
>>> slug = md5.md5(str(datetime.datetime.now())).hexdigest()
>>> slug
'684a0195179447705f07fc205f32f74c'
>>>


Be seeing you,
--
Jorge Godoy <jgo...@gmail.com>

Lee McFadden

unread,
Dec 7, 2005, 6:51:17 AM12/7/05
to turbo...@googlegroups.com
Jorge,

IMHO, what you're suggesting isn't a slug. A slug is normally human
readable afaik... generating MD5 hashes for content wouldn't make for
easy URL's. :)

Lee

Jorge Godoy

unread,
Dec 7, 2005, 7:36:28 AM12/7/05
to turbo...@googlegroups.com
Lee McFadden <sple...@gmail.com> writes:

> IMHO, what you're suggesting isn't a slug. A slug is normally human
> readable afaik... generating MD5 hashes for content wouldn't make for
> easy URL's. :)

Ah! Then it is a column with a unique value containing only ascii
alphanumerical characters (sans some special chars). I see now.

I believe this is so application dependent that I don't see where I would use
that in one of the last 5 apps that I wrote with TG. But then, if I had that,
maybe I thought different. :-)

--
Jorge Godoy <jgo...@gmail.com>

Kevin Dangoor

unread,
Dec 7, 2005, 8:34:37 AM12/7/05
to turbo...@googlegroups.com
On 07 Dec 2005 10:36:28 -0200, Jorge Godoy <jgo...@gmail.com> wrote:
> Ah! Then it is a column with a unique value containing only ascii
> alphanumerical characters (sans some special chars). I see now.
>
> I believe this is so application dependent that I don't see where I would use
> that in one of the last 5 apps that I wrote with TG. But then, if I had that,
> maybe I thought different. :-)

Here are a few examples:

A blog: title="My Article", slug="my_article"
http://yoursite/blog/my_article

An address book: name="Kevin Dangoor", slug="kevin_dangoor"
http://yoursite/addresses/kevin_dangoor

The idea is just a human-readable ID that is generally automatically
created as much as possible.

Tracy Ruggles

unread,
Dec 7, 2005, 9:21:45 AM12/7/05
to turbo...@googlegroups.com

On Dec 6, 2005, at 10:54 AM, Kevin Dangoor wrote:

> On 12/6/05, paron <rphi...@engineer.co.summit.oh.us> wrote:
>> I think that's RESTful, but that's just me. How about asking at the
>> REST Yahoo group? Kind of a second opinion? Some of the heavyweights
>> that don't hang around here, hang around there.
>
> I've heard about that group, and I'm afraid we just don't have that
> kind of time to wait for a conclusive answer :)
>
> Enough of this thread seems to be in favor of choice #1, so that's
> what I'm going to do for starters.

POST /link_styles/1/vote HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 12

support=true


Kevin Dangoor

unread,
Dec 7, 2005, 9:24:56 AM12/7/05
to turbo...@googlegroups.com
On 12/7/05, Tracy Ruggles <tracy...@gmail.com> wrote:
> POST /link_styles/1/vote HTTP/1.1
> Content-Type: application/x-www-form-urlencoded
> Content-Length: 12
>
> support=true

200 OK

anders pearson

unread,
Dec 7, 2005, 10:42:56 AM12/7/05
to turbo...@googlegroups.com
Krys Wilken <krysw...@gmail.com> writes:

> my_model_instance.my_field =
> turbogears.util.slugify(some_other_text_field_data)
>
> ... or something like that, anyway.

this is what i use on thraxil.org:

def make_slug(title="no title"):
title = title.strip()
slug = re.sub(r"[\W\-]+","-",title)
slug = re.sub(r"^\-+","",slug)
slug = re.sub(r"\-+$","",slug)
return slug

not very sophisticated, but it works for my purposes. i also have a
modified version that does some rough conversion of unicode down to
ascii that i wrote for the 'Wicked' product for Plone.

one general purpose slugification routine may not be the best solution
though. some applications will have specific requirements. i've worked
with the journalism school here and they sometimes require a slug to be
in a specific format like 'authorlastname-oneworddescriptionofstory'.
other times autogenerated ones would be ok.

one other bit of advice is to prefer '-' over '_'. both are about
equally readable but search engines are smarter about '-'. if you have
/foo-bar/ in the url of a page, google will index it and relate 'foo'
and 'bar' to the page but if it's /foo_bar/, it doesn't recognize the
underscore as a word seperator so it will relate it to 'foo_bar'.

Ian Bicking

unread,
Dec 7, 2005, 12:29:11 PM12/7/05
to turbo...@googlegroups.com
anders pearson wrote:
> not very sophisticated, but it works for my purposes. i also have a
> modified version that does some rough conversion of unicode down to
> ascii that i wrote for the 'Wicked' product for Plone.

Here's a script for generally deunicodifying*:
http://www.crummy.com/cgi-bin/msm/map.cgi/ASCII,+Dammit

I'd say that it's bad to deunicodify, but in this case it seems like a
good idea.

[*] invented word

Krys Wilken

unread,
Dec 11, 2005, 10:44:21 AM12/11/05
to turbo...@googlegroups.com
anders pearson wrote:
>
> one general purpose slugification routine may not be the best solution
> though. some applications will have specific requirements. i've worked
> with the journalism school here and they sometimes require a slug to be
> in a specific format like 'authorlastname-oneworddescriptionofstory'.
> other times autogenerated ones would be ok.
>
> one other bit of advice is to prefer '-' over '_'. both are about
> equally readable but search engines are smarter about '-'. if you have
> /foo-bar/ in the url of a page, google will index it and relate 'foo'
> and 'bar' to the page but if it's /foo_bar/, it doesn't recognize the
> underscore as a word seperator so it will relate it to 'foo_bar'.
>

Hi Anders,

I agree that a generic slug field would not fit all situations, I also
think that a simple generic slug field with consistent rules might be a
simple and convenient addition to TG. Since they are also not hard to
write, then for those that have special needs, they can just whip one up
themselves.

On the other hand, I don't think it would be too hard to tell the slug
field to slug a list of other columns instead of just one. Also a
custom s regexp could be provided to change the rules. This might make
it more useful for special needs. But the default should be very simple.

If I get a chance, I will try to work on this. I generally don't have
time to contribute must to projects, but this may be something that I
can put a bit of time into.

Good thinking about generalizing it more.

Thanks,
Krys

Krys Wilken

unread,
Dec 11, 2005, 10:46:29 AM12/11/05
to turbo...@googlegroups.com
Exactly. A general helper, nothing fancy. If you don't need it, then
don't use it, but having it there would be convenient for more than a
handful of people, I would imagine.

Krys

Reply all
Reply to author
Forward
0 new messages