A little bit of history

In May, Vinay Sajip published a fork proving that it was possible to support both Python 2 and 3 with a single codebase, and pass the entire test suite. This fork built upon earlier efforts by Martin von Löwis.

Shortly after, the core team decided to use six as a compatibility layer, rather than an ad-hoc library. The goal was not only to cover Django's needs, but also to choose a solution that would work for the ecosystem at large. Starting with Django 1.5, six will be bundled as django.utils.six , and thus available for all Django applications that use the same porting strategy as Django itself.

The first actual step towards Python 3 support was to remove the u prefixes of unicode strings and add the unicode_literals future import. This change was committed at the DjangoCon Europe sprints in July.

Then the biggest part of the work happened over the last few weeks, with hundreds of commits updating various parts of Django.

The core team focused on writing Python 3 code with Python 2 compatibility, rather than the opposite, in order to future-proof the code base. In order to avoid confusion, functions and classes whose name included string or unicode were renamed to bytes and text respectively. As a consequence, the port was significantly more invasive than Vinay Sajip's proof of concept.

On the bright side, since string encoding and decoding operations need to be correct under Python 3, several approximations were corrected during the porting effort. Even though the compatibility code adds some noise, the resulting code is cleaner in many places.

I'd like to thank all the community members and core developers who contributed to this huge project.