Mimsy Were the Borogoves

Hacks: Articles about programming in Python, Perl, PHP, and whatever else I happen to feel like hacking at.

From Django .96.2 to 1.0

Jerry Stratton, September 10, 2008

If you’re updating from Django .96 to 1.0, the first place to go is Porting your apps from Django 0.96 to 1.0. But there are a bunch of things not mentioned there that can bring a .96 app to a grinding crash.

Model-level validation is gone

The “validator_list” option to model fields has gone away in favor of validation functions, but that functionality isn’t yet a part of Django. I solve this by moving the validation function to the model, and then overriding the save function to always call it. That isn’t quite as useful, because it causes errors that need to be trapped, but it should work until model-level validation returns.

Note that this actually changed on moving from oldforms to newforms. The new forms system didn’t call the validator_list functions even under 0.96.2.

Overriding save requires special parameters

The save method didn’t use to need any parameters. It does now. There’s force_insert and force_update. The Writing Models documentation recommends:

[toggle code]

  • def save(self, force_insert=False, force_update=False):
    • do_something()
    • super(Blog, self).save(force_insert, force_update) # Call the "real" save() method.
    • do_something_else()

I don’t know why that’s recommended over using *args and **kwargs; that would seem to me to be a longer-lasting fix.

Core=True is unnecessary

There is no core=True. This was used to determine whether or not an inline record needed to be removed. Now, there’s a delete checkbox next to each inline record.

HttpResponse mimetype must be str

This may be a mod_python requirement, but the mimetype given to HttpResponse must be type str, not unicode, but Django is treating all strings as unicode strings. So if you used to pull the mimetype from the database, something like this:

  • response = HttpResponse(page, mimetype=mimetype)

You’ll have to coerce the variable to str:

  • response = HttpResponse(page, mimetype=str(mimetype))

PhoneNumberField is gone

The model.PhoneNumberField is gone in favor of “local flavor”. You’ll need to import the model from localflavor.countrycode. For example:

  • from django.contrib.localflavor.us.models import PhoneNumberField
  • phone = PhoneNumberField('Phone Number', blank=True)

mod_python PATH_INFO matches development server

Under 0.96, the PATH_INFO META under mod_python was a partial path, though it was a full path in the development server. In 1.0, it is now a full path under mod_python also.

Fields automatically escape HTML

I’ve saved the best for last. I have to say I’m torn on the propriety of magically escaping everything that goes through a template. On the one hand it does make sure that nobody forgets to escape a field; on the other hand, if you’ve been using templates to assiduously avoid hard-coding any HTML in your code, you’re going to find that this change goes pretty deep and requires extreme care to avoid exposing HTML or other code in your pages.

One of the reasons I switched to Django is that it managed to handle escaping HTML code in forms perfectly. Because I often put sample code in my blog entries and I often put real HTML in my blog entries, I know how much of a pain it is to know when to escape code and when not to; Django 0.96 did exactly what I needed. It escaped what needed to be escaped, and left what needed to be left. Under 1.0, as far as I can tell, it always escapes or it never escapes.

My first step was to change the method that returns the content field of my pages; I ran the final value through mark_safe(), like this:

[toggle code]

  • from django.utils.safestring import mark_safe
  • def pageContent
    • text = mark_safe(text)
    • return text

However, some of the fields are normally accessed directly. Since I know that these fields always need to be HTML, I looked up the possibility of an “HTMLField” and found some partial examples. Note that the on-line examples often omit SubfieldBase, but you need that or Django will ignore to_python in the subclass.

[toggle code]

  • class HTMLField(models.TextField):
    • __metaclass__ = models.SubfieldBase
    • def to_python(self, value):
      • value= mark_safe(value)
      • return value

Yesterday, while editing one of my pages with sample code in it, I suddenly saw the entire page disappear from the live version. Going back to the editor, I noticed that the sample code had become real code. Apparently, telling Django that a value is safe short-circuits whatever Django used to do when putting a value into a textfield on a custom form and on the admin form—both were showing entities as themselves rather than as their encoded form. &lt; becomes <, with all the resultant hilarity you would expect when sample code suddenly starts rendering.

So I added a line to replace ampersands with their entity:

[toggle code]

  • class HTMLField(models.TextField):
    • __metaclass__ = models.SubfieldBase
    • def to_python(self, value):
      • value=value.replace('&', '&amp;')
      • value= mark_safe(value)
      • return value

Now the forms worked. On re-uploading the resultant file to the server, however, suddenly the example code was double-escaped!

In the end, I removed the custom HTMLField and went through all of the non-form templates to add {% autoescape off %} {% endautoescape %} around each of the fields that can validly contain HTML.

This is the kind of cascading problem that I moved to Django to avoid; hopefully that’s the end of the hacks, but I suspect more will be necessary. It would be nice to have a model field option that says “escape this like you did in 0.96, because that was what I needed”.

Update: Added PhoneNumberField

  1. <- Media file duration
  2. Django Add Related Item ->