<div dir="ltr"><div dir="ltr">And including this in loadModels() is a fairly safe way to remove the copied model files from previous versions:<div><br></div><div><div>            modelsFiles = glob.glob(os.path.join(self.modelsPath, '*.model3'))</div><div><br></div><div>            for modelFileName in modelsFiles:<br></div><div>                destFilename = os.path.join(ModelerUtils.defaultModelsFolder(), os.path.basename(modelFileName))<br></div><div>                try:<br></div><div>                    if os.path.exists(destFilename):</div><div>                        move(destFilename, destFilename + '.bak')</div><div>                except Exception as ex:</div><div>                    QgsMessageLog.logMessage(<a href="http://self.tr">self.tr</a>('Failed to rename existing model: {} - {}'.format(destFilename, str(ex))), self.messageTag, Qgis.Warning)</div><div>                    continue</div></div><div><br></div><div>It does mean that if someone happens to have a model with the same name as one provided by the plugin, it will disappear, but at least it will still be there as a *.bak file, rather than just being overwritten.</div></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Tue, 16 May 2023 at 13:19, Alister Hood <<a href="mailto:alister.hood@gmail.com">alister.hood@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div>Further to this, I am now using the following code to load the models directly from the plugin folder, rather than copying them into <span style="font-family:monospace">%AppData%\Roaming\QGIS\QGIS3\</span><span style="font-family:monospace">profiles\default\processing\</span><span style="font-family:monospace">models</span></div><div><br></div><div><br></div><div><font face="monospace">    def loadModels(self):<br></font></div><div><div><font face="monospace">            modelsFolders=ModelerUtils.modelsFolders()</font></div><div><font face="monospace">            if self.modelsPath not in modelsFolders:</font></div><div><font face="monospace">                modelsFolders.append(self.modelsPath)</font></div><div><font face="monospace">                ProcessingConfig.setSettingValue(ModelerUtils.MODELS_FOLDER, ';'.join(modelsFolders))</font></div></div><div><div><font face="monospace">            QgsApplication.processingRegistry().providerById('model').refreshAlgorithms()</font></div></div><div><font face="monospace"><br></font></div><div><div><font face="monospace">    def unloadModels(self):</font></div><div><font face="monospace">            modelsFolders=ModelerUtils.modelsFolders()</font></div><div><font face="monospace">            if self.modelsPath in modelsFolders:</font></div><div><font face="monospace">                modelsFolders.remove(self.modelsPath)</font></div><div><font face="monospace">                ProcessingConfig.setSettingValue(ModelerUtils.MODELS_FOLDER, ';'.join(modelsFolders))</font></div></div><div><font face="monospace">            QgsApplication.processingRegistry().providerById('model').refreshAlgorithms()</font><br></div><div><font face="monospace"><br></font></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Fri, 28 Apr 2023 at 15:07, Alister Hood <<a href="mailto:alister.hood@gmail.com" target="_blank">alister.hood@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr">Hi, I maintain an in-house plugin for our team of civil engineers, who apart from myself are not very advanced GIS users.<div>It does things like set useful defaults and install a set of useful plugins.  It also includes some processing models, which are loaded with the code below, which seems to be the conventional way models are loaded in publicly available plugins which ship processing models.  This code copies the model files included in the plugin to the default folder for processing models - somewhere like <font face="monospace">%AppData%\Roaming\QGIS\QGIS3\profiles\default\processing\models</font></div><div><br></div><div>One of my colleagues did some QGIS training and created a processing model, which coincidentally had the same filename as a model I created on the same day, so his model got overwritten when my plugin was updated.  This is not a problem for us as we are a small team and can be careful what we do going forward.  But it highlights that the way plugin authors are loading model files is what seems to me to be pretty poor practice, for two reasons.</div><div><br></div><div>Firstly models are available in qgis even when the plugin is unloaded or uninstalled, which is probably not desirable.</div><div>It is trivial to implement an unloadModels function to rectify this, and I have done so in my in-house plugin.  I'm wondering - should plugin authors be encouraged to do the same?<br></div><div><br></div><div>Secondly, loading the plugin will silently overwrite any existing models with the same filename, as occurred here.  Plugins generally contain models with fairly special filenames, so hopefully this is unlikely to occur, unless:</div><div>- someone produces a plugin that is a fork of another one, or </div><div>- someone ships a plugin with a model that is previously publicly available somewhere else, or that was created by following an online tutorial, or</div><div>- a user edits a model provided by a plugin.</div><div>In any case, would it be a good idea to encourage plugin authors to load models another way, without copying them into <span style="font-family:monospace">%AppData%\Roaming\QGIS\QGIS3\profiles\default\processing\models</span>?  </div><div>I realise that to change an existing plugin there is the added complication of what to do when updating from an old version - the only safeish way I can think of to automatically clean up obsolete model files would be to check for them and then use a checksum to verify whether they should be deleted.</div><div>For what it's worth, I note that when a user tries to manually "add model to toolbox", QGIS refuses if there is an existing model with the same filename.  </div><div><br></div><div>Thanks.</div><div><br></div><div><br></div><div><div><font face="monospace" size="1">    def loadModels(self):</font></div><div><font face="monospace" size="1">            '''Register models present in models folder of the plugin.'''</font></div><div><font face="monospace" size="1">            try:</font></div><div><font face="monospace" size="1">                iface.initializationCompleted.disconnect(self.loadModels)</font></div><div><font face="monospace" size="1">            except:</font></div><div><font face="monospace" size="1">                pass</font></div><div><font face="monospace" size="1"><br></font></div><div><font face="monospace" size="1">            #QgsMessageLog.logMessage(<a href="http://self.tr" target="_blank">self.tr</a>('testing'), self.messageTag, Qgis.Warning)</font></div><div><font face="monospace" size="1">            modelsFiles = glob.glob(os.path.join(self.modelsPath, '*.model3'))</font></div><div><font face="monospace" size="1"><br></font></div><div><font face="monospace" size="1">            for modelFileName in modelsFiles:</font></div><div><font face="monospace" size="1">                alg = QgsProcessingModelAlgorithm()</font></div><div><font face="monospace" size="1">                if not alg.fromFile(modelFileName):</font></div><div><font face="monospace" size="1">                    QgsMessageLog.logMessage(<a href="http://self.tr" target="_blank">self.tr</a>('Not well formed model: {}'.format(modelFileName)), self.messageTag, Qgis.Warning)</font></div><div><font face="monospace" size="1">                    continue</font></div><div><font face="monospace" size="1"><br></font></div><div><font face="monospace" size="1">                destFilename = os.path.join(ModelerUtils.modelsFolders()[0], os.path.basename(modelFileName))</font></div><div><font face="monospace" size="1">                try:</font></div><div><font face="monospace" size="1">                    if os.path.exists(destFilename):</font></div><div><font face="monospace" size="1">                        os.remove(destFilename)</font></div><div><font face="monospace" size="1"><br></font></div><div><font face="monospace" size="1">                    if isWindows():</font></div><div><font face="monospace" size="1">                        copyfile(modelFileName, destFilename)</font></div><div><font face="monospace" size="1">                    else:</font></div><div><font face="monospace" size="1">                        os.symlink(modelFileName, destFilename)</font></div><div><font face="monospace" size="1">                except Exception as ex:</font></div><div><font face="monospace" size="1">                    QgsMessageLog.logMessage(<a href="http://self.tr" target="_blank">self.tr</a>('Failed to install model: {} - {}'.format(modelFileName, str(ex))), self.messageTag, Qgis.Warning)</font></div><div><font face="monospace" size="1">                    continue</font></div><div><font face="monospace" size="1"><br></font></div><div><font face="monospace" size="1">            QgsApplication.processingRegistry().providerById('model').refreshAlgorithms()</font></div></div></div></div></div></div></div></div></div></div>
</blockquote></div></div></div></div></div></div></div></div>
</blockquote></div>