1

Using QGIS 3.4 I am currently creating new and also cloning vector layers with postgres datasource in an existing project using PyQGIS. This works OK but I need to publish these layers and enable update, insert and delete. In other words, do the equivalent of going to Project Properties - QGIS Server, and then check checkboxes for "Published", "Update", "Insert" and "Delete" (in QGIS client software).

Is this possible using PyQGIS?

Kadir Şahbaz
  • 76,800
  • 56
  • 247
  • 389

1 Answers1

2

It's possible to set the configuration from PyQGIS although it has been a bit annoying to look at the C code to find the keys to write in the QGIS project file (https://github.com/qgis/QGIS/blob/9a0a1297c2e585cdbc3dbeeb64f5792024a451f9/src/app/qgsprojectproperties.cpp#L1479)

You can find below a recipe. After running it, you will be able to see if it works fine by opening the "Project properties | QGIS Server" panel

Limit of the recipe: we didn't try to catch error when the layer name in the config does not match the layers in the QGIS project

vectorLayers = {layer.id(): layer.name() for layer in QgsProject.instance().mapLayers().values() if isinstance(layer, QgsVectorLayer)}

wfsLayersConfig = [
  {
    "name": "your layer name 1",
    "published": True,
    "precision": 8,
    "Update": True,
    "Insert": False,
    "Delete": True
   },
   {
    "name": "your layer name 2",
    "published": False,
    "precision": 8,
    "Update": False,
    "Insert": True,
    "Delete": False
   }
]

# To join by name as a key instead of identifier
# Weak but to be generic, more simple to use layer name
# whereas layers identifiers hidden
vectorLayersKeyValReversed = {v: k for k, v in vectorLayers.items()}
# To set if published
QgsProject.instance().writeEntry( "WFSLayers" , "/", [vectorLayersKeyValReversed[l['name']] for l in wfsLayersConfig if l["published"]]);
# Set precision (need to loop as the xml tag is the layer identifier)
[QgsProject.instance().writeEntry("WFSLayersPrecision", "/" + vectorLayersKeyValReversed[l['name']], l["precision"]) for l in wfsLayersConfig]
# Set Update
QgsProject.instance().writeEntry( "WFSTLayers" , "Update", [vectorLayersKeyValReversed[l['name']] for l in wfsLayersConfig if l["Update"]]);
# Set Insert
QgsProject.instance().writeEntry( "WFSTLayers" , "Insert", [vectorLayersKeyValReversed[l['name']] for l in wfsLayersConfig if l["Insert"]]);
# Set Delete
QgsProject.instance().writeEntry( "WFSTLayers" , "Delete", [vectorLayersKeyValReversed[l['name']] for l in wfsLayersConfig if l["Delete"]]);

QgsProject.instance().write()
ThomasG77
  • 30,725
  • 1
  • 53
  • 93
  • How fantastic to get such a good response! Your example sets the layer as published. Unfortunately not all is OK. I am loading projects using QGIS Web Client, and I receive this error on load: "Error code: 200. Map CRS not published in QGIS Project properties OWS Server!" When I then open the project in QGIS client software again, and just saves it (without any changes), things are working. So it looks like there is something more that must be done via PyQGIS to get this working. Do you have any suggestions? – Stefhan Jonas May 27 '20 at 07:57
  • Process = keep original, run the PyQGIS script on the copy of the original, do a backup of this new version. Go through "When I then open the project in QGIS client software again, and just saves it (without any changes), things are working.", make a backup. Then, compare the two backup files XML if qgs (if extension = qgz, uncompress the .qgz files to qgs https://gis.stackexchange.com/questions/331691/open-qgz-files-to-edit-file-paths) using a software for diff (I use Meld personally) – ThomasG77 May 27 '20 at 10:05
  • The issue can be in my script or in the publication process required by QGIS Web Client (what version is it, 1 or 2 ?) As I'm not sure, the best is to look at the files difference as it translates the difference in QGIS configuration to see what is missing here. – ThomasG77 May 27 '20 at 10:10
  • You may add QgsProject.instance().writeEntry( "WMSServiceCapabilities" , "/", True); to tick the "Service Capabilities" as it seems the QGIS Web Client is reading info from the capabilities – ThomasG77 May 27 '20 at 11:16
  • The script is overwriting some sections in .qgs file. A missing 'mapcanvas' section is responsible for the error, and adding it back make things work. But also a section for 'legend' should not be removed. Any idea how to keep script from removing these sections? Missing sections here: https://newtextdocument.com/85c35f9857. . – Stefhan Jonas May 28 '20 at 10:21
  • To be sure, when you say, "The script is overwriting some sections in .qgs file", do you speak about my script without any other manipulation or including also some code you do not publish here? It's because except QgsProject.instance().write() that could have side effects to preserve QGIS file overall integrity, I do no see how a section unrelated to my modifications e.g mapcanvas could be removed. – ThomasG77 May 28 '20 at 12:13
  • I've tested my script before and after only running my code from the answer and the mapcanvas is never dropped. Seems unrelated to my code but could be wrong as I do not have your QGIS project context and maybe biased when testing (I know when it could fails) – ThomasG77 May 28 '20 at 12:37
  • Yes, it looks like QgsProject.instance().write() which I must do to update the file overwrites the sections with this script. Adding layers etc. and doing write is OK. I am doing this on a Windows computer before script code: QgsApplication.setPrefixPath("C:\OSGEO4~1\apps\qgis-ltr", True) qgs = QgsApplication([], False) qgs.initQgis() project = QgsProject.instance() project.read('P:/30093_434_5e621c1a311b8.qgs') layer_name = "Test" Then I do the code above and at the end write to file. Do you have any clue whats going on? Or maybe I need to extract missing sections and insert again. – Stefhan Jonas May 29 '20 at 06:22
  • No clue: I'm unable to reproduce the issue you got (PS: on Linux). For "Or maybe I need to extract missing sections and insert again", not recommended as it's a way to bypass but does not solve the real issue. If you have no choice to not be stuck, maybe... – ThomasG77 May 31 '20 at 01:35
  • Will try on Linux as well. One last question: This script will set the defined layers as published, but existing WFS layers in project will be unpublished. How can I add new layers to existing WFS layers? I was thinking getting existing WFS layers, add to this list, and write. I am trying to get existing with QgsProject.instance().readEntry( "WFSLayers" , "/"), but I am only getting output ('', False) when I have multiple existing layers published. But when I have only one layer published, it returns this layer. I am extremely new to this, so probably a logical explanation. – Stefhan Jonas Jun 08 '20 at 12:05
  • Your logic is right. What you need to retrieve is a list here, try QgsProject.instance().readListEntry( "WFSLayers", "/") instead of the line you mention to read all the entries. A particular case is for precision: use [QgsProject.instance().readEntry("WFSLayersPrecision", "/" + k) for k in vectorLayers.keys()] – ThomasG77 Jun 08 '20 at 14:08
  • Yes, this works. First one lists the layer names, and last one lists setting for precision. I think I am almost there. What will be the commands for getting existing layers and if "Insert", "Update", and "Delete" is true/false? As I do with precision. – Stefhan Jonas Jun 12 '20 at 12:58
  • QgsProject.instance().readListEntry( "WFSTLayers" , "Insert"), QgsProject.instance().readListEntry( "WFSTLayers" , "Delete") and QgsProject.instance().readListEntry( "WFSTLayers" , "Update") – ThomasG77 Jun 12 '20 at 13:05
  • With all your good help and code, I now have working script which does everything it needs to. Fantastic. The only problem remaining is this missing mapcanvas section. I see that when running this code in QGIS client, it works. But as standalone on both Windows and Ubuntu the script removes the mapcanvas section. Do you have any last suggestions? Maybe how to add this section back as workaround? I am beginning Linux script with: from qgis.core import * QgsApplication.setPrefixPath("/usr", True) qgs = QgsApplication([], False) qgs.initQgis() – Stefhan Jonas Jun 16 '20 at 07:38
  • Try to change qgs = QgsApplication([], False) with qgs = QgsApplication([], True) to see if calling with GUI enabled changes the result (the map canvas going away...) – ThomasG77 Jun 16 '20 at 09:08
  • Very good suggestion. I had high hopes but unfortunately it didn't work. Regarding possible workaround, I found this: https://www.qgis.org/api/classQgsMapCanvas.html#aefc66e2d91d588319f4fa7e6d63cbddc. Can something like this be done, or am I totally off track? Read canvas settings, write project, then write canvas settings. Not sure how to use this. file = open('P:/30093_434_5e621c1a311b8.qgs') xml = file.read() document = QDomDocument(xml) cs = QgsMapCanvas().readProject(document) QgsProject.instance().write() Then somehow use QgsMapCanvas().writeProject() to add again. – Stefhan Jonas Jun 16 '20 at 11:56
  • I am also just getting <QgsRectangle: 0 0, 0 0> when running canvas.extent(), also directly from QGIS client software. But extent is set in qgs file. canvas.readProject(document) in last comment yields None, but I am probably doing something wrong. Really don't want to use last resort - manually insert missing section in xml file. :) – Stefhan Jonas Jun 16 '20 at 12:07
  • I have solved this for now by doing after write:
    QgsMapCanvas().setDestinationCrs(QgsCoordinateReferenceSystem("EPSG:3857"))
    This is the thing missing which causes the problem. I am working on finding out how to also get the map extent and set this. Cannot use iface class in standalone script so don't know yet. I have separate question for this on forum. Thank you for you incredible help.
    – Stefhan Jonas Jun 17 '20 at 10:26
  • Not sure but it seems to me you don't reuse QgsMapCanvas instance. At the beginning, you set canvas = QgsMapCanvas() and then reuse object canvas everywhere e.g canvas.setDestinationCrs(QgsCoordinateReferenceSystem("EPSG:3857")). If you do everywhere QgsMapCanvas().someMethod you create as many canvas as you create calls. It will clearly lead to many issues. – ThomasG77 Jun 17 '20 at 10:48