Updating A Plugin From QGIS 2 to QGIS 3

I have two plugins in the QGIS plugin repository, and with the release of QGIS 3 looming it was time to upgrade them for QGIS 3.

There is a short guide by the QGIS dev team that is a good starting point at:
https://github.com/qgis/QGIS/wiki/Plugin-migration-to-QGIS-3

But I had not done any development on these plugins for a while so a more step by step guide was useful, so hopefully, write the guide for the first plugin and follow it step by step for the second.

I am working on Windows, with OSGeo4W.

Before we start we will need to insure a couple of extras are installed through the OSGeo4w Installer:
Desktop:
qgis-dev
Libs:
python-future

Assuming GitHUB is the repo.
In git shell:

git clone https://github.com/HeikkiVesanto/QGIS_Multi_Ring_Buffer.git

There is a conversion script for QGIS plugins provided by the QGIS devs in the main repo.

UPDATE:
The qgis2to3 packages can be found on pip now:

https://github.com/opengisch/qgis2to3/

We can download just the scripts folder using the following link:
https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/qgis/QGIS/tree/master/scripts

Extract that into a location of your choice.

Then we can run the 2to3 script from the OSGeo4W console (cd to the folder you extracted the script to):

python 2to3 C:\path_to_plugin\QGIS_Multi_Ring_Buffer

This will print out changes that need to be made to convert from QGIS2 to QGIS3.

My first run resulted in many lines of:

RefactoringTool: Line 31: could not convert: from PyQt4.QtCore import *
RefactoringTool: Cannot handle star imports.

So my plugins line of:

from PyQt4.QtCore import *

Was impossible to convert with the tool, since I was not 100% sure what I needed from the QtCore library (I was young when I wrote the plugin). I commented out the line for the plugin in QGIS 2.8, booted up QGIS 2.8 and tried running the plugin.

So python errors:
NameError: global name ‘QVariant’ is not defined
NameError: global name ‘Qt’ is not defined
Later. I ended up expanding my other import from QtCore to:

from PyQt4.QtCore import QSettings, QTranslator, qVersion, QCoreApplication, QVariant, Qt

Running the 2to3 script again looked ok, with a number of changes required. These changes can be applied with –w flag:

python 2to3 C:\path_to_plugin\QGIS_Multi_Ring_Buffer -w

For the next step I booted up my favourite IDE PyCharm. I created a bat file that launched PyCharm with the QGIS dev environmental variables. So copying the “python-qgis-dev.bat” file from:

I changed the final line of:

"%PYTHONHOME%\python" %*

To:

start /d "C:\Program Files\JetBrains\PyCharm Community Edition 2017.2.1\bin\" pycharm64.exe

Then from File> Settings> Project:> Project Interpreter> Set to “C:\OSGeo4W64\apps\Python36\python.exe”

It takes a while to update the interpreter.

I only had 2 errors, both for:
QgsMapLayerRegistry.instance().addMapLayer(vl)

There is a list of API breaks between QGIS 2 and QGIS 3 at:
https://qgis.org/api/api_break.html

Looks like QgsMapLayerRegistry was moved to QgsProject. So I edit it to:

QgsProject.instance().addMapLayer(vl)

Then we can edit the metadata.txt to QGIS 3:
qgisMinimumVersion=3.0

And increase the version number.

Then we need to recompile the icon and ui for Python3 and QT5.

I was struggling a bit with the environmental variables to get it working, and ended up using a great batch script form StackExchange:
https://gis.stackexchange.com/questions/260743/how-to-compile-qtdesigner-user-interface-ui-and-resource-qrc-files-with-qg

@ECHO OFF

set OSGEO4W_ROOT=C:\OSGeo4W64

set PATH=%OSGEO4W_ROOT%\bin;%PATH%
set PATH=%PATH%;%OSGEO4W_ROOT%\apps\qgis\bin

@echo off
call "%OSGEO4W_ROOT%\bin\o4w_env.bat"
call "%OSGEO4W_ROOT%\bin\qt5_env.bat"
call "%OSGEO4W_ROOT%\bin\py3_env.bat"
@echo off
path %OSGEO4W_ROOT%\apps\qgis-dev\bin;%OSGEO4W_ROOT%\apps\grass\grass-7.2.2\lib;%OSGEO4W_ROOT%\apps\grass\grass-7.2.2\bin;%PATH%

cd /d %~dp0

@ECHO ON
::Ui Compilation
call pyuic5 multi_ring_buffer_dialog_base.ui -o multi_ring_buffer_dialog_base.py          

::Resources
call pyrcc5 resources.qrc -o resources_rc.py

@ECHO OFF
GOTO END

:ERROR
   echo "Failed!"
   set ERRORLEVEL=%ERRORLEVEL%
   pause

:END
@ECHO ON

So create the .bat file and run it in the folder of you plugin (editing where needed). Note: Your resources_rc may be called resource_rc or something slightly different.

Move the plugin folder to:
C:\\Users\\USERNAME\\AppData\\Roaming\\QGIS\\QGIS3\\profiles\\default\\python\\plugins\\

Boot up QGIS2.99/3.

I had a few more issues.

It seems QGIS 3 deals with the icon slightly differently.

icon_rc.py is no longer needed, and it seems was not used on my other plugin either.

So I removed the reference to it in the main python script:
from . import icon_rc

I still had some errors.

AttributeError: module ‘qgis.PyQt.QtGui’ has no attribute ‘QDialog’

It seems QDialog has moved to PyQt.QtWidgets.

So in my multi_ring_buffer_dialog.py file I needed to change some lines:

Add:

from qgis.PyQt.QtWidgets import QDialog, QDialogButtonBox

Change:
QtGui.QDialog
to:
QDialog
In the two instances in that file.

Working plugin!

Commit the changes back to the repo. Cd to the directory in git shell.

git add -A
git commit –m “Updated for QGIS 3”
git push

Zip the plugin up.
Upload to https://plugins.qgis.org/plugins/

Second plugin:
Same issue with import *
1 error with QgsMapLayerRegistry
My resources_rc file was called resource_rc so the batch script needed to be edited to:
call pyrcc5 resources.qrc -o resource_rc.py
Same issues with QtGui.QDialog

Now time for some improvements.