Django Upgrade Maverick Style
August 21, 2015
I’m upgrading a website that was originally made in 2013. It’s only had minor edits in the last two years, and is stuck on Django 1.5.
The site is for job applications and has a relatively small codebase. It lets applicants sign-up with a multistep form, they get emailed when they complete the form. The site uses Django Admin for content management and the admin can export all applicant data as a PDF. It’s a relatively small codebase so I decide I’m going to upgrade the site to Python 3, because I’m that kind of guy. Let the yak shaving commence!
Django 1.5 was the first version of Django to support Python 3. I could try and port the code to Python 3 straight away. However Django 1.5 support finished on September 2nd 2014 so I definitely want to upgrade.
I could upgrade to 1.7 and have support until December 2015, but I don’t want to upgrade to 1.7 because Django 1.7 is for losers. The newest supported version of Django at the time of writing is 1.8.3. Django 1.8 is an LTS (Long Term Support) version, this will give me support to at least April 2018.
We start with the following dependencies:
It’s a good idea to upgrade Django versions one at a time. I’m a maverick who doesn’t play by the rules, but gets results, so I upgrade straight to 1.8:
I need to remove south
from INSTALLED_APPS
to get the site to run. south
isn’t supported as Django migrations were introduced in 1.7.
I also need to delete my migrations
folder from my apps to get the site to run without error. This flattens my migrations, I’m fine with this as production is up to date. I go to run my dev site:
Django is complaining, I try and shut it up!
Argh! A big ugly stack trace. I’m starting to question my cavalier approach. But then I try again but with the --fake-initial
flag:
OK that seems to have worked. Still got it. Let’s run the site and check:
Another stack trace, but this time in the browser. Let’s try and fix it by editing forms.py
and adding the exclude = {}
line:
I run the site agin, the anticipation is killing me as I refresh the browser, it works! That was easy. Not so fast, some quick testing on the admin reveals another error:
The root of the error appears to be the adminsortable
package so I upgrade this from 1.5.4:
We’re on version 1.8.4 now, and feeling much more futuristic. I test again. Success!
The next error found is on our export applicants as PDF functionality. The root of this error is the following line:
A quick search reveals passing the mimetype
argument to HttpResponse
was removed in Django 1.7. That Django 1.7 has a lot to answer for. I change the code to use content_type
:
I can now download my applications as a zip file containing PDFs. Nice.
Next task is to test the multi-step application form is working correctly. The first thing I notice is the email fields displaying strangely. On further inspection it turns out the HTML for the emails fields has changed from:
To:
A quick CSS edit later and we’re looking good. I fill out the first step of the form and submit:
Argh!
The multi-step form works by saving to the DB on the first step then updating the database at each step.
I can see that the problem column is require_uk_work_permit_or_visa
. From running a manual test on the staging server (which is still running Django 1.5) I can see this field is being set to false after the first step saves. Time to check the models.
The issue is require_uk_work_permit_or_visa
is a non-null boolean field and it doesn’t have a default. I give it a default, explicit is better than implicit after all:
All steps of our form now work. Next task is to see what other packages need updating. Pip has a handy option to do this:
This prints a list of outdated requirements in a human readable format. You can then decide which you want to update, by editing the version numbers in requirement.txt. Here’s what the updated file looks like:
Time to test the new site now we’ve upgraded packages. Yes more manual testing! I really missed my calling as a QA. This project is one of the few I work on that doesn’t have automated tests. Tests weren’t written when the project was originally built because:
- It was a small project.
- We made it in on a tight deadline.
Having unit tests in place would have made upgrading easier. The moral of the story; remember to write your tests kids, it will save you time in the long run.
Ok so now we’ve upgraded Django and our other requirements. Is the fun over? Hell no it’s just started, we’ll be upgrading to Python 3 in the next part.
If you'd lke to contact me about this article then use twitter @petexgraham or email articles@petegraham.co.uk.