diff --git a/.gitignore b/.gitignore
index 2267b36..55e9f48 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,4 @@
 /lib
 /build
 .Python
+.tox
diff --git a/examples/protected_downloads/download/tests.py b/examples/protected_downloads/download/tests.py
deleted file mode 100644
index 501deb7..0000000
--- a/examples/protected_downloads/download/tests.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
-This file demonstrates writing tests using the unittest module. These will pass
-when you run "manage.py test".
-
-Replace this with more appropriate tests for your application.
-"""
-
-from django.test import TestCase
-
-
-class SimpleTest(TestCase):
-    def test_basic_addition(self):
-        """
-        Tests that 1 + 1 always equals 2.
-        """
-        self.assertEqual(1 + 1, 2)
diff --git a/examples/protected_downloads/example/__init__.py b/examples/protected_downloads/example/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/examples/protected_downloads/settings.py b/examples/protected_downloads/example/settings.py
similarity index 98%
rename from examples/protected_downloads/settings.py
rename to examples/protected_downloads/example/settings.py
index 245739c..639a11e 100644
--- a/examples/protected_downloads/settings.py
+++ b/examples/protected_downloads/example/settings.py
@@ -67,7 +67,7 @@
     'django.contrib.auth.middleware.AuthenticationMiddleware',
 )
 
-ROOT_URLCONF = 'protected_downloads.urls'
+ROOT_URLCONF = 'example.urls'
 
 TEMPLATE_DIRS = (
     os.path.join(PROJECT_ROOT, 'templates'),
diff --git a/examples/protected_downloads/urls.py b/examples/protected_downloads/example/urls.py
similarity index 100%
rename from examples/protected_downloads/urls.py
rename to examples/protected_downloads/example/urls.py
diff --git a/examples/protected_downloads/manage.py b/examples/protected_downloads/manage.py
index 3e098b0..2605e37 100644
--- a/examples/protected_downloads/manage.py
+++ b/examples/protected_downloads/manage.py
@@ -1,14 +1,10 @@
 #!/usr/bin/env python
+import os
+import sys
 
-from __future__ import absolute_import
+if __name__ == "__main__":
+    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings")
 
-from django.core.management import execute_manager
-try:
-    from . import settings  # Assumed to be in the same directory.
-except ImportError:
-    import sys
-    sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
-    sys.exit(1)
+    from django.core.management import execute_from_command_line
 
-if __name__ == "__main__":
-    execute_manager(settings)
+    execute_from_command_line(sys.argv)
diff --git a/examples/protected_downloads/sendfile b/examples/protected_downloads/sendfile
deleted file mode 120000
index fca7c70..0000000
--- a/examples/protected_downloads/sendfile
+++ /dev/null
@@ -1 +0,0 @@
-../../sendfile
\ No newline at end of file
diff --git a/sendfile/backends/_internalredirect.py b/sendfile/backends/_internalredirect.py
index 65edd61..09511c0 100644
--- a/sendfile/backends/_internalredirect.py
+++ b/sendfile/backends/_internalredirect.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
 from django.conf import settings
 from django.utils.http import urlquote
 import os.path
@@ -12,4 +14,4 @@ def _convert_file_to_url(filename):
         relpath, head = os.path.split(relpath)
         url.insert(1, head)
 
-    return urlquote(u'/'.join(url))
+    return urlquote('/'.join(url))
diff --git a/sendfile/backends/xsendfile.py b/sendfile/backends/xsendfile.py
index 1f614ee..1df3057 100644
--- a/sendfile/backends/xsendfile.py
+++ b/sendfile/backends/xsendfile.py
@@ -4,6 +4,6 @@
 
 def sendfile(request, filename, **kwargs):
     response = HttpResponse()
-    response['X-Sendfile'] = smart_str(unicode(filename))
+    response['X-Sendfile'] = smart_str(filename)
 
     return response
diff --git a/sendfile/tests.py b/sendfile/tests.py
index 2e68e8b..79b11d9 100644
--- a/sendfile/tests.py
+++ b/sendfile/tests.py
@@ -1,5 +1,7 @@
 # coding=utf-8
 
+from __future__ import unicode_literals
+
 from django.conf import settings
 from django.test import TestCase
 from django.http import HttpResponse, Http404, HttpRequest
@@ -102,7 +104,7 @@ def test_correct_file_in_xsendfile_header(self):
         self.assertEqual(filepath, response['X-Sendfile'])
 
     def test_xsendfile_header_containing_unicode(self):
-        filepath = self.ensure_file(u'péter_là_gueule.txt')
+        filepath = self.ensure_file('péter_là_gueule.txt')
         response = real_sendfile(HttpRequest(), filepath)
         self.assertTrue(response is not None)
         self.assertEqual(smart_str(filepath), response['X-Sendfile'])
@@ -124,7 +126,7 @@ def test_correct_url_in_xaccelredirect_header(self):
         self.assertEqual('/private/readme.txt', response['X-Accel-Redirect'])
 
     def test_xaccelredirect_header_containing_unicode(self):
-        filepath = self.ensure_file(u'péter_là_gueule.txt')
+        filepath = self.ensure_file('péter_là_gueule.txt')
         response = real_sendfile(HttpRequest(), filepath)
         self.assertTrue(response is not None)
         self.assertEqual('/private/p%C3%A9ter_l%C3%A0_gueule.txt', response['X-Accel-Redirect'])
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..412d336
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,3 @@
+[wheel]
+# create "py2.py3-none-any.whl" package
+universal = 1
diff --git a/setup.py b/setup.py
index 77d66be..db47819 100644
--- a/setup.py
+++ b/setup.py
@@ -1,9 +1,4 @@
-from distutils.core import setup
-
-try:
-    from distutils.command.build_py import build_py_2to3 as build_py
-except ImportError:
-    from distutils.command.build_py import build_py
+from setuptools import setup
 
 
 version = __import__('sendfile').__version__
@@ -19,8 +14,8 @@
     url='https://github.com/johnsensible/django-sendfile',
     license='BSD',
 
-    requires=['Django (>=1.3)', 'Unidecode'],
-    install_requires=['Django>=1.3', 'Unidecode'],
+    requires=['Django (>=1.4.2)', 'Unidecode'],
+    install_requires=['Django >=1.4.2', 'Unidecode'],
 
     packages=['sendfile', 'sendfile.backends'],
     package_dir={
@@ -40,8 +35,11 @@
         'License :: OSI Approved :: BSD License',
         'Operating System :: OS Independent',
         'Programming Language :: Python',
+        'Programming Language :: Python :: 2.6',
+        'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3.2',
+        'Programming Language :: Python :: 3.3',
+        'Programming Language :: Python :: 3.4',
         'Topic :: Software Development :: Libraries :: Python Modules',
     ],
-
-    cmdclass={'build_py': build_py},
 )
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..7018928
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,36 @@
+# Tox (http://codespeak.net/~hpk/tox/) is a tool for running tests
+# in multiple virtualenvs. This configuration file will run the
+# test suite on all supported python versions. To use it, "pip install tox"
+# and then run "tox" from this directory.
+
+[tox]
+minversion=1.8.0
+envlist =
+    py26-django14,
+    py26-django15,
+
+    py27-django14,
+    py27-django15,
+    py27-django16,
+    py27-django17,
+
+    py32-django15,
+    py32-django16,
+    py32-django17,
+
+    py33-django15,
+    py33-django16,
+    py33-django17,
+
+    py34-django15,
+    py34-django16,
+    py34-django17,
+
+[testenv]
+changedir = examples/protected_downloads
+commands = python manage.py test sendfile
+deps =
+    django14: django >=1.4.2,<1.5
+    django15: django >=1.5,<1.6
+    django16: django >=1.6,<1.7
+    django17: django >=1.7,<1.8