This guide helps you migrate the Django Reusable Components Library from Python 2.x / Django 1.x to Python 3.x and modern Django versions (2.x, 3.x, 4.x, 5.x).
- Overview
- Python 2 to Python 3
- Django 1.x to Modern Django
- Component-Specific Changes
- Testing Migrations
- Common Issues
- Python: 2.x syntax
- Django: 1.0-1.1 APIs
- Status: Functional but outdated
- Python: 3.8+ compatibility
- Django: 3.2+ (LTS) or 4.x/5.x
- Maintain: Core functionality
- Remove: GAE-specific dependencies (optional)
- Automatic tools: ~70% of work
- Manual fixes: ~30% of work
- Testing: Critical for production use
# Backup first!
cp -r Reusable Reusable.backup
# Run 2to3 on each component
2to3 -w -n rendertag/
2to3 -w -n paginatortag/
2to3 -w -n objectlisttag/
2to3 -w -n renderblock/
2to3 -w -n generic_view_patch/
# For GAE components (optional)
2to3 -w -n PageRank/
2to3 -w -n urlinfo/
2to3 -w -n sitemesh/Before (Python 2):
raise Exception, "Template not found"After (Python 3):
raise Exception("Template not found")Affected Files:
rendertag/templatetags/render.py:85renderblock/renderblock.py:multiple
Find and fix:
# Find all raise statements
grep -r "raise.*," . --include="*.py"
# Manual fix required for eachBefore (Python 2):
bits.next()After (Python 3):
next(bits)Affected Files:
rendertag/templatetags/render.py:parse_args_kwargs_and_as_var()- Any custom parsing code
Find and fix:
# Find .next() calls
grep -r "\.next()" . --include="*.py"Before (Python 2):
if isinstance(value, basestring):
# ...After (Python 3):
if isinstance(value, str):
# ...Compatibility approach:
import six # pip install six
if six.PY2:
string_types = basestring
else:
string_types = str
# Use in code
if isinstance(value, string_types):
# ...Before (Python 2):
for key, value in dict.iteritems():
# ...After (Python 3):
for key, value in dict.items():
# ...Note: 2to3 usually handles this automatically.
Before (Python 2):
print "Debug:", valueAfter (Python 3):
print("Debug:", value)Note: 2to3 handles this automatically.
Before (Python 2):
import urlparse
import urllib2After (Python 3):
from urllib import parse as urlparse
from urllib import request as urllib2Affected: urlinfo/ component (if migrating)
For gradual migration supporting both Python 2 and 3:
# requirements.txt
six>=1.16.0
# In code
import six
from six.moves import range
from six.moves.urllib.parse import urlparse
if six.PY2:
# Python 2 specific code
string_types = basestring
else:
# Python 3 specific code
string_types = strBefore (Django 1.x):
from django.conf.urls.defaults import patterns, url, include
urlpatterns = patterns('',
url(r'^articles/$', 'myapp.views.list_articles'),
url(r'^urlinfo/', include('urlinfo.urls')),
)After (Django 2.x+):
from django.urls import path, include
from myapp import views
urlpatterns = [
path('articles/', views.list_articles, name='articles_list'),
path('urlinfo/', include('urlinfo.urls')),
# Or with regex (re_path)
# from django.urls import re_path
# re_path(r'^articles/$', views.list_articles),
]Affected: All url configurations
Before (Django 1.x):
from django.template import Context, Template
from django.template.loader import get_template
t = get_template('template.html')
html = t.render(Context({'var': value}))After (Django 2.x+):
from django.template.loader import get_template
t = get_template('template.html')
html = t.render({'var': value}) # Dict, not ContextAffected: renderblock/renderblock.py
Before (Django 1.x):
from django.template import RequestContext
def my_view(request):
return render_to_response('template.html',
{'var': value},
context_instance=RequestContext(request))After (Django 2.x+):
from django.shortcuts import render
def my_view(request):
return render(request, 'template.html', {'var': value})Note: The render() shortcut automatically uses RequestContext.
Current code should work, but verify:
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
# This API is stable across versions
paginator = Paginator(object_list, 10)
page_obj = paginator.get_page(page_number) # Django 2.0+Note: get_page() is preferred over page() in Django 2.0+
Before (Django 1.x):
from django import template
register = template.Library()
@register.tag(name='render')
def do_render(parser, token):
# ...After (Django 2.x+):
# Same syntax still works!
from django import template
register = template.Library()
@register.tag(name='render')
def do_render(parser, token):
# ...Note: Template tag API is largely unchanged.
Update settings.py for modern Django:
# settings.py for Django 3.2+
# TEMPLATES setting (replaces old TEMPLATE_* settings)
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'], # Django 3.1+ Path support
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
# Use pathlib for paths (Django 3.1+)
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
# STATIC and MEDIA
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_DIRS = [BASE_DIR / 'static']
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'Django 1.x to 2.x+:
# Old
from django.core.urlresolvers import reverse
from django.conf.urls import url, include
# New
from django.urls import reverse
from django.urls import path, re_path, includeFile: rendertag/templatetags/render.py
Changes needed:
- Fix exception syntax:
# Line ~85
# Before
raise Exception, "err: template %s not found. object type: %s" % (template_name, type(obj))
# After
raise Exception("err: template %s not found. object type: %s" % (template_name, type(obj)))- Fix iterator:
# In parse_args_kwargs_and_as_var()
# Before
token = bits.next()
# After
token = next(bits)- Update template rendering:
# Before
return template.render(Context(context))
# After
return template.render(context)Modernized version:
import logging
from django import template
from django.template.loader import get_template
from django.template import TemplateDoesNotExist
register = template.Library()
class RenderNode(template.Node):
def __init__(self, obj, template=None, listtemplate=None, templatetype=None):
self.obj = obj
self.template = template
self.listtemplate = listtemplate
self.templatetype = templatetype
def render(self, context):
obj = self.obj.resolve(context)
# Handle None
if obj is None:
return ''
# Handle callable
if callable(obj):
obj = obj()
# Determine template
template_name = self._get_template_name(obj)
try:
tmpl = get_template(template_name)
# Django 2.x+ accepts dict directly
render_context = {'object': obj}
render_context.update(context.flatten())
return tmpl.render(render_context)
except TemplateDoesNotExist:
logging.error(f"Template not found: {template_name}")
return f"[err: template {template_name} not found]"
def _get_template_name(self, obj):
if self.template:
return self.template
class_name = obj.__class__.__name__.lower()
if self.templatetype:
return f"components/{class_name}_{self.templatetype}.html"
return f"components/{class_name}.html"
@register.tag(name='render')
def do_render(parser, token):
bits = iter(token.split_contents())
next(bits) # Skip tag name
# Parse arguments
obj = parser.compile_filter(next(bits))
template = None
listtemplate = None
templatetype = None
for bit in bits:
if '=' in bit:
name, value = bit.split('=', 1)
value = value.strip('\'"')
if name == 'template':
template = value
elif name == 'listtemplate':
listtemplate = value
elif name == 'templatetype':
templatetype = value
return RenderNode(obj, template, listtemplate, templatetype)File: paginatortag/templatetags/paginator.py
Changes needed:
- Minimal changes required
- Verify Paginator API usage
- Update any string type checks
File: objectlisttag/templatetags/makeobjectlist.py
Changes needed:
- Security: Replace eval() with safer alternatives:
# Before (UNSAFE)
if addition_filter:
object_list = eval('object_list' + addition_filter)
# After (SAFER - but still validate input!)
if addition_filter:
# Only allow simple filter chains
if not re.match(r'^(\.\w+\([^)]*\))+$', addition_filter):
raise ValueError("Invalid filter format")
# Use exec with restricted namespace
namespace = {'object_list': object_list}
exec(f'result = object_list{addition_filter}', namespace)
object_list = namespace['result']
# Even better: Use Q objects
from django.db.models import Q
# Accept filter kwargs instead
# {% makeobjectlist articles published=True category="tech" paginate_by=10 %}- Update exception handling:
# Modern Django
try:
page_obj = paginator.page(page)
except (EmptyPage, PageNotAnInteger):
page_obj = paginator.page(1)File: renderblock/renderblock.py
Changes needed:
- Context usage:
# Before
from django.template import Context
c = Context(context)
# After
c = context # Already a dict- Template rendering:
# Before
return template.render(Context(context))
# After
return template.render(context)File: generic_view_patch/__init__.py
Status: Deprecated - Use Class-Based Views instead
Modern Alternative:
# Instead of patching generic views, use CBVs
from django.views.generic import CreateView, UpdateView
class ArticleCreateView(CreateView):
model = Article
form_class = ArticleForm
def form_valid(self, form):
# Set extra fields
form.instance.author = self.request.user
form.instance.created_date = timezone.now()
return super().form_valid(form)
# urls.py
path('article/create/', ArticleCreateView.as_view(), name='article_create')Status: Requires major rewrite or replacement
Options:
-
Replace with modern alternatives:
- PageRank: Use SEO APIs (Moz, Ahrefs, SEMrush)
- urlinfo: Use requests library + BeautifulSoup
- sitemesh: Use Django's cache framework
-
Remove if not needed:
# Don't include in INSTALLED_APPS
# Remove from requirements# Python 3.8+
python3 -m venv venv-py3
source venv-py3/bin/activate
# Install Django
pip install django==3.2 # or 4.2, 5.0# test_rendertag.py
import unittest
from django.test import TestCase
from django.template import Template, Context
class RenderTagTestCase(TestCase):
def test_basic_render(self):
template = Template('{% load render %}{% render obj %}')
# ... test cases# Create test project
django-admin startproject testproject
cd testproject
# Copy components
cp -r ../Reusable/rendertag .
# Add to INSTALLED_APPS and run tests
python manage.py test rendertag# Start development server
python manage.py runserver
# Test each feature:
# - Object rendering
# - Pagination
# - CSS/JS loading
# - etc.Error: TypeError: render() takes 1 positional argument but 2 were given
Cause: Django 1.x used Context, Django 2.x+ uses dict
Fix:
# Remove Context wrapper
# Before
template.render(Context(context_dict))
# After
template.render(context_dict)Error: ImportError: No module named django.conf.urls.defaults
Cause: Module removed in Django 2.0
Fix:
# Before
from django.conf.urls.defaults import patterns, url
# After
from django.urls import path, re_pathError: ImportError: cannot import name 'render_to_response'
Cause: Removed in Django 3.0
Fix:
# Before
from django.shortcuts import render_to_response
# After
from django.shortcuts import renderWarning: RemovedInDjango110Warning: context_instance is deprecated
Fix: Use render() shortcut instead of render_to_response()
- Run 2to3 on all Python files
- Fix exception syntax (
raise X, Y→raise X(Y)) - Fix iterator calls (
.next()→next()) - Update URL patterns (patterns → list)
- Update template rendering (remove Context wrapper)
- Replace eval() in objectlisttag
- Update imports (urlresolvers → urls)
- Test all template tags
- Test pagination
- Test media file serving
- Update documentation
- Run full test suite
- Deploy to staging environment
- User acceptance testing
-
Phase 1: Python 3 compatibility
- Run automated tools
- Fix syntax errors
- Test basic functionality
-
Phase 2: Django 2.2 LTS
- Update to Django 2.2 (last Python 2 compatible)
- Fix deprecation warnings
- Update URL patterns
-
Phase 3: Django 3.2 LTS
- Ensure Python 3 only
- Update all deprecated APIs
- Full regression testing
-
Phase 4: Django 4.2 LTS (Current)
- Optional: migrate to modern patterns
- Replace function-based with class-based views
- Use modern async features if needed
- Django 2.0 Release Notes
- Django 3.0 Release Notes
- Django 4.0 Release Notes
- Python 2to3 Tool
- Six: Python 2 and 3 Compatibility
Last updated: 2025-11-06