Upgrade old Django code to Python 3 compatible

 Tips to fix bugs faster
Use django-upgrade to automatically fix code:

pip install django-upgrade
django-upgrade your_project/
 

Bulk search and replace (e.g. with sed or VSCode search/replace)
Set DEBUG=True in settings.py for error details
Use your logs: tail -f /var/log/apache2/your_error.log

django-upgrade --recursive nl_vindazo/

python manage.py check

django-upgrade nl_vindazo/urls.py
django-upgrade nl_vindazo/jobfor/wsgi.py


ModuleNotFoundError: No module named '...'

Install right verison for example

ModuleNotFoundError: No module named 'formtools'

pip install django-formtools

 

Fix all imports

for example

from .models import *

from . import choices

from .choices import ORDER_STATUS_CHOICES

 

Application labels aren't unique, duplicates: cyclusemail

Check dubbel import or 

cyclusemail/apps.py

class CyclusemailConfig(AppConfig):
    name = 'apps.cyclusemail'
    label = 'cyclusemail'  # <- mogelijk conflict

smart_text

In Python 3 there is no difference anymore:

Everything is standard Unicode

smart_str() works just like the old smart_text()

So: smart_text() has become redundant and has been removed

from django.utils.encoding import smart_str as smart_text
 

Why does this work?


smart_text existed in Django ≤2.2

In Django ≥3.0 it was removed

smart_str is the modern replacement that works identically under Python 3

 from django.utils.encoding import smart_str
smart_text = smart_str

sed -i 's/from django.utils.encoding import smart_text/from django.utils.encoding import smart_str\nsmart_text = smart_str/' $(grep -rl 'from django.utils.encoding import smart_text' .)

clean bak files

 find . -name "*.bak" -delete

 Different comands to find and repalce.

find ./job -name "*.pyc" -delete
find ./crawler -name "*.pyc" -delete

 while read file; do   sed -i.bak -E 's/(ForeignKey\([^)]*)(\))/\1, on_delete=models.SET_NULL\2/' "$file" done < models_paths.txt


while read file; do     sed -i.bak -E 's/(OneToOneField\([^)]*)(\))/\1, on_delete=models.CASCADE\2/' "$file"done < models_paths.txt
find . -name "urls.py" -exec sed -i.bak 's/\<url(/re_path(/g' {} +
 

 find . -name "urls.py" -exec sed -i.bak 's|from django.conf.urls import include, url|from django.urls import re_path, include|' {} +

# test to find urls

grep -rHn --include="urls.py" "url(" . | awk -F: '!seen[$1]++' > urls_with_url_usage.txt
 

find . -name "*.bak" -delete
 

find . -name "*.py" -exec sed -i.bak 's|from django.shortcuts import render_to_response, render|from django.shortcuts import render|' {} +

grep -rl "OneToOneField(" --include="models.py" . | xargs grep -L "on_delete" > models_paths.txt

 

SyntaxError: Missing parentheses in call to 'print'

print 'Hello world'

2to3

pyupgrade

✅ 2. Use 2to3 tool (automatic)
Python has a built-in migration tool: 2to3

Run this to scan your entire project:

2to3 -f print -W -n .

2to3 --version

ImportError: cannot import name 'ugettext_lazy' from 'django.utils.translation'

 Since Django 4.0, ugettext_lazy (and family like ugettext, ugettext_noop) has been removed.

So, 

from django.utils.translation import ugettext_lazy as _      =>
from django.utils.translation import gettext_lazy as _ 

grep -rl "ugettext_lazy" . | xargs sed -i 's/ugettext_lazy/gettext_lazy/g'
 

grep -rl "ugettext_lazy" . | xargs sed -i.bak 's/ugettext_lazy/gettext_lazy/g'
 

TypeError: ForeignKey.__init__() missing 1 required positional argument: 'on_delete' 

Solution

location = models.ForeignKey(Location, on_delete=models.SET_NULL, verbose_name=_("Region"), null=True, blank=True)
 

on_delete What it does
models.CASCADE Automatically delete linked object when Location is gone
models.SET_NULL Set to NULL when Location is deleted
models.PROTECT Fail to delete Location
models.SET_DEFAULT Set to default
models.DO_NOTHING Does literally nothing (dangerous without extra checks)

🔧 Auto fix with sed

find . -name "models.py" -exec sed -i.bak -E 's/(ForeignKey\([^)]*)(\))/\1, on_delete=models.SET_NULL\2/' {} +

If 50/50 fixed. 

find . -name "models.py" -exec sed -i.bak -E '/ForeignKey\(.*(on_delete=)?/! s/(ForeignKey\([^)]*)(\))/\1, on_delete=models.SET_NULL\2/' {} +

find . -name "models.py" -not -path "./job/*" \
-exec sed -i.bak -E 's/(ForeignKey\([^)]*)(\))$/\1, on_delete=models.SET_NULL\2/' {} +
 

 🔍 Explanation:
find . -name "models.py" → find all models.py files

-not -path "./job/*" → skip everything in the ./job/ folder

sed ... → adds on_delete=models.SET_NULL

.bak → automatically backs up every modified file

find . -name "*.bak" -delete
 

MIDDLEWARE

✅ MIDDLEWARE is the only valid key in Django 2.0 and later.
❌ MIDDLEWARE_CLASSES is completely ignored.

🔍 The reason you keep getting this error is because you are still using MIDDLEWARE_CLASSES, which is deprecated since Django 1.x and removed in Django 2.0+.


ImportError: cannot import name 'url' from 'django.conf.urls'


In Django 4.0, url() has been removed from django.conf.urls.

✅ In Django 2.x → deprecated
❌ In Django 4.x+ → completely removed

from django.urls import path, re_path, include
from . import views


urlpatterns = [
url(r'^contact/$', views.contact),
]

grep -rHn --include="urls.py" "url(" . | awk -F: '!seen[$1]++' > urls_with_url_usage.txt
 

ImportError: cannot import name 'render_to_response' from 'django.shortcuts'

❌ Why?
render_to_response has been removed since Django 3.0
And was already deprecated since Django 1.8


ModuleNotFoundError: No module named 'pure_pagination'


✅ Alternatieven voor django-pure-pagination

1. Django's built-in pagination (django.core.paginator)
✅ Recommended if you don't need custom logic

Django comes with a solid Paginator class by default: 

from django.core.paginator import Paginator

paginator = Paginator(queryset, 10)  # 10 items per page
page_number = request.GET.get("page")
page_obj = paginator.get_page(page_number)
 

{% for item in page_obj %}
  {{ item }}
{% endfor %}

<div>
  {% if page_obj.has_previous %}
    <a href="?page={{ page_obj.previous_page_number }}">Vorige</a>
  {% endif %}

  Pagina {{ page_obj.number }} van {{ page_obj.paginator.num_pages }}

  {% if page_obj.has_next %}
    <a href="?page={{ page_obj.next_page_number }}">Volgende</a>
  {% endif %}
</div>

sed -i 's/from collections import Iterable/from collections.abc import Iterable/' /home/admin/venv_vindazo/lib/python3.10/site-packages/pure_pagination/paginator.py
 

from django.core.paginator import Paginator

find . -name "*.py" -exec sed -i 's/from pure_pagination import Paginator/from django.core.paginator import Paginator/' {} +


 

ModuleNotFoundError: No module named 'guess_language' 

Slow code loading

A slow manage.py check or runserver can have multiple causes, especially after an upgrade or migration of your Django project.

python -X importtime manage.py check > importtime.log 2>&1

InvalidCacheBackendError: Could not find backend 'django.core.cache.backends.memcached.MemcachedCache':

Module "django.core.cache.backends.memcached" does not define a "MemcachedCache" attribute/class 

Django tries to use MemcachedCache as a caching backend, but:

You don't have a memcached-package installed

pip uninstall python-memcached pymemcache -y

Or you're using an outdated/incorrect backend string

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
        'LOCATION': 'localhost:11211',
        'TIMEOUT': 86400,
    }
}

pip install python-memcached

apt install memcached
systemctl start memcached
systemctl enable memcached
netstat -tulpen | grep 11211

ImportError: cannot import name 'get_ip' from 'ipware.ip' 


from ipware import get_client_ip
ip, is_routable = get_client_ip(request) 

ip = get_client_ip(request)[0] or ''

find . -name "*.py" -exec sed -i.bak \
    -e 's/from ipware.ip import get_ip/from ipware import get_client_ip/' \
    -e 's/get_ip(request)/get_client_ip(request)[0] or ""/g' \
    {} +

find . -name "*.py" -exec sed -i.bak 's/\bget_ip(request)\b/get_client_ip(request)[0] or ""/g' {} +
 

Explanation:
\b makes sure it is exactly get_ip(request) (not get_ip_address or other variants).

get_client_ip(request)[0] or "" only takes the IP address, and returns empty string if it is None (safe for logging, storing etc.).

.bak makes a backup of each .py file for safety.

django.core.exceptions.ImproperlyConfigured: Passing a 3-tuple to include() is not supported. Pass a 2-tuple containing the list of patterns and app_name, and provide the namespace argument to include() instead.

You're running a modern version of Django, and in newer versions (since Django 1.9+), the old way of including admin URLs like this:

re_path(r'^admin/', include(admin.site.urls)),  # ❌ WRONG
re_path(r'^admin/', admin.site.urls),  # ✅ CORRECT
 

That will eliminate the ImproperlyConfigured: Passing a 3-tuple to include() error immediately.

ImportError: cannot import name 'force_unicode' from 'django.utils.encoding'

 from django.utils.encoding import force_str

force_str is the replacement for force_unicode in modern Django versions (from Django 3.x onwards).

ImportError: cannot import name 'ugettext' from 'django.utils.translation'

 from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _

find . -name "*.py" -exec sed -i 's/from django.utils.translation import ugettext as _/from django.utils.translation import gettext as _/g' {} +

ModuleNotFoundError: No module named 'autocomplete_light'

pip install autocomplete_light  # ❌ WRONG import autocomplete_light
pip uninstall autocomplete_light  # als die bestaat

pip install django-autocomplete-light

import dal
from dal import autocomplete
 

INSTALLED_APPS = [
...
'dal',
'dal_select2',
]

ModuleNotFoundError: No module named 'StringIO'

from io import StringIO

find . -name "*.py" -exec sed -i.bak 's|from StringIO import StringIO|from io import StringIO|g' {} +


ImportError: cannot import name 'get_real_ip' from 'ipware.ip'"
🧠 What has changed in ipware?
In the new versions (v3+) of ipware:

✅ get_client_ip(request) is the recommended function.

❌ get_real_ip no longer exists.

from ipware import get_client_ip
ip, is_routable = get_client_ip(request)
 

find . -name "*.py" -exec sed -i.bak 's/from ipware\.ip import get_real_ip/from ipware import get_client_ip/g' {} +
find . -name "*.py" -exec sed -i.bak 's/get_real_ip(/get_client_ip(/g' {} +
 

ModuleNotFoundError: No module named 'django.core.urlresolvers'

from django.core.urlresolvers import reverse_lazy
from django.urls import reverse_lazy

find . -name "*.py" -exec sed -i.bak 's/from django\.core\.urlresolvers/from django.urls/g' {} +
 

ImportError: cannot import name 'urlunquote' from 'django.utils.http'

from django.utils.http import urlunquote
from urllib.parse import unquote as urlunquote

from django.utils.http import urlunquote as dj_urlunquote
from urllib.parse import unquote as dj_urlunquote


sed -i.bak 's/from django.utils.http import urlunquote as dj_urlunquote/from urllib.parse import unquote as dj_urlunquote/' $(grep -rl "from django.utils.http import urlunquote")
 

The snowpenguin.django.recaptcha3 package still uses the outdated ugettext_lazy, which is deprecated in Django 4+.

pip uninstall django-recaptcha3
pip install django-recaptcha
 

except stripe.error.CardError, e: ^^^^^^^^^^^^^^^^^^^^^^^^^ SyntaxError: multiple exception types must be parenthesize

except SomeError, e:
except SomeError as e:

 AttributeError: 'Settings' object has no attribute 'ROOT_URLCONF' 

ROOT_URLCONF = 'jobfor.urls'

ModuleNotFoundError: No module named 'django.core.context_processors'

The module django.core.context_processors is defunct since Django version 1.8. Those context processors have been moved to: 

django.template.context_processors

TEMPLATES = [
    {
        ...
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.request',  # ✅ correct

 

TypeError: Elasticsearch.search() got an unexpected keyword argument 'doc_type'

❌ What's wrong?
In older versions of Elasticsearch, doc_type was used as an argument. In newer versions (7.x and 8.x), doc_type has been removed, but your Haystack backend is still trying to use it. 

HAYSTACK_CONNECTIONS = {
    'default': {
        'ENGINE': 'haystack.backends.elasticsearch7_backend.Elasticsearch7SearchEngine',
        'URL': 'http://localhost:9200/',
        'INDEX_NAME': 'vindazo',
    },
}

 pip uninstall django-haystack
pip install git+https://github.com/django-haystack/django-haystack.git

TypeError: Elasticsearch.search() got an unexpected keyword argument 'doc_type'

💥 Problem
You are using:

Haystack 3.x, which does not support ES 2.x

elasticsearch-py 7.17, which also does not talk to ES 2.3.5

sudo systemctl stop elasticsearch

✅ Solution: Upgrade Elasticsearch server
Since your client-side (Django + Python) is already ready for ES 7.x, you only need to upgrade the server itself:

# Backup data als nodig
sudo tar -czf /root/es_backup.tar.gz /var/lib/elasticsearch

# Voeg Elastic repo toe (indien niet gedaan)
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
sudo apt-get install apt-transport-https -y
echo "deb https://artifacts.elastic.co/packages/7.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elastic-7.x.list

sudo apt update
sudo apt install elasticsearch=7.17.12

# Start service
sudo systemctl daemon-reexec
sudo systemctl enable elasticsearch
sudo systemctl start elasticsearch

# Test
curl http://localhost:9200


curl http://localhost:9200 { "name" : "Ubuntu-2204-jammy-amd64-base", "cluster_name" : "elasticsearch", "cluster_uuid" : "C5a2_pmyQsqK2i8XjAQkTg", "version" : { "number" : "7.17.12", "build_flavor" : "default", "build_type" : "deb", "build_hash" : "e3b0c3d3c5c130e1dc6d567d6baef1c73eeb2059", "build_date" : "2023-07-20T05:33:33.690180787Z", "build_snapshot" : false, "lucene_version" : "8.11.1", "minimum_wire_compatibility_version" : "6.8.0", "minimum_index_compatibility_version" : "6.0.0-beta1" }, "tagline" : "You Know, for Search" }

raise HTTP_EXCEPTIONS.get(status_code, TransportError)( elasticsearch.exceptions.RequestError: RequestError(400, 'parsing_exception', 'unknown query [filtered]') [28/Mar/2025 18:37:16] "GET /job/?q=technisch&l= HTTP/1.1" 200 32233

 elasticsearch.exceptions.RequestError: RequestError(400, 'parsing_exception', 'unknown query [filtered]')
The filtered query was previously used in Haystack in older versions of django-haystack or custom backends. But in Elasticsearch 5+ it has been replaced by bool -> must, filter, should, etc.

base_sqs = SearchQuerySet().using('default').filter(payed=True)

Upgrade elastic and config in Setting file.   ✅ correct

 

ImportError: cannot import name 'utc' from 'django.utils.timezone'


This error means that django.utils.timezone.utc is no longer available in the version of Django you are using (probably Django 5.x). That is correct: since Django 5.0, timezone.utc has been removed, as you should now use Python's own datetime.timezone.utc by default.

from django.utils.timezone import utc ❌ ols code:

from datetime import timezone
utc = timezone.utc
 

 Exception Type: AttributeError  
Exception Value: 'QueryDict' object has no attribute 'has_key'

The has_key() method is deprecated and removed in Python 3.x. You use this:

if self.cleaned_data['mr'] == True or self.data.has_key('mr') == False: ❌ ols code:
if self.cleaned_data.get('mr') is True or 'mr' not in self.data:

 Exception Type: TypeError at /job/ Exception Value: 'bool' object is not callable

 In Django 1.x, is_authenticated() was a method.
From Django 2.0 and up, it is a property, so without parentheses.

if self.request.user.is_authenticated():❌ ols code:

if self.request.user.is_authenticated:
 

Exception Type: TemplateSyntaxError at /job/ Exception Value: Invalid block tag on line 10: 'ifequal', expected 'elif', 'else' or 'endif'. Did you forget to register or load this tag?


{% ifequal mypage page.number %}  ❌ ols code:
<li class="active"><a href="#">{{ mypage }}</a></li>
{% else %}
<li><a href="?{{ mypage.querystring }}" rel="nofollow">{{ mypage }}</a></li>
{% endifequal %}

{% if mypage == page.number %} ✅
<li class="active"><a href="#">{{ mypage }}</a></li>
{% else %}
<li><a href="?{{ mypage.querystring }}" rel="nofollow">{{ mypage }}</a></li>
{% endif %}

Exception Type: AttributeError at /job/ Exception Value: module 'collections' has no attribute 'Iterable' 


from collections import Iterable❌ ols code:
from collections.abc import Iterable

 

Exception Type: TemplateSyntaxError at /job/viewjob/26230496/webcare-medewerker-30483.html Exception Value: Invalid filter: 'removetags'

 removetags was a standard filter in older versions of Django (such as your old Python 2.7 environment), but has been removed in more recent Django versions (such as Django 5.x, which you are using now). That's why you now get the Invalid filter: 'removetags' error.

✅ Create template tags in your app (e.g. job/templatetags/html_filters.py)

mkdir -p job/templatetags
touch job/templatetags/__init__.py
     

import re
from django import template

register = template.Library()

@register.filter
def removetags(value, tags):
    """
    Simuleert het oude Django removetags filter.
    Gebruik: {{ value|removetags:"b i script" }} of 'all' om alles te strippen.
    """
    if tags == "all":
        return re.sub(r'<[^>]+>', '', value)
    tags = tags.split()
    for tag in tags:
        value = re.sub(rf'</?{tag}[^>]*?>', '', value)
    return value
 

{{ job.description|cleanjob|safe }}
 

# myapp/templatetags/clean_filters.py

from django import template
import bleach

register = template.Library()

ALLOWED_TAGS = ['p', 'br', 'strong', 'b', 'i', 'em', 'ul', 'ol', 'li', 'a']
ALLOWED_ATTRIBUTES = {
    'a': ['href', 'title', 'rel', 'target']
}

@register.filter(name='cleanhtml')
def cleanhtml(value):
    return bleach.clean(value, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES, strip=True)
 

{% load clean_filters %}

{{ job.description|cleanhtml|safe }}
 

from django.utils.encoding import smart_unicode
 


 

 


 

 


 

Comments