Блог Синявского
  • Разделы
  • Метки
  • Все статьи

Обновление проекта с Django 1.7 на 3.1

1

Обновление проекта с Django 1.7 на 3.1

Если невозможно старый код, стандартизировать с общей кодовой базой, не получается поддерживать, а так же если без рефакторинга не добавить новые возможности. То эта статья для вас. В противном случае пусть работает на Python2. Python2 работает быстрее Python3.
Важно помнить, что на старое приложение тоже было затрачено время и удалять старый код в корне неправильно. Видимо раньше было упущено время когда нужно было переходить на новые библиотеки, нужно это сделать сейчас. Для решения проблемы перевода старого django приложения, на Python3, необходимо пройти несколько стадий.

  1. Поменять синтаксические конструкции в коде (print, unicodestr и т.п.)
  2. Поменять urls.py (path, re_path)
  3. Поменять settings.py (middleware, context_preprocessors)
  4. Поменять различные изменения в django (что-то ушло, что-то переместилось)
  5. Деплой приложения через 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.


  • ← сюда
  • туда →

comments powered by Disqus

Опубликовано

02.05.2022

Обновление

05.05.2022

Категории

django

Тэги

  • cms 4
  • django 12
  • example 16
  • python 30

Всегда на связи

  • Блог Синявского - Ничего не переносить на завтра, это тоже проблема с прокастинацией?
  • © Алексей Синявский, по лицензии CC BY-SA если не указано иное.
  • С использованием Pelican. Тема: Elegant от Talha Mansoor