Обновление проекта с Django 1.7 на 3.1
Если невозможно старый код, стандартизировать с общей кодовой базой, не получается поддерживать, а так же если без рефакторинга не добавить новые возможности. То эта статья для вас. В противном случае пусть работает на Python2. Python2 работает быстрее Python3.
Важно помнить, что на старое приложение тоже было затрачено время и удалять старый код в корне неправильно. Видимо раньше было упущено время когда нужно было переходить на новые библиотеки, нужно это сделать сейчас.
Для решения проблемы перевода старого django приложения, на Python3, необходимо пройти несколько стадий.
- Поменять синтаксические конструкции в коде (print, unicodestr и т.п.)
- Поменять urls.py (path, re_path)
- Поменять settings.py (middleware, context_preprocessors)
- Поменять различные изменения в django (что-то ушло, что-то переместилось)
- Деплой приложения через supervisor (Flup, и т.п. в django3 больше не работает, так как асинхронность).
Кому-то возможно покажется это очень нудным, но мне нравятся подобные работы. По-шагам разбираешься и доходишь до результата.
На каждом шаге проверяем python manage.py runserver
Python сам подскажет, что у него не получилось.
Python 3
Самое первое unicode строки меняем на обычные. u'' на ''
, тоже самое делаем, и для двойных кавычек, для тройных одинарных и тройных двойных.
Удаяем строки объявления кодировки файла # -*- coding: utf-8 -*-
или # coding=utf8
из каждого файла *.py
Об этом написано очень много информации. И проблема уже не особенно актуальна, но переделывая старый проект решил отметить несколько моментов.
Раньше часто спрашивали на собеседованиях, чем отличается Python2 от Python3. Я всегда отвечал, что раньше print
был операндом, а сейчас стал функцией.
Необходимо заменить в проекте все print
.
- Нажимаем Ctrl+Shift+R
- Выбираем маску *.py и поддержку регулярных выражений в замене.
- И меняем print\s([^\n])\n
на print($1)
В Python2 было отличие по функции генерирования последовательности было две версии xrange
генератор и range
конструктор массива с содержимым. Надо менять меняем xrange
на range
.
ModuleNotFoundError: No module named 'urllib2'
Смотря какая функция импортится, но скорее всего urlopen
.
Для универсальности можно так.
#! /usr/bin/env python
try:
# For Python 3.0 and later
from urllib.request import urlopen
except ImportError:
# Fall back to Python 2's urllib2
from urllib2 import urlopen
html = urlopen("http://www.google.com/")
print(html.read())
Для полного перехода на Python3 меняем
import urllib
на
import urllib.request
ModuleNotFoundError: No module named 'simplejson'
В Python2 необходимо было устанавливать пакет pip install simplejson
В Python3 пакет включен в стандартную библиотеку и устанавливать нет необходимости.
Меняем import simplejson
на import json as simplejson
.
Или импортировать json
и поменять во всем проекте simplejson.loads
на json.loads
т.д.
Можно все через автозамену simplejson
на json
, все будет работать.
Ошибки приложение связанные с Django 3.1
В Django изменилась система регистрации модулей.
Все папки с app, кроме главного приложения в котором находится точка входа с urls, settings копируем в apps.
В каждый app внутри apps, добавляем app.config с зависимостями, порядком запуска и переводом для православной админки.
В __init__.py
добавляем:
default_app_config = 'apps.<yours_app>.apps.YourAppConfig'
В apps.py
:
from django.apps import AppConfig
class YourAppConfig(AppConfig):
name = 'apps.your_app'
verbose_name = 'Название для админки'
Регистрируем приложение в settings.py
INSTALLED_APPS = (
'apps.your_app',
)
Работаем с models.py
Все импорты необходимо поправить
from ([^\.]+).models import
меняем через regexp замену
from apps.$1.models import
Импорты из текущего каталога меняем типа такого
from models import YourModel
на
from .models import YourModel
В моделях магический метод __unicode__
надо заменить на __str__
:
def __unicode__(self):
return '%s' % self.title
def __str__(self):
return self.title
ModuleNotFoundError: No module named 'StringIO'
try:
from cStringIO import StringIO
except ImportError:
try:
from StringIO import StringIO
except ModuleNotFoundError:
from io import StringIO
AttributeError: module 'django.db.transaction' has no attribute 'commit_on_success'
Необходимо заменить на
transaction.on_commit
Документация django
ModuleNotFoundError: No module named 'django.db.models.loading'
from django.apps import apps
get_model = apps.get_model
Работаем с urls.py
В Django3 убрали объекты класса url и patterns, поменяли на path и re_path и простой список (массив) с точки зрения оптимизации, можно обойтись и без регулярных выражений.
ImportError: cannot import name 'patterns' from 'django.conf.urls'
Было по-старому
from django.conf.urls import patterns, include, url
urlpatterns = patterns('',
url(r'^pages/$', never_cache(login_required(ListView.as_view())), name='pages_list'),
url(r'^pages/(?P<page_id>[1234567890]+)/$', never_cache(login_required(DetailView.as_view())), name='page_item'),
)
Должно быть по-новому, необходимо также указать name_space
from django.urls import path
from .views import ListView, DetailView
app_name = 'pages'
urlpatterns = [
path('', ListView.as_view(), name='pages_list'),
path('<int:pk>/', DetailView.as_view(), name='page_item'),
]
Так же не работает include(admin.site.urls)),
Пишет, что
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.
Сейчас инклудить урлы нужно либо так:
path('path/', include('my_app.urls')),
или
path('path/', include('my_app.urls', namespace='my_app')),
namespace
используется для вычисления пути reverse('my_app:path')
Либо просто написать, путь через точечную нотацию, он сам там заимпортит:
path('admin/', admin.site.urls),
TypeError: view must be a callable or a list/tuple in the case of include(). Было
path('logout/', 'django.contrib.auth.logout', kwargs={'next_page': settings.LOGOUT_REDIRECT_URL}, name='logout'),
Стало
from django.contrib.auth import logout
path('logout/', logout, {'next_page': settings.LOGOUT_REDIRECT_URL}, name='logout'),
далее сложная процедура, найти и заменить все реверсы
Проще всего это сделать регуляркой \{% url ['|"][^:]+?['|"]
- она ищет урлы без двоеточия.
Различные ошибки Django в основном во views.py (ModuleNotFoundError, ImportError)
Сильные изменения в django.contrib.auth
View functions разработчики заменили на class based views, поэтому нужно поменять
from django.contrib.auth.views import password_reset, password_reset_confirm
password_reset(request, ...)
password_reset_confirm(request, ...)
соответственно на нижеследующее, заполняем аттрибуты класса
from django.contrib.auth.views import PasswordResetView, PasswordResetConfirmView
class CustomPasswordResetView(PasswordResetView):
email_template_name = 'custom_registration/custom_password_reset_email.txt'
subject_template_name = 'custom_registration/custom_password_reset_email_subject.txt'
form_class = CustomPasswordResetForm
success_url = reverse_lazy('registration:portal_auth_password_reset_done')
template_name = 'custom_registration/custom_password_reset_form.html'
title = 'Сброс пароля'
class CustomPasswordResetConfirmView(PasswordResetConfirmView):
form_class = CustomSetPasswordForm
success_url = reverse_lazy('registration:portal_auth_password_reset_done')
template_name = 'custom_registration/custom_set_password_form.html'
title = 'Введите новый пароль'
и в urls.py
добавляем View.as_view()
ImportError: cannot import name 'RequestSite' from 'django.contrib.sites.models'
Переехал, меняем на from django.contrib.sites.requests import RequestSite
ImportError: cannot import name 'get_current_site' from 'django.contrib.sites.models'
Меняем на from django.contrib.sites.shortcuts import get_current_site
ModuleNotFoundError: No module named 'django.core.urlresolvers'
Это можно менять автозаменой
from django.core.urlresolvers import reverse
поменялось на
from django.urls import reverse
ImportError: cannot import name 'render_to_response' from 'django.shortcuts'
render_to_response
больше нет в django заменили на render
, набор аттрибутов тоже изменился, все упростилось, стало лучше
from django.shortcuts import render_to_response
from django.views.generic import View
class SomeView(View)
def get(self, request):
...
context = {}
return render_to_response(self.template_name, context, context_instance=RequestContext(request))
Решение такое:
from django.shortcuts import render
from django.views.generic import View
class SomeView(View)
def get(self, request):
...
context = {}
return render(request, self.template_name, context)
И удалить везде from django.template import RequestContext
Изменились template tags
Это важно, так как уже много раз нарывался на эту проблему.
Убрали assignment_tag
в django 1.9
Меняем на @register.simple_tag
settings.py
Меняем конфигурацию middleware и context процессоров.
Тот контекст, который мы добавляем в шаблон теперь конфигурируется иначе.
Было:
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.static',
'django.core.context_processors.media',
'django.contrib.messages.context_processors.messages',
)
Стало:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.join(BASE_DIR, 'templates')
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.template.context_processors.static',
'django.template.context_processors.media',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
Обработчики request и response - middleware принципиально изменились, код придется переписать с классов на функции (более верное решение) согласно инструкции , но принцип остался прежним.
Или оставить классом, но переписав, так же можно отнаследовать Middleware от from django.utils.deprecation import MiddlewareMixin
.
Было:
MIDDLEWARE_CLASSES = (
'django.middleware.cache.UpdateCacheMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware'
)
Стало:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
Пример правильного middleware на базе классов
Нужно чтобы было так:
class TimeStampMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
request.timestamp = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
response = self.get_response(request)
return response
Или так:
from django.utils.deprecation import MiddlewareMixin
class CustomMiddleware(MiddlewareMixin):
def process_request(self, request):
# Process the request
pass
def process_response(self, request, response):
# Process the response
return response
Перед деплоем необходимо проверить, что правильно добавлены хосты в ALLOWED_HOSTS.
Случилась победа, проект заработал.
System check identified no issues (0 silenced).
May 02, 2022 - 22:22:22
Django version 3.1, using settings 'med.settings.development'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.