2011-05-25
More not supporting random query parameters in URLs
A while ago I wrote Websites should not accept random parameters in requests, arguing that your web server or application should not accept query parameters that it doesn't expect. Recently there was a comment there that argued:
Postel's law does apply in the case. Consider the aspect of client and server versioning. Moreover, links may be recorded and followed at a later date. Your proposal of not serving a page with unexpected URLs will break links as soon as the server is modified.
I feel that this incorrectly merges the two difference cases of query parameters that you once used but no longer do and query parameters that you never used at all.
Query parameters that you've used in the past are a case of Cool URIs don't change. Any time you consider changing URLs, you should be making a conscious and deliberate decision. Ideally you'll carefully evaluate what URLs you want to stay and what URLs you want to stop working. And yes, sometimes I think that the right decision is to remove old URLs, because cool URLs are hard.
While it's tempting to 'support' old query parameters by accepting them and then ignoring them, I think that this is often a mistake. Mapping old URLs to new results is not necessarily doing anyone any favours; you really do need to return the same sort of thing that people are expecting. You can do a certain amount of remapping, but something like turning an Atom syndication feed into an HTML page is not actually any better than removing the URL entirely and returning a 404 result. If you want to really continue supporting the query parameters, you can't necessarily ignore them; you may well need to generate some redirects to the appropriate new versions of the pages.
Or in short: you don't want to just serve up any old page in response to old URLs, you want to serve up a useful result for the people who are using those old URLs.
(And if you can't serve up a useful result, the last thing you want to do is preserve the URL as something useful. Either return a 404 or serve up a redirect to some URL that you actually want people to use now.)
For query parameters that you have never used, the logic of my original argument applies in full force. You have no idea what effect or result the client expects to get from the query parameter, so robustness argues that you should not guess. That you might use some query arguments in the future is a weak argument; why is the client generating them now? The time to add support for them is when you actually use them (either in URLs you generate on the server or in a version of software that you push to clients).
How Django's form field ordering works
In Django, forms are defined in what seems to be the conventional approach; each form is a Python class, and fields are variables defined in the class definition (what would be class variables if this was anything like a conventional Python class). Although I don't think that the Django documentation ever states this explicitly, the natural order of form fields in the form (the order that is used if you just tell the form to render itself as HTML or iterate over every field) is the order that you defined the fields in. All of this is perfectly natural and intuitive at first glance.
If you know Python, though, you are probably very startled about right now. You see, in Python class elements have no particular ordering. For most classes the class namespace is literally a Python dictionary, and dictionaries have no particular set order of elements. Certainly you can't work out the order that entries were created in them, and thus for normal classes there's no way to know which order the fields were defined in.
If you know a bit of Python, you might expect that Django is doing something funny with a metaclass. But it turns out that it's not, because even a metaclass can't get this information; metaclasses take effect after the class namespace has been set up (more or less). What Django is doing is simpler and more clever than a metaclass; it's keeping count. Literally. The parent class for all form fields has a global counter, and every time you create a form field Django saves the current counter value in the new field instance and increments it. Then when it wants to know what order fields were created in, it just looks at all of their counter values.
(I have to admire this approach; it's a great triumph of brute force over trying too hard to be clever.)
This has important consequences if you define fields outside of specific forms, perhaps because you want to reuse the same complex field definition between several related forms. Unless you are doing something that specifically dictates field ordering, the global order that the fields were defined in will control their order in a specific form and all fields defined outside the form will be ordered before any fields defined just in it. If you dynamically create fields (and perhaps forms) on the fly, I suspect that things get even harder to keep track of.
This also means that you can artificially manipulate the field order if you absolutely have to. I'm not going to tell you what internal variable to change on field instances, because anyone who ought to be doing this can find the relevant variable in the Django source (and re-find it again if the Django people change its name or the implementation of all of this). The usual cautions apply.
(All of this is for Django 1.2.5. Yes, we're slightly behind the times.)
Sidebar: how this interacts with inherited form classes
Suppose you have one Form class that inherits from another Form class, and both classes define fields. Better yet, suppose that both Form classes contain fields defined globally, in some order. What order are the fields in?
As of Django 1.2.5, the answer appears to be that each separate Form class has its own internal order for its own fields. When you subclass a Form class, all of your fields (in your order) go before your parent's fields (in its order), which are before any parent it may have, and so on.
Don't ask how this interacts with multiple inheritance, because I don't know and I'm not going to do the experiments to find out.