diff --git a/.github/workflows/on-pull-request.yml b/.github/workflows/on-pull-request.yml new file mode 100644 index 0000000..da972f0 --- /dev/null +++ b/.github/workflows/on-pull-request.yml @@ -0,0 +1,17 @@ +name: flake8 Lint + +on: [push, pull_request] + +jobs: + flake8-lint: + runs-on: ubuntu-latest + name: Lint + steps: + - name: Check out source repository + uses: actions/checkout@v5 + - name: Set up Python environment + uses: actions/setup-python@v6 + with: + python-version: "3.13" + - name: flake8 Lint + uses: py-actions/flake8@v2 diff --git a/.gitignore b/.gitignore index 894a44c..a05ebcc 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,7 @@ venv.bak/ # mypy .mypy_cache/ + +# Intellij IDEA +.idea/ +*.iml diff --git a/connection/networkaccessmanager.py b/connection/networkaccessmanager.py index d28fa87..d87a55b 100644 --- a/connection/networkaccessmanager.py +++ b/connection/networkaccessmanager.py @@ -22,35 +22,38 @@ """ -__author__ = 'Alessandro Pasotti' -__date__ = 'August 2016' - import sys - -from qgis.PyQt.QtCore import QUrl -from qgis.PyQt.QtCore import pyqtSlot, QEventLoop -from qgis.PyQt.QtNetwork import QNetworkRequest, QNetworkReply import urllib -from qgis.core import QgsNetworkAccessManager, QgsAuthManager, QgsMessageLog +from qgis.core import QgsAuthManager, QgsMessageLog, QgsNetworkAccessManager +from qgis.PyQt.QtCore import QEventLoop, QUrl +from qgis.PyQt.QtNetwork import QNetworkReply, QNetworkRequest + +__author__ = "Alessandro Pasotti" +__date__ = "August 2016" # FIXME: ignored DEFAULT_MAX_REDIRECTS = 4 + class RequestsException(Exception): pass + class RequestsExceptionTimeout(RequestsException): pass + class RequestsExceptionConnectionError(RequestsException): pass + class Map(dict): """ Example: m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer']) """ + def __init__(self, *args, **kwargs): super(Map, self).__init__(*args, **kwargs) for arg in args: @@ -83,9 +86,11 @@ def __delitem__(self, key): class Response(Map): pass + PYTHON_VERSION = sys.version_info[0] -class NetworkAccessManager(): + +class NetworkAccessManager: """ This class mimicks httplib2 by using QgsNetworkAccessManager for all network calls. @@ -114,8 +119,13 @@ class NetworkAccessManager(): """ - - def __init__(self, authid=None, disable_ssl_certificate_validation=False, exception_class=None, debug=True): + def __init__( + self, + authid=None, + disable_ssl_certificate_validation=False, + exception_class=None, + debug=True, + ): self.disable_ssl_certificate_validation = disable_ssl_certificate_validation self.authid = authid self.reply = None @@ -134,40 +144,55 @@ def msg_log(self, msg): if self.debug: QgsMessageLog.logMessage(msg, "NetworkAccessManager") - def request(self, url, method="GET", body=None, headers=None, redirections=DEFAULT_MAX_REDIRECTS, connection_type=None, authenticate = True): + def request( + self, + url, + method="GET", + body=None, + headers=None, + redirections=DEFAULT_MAX_REDIRECTS, + connection_type=None, + authenticate=True, + ): """ Make a network request by calling QgsNetworkAccessManager. redirections argument is ignored and is here only for httplib2 compatibility. """ - self.msg_log(u'http_call request: {0}'.format(url)) - self.http_call_result = Response({ - 'status': 0, - 'status_code': 0, - 'status_message': '', - 'text' : '', - 'ok': False, - 'headers': {}, - 'reason': '', - 'exception': None, - }) + self.msg_log("http_call request: {0}".format(url)) + self.http_call_result = Response( + { + "status": 0, + "status_code": 0, + "status_message": "", + "text": "", + "ok": False, + "headers": {}, + "reason": "", + "exception": None, + } + ) req = QNetworkRequest() - req.setAttribute(QNetworkRequest.CookieSaveControlAttribute, QNetworkRequest.Manual) - req.setAttribute(QNetworkRequest.CookieLoadControlAttribute, QNetworkRequest.Manual) + req.setAttribute( + QNetworkRequest.CookieSaveControlAttribute, QNetworkRequest.Manual + ) + req.setAttribute( + QNetworkRequest.CookieLoadControlAttribute, QNetworkRequest.Manual + ) # Avoid double quoting form QUrl url = urllib.parse.unquote(url) req.setUrl(QUrl(url)) if self.cookie is not None: if headers is not None: - headers['Cookie'] = self.cookie + headers["Cookie"] = self.cookie else: - headers = {'Cookie':self.cookie} + headers = {"Cookie": self.cookie} if self.basicauth is not None and authenticate: if headers is not None: - headers['Authorization'] = self.basicauth + headers["Authorization"] = self.basicauth else: - headers = {'Authorization':self.basicauth} + headers = {"Authorization": self.basicauth} if headers is not None: # This fixes a wierd error with compressed content not being correctly @@ -177,15 +202,15 @@ def request(self, url, method="GET", body=None, headers=None, redirections=DEFAU # encoding processing". # See: https://bugs.webkit.org/show_bug.cgi?id=63696#c1 try: - del headers['Accept-Encoding'] + del headers["Accept-Encoding"] except KeyError: pass for k, v in headers.items(): if PYTHON_VERSION >= 3: if isinstance(k, str): - k = k.encode('utf-8') + k = k.encode("utf-8") if isinstance(v, str): - v = v.encode('utf-8') + v = v.encode("utf-8") req.setRawHeader(k, v) if self.authid: @@ -193,20 +218,22 @@ def request(self, url, method="GET", body=None, headers=None, redirections=DEFAU QgsAuthManager.instance().updateNetworkRequest(req, self.authid) if self.reply is not None and self.reply.isRunning(): self.reply.close() - if method.lower() == 'delete': - func = getattr(QgsNetworkAccessManager.instance(), 'deleteResource') + if method.lower() == "delete": + func = getattr(QgsNetworkAccessManager.instance(), "deleteResource") else: func = getattr(QgsNetworkAccessManager.instance(), method.lower()) # Calling the server ... # Let's log the whole call for debugging purposes: - self.msg_log("Sending %s request to %s" % (method.upper(), req.url().toString())) + self.msg_log( + "Sending %s request to %s" % (method.upper(), req.url().toString()) + ) headers = {str(h): str(req.rawHeader(h)) for h in req.rawHeaderList()} for k, v in headers.items(): self.msg_log("%s: %s" % (k, v)) - if method.lower() in ['post', 'put']: + if method.lower() in ["post", "put"]: if PYTHON_VERSION >= 3: if isinstance(body, str): - body = body.encode('utf-8') + body = body.encode("utf-8") self.reply = func(req, body) else: self.reply = func(req) @@ -226,11 +253,17 @@ def request(self, url, method="GET", body=None, headers=None, redirections=DEFAU try: self.el.exec_() # Let's log the whole response for debugging purposes: - self.msg_log("Got response %s %s from %s" % \ - (self.http_call_result.status_code, - self.http_call_result.status_message, - self.reply.url().toString())) - headers = {str(h): str(self.reply.rawHeader(h)) for h in self.reply.rawHeaderList()} + self.msg_log( + "Got response %s %s from %s" + % ( + self.http_call_result.status_code, + self.http_call_result.status_message, + self.reply.url().toString(), + ) + ) + headers = { + str(h): str(self.reply.rawHeader(h)) for h in self.reply.rawHeaderList() + } for k, v in headers.items(): self.msg_log("%s: %s" % (k, v)) if len(self.http_call_result.text) < 1024: @@ -255,17 +288,19 @@ def request(self, url, method="GET", body=None, headers=None, redirections=DEFAU raise self.exception_class(self.http_call_result.reason) return (self.http_call_result, self.http_call_result.text) - #@pyqtSlot() + # @pyqtSlot() def downloadProgress(self, bytesReceived, bytesTotal): """Keep track of the download progress""" - #self.msg_log("downloadProgress %s of %s ..." % (bytesReceived, bytesTotal)) + # self.msg_log("downloadProgress %s of %s ..." % (bytesReceived, bytesTotal)) pass - #@pyqtSlot() + # @pyqtSlot() def replyFinished(self): err = self.reply.error() httpStatus = self.reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) - httpStatusMessage = self.reply.attribute(QNetworkRequest.HttpReasonPhraseAttribute) + httpStatusMessage = self.reply.attribute( + QNetworkRequest.HttpReasonPhraseAttribute + ) self.http_call_result.status_code = httpStatus self.http_call_result.status = httpStatus self.http_call_result.status_message = httpStatusMessage @@ -274,7 +309,8 @@ def replyFinished(self): self.http_call_result.headers[str(k).lower()] = str(v) if err != QNetworkReply.NoError: msg = "Network error #{0}: {1}".format( - self.reply.error(), self.reply.errorString()) + self.reply.error(), self.reply.errorString() + ) self.http_call_result.reason = msg self.http_call_result.ok = False self.msg_log(msg) @@ -288,13 +324,13 @@ def replyFinished(self): # since Python 3 readAll() returns a PyQt5.QByteArray, we # want only the data if PYTHON_VERSION >= 3: - self.http_call_result.text = self.reply.readAll().data().decode('utf-8') + self.http_call_result.text = self.reply.readAll().data().decode("utf-8") else: self.http_call_result.text = str(self.reply.readAll()) self.http_call_result.ok = True self.reply.deleteLater() - #@pyqtSlot() + # @pyqtSlot() def sslErrors(self, reply, ssl_errors): """ Handle SSL errors, logging them if debug is on and ignoring them diff --git a/connection/shogunressource.py b/connection/shogunressource.py index 2bec9ff..3102254 100644 --- a/connection/shogunressource.py +++ b/connection/shogunressource.py @@ -1,167 +1,166 @@ # -*- coding: utf-8 -*- -''' +""" (c) 2025 terrestris GmbH & Co. KG, https://www.terrestris.de/en/ This code is licensed under the GPL 2.0 license. -''' +""" -__author__ = 'ntreff' -__date__ = 'July 2025' -import sys -from base64 import b64encode -import urllib import json import os -from urllib.request import urlretrieve +import sys import webbrowser +from base64 import b64encode +from qgis.core import QgsApplication +from qgis.PyQt.QtCore import QFile, QIODevice, QSize from qgis.PyQt.QtGui import QIcon +from qgis.PyQt.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest from qgis.PyQt.QtXml import QDomDocument -from qgis.PyQt.QtNetwork import QNetworkRequest, QHttpPart, QHttpMultiPart -from qgis.PyQt.QtCore import QFile, QIODevice, QSize -from qgis.core import QgsApplication -from qgis.gui import QgsMessageBar - -from .networkaccessmanager import NetworkAccessManager, RequestsExceptionConnectionError, RequestsException from ..layerutils import createAndParseSld - +from .networkaccessmanager import ( + NetworkAccessManager, + RequestsException, + RequestsExceptionConnectionError, +) PYTHON_VERSION = sys.version_info[0] +__author__ = "ntreff" +__date__ = "July 2025" + + class ShogunRessource: - ''' This class controls all interactions between QGIS and the Shogun ressource, + """This class controls all interactions between QGIS and the Shogun ressource, apart from creating wfs/wms layers (see layerutils for this). It makes use of the class NetworkAccessManager, which calls QgsNetworkAccessManager for all http - requests''' + requests""" - def __init__(self, iface, url, name, user = None, pw = None): + def __init__(self, iface, url, name, user=None, pw=None): self.iface = iface - if url.endswith('webapp'): - url+='/' - elif url.endswith('rest/'): + if url.endswith("webapp"): + url += "/" + elif url.endswith("rest/"): url = url[:-5] - elif url.endswith('rest'): + elif url.endswith("rest"): url = url[:-4] - self.baseurl = url #url should have the format: 'https:.../shogun2-webapp/' + self.baseurl = url # url should have the format: 'https:.../shogun2-webapp/' self.name = name self.applications = [] self.layers = [] self.mapconfigs = [] self.extents = [] - self.http = NetworkAccessManager(debug = False) + self.http = NetworkAccessManager(debug=False) - self.icondir = os.path.join(os.path.dirname(__file__), '..', 'images', 'custom-symbols') + self.icondir = os.path.join( + os.path.dirname(__file__), "..", "images", "custom-symbols" + ) if not os.path.isdir(self.icondir): os.makedirs(self.icondir) - if user is not None and pw is not None: if PYTHON_VERSION >= 3: - bytes = (user + ':' + pw).encode('utf-8') - self.basicauth = b64encode(bytes) - self.http.setBasicauth('Basic '.encode('utf-8') + self.basicauth) + read_bytes = (user + ":" + pw).encode("utf-8") + self.basicauth = b64encode(read_bytes) + self.http.setBasicauth("Basic ".encode("utf-8") + self.basicauth) else: - self.basicauth = b64encode(user + ':' + pw) - self.http.setBasicauth('Basic ' + self.basicauth) - + self.basicauth = b64encode(user + ":" + pw) + self.http.setBasicauth("Basic " + self.basicauth) def checkConnection(self): - # returning a tuple of ('true/false', 'message') + # returning a tuple of ('true/false', 'message') - # # TODO: here we check only the cases of http/https faults in the user's - # input. Maybe there should be more error checks for in case the user has - # written an url which is completely different (no http/ https at beginning) + # # TODO: here we check only the cases of http/https faults in the user's + # input. Maybe there should be more error checks for in case the user has + # written an url which is completely different (no http/ https at beginning) - testurl = self.baseurl + 'rest/applications' + testurl = self.baseurl + "rest/applications" try: - testresponse = self.http.request(testurl, method = 'HEAD') - if testresponse[0]['status'] > 199 and testresponse[0]['status'] < 210: - return (True, '') + testresponse = self.http.request(testurl, method="HEAD") + if testresponse[0]["status"] > 199 and testresponse[0]["status"] < 210: + return (True, "") except RequestsException as e: - if self.baseurl.startswith('https'): - # if the first try with 'https' was not successfull try http: - testurl = 'http' + testurl[5:] + print('Error during connection test: ' + str(e)) + if self.baseurl.startswith("https"): + # if the first try with 'https' was not successful try http: + testurl = "http" + testurl[5:] try: - testresponse = self.http.request(testurl, method = 'HEAD') - if testresponse[0]['status'] > 199 and testresponse[0]['status'] < 210: - self.baseurl = 'http' + self.baseurl[5:] - return (True, '') + testresponse = self.http.request(testurl, method="HEAD") + if testresponse[0]["status"] > 199 and testresponse[0]["status"] < 210: + self.baseurl = "http" + self.baseurl[5:] + return (True, "") except RequestsException as e: + print('Could not fetch fetch applications ' + str(e)) pass - return (False, 'Error : Failed to connect to the server') - - + return (False, "Error : Failed to connect to the server") def userInfo(self, status, objectName, method): - #method = 'created'/'updated'/'deleted' + # method = 'created'/'updated'/'deleted' if status > 199 and status < 300: - msg = objectName + ' was successfully ' + method - self.iface.messageBar().pushSuccess('Shogun Editor Info:', msg) + msg = objectName + " was successfully " + method + self.iface.messageBar().pushSuccess("Shogun Editor Info:", msg) else: - msg = 'Error: ' + objectName + ' could not be ' + method - self.iface.messageBar().pushCritical('Shogun Editor Info:', msg) - + msg = "Error: " + objectName + " could not be " + method + self.iface.messageBar().pushCritical("Shogun Editor Info:", msg) - def editApplication(self, id, data): + def editApplication(self, _id, data): data = json.dumps(data) - url = self.baseurl + 'rest/applications/' +str(id) - header = {'Content-type':'application/json'} - response = self.http.request(url, method='PUT', body = data, headers = header) - self.userInfo(response[0]['status'], 'Application', 'edited') - return response[0]['status'] - + url = self.baseurl + "rest/applications/" + str(_id) + header = {"Content-type": "application/json"} + response = self.http.request(url, method="PUT", body=data, headers=header) + self.userInfo(response[0]["status"], "Application", "edited") + return response[0]["status"] - def editLayer(self, id, data): + def editLayer(self, _id, data): data = json.dumps(data) - url = self.baseurl + 'rest/layers/' +str(id) - header = {'Content-type':'application/json'} - response = self.http.request(url, method='PUT', body = data, headers = header) - self.userInfo(response[0]['status'], 'Layer', 'edited') - return response[0]['status'] - + url = self.baseurl + "rest/layers/" + str(_id) + header = {"Content-type": "application/json"} + response = self.http.request(url, method="PUT", body=data, headers=header) + self.userInfo(response[0]["status"], "Layer", "edited") + return response[0]["status"] def uploadNewApplication(self, data): - #data as a json-like dict - url = self.baseurl + 'projectapps/create.action' - header = {'Content-type' : 'application/json'} + # data as a json-like dict + url = self.baseurl + "projectapps/create.action" + header = {"Content-type": "application/json"} body = json.dumps(data) - response = self.http.request(url, method='POST', body = body, headers = header) - name = 'Application ' + data['name'] - self.userInfo(response[0]['status'], name, 'created') - if response[0]['status'] == 200 or response[0]['status'] == 201: + response = self.http.request(url, method="POST", body=body, headers=header) + name = "Application " + data["name"] + self.userInfo(response[0]["status"], name, "created") + if response[0]["status"] == 200 or response[0]["status"] == 201: return True else: return False - - def editMapConfig(self, id, data): - edit = None - for mapconfig in self.mapconfigs: - if mapconfig['id'] == id: - edit = mapconfig - - url = self.baseurl+'mapconfigs/'+str(id) - body = '{"id":'+str(id)+',"center":{"x":'+str(data['center']['x'])+',"y":' - body += str(data['center']['y'])+'},"zoom":'+str(data['zoom'])+'}' - h = {'Content-type':'application/json'} - response = self.http.request(url, method='PUT', body = body, headers = h) - self.userInfo(response[0]['status'], 'Homeview', 'updated') - - def editObjectPermission(self, id, objectType, permissionType, data): - url = self.baseurl + 'rest/entitypermission/' + objectType + '/' + str(id) - if permissionType == 'User': - url += '/ProjectUser' - elif permissionType == 'UserGroup': - url += '/ProjectUserGroup' + def editMapConfig(self, _id, data): + # edit = None + # for mapconfig in self.mapconfigs: + # if mapconfig["id"] == _id: + # edit = mapconfig + + url = self.baseurl + "mapconfigs/" + str(_id) + body = ( + '{"id":' + str(_id) + ',"center":{"x":' + str(data["center"]["x"]) + ',"y":' + ) + body += str(data["center"]["y"]) + '},"zoom":' + str(data["zoom"]) + "}" + h = {"Content-type": "application/json"} + response = self.http.request(url, method="PUT", body=body, headers=h) + self.userInfo(response[0]["status"], "Homeview", "updated") + + def editObjectPermission(self, _id, objectType, permissionType, data): + url = self.baseurl + "rest/entitypermission/" + objectType + "/" + str(_id) + if permissionType == "User": + url += "/ProjectUser" + elif permissionType == "UserGroup": + url += "/ProjectUserGroup" else: return - header = {'Content-type' : 'application/json'} + header = {"Content-type": "application/json"} body = json.dumps(data) - response = self.http.request(url, method='POST', body = body, headers = header) - if response[0]['status'] == 200 or response[0]['status'] == 201: + response = self.http.request(url, method="POST", body=body, headers=header) + if response[0]["status"] == 200 or response[0]["status"] == 201: return True else: return False @@ -173,103 +172,102 @@ def updateData(self): self.updateExtentsAndMapConfigs() return True except RequestsExceptionConnectionError: - self.iface.messageBar().pushCritical('Connection Error:', - 'Could not connect to given SHOGUN host application - Please review url') + self.iface.messageBar().pushCritical( + "Connection Error:", + "Could not connect to given SHOGUN host application - Please review url", + ) return False def updateApplications(self): - url = self.baseurl + 'rest/applications' + url = self.baseurl + "rest/applications" response = self.http.request(url) self.applications = json.loads(response[1]) def updateLayers(self): - url = self.baseurl + 'rest/layers' + url = self.baseurl + "rest/layers" response = self.http.request(url) self.layers = json.loads(response[1]) - def updateSingleApplication(self, id): - url = self.baseurl + 'rest/applications/' + str(id) + def updateSingleApplication(self, _id): + url = self.baseurl + "rest/applications/" + str(_id) response = self.http.request(url) updatedApplication = json.loads(response[1]) for app in enumerate(self.applications): - if app[1]['id'] == id: + if app[1]["id"] == _id: self.layers[app[0]] = updatedApplication return updatedApplication - def updateSingleLayer(self, id): - url = self.baseurl + 'rest/layers/' + str(id) + def updateSingleLayer(self, _id): + url = self.baseurl + "rest/layers/" + str(_id) response = self.http.request(url) updatedLayer = json.loads(response[1]) for layer in enumerate(self.layers): - if layer[1]['id'] == id: + if layer[1]["id"] == _id: self.layers[layer[0]] = updatedLayer return updatedLayer - #one method for retrieving user and groups permissions (permissionType) - #for layers or applications (objectType) - def getObjectPermissions(self, id, objectType, permissionType): - url = self.baseurl + 'rest/entitypermission/Project' + objectType + # one method for retrieving user and groups permissions (permissionType) + # for layers or applications (objectType) + def getObjectPermissions(self, _id, objectType, permissionType): + url = self.baseurl + "rest/entitypermission/Project" + objectType # objectType = 'Application' or 'Layer' # permissionType = 'User' or 'UserGroup' - url += '/'+ str(id) + '/Project' + permissionType + '?' + url += "/" + str(_id) + "/Project" + permissionType + "?" response = self.http.request(url) return json.loads(response[1]) - def updateExtentsAndMapConfigs(self): - url = self.baseurl + 'rest/extents' + url = self.baseurl + "rest/extents" response = self.http.request(url) self.extents = json.loads(response[1]) - url = self.baseurl + 'rest/mapconfigs' + url = self.baseurl + "rest/mapconfigs" response = self.http.request(url) self.mapconfigs = json.loads(response[1]) - def getHomeviewByIds(self, mapconfigid, extentid): + def getHomeviewByIds(self, mapconfigid, extent_id): homeview = {} for mapcf in self.mapconfigs: - if mapcf['id'] == mapconfigid: - homeview['mapconfig']=mapcf + if mapcf["id"] == mapconfigid: + homeview["mapconfig"] = mapcf for ext in self.extents: - if ext['id'] == extentid: - homeview['extent']=ext + if ext["id"] == extent_id: + homeview["extent"] = ext return homeview - def getApplicationIdsAndNames(self, reload = False): + def getApplicationIdsAndNames(self, reload=False): if reload: self.updateApplications() - return [(x['id'], x['name']) for x in self.applications] + return [(x["id"], x["name"]) for x in self.applications] - def getLayerIdsAndNames(self, reload = False): + def getLayerIdsAndNames(self, reload=False): if reload: self.updateLayers() - return [(x['id'], x['name'], x['dataType'], x['source']) for x in self.layers] + return [(x["id"], x["name"], x["dataType"], x["source"]) for x in self.layers] - def getApplicationAttrsById(self, id): - for x in self.applications: - if x['id'] == id: - return x + def getApplicationAttrsById(self, _id): + for x in self.applications: + if x["id"] == _id: + return x - def getLayerAttrsById(self, id): + def getLayerAttrsById(self, _id): for x in self.layers: - if x['id'] == id: + if x["id"] == _id: return x def getGroupNames(self): - return [x['name'] for x in self.groups] + return [x["name"] for x in self.groups] def getUserNames(self): - return [(x['lastName']+', '+x['firstName']) for x in self.users] - + return [(x["lastName"] + ", " + x["firstName"]) for x in self.users] def downloadStyle(self, qgisLayerItem): - #this downloads the layer's style, saves it as an sld and returns two - #things: 1. the path to the .sld, 2. the specific name of the style - #as it is saved in the background geoserver from shogun, obtained from - #the xml-node sld:UserStyle - sld:Name - #the specific name is later needed for re-uploading the edited style + # this downloads the layer's style, saves it as an sld and returns two + # things: 1. the path to the .sld, 2. the specific name of the style + # as it is saved in the background geoserver from shogun, obtained from + # the xml-node sld:UserStyle - sld:Name + # the specific name is later needed for re-uploading the edited style - - ## TODO: + # TODO: # maybe a better implementation would be to pass the QDomDocument directly # to the layer and set it's style from sld... @@ -278,19 +276,19 @@ def downloadStyle(self, qgisLayerItem): # can someone fix it?# shogunlayer = qgisLayerItem.parentShogunLayer - url = shogunlayer.source['url'] - if url.startswith('/shogun2-webapp'): - url = self.baseurl.rstrip('/shogun2-webapp/rest/') + url - url += '?service=WMS&request=GetStyles&version=1.1.1&layers=' - url += shogunlayer.source['layerNames'] - response = self.http.request(url, authenticate = False) + url = shogunlayer.source["url"] + if url.startswith("/shogun2-webapp"): + url = self.baseurl.rstrip("/shogun2-webapp/rest/") + url + url += "?service=WMS&request=GetStyles&version=1.1.1&layers=" + url += shogunlayer.source["layerNames"] + response = self.http.request(url, authenticate=False) mydoc = QDomDocument() mydoc.setContent(response[1]) - root = mydoc.firstChildElement('sld:StyledLayerDescriptor') - namedLayerNode = root.firstChildElement('sld:NamedLayer') - userStyleNode = namedLayerNode.firstChildElement('sld:UserStyle') - sldNameNode = userStyleNode.firstChildElement('sld:Name') + root = mydoc.firstChildElement("sld:StyledLayerDescriptor") + namedLayerNode = root.firstChildElement("sld:NamedLayer") + userStyleNode = namedLayerNode.firstChildElement("sld:UserStyle") + sldNameNode = userStyleNode.firstChildElement("sld:Name") geoServerStyleName = sldNameNode.text() # # TODO: implement more than point style @@ -300,14 +298,16 @@ def downloadStyle(self, qgisLayerItem): # exchange of icons # problem is that shogun2 only serves png icons, and qgis needs # svg to turn them into a style - if '' in response[1]: - self.iface.messageBar().pushInfo('Info', - 'The downloaded style for the current layer contains custom icons ' - 'from SHOGUN, which only serves them as PNG pictures, but QGIS ' - 'can only read SVG. Until this is fixed, you see a default QGIS ' - 'style for the layer') - - ''' + if "" in response[1]: + self.iface.messageBar().pushInfo( + "Info", + "The downloaded style for the current layer contains custom icons " + "from SHOGUN, which only serves them as PNG pictures, but QGIS " + "can only read SVG. Until this is fixed, you see a default QGIS " + "style for the layer", + ) + + """ featureTypeStyleNode = userStyleNode.firstChildElement('sld:FeatureTypeStyle') rules = featureTypeStyleNode.elementsByTagName('sld:Rule') for x in range(rules.length()): @@ -319,23 +319,23 @@ def downloadStyle(self, qgisLayerItem): url = attributes.namedItem('xlink:href').nodeValue() id = url.split('getThumbnail.action?id=')[1] iconPath = self.downloadIconThumbnail(id) - ''' + """ dirpath = os.path.dirname(__file__) - filename = os.path.join(dirpath, 'latest-symbology.sld') - with open(filename, 'w') as file: + filename = os.path.join(dirpath, "latest-symbology.sld") + with open(filename, "w") as file: file.write(response[1]) return filename, geoServerStyleName def uploadStyle(self, qgisLayerItem): - url = self.baseurl.rstrip('rest/') + '/sld/update.action' - h = {'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8'} + url = self.baseurl.rstrip("rest/") + "/sld/update.action" + h = {"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"} sld = createAndParseSld(qgisLayerItem) - b = 'sld=' + sld + '&sldName=' + qgisLayerItem.stylename + '&layerId=' + b = "sld=" + sld + "&sldName=" + qgisLayerItem.stylename + "&layerId=" b += str(qgisLayerItem.parentShogunLayer.id) - response = self.http.request(url, method = 'POST', body = b, headers = h) - if json.loads(response[1])['success']: + response = self.http.request(url, method="POST", body=b, headers=h) + if json.loads(response[1])["success"]: return True else: return False @@ -343,27 +343,26 @@ def uploadStyle(self, qgisLayerItem): def prepareIconForUpload(self, svgIconName): svgPaths = QgsApplication.svgPaths() svgIconPath = None - for dir in svgPaths: - if os.path.isfile(os.path.join(dir, svgIconName)): - svgIconPath = os.path.join(dir, svgIconName) - if svgIconPath is None or not svgIconPath.endswith('svg'): + for _dir in svgPaths: + if os.path.isfile(os.path.join(_dir, svgIconName)): + svgIconPath = os.path.join(_dir, svgIconName) + if svgIconPath is None or not svgIconPath.endswith("svg"): return False name = os.path.splitext(svgIconName)[0] - outputPath = os.path.join(self.icondir, 'qgis-' + name +'.png') - img = QIcon(svgIconPath).pixmap(QSize(100,100)).toImage() + outputPath = os.path.join(self.icondir, "qgis-" + name + ".png") + img = QIcon(svgIconPath).pixmap(QSize(100, 100)).toImage() if img.save(outputPath): newId = self.uploadImage(outputPath) if newId: - iconUrl = self.baseurl + 'projectimage/getThumbnail.action?id=' + iconUrl = self.baseurl + "projectimage/getThumbnail.action?id=" iconUrl += str(newId) return iconUrl - self.userInfo(400, 'Custom symbology icon', 'uploaded') + self.userInfo(400, "Custom symbology icon", "uploaded") return False - # # NOTE: the following method is still not used: - # def downloadIconThumbnail(self, id): - # iconPath = os.path.join(self.icondir, str(icon['id']) + '.png') + # def downloadIconThumbnail(self, _id): + # iconPath = os.path.join(self.icondir, str(icon['_id']) + '.png') # if os.path.isfile(iconPath): # return iconPath # else: @@ -371,58 +370,57 @@ def prepareIconForUpload(self, svgIconName): # urlretrieve(url, iconPath) # return iconPath - def uploadImage(self, pathToImage): img = QFile(pathToImage) img.open(QIODevice.ReadOnly) imgPart = QHttpPart() txt = 'form-data; name="file"; filename="' + os.path.basename(pathToImage) + '"' imgPart.setHeader(QNetworkRequest.ContentDispositionHeader, txt) - imgPart.setHeader(QNetworkRequest.ContentTypeHeader, 'image/zip') + imgPart.setHeader(QNetworkRequest.ContentTypeHeader, "image/zip") imgPart.setBodyDevice(img) multiPart = QHttpMultiPart(QHttpMultiPart.FormDataType) multiPart.append(imgPart) - url = self.baseurl + 'projectimage/upload.action?' + url = self.baseurl + "projectimage/upload.action?" - response = self.http.request(url, method = 'POST', body = multiPart) - if response[0]['status'] > 199 and response[0]['status'] < 210: + response = self.http.request(url, method="POST", body=multiPart) + if response[0]["status"] > 199 and response[0]["status"] < 210: # if icon upload was successfull, server returns id of the new icon # in it's database - id = json.loads(response[1])['data']['id'] - return id + return json.loads(response[1])["data"]["id"] else: return False - - def publishWmsLayer(self, wmsUri): - url = self.baseurl + '/ogcservicehelper/publishlayer.action?' + url = self.baseurl + "/ogcservicehelper/publishlayer.action?" url += wmsUri - #header = {'Cookie': self.cookie} - response = self.http.request(url, method = 'GET') - self.userInfo(response[0]['status'], 'New WMS layer', 'published') - + # header = {'Cookie': self.cookie} + response = self.http.request(url, method="GET") + self.userInfo(response[0]["status"], "New WMS layer", "published") def uploadLayer(self, pathToZipFile, dataType): - url = self.baseurl + '/import/create-layer.action' + url = self.baseurl + "/import/create-layer.action" # the following creates a QHttpMultiPart with 2 parts, one defining the # dataType (i.e. Vector or Raster) and one with the binary file data itself textpart = QHttpPart() - textpart.setHeader(QNetworkRequest.ContentDispositionHeader, 'form-data; name="dataType"') + textpart.setHeader( + QNetworkRequest.ContentDispositionHeader, 'form-data; name="dataType"' + ) if PYTHON_VERSION >= 3: - textpart.setBody(dataType.encode('utf-8')) + textpart.setBody(dataType.encode("utf-8")) else: textpart.setBody(dataType) file = QFile(pathToZipFile) file.open(QIODevice.ReadOnly) - lyr = 'form-data; name="file"; filename="' + os.path.basename(pathToZipFile) + '"' + lyr = ( + 'form-data; name="file"; filename="' + os.path.basename(pathToZipFile) + '"' + ) layerpart = QHttpPart() - layerpart.setHeader(QNetworkRequest.ContentTypeHeader, 'application/zip') + layerpart.setHeader(QNetworkRequest.ContentTypeHeader, "application/zip") layerpart.setHeader(QNetworkRequest.ContentDispositionHeader, lyr) layerpart.setBodyDevice(file) @@ -430,82 +428,82 @@ def uploadLayer(self, pathToZipFile, dataType): multipart.append(textpart) multipart.append(layerpart) - response = self.http.request(url, method = 'POST', body = multipart) + response = self.http.request(url, method="POST", body=multipart) res = json.loads(response[1]) - if res['success']: - self.userInfo(response[0]['status'], 'New Vector Layer', 'uploaded') + if res["success"]: + self.userInfo(response[0]["status"], "New Vector Layer", "uploaded") else: - if res['error'] == 'NO_CRS': - self.requestCrsUpdateOnLayer(res['importJobId']) + if res["error"] == "NO_CRS": + self.requestCrsUpdateOnLayer(res["importJobId"]) else: - self.userInfo(response[0]['status'], 'New Vector Layer', 'uploaded') - return response[0]['status'] + self.userInfo(response[0]["status"], "New Vector Layer", "uploaded") + return response[0]["status"] def requestCrsUpdateOnLayer(self, importJobId): - url = self.baseurl + '/import/update-crs-for-import.action' - data = 'importJobId=' + str(importJobId) + '&taskId=0&fileProjection=EPSG%3A3857&layerName=&dataType=Vector' - h = {'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8'} - response = self.http.request(url, method = 'POST', body = data, headers = h) + url = self.baseurl + "/import/update-crs-for-import.action" + data = ( + "importJobId=" + + str(importJobId) + + "&taskId=0&fileProjection=EPSG%3A3857&layerName=&dataType=Vector" + ) + h = {"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"} + self.http.request(url, method="POST", body=data, headers=h) def getFieldNamesFromWfs(self, layerRessourceName): - url = self.baseurl + 'geoserver-noauth.action?service=WFS&request=DescribeFeatureType&typeName=' - url += layerRessourceName + '&outputFormat=application/json' + url = ( + self.baseurl + + "geoserver-noauth.action?service=WFS&request=DescribeFeatureType&typeName=" + ) + url += layerRessourceName + "&outputFormat=application/json" response = self.http.request(url) - if response[0]['status'] == 200 or response[0]['status'] == 201: + if response[0]["status"] == 200 or response[0]["status"] == 201: fieldNames = [] answer = json.loads(response[1]) - if answer['featureTypes'][0]['properties']: - for prop in answer['featureTypes'][0]['properties']: - fieldNames.append(prop['name']) + if answer["featureTypes"][0]["properties"]: + for prop in answer["featureTypes"][0]["properties"]: + fieldNames.append(prop["name"]) if len(fieldNames) > 0: return fieldNames return False - - def deleteLayer(self, id): - url = self.baseurl + 'rest/projectlayers/' + str(id) - response = self.http.request(url, method = 'DELETE') - self.userInfo(response[0]['status'], 'Layer', 'deleted') - - - def deleteApplication(self, id): - url = self.baseurl + 'rest/projectapps/' + str(id) - response = self.http.request(url, method = 'DELETE') - self.userInfo(response[0]['status'], 'Application', 'deleted') - - - def copyApplication(self, id, applicationName): - url = self.baseurl + 'projectapps/copy.action' - data = 'appId=' + str(id) + '&appName=' + applicationName + '-Copy' - h = {'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8'} - response = self.http.request(url, method = 'POST', body = data, headers = h) - self.userInfo(response[0]['status'], 'Application', 'copied') - - - def viewApplicationOnline(self, id): - url = self.baseurl + 'client/?id=' + str(id) + def deleteLayer(self, _id): + url = self.baseurl + "rest/projectlayers/" + str(_id) + response = self.http.request(url, method="DELETE") + self.userInfo(response[0]["status"], "Layer", "deleted") + + def deleteApplication(self, _id): + url = self.baseurl + "rest/projectapps/" + str(_id) + response = self.http.request(url, method="DELETE") + self.userInfo(response[0]["status"], "Application", "deleted") + + def copyApplication(self, _id, applicationName): + url = self.baseurl + "projectapps/copy.action" + data = "appId=" + str(_id) + "&appName=" + applicationName + "-Copy" + h = {"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"} + response = self.http.request(url, method="POST", body=data, headers=h) + self.userInfo(response[0]["status"], "Application", "copied") + + def viewApplicationOnline(self, _id): + url = self.baseurl + "client/?id=" + str(_id) webbrowser.open(url) - def createLayerTreeItem(self, data): - url = self.baseurl + 'rest/layertree' - h = {'Content-type':'application/json'} + url = self.baseurl + "rest/layertree" + h = {"Content-type": "application/json"} body = json.dumps(data) - response = self.http.request(url, method = 'POST', body = body, headers = h) - return response[0]['status'] - + response = self.http.request(url, method="POST", body=body, headers=h) + return response[0]["status"] def updateLayerTreeItem(self, layerTreeItemId, data): - url = self.baseurl + 'rest/layertree/' + str(layerTreeItemId) - h = {'Content-type':'application/json'} + url = self.baseurl + "rest/layertree/" + str(layerTreeItemId) + h = {"Content-type": "application/json"} body = json.dumps(data) - response = self.http.request(url, method = 'PUT', body = body, headers = h) - return response[0]['status'] - - - def deleteLayerTreeItem(self, layerTreeItemIdd): - url = self.baseurl + 'rest/layertree/' + str(layerTreeItemIdd) - h = {'Content-type':'application/json'} - body = json.dumps({'id' : layerTreeItemIdd}) - response = self.http.request(url, method = 'DELETE', body = body, headers = h) - return response[0]['status'] + response = self.http.request(url, method="PUT", body=body, headers=h) + return response[0]["status"] + + def deleteLayerTreeItem(self, layerTreeItemId): + url = self.baseurl + "rest/layertree/" + str(layerTreeItemId) + h = {"Content-type": "application/json"} + body = json.dumps({"id": layerTreeItemId}) + response = self.http.request(url, method="DELETE", body=body, headers=h) + return response[0]["status"] diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 5d31345..30984b4 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -1,15 +1,13 @@ services: qgis: - image: qgis/qgis:final-3_34_13 + image: qgis/qgis:3.44 command: ["/bin/bash", "-c", "pip install debugpy; qgis /root/shogun_qgis_konfigurator.qgs"] environment: DISPLAY: "unix${DISPLAY}" RUN_IN_DOCKER: "1" ports: - - 5678:5678 + - "5678:5678" volumes: - /tmp/.X11-unix:/tmp/.X11-unix - ./qgis_plugins:/root/.local/share/QGIS/QGIS3/profiles/default/python/plugins - - ../shogun_qgis_configurator:/root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/shogun_qgis_configurator - ../qgis-shogun-editor:/root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/qgis-shogun-editor - - ./shogun_qgis_konfigurator.qgs:/root/shogun_qgis_konfigurator.qgs diff --git a/gui/dialog_bases/addraster.py b/gui/dialog_bases/addraster.py index 08eb307..d488b6e 100644 --- a/gui/dialog_bases/addraster.py +++ b/gui/dialog_bases/addraster.py @@ -1,42 +1,44 @@ # -*- coding: utf-8 -*- -''' +""" (c) 2025 terrestris GmbH & Co. KG, https://www.terrestris.de/en/ This code is licensed under the GPL 2.0 license. -''' - -__author__ = 'ntreff' -__date__ = 'July 2025' - +""" import sys if sys.version_info[0] >= 3: - from qgis.PyQt.QtWidgets import QDialog, QPushButton, QLabel from qgis.PyQt import QtCore + from qgis.PyQt.QtWidgets import QDialog, QLabel, QPushButton else: - from PyQt4.QtGui import QDialog, QPushButton, QLabel from PyQt4 import QtCore + from PyQt4.QtGui import QDialog, QLabel, QPushButton + +__author__ = "ntreff" +__date__ = "July 2025" + class AddRasterDialog(QDialog): def __init__(self): super(QDialog, self).__init__() self.resize(410, 200) - self.setWindowTitle('Add Raster Layer Dialog') + self.setWindowTitle("Add Raster Layer Dialog") self.label = QLabel(self) - self.label.setGeometry(QtCore.QRect(20,20,300,100)) + self.label.setGeometry(QtCore.QRect(20, 20, 300, 100)) self.label.setAlignment(QtCore.Qt.AlignCenter) - self.label.setText('The requested ressource is a raster layer. \n' - 'Depending on it\'s size\', downloading \nand importing it to QGIS' - 'may \ntake while. You can also consider to only import the\n' - 'layer as a WMS if you only need to view it') + self.label.setText( + "The requested ressource is a raster layer. \n" + "Depending on it's size', downloading \nand importing it to QGIS" + "may \ntake while. You can also consider to only import the\n" + "layer as a WMS if you only need to view it" + ) self.cancelbutton = QPushButton(self) - self.cancelbutton.setText('Cancel') - self.cancelbutton.setGeometry(QtCore.QRect(20,150,125,30)) + self.cancelbutton.setText("Cancel") + self.cancelbutton.setGeometry(QtCore.QRect(20, 150, 125, 30)) self.wmsbutton = QPushButton(self) - self.wmsbutton.setText('Add as WMS Layer') - self.wmsbutton.setGeometry(QtCore.QRect(150,150,125,30)) + self.wmsbutton.setText("Add as WMS Layer") + self.wmsbutton.setGeometry(QtCore.QRect(150, 150, 125, 30)) self.rasterbutton = QPushButton(self) - self.rasterbutton.setText('Add as Raster Layer') - self.rasterbutton.setGeometry(QtCore.QRect(280,150,125,30)) + self.rasterbutton.setText("Add as Raster Layer") + self.rasterbutton.setGeometry(QtCore.QRect(280, 150, 125, 30)) # we reconfigure the already existing slot self.done() (inherited # from QDialog to emit different ints - self.done() is called by diff --git a/gui/dialog_bases/applicationSettings.py b/gui/dialog_bases/applicationSettings.py index 7f5d5c2..f8872ae 100644 --- a/gui/dialog_bases/applicationSettings.py +++ b/gui/dialog_bases/applicationSettings.py @@ -1,31 +1,26 @@ # -*- coding: utf-8 -*- -''' +""" (c) 2025 terrestris GmbH & Co. KG, https://www.terrestris.de/en/ This code is licensed under the GPL 2.0 license. -''' +""" -__author__ = 'ntreff' -__date__ = 'July 2025' - -import sys - - -from qgis.PyQt.QtCore import QRect, Qt # we are faking the old way of QtGui, not the best style, but makes it easier # for switching betweeng version 2 and 3 from qgis.PyQt import QtWidgets as QtGui +from qgis.PyQt.QtCore import QRect, Qt from qgis.PyQt.QtGui import QFont +__author__ = "ntreff" +__date__ = "July 2025" -from qgis.gui import QgsExtentGroupBox class LayerListItem(QtGui.QListWidgetItem): def __init__(self, text, layerId): super(LayerListItem, self).__init__(text) - self.setFlags(Qt.ItemIsEnabled | Qt.ItemIsDragEnabled | - Qt.ItemIsSelectable) + self.setFlags(Qt.ItemIsEnabled | Qt.ItemIsDragEnabled | Qt.ItemIsSelectable) self.layerId = layerId + class LayerListWidget(QtGui.QListWidget): def __init__(self, parent): super(LayerListWidget, self).__init__(parent) @@ -34,7 +29,7 @@ def __init__(self, parent): def populateList(self, layers): for layer in layers: - item = LayerListItem(text = layer[1], layerId = layer[0]) + item = LayerListItem(text=layer[1], layerId=layer[0]) self.addItem(item) def dragEnterEvent(self, e): @@ -44,85 +39,83 @@ def dragEnterEvent(self, e): # for drag and drop. For reasons of simplicity we just add the # layerId and name to one string which we pass and decode it later # we use '&;*&' so this code must not appear in layer names - mimeText = item.text() + '&;*&' + str(item.layerId) + mimeText = item.text() + "&;*&" + str(item.layerId) e.mimeData().setText(mimeText) class LayerTreeItem(QtGui.QTreeWidgetItem): def __init__(self, parent): super(LayerTreeItem, self).__init__(parent) - self.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable | - Qt.ItemIsDragEnabled | Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | - Qt.ItemIsDropEnabled) + self.setFlags( + Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled | + Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsDropEnabled + ) self.savedAttributes = {} self.newAttributes = {} self.layerId = None self.id = None - def setSavedAttributes(self, savedAttributes): self.savedAttributes = savedAttributes - self.setText(0, self.savedAttributes['text']) - self.role = self.savedAttributes['@class'] - self.id = self.savedAttributes['id'] - if self.savedAttributes['root']: + self.setText(0, self.savedAttributes["text"]) + self.role = self.savedAttributes["@class"] + self.id = self.savedAttributes["id"] + if self.savedAttributes["root"]: return - if self.savedAttributes['checked'] == True : - self.setCheckState(0,Qt.Checked) + if self.savedAttributes["checked"]: + self.setCheckState(0, Qt.Checked) else: - self.setCheckState(0,Qt.Unchecked) - + self.setCheckState(0, Qt.Unchecked) def updateNewAttributes(self): - self.newAttributes['text'] = self.text(0) - self.newAttributes['root'] = False - self.newAttributes['@class'] = self.role + self.newAttributes["text"] = self.text(0) + self.newAttributes["root"] = False + self.newAttributes["@class"] = self.role if self.checkState(0) == Qt.Checked: - self.newAttributes['checked'] = True + self.newAttributes["checked"] = True else: - self.newAttributes['checked'] = False + self.newAttributes["checked"] = False if self.parent() is None: - self.newAttributes['parentId'] = self.treeWidget().rootId - self.newAttributes['index'] = self.treeWidget().indexOfTopLevelItem(self) + self.newAttributes["parentId"] = self.treeWidget().rootId + self.newAttributes["index"] = self.treeWidget().indexOfTopLevelItem(self) else: - self.newAttributes['parentId'] = self.parent().id - self.newAttributes['index'] = self.parent().indexOfChild(self) + self.newAttributes["parentId"] = self.parent().id + self.newAttributes["index"] = self.parent().indexOfChild(self) - if self.role == 'de.terrestris.appshogun.model.tree.LayerTreeLeaf': - self.newAttributes['expandable'] = False - self.newAttributes['expanded'] = False - self.newAttributes['leaf'] = True + if self.role == "de.terrestris.appshogun.model.tree.LayerTreeLeaf": + self.newAttributes["expandable"] = False + self.newAttributes["expanded"] = False + self.newAttributes["leaf"] = True else: - self.newAttributes['expandable'] = True - self.newAttributes['expanded'] = True - self.newAttributes['leaf'] = False + self.newAttributes["expandable"] = True + self.newAttributes["expanded"] = True + self.newAttributes["leaf"] = False if self.layerId is not None: - self.newAttributes['layer'] = self.layerId - #self.newAttributes['expanded'] = self.isExpanded() - + self.newAttributes["layer"] = self.layerId + # self.newAttributes['expanded'] = self.isExpanded() def getItemChange(self): if len(self.savedAttributes) == 0: return self.newAttributes else: change = {} - for (key, value) in self.savedAttributes.items(): + for key, value in self.savedAttributes.items(): if key in self.newAttributes: if value != self.newAttributes[key]: change[key] = self.newAttributes[key] if len(change) == 0: return None else: - change['id'] = self.id + change["id"] = self.id return change class LayerTreeWidget(QtGui.QTreeWidget): - SHOGUN_TREE_LEAF = 'de.terrestris.appshogun.model.tree.LayerTreeLeaf' - SHOGUN_TREE_FOLDER = 'de.terrestris.appshogun.model.tree.LayerTreeFolder' + SHOGUN_TREE_LEAF = "de.terrestris.appshogun.model.tree.LayerTreeLeaf" + SHOGUN_TREE_FOLDER = "de.terrestris.appshogun.model.tree.LayerTreeFolder" def __init__(self, parentWindow): super(LayerTreeWidget, self).__init__(parentWindow) @@ -137,61 +130,57 @@ def __init__(self, parentWindow): def setupNewTree(self): folder = self.addNewFolder(None) - folder.setText(0, 'Background layer') - + folder.setText(0, "Background layer") def populateTree(self, layerTree): # delete all items, then populate the tree with items representing # the layer tree structure self.clear() - self.rootId = layerTree['id'] - self.constructTreeChildrenRecursive(self.invisibleRootItem(), layerTree['children']) + self.rootId = layerTree["id"] + self.constructTreeChildrenRecursive( + self.invisibleRootItem(), layerTree["children"] + ) - iter = QtGui.QTreeWidgetItemIterator(self) - val = iter.value() + iterator = QtGui.QTreeWidgetItemIterator(self) + val = iterator.value() while val: val.setExpanded(True) - iter += 1 - val = iter.value() + iterator += 1 + val = iterator.value() def constructTreeChildrenRecursive(self, parent, children): for child in children: item = LayerTreeItem(parent) - attrs = {key : value for (key, value) in child.items() if key != 'children'} + attrs = {key: value for (key, value) in child.items() if key != "children"} item.setSavedAttributes(attrs) - if 'children' in child.keys(): - if not child['children']: + if "children" in child.keys(): + if not child["children"]: return # 'children' is a list, to make sure the items are put into the # tree in the right order according to there index, we sort them - sortedChildren = sorted(child['children'], key = lambda x : x['index']) + sortedChildren = sorted(child["children"], key=lambda x: x["index"]) self.constructTreeChildrenRecursive(item, sortedChildren) - def getLayerTreeChanges(self): - allChanges = { - 'newItems' : [], - 'changeItems' : [], - 'deleteItems' : [] - } + allChanges = {"newItems": [], "changeItems": [], "deleteItems": []} # iterate through all items in the layertree and find new or # changed items - iter = QtGui.QTreeWidgetItemIterator(self) - treeitem = iter.value() + iterator = QtGui.QTreeWidgetItemIterator(self) + treeitem = iterator.value() while treeitem: treeitem.updateNewAttributes() change = treeitem.getItemChange() if change is not None: - if not 'id' in change: - allChanges['newItems'].append(change) + if "id" not in change: + allChanges["newItems"].append(change) else: - allChanges['changeItems'].append(change) + allChanges["changeItems"].append(change) - iter += 1 - treeitem = iter.value() + iterator += 1 + treeitem = iterator.value() if len(self.deletedItemIds) > 0: - allChanges['deleteItems'] = [x for x in self.deletedItemIds] + allChanges["deleteItems"] = list(self.deletedItemIds) self.deletedItemIds = [] for x in allChanges: @@ -199,18 +188,17 @@ def getLayerTreeChanges(self): return allChanges return None - def dropEvent(self, e): dropItem = self.itemAt(e.pos()) mime = e.mimeData() if dropItem is None: if mime.hasText(): - newItem = LayerTreeItem(parent = self) - layerName, layerId = mime.text().split('&;*&') + newItem = LayerTreeItem(parent=self) + layerName, layerId = mime.text().split("&;*&") newItem.setText(0, layerName) newItem.role = self.SHOGUN_TREE_LEAF newItem.layerId = int(layerId) - newItem.setCheckState(0,Qt.Checked) + newItem.setCheckState(0, Qt.Checked) else: self.changePositionInTree(self.invisibleRootItem()) else: @@ -224,21 +212,20 @@ def dropEvent(self, e): # if mime has Text its coming from the layerlistwidget if mime.hasText(): - newItem = LayerTreeItem(parent = dropItem) - layerName, layerId = mime.text().split('&;*&') + newItem = LayerTreeItem(parent=dropItem) + layerName, layerId = mime.text().split("&;*&") newItem.setText(0, layerName) newItem.role = self.SHOGUN_TREE_LEAF newItem.layerId = int(layerId) - newItem.setCheckState(0,Qt.Checked) + newItem.setCheckState(0, Qt.Checked) else: self.changePositionInTree(dropItem) - iter = QtGui.QTreeWidgetItemIterator(self) - val = iter.value() + iterator = QtGui.QTreeWidgetItemIterator(self) + val = iterator.value() while val: val.setSelected(False) - iter += 1 - val = iter.value() - + iterator += 1 + val = iterator.value() def changePositionInTree(self, newParentItem): selectedItem = self.selectedItems()[0] @@ -258,30 +245,29 @@ def changePositionInTree(self, newParentItem): newParentItem.setExpanded(True) selectedItem.setExpanded(True) - def on_context_menu(self, point): item = self.itemAt(point) acts = [] if item is None: - a1 = QtGui.QAction('Add Folder (top level)', None) + a1 = QtGui.QAction("Add Folder (top level)", None) a1.triggered.connect(lambda: self.addNewFolder(None)) acts.append(a1) - a2 = QtGui.QAction('Delete Tree Contents completely', None) + a2 = QtGui.QAction("Delete Tree Contents completely", None) a2.triggered.connect(self.deleteAll) acts.append(a2) else: - a1 = QtGui.QAction('Rename', None) + a1 = QtGui.QAction("Rename", None) a1.triggered.connect(lambda: self.renameItem(item)) acts.append(a1) if item.role == self.SHOGUN_TREE_LEAF: - a2 = QtGui.QAction('Delete Leaf', None) + a2 = QtGui.QAction("Delete Leaf", None) a2.triggered.connect(lambda: self.deleteLeaf(item)) acts.append(a2) else: - a2 = QtGui.QAction('New Folder (inside selected)', None) + a2 = QtGui.QAction("New Folder (inside selected)", None) a2.triggered.connect(lambda: self.addNewFolder(item)) acts.append(a2) - a3 = QtGui.QAction('Delete Folder', None) + a3 = QtGui.QAction("Delete Folder", None) a3.triggered.connect(lambda: self.deleteLeaf(item)) acts.append(a3) menu = QtGui.QMenu() @@ -289,14 +275,13 @@ def on_context_menu(self, point): point = self.mapToGlobal(point) menu.exec_(point) - def addNewFolder(self, item): if item is None: parent = self else: parent = item new = LayerTreeItem(parent) - new.setText(0, 'New folder') + new.setText(0, "New folder") new.role = self.SHOGUN_TREE_FOLDER new.setCheckState(0, Qt.Checked) return new @@ -308,8 +293,9 @@ def deleteAll(self): topitem.removeChild(child) def renameItem(self, item): - text, ok = QtGui.QInputDialog.getText(self, - 'Text Input Dialog', 'Enter the new name:') + text, ok = QtGui.QInputDialog.getText( + self, "Text Input Dialog", "Enter the new name:" + ) if ok: item.setText(0, text) @@ -325,7 +311,6 @@ def getSubtreeIds(self, item): idList.extend(self.getSubtreeIds(item.child(x))) return idList - def deleteLeaf(self, item): self.deletedItemIds.extend(self.getSubtreeIds(item)) @@ -335,34 +320,59 @@ def deleteLeaf(self, item): parent.removeChild(item) - class ApplicationSettingsDialog(QtGui.QDialog): - def __init__(self): + def __init__(self): QtGui.QDialog.__init__(self) - self.tabs = [] #All child-tabWidgets - self.tabedits = [] #All QLineEdits per tabWidget in a list - self.tabboxes = [] #All QCheckBoxes per tabWidget in a list + self.tabs = [] # All child-tabWidgets + self.tabedits = [] # All QLineEdits per tabWidget in a list + self.tabboxes = [] # All QCheckBoxes per tabWidget in a list self.moreObjects = [] self.setupUi() def setupUi(self): self.resize(550, 550) - self.setWindowTitle('Settings') + self.setWindowTitle("Settings") - #create tabWidget that holds the tabs + # create tabWidget that holds the tabs self.tabWidget = QtGui.QTabWidget(self) self.tabWidget.setGeometry(QRect(10, 20, 500, 480)) - self.tabWidget.setObjectName('tabWidget') - tab0labels=[['Name',(50, 50, 56, 17)], ['Description', (50,100,70,25)], ['Language', (50, 150, 56, 17)]] - tab1labels = [['Which tools/ buttons shall be activated in the application:', (50, 25, 56, 17)]] - tab2labels = [['Center:', (50, 50, 70, 17)], ['X:', (160, 53, 10, 10)], ['Y:', (320, 53, 10, 10)], ['Zoom:', (50, 100, 70, 17)], - ['Extent:', (50, 363, 70, 17)], ['MinX:', (132, 367, 40, 17)], ['MinY:', (222, 327, 40, 17)], ['MaxX:', (306, 367, 40, 17)], - ['MaxY:', (222, 407, 40, 17)]] - tab3labels = [['All Layers', (90, 40, 80, 30)], ['Layer Tree', (325, 40, 80, 30)]] - tab4labels = [['Users', (100, 10, 50, 20)], ['Groups', (320, 10, 50, 20)]] - tabwidgets = [['General', tab0labels], ['Tools', tab1labels], ['Homeview', tab2labels], ['Layer', tab3labels], ['Permissions', tab4labels]] - - #first set the labes for all tabwwidgets in a loop: + self.tabWidget.setObjectName("tabWidget") + tab0labels = [ + ["Name", (50, 50, 56, 17)], + ["Description", (50, 100, 70, 25)], + ["Language", (50, 150, 56, 17)], + ] + tab1labels = [ + [ + "Which tools/ buttons shall be activated in the application:", + (50, 25, 56, 17), + ] + ] + tab2labels = [ + ["Center:", (50, 50, 70, 17)], + ["X:", (160, 53, 10, 10)], + ["Y:", (320, 53, 10, 10)], + ["Zoom:", (50, 100, 70, 17)], + ["Extent:", (50, 363, 70, 17)], + ["MinX:", (132, 367, 40, 17)], + ["MinY:", (222, 327, 40, 17)], + ["MaxX:", (306, 367, 40, 17)], + ["MaxY:", (222, 407, 40, 17)], + ] + tab3labels = [ + ["All Layers", (90, 40, 80, 30)], + ["Layer Tree", (325, 40, 80, 30)], + ] + tab4labels = [["Users", (100, 10, 50, 20)], ["Groups", (320, 10, 50, 20)]] + tabwidgets = [ + ["General", tab0labels], + ["Tools", tab1labels], + ["Homeview", tab2labels], + ["Layer", tab3labels], + ["Permissions", tab4labels], + ] + + # first set the labes for all tabwwidgets in a loop: for tab in tabwidgets: t = QtGui.QWidget() t.setObjectName(tab[0]) @@ -370,47 +380,56 @@ def setupUi(self): self.tabWidget.addTab(t, tab[0]) for label in tab[1]: - l = QtGui.QLabel(t) - l.setText(label[0]) - l.setGeometry(QRect(label[1][0],label[1][1],label[1][2],label[1][3])) - if (tab[0] == 'Layer'): - font = QFont('Arial',12) + l2 = QtGui.QLabel(t) + l2.setText(label[0]) + l2.setGeometry(QRect(label[1][0], label[1][1], label[1][2], label[1][3])) + if tab[0] == "Layer": + font = QFont("Arial", 12) font.setBold(True) - l.setFont(font) - + l2.setFont(font) self.tabWidget.setCurrentIndex(0) - #then populate the specific tabwidgets with other QObjects: - #tab 0 = 'General': + # then populate the specific tabwidgets with other QObjects: + # tab 0 = 'General': self.nameEdit = QtGui.QLineEdit(self.tabs[0]) self.nameEdit.setGeometry(QRect(250, 40, 150, 27)) self.tabedits.append(self.nameEdit) self.descriptionEdit = QtGui.QLineEdit(self.tabs[0]) - self.descriptionEdit.setGeometry(QRect(250, 90, 150,27)) + self.descriptionEdit.setGeometry(QRect(250, 90, 150, 27)) self.tabedits.append(self.descriptionEdit) self.languageBox = QtGui.QComboBox(self.tabs[0]) - self.languageBox.setGeometry(QRect(250, 140, 113,27)) - self.languageBox.addItems(['en','de']) + self.languageBox.setGeometry(QRect(250, 140, 113, 27)) + self.languageBox.addItems(["en", "de"]) self.tabedits.append(self.languageBox) self.boxPublic = QtGui.QCheckBox(self.tabs[0]) self.boxPublic.setGeometry(QRect(250, 180, 80, 17)) - self.boxPublic.setText('Public') + self.boxPublic.setText("Public") self.tabboxes.append(self.boxPublic) self.boxActive = QtGui.QCheckBox(self.tabs[0]) self.boxActive.setGeometry(QRect(250, 230, 80, 17)) - self.boxActive.setText('Active') + self.boxActive.setText("Active") self.tabboxes.append(self.boxActive) - - #tab 1 = 'Tools': - toollist = ['Zoom in button', 'Zoom out button', 'Zoom to extent button', 'Step back to previous extent button', - 'Step forward to next extent button', 'Activate hover-select tool', 'Print button', 'Show measure tools button', - 'Show redlining tools button', 'Show workstate tools button', 'Show addwms tools button', 'Show meta toolbar button'] + # tab 1 = 'Tools': + toollist = [ + "Zoom in button", + "Zoom out button", + "Zoom to extent button", + "Step back to previous extent button", + "Step forward to next extent button", + "Activate hover-select tool", + "Print button", + "Show measure tools button", + "Show redlining tools button", + "Show workstate tools button", + "Show addwms tools button", + "Show meta toolbar button", + ] y = 50 self.tools = {} # a dictonary with toolbutton id as key and reference to the QCheckBox @@ -424,8 +443,7 @@ def setupUi(self): y += 30 tcount += 1 - - #tab 2 = 'Homeview': + # tab 2 = 'Homeview': self.homeviewCenterEditX = QtGui.QLineEdit(self.tabs[2]) self.homeviewCenterEditX.setGeometry(QRect(170, 50, 125, 25)) self.tabedits.append(self.homeviewCenterEditX) @@ -454,7 +472,7 @@ def setupUi(self): maxY.setGeometry(265, 400, 120, 25) self.extentEdits.append(maxY) - style = 'QLineEdit { background-color : #a6a6a6; color : white; }' + style = "QLineEdit { background-color : #a6a6a6; color : white; }" for edit in self.extentEdits: edit.setReadOnly(True) edit.lower() @@ -462,30 +480,31 @@ def setupUi(self): self.origExtentButton = QtGui.QPushButton(self.tabs[2]) self.origExtentButton.setGeometry(100, 150, 190, 30) - self.origExtentButton.setText('Set original homview') + self.origExtentButton.setText("Set original homview") self.moreObjects.append(self.origExtentButton) self.qgsExtentButton = QtGui.QPushButton(self.tabs[2]) self.qgsExtentButton.setGeometry(290, 150, 190, 30) - self.qgsExtentButton.setText('Set current QGIS view') + self.qgsExtentButton.setText("Set current QGIS view") self.moreObjects.append(self.qgsExtentButton) self.homeviewEpsgWarning = QtGui.QLabel(self.tabs[2]) self.homeviewEpsgWarning.setGeometry(QRect(50, 220, 435, 80)) - self.homeviewEpsgWarning.setFont(QFont('Arial', 9)) + self.homeviewEpsgWarning.setFont(QFont("Arial", 9)) self.jumpButtonOrig = QtGui.QPushButton(self.tabs[2]) self.jumpButtonOrig.setGeometry(QRect(115, 192, 160, 20)) - self.jumpButtonOrig.setText('Jump to original homeview') - self.jumpButtonOrig.setStyleSheet('QPushButton { background-color : #a6a6a6; color : white; }') + self.jumpButtonOrig.setText("Jump to original homeview") + self.jumpButtonOrig.setStyleSheet( + "QPushButton { background-color : #a6a6a6; color : white; }" + ) self.jumpButtonNew = QtGui.QPushButton(self.tabs[2]) self.jumpButtonNew.setGeometry(QRect(305, 192, 160, 20)) - self.jumpButtonNew.setText('Jump to new homeview') + self.jumpButtonNew.setText("Jump to new homeview") self.moreObjects.append(self.jumpButtonNew) - - #tab 3 = 'Layer' (layertree) + # tab 3 = 'Layer' (layertree) self.layerlistwidget = LayerListWidget(self.tabs[3]) self.layerlistwidget.setGeometry(QRect(25, 70, 210, 350)) @@ -493,71 +512,67 @@ def setupUi(self): self.layertreewidget = LayerTreeWidget(self.tabs[3]) self.layertreewidget.setGeometry(QRect(260, 70, 210, 350)) - - #tab 4 = 'Permissions' + # tab 4 = 'Permissions' self.usertabel = QtGui.QTableWidget(self.tabs[4]) self.usertabel.setGeometry(QRect(10, 30, 230, 300)) self.usertabel.setColumnCount(3) - self.usertabel.setHorizontalHeaderLabels(['Read', 'Update', 'Delete']) + self.usertabel.setHorizontalHeaderLabels(["Read", "Update", "Delete"]) self.moreObjects.append(self.usertabel) self.groupstabel = QtGui.QTableWidget(self.tabs[4]) self.groupstabel.setGeometry(QRect(250, 30, 230, 300)) self.groupstabel.setColumnCount(3) - self.groupstabel.setHorizontalHeaderLabels(['Read', 'Update', 'Delete']) + self.groupstabel.setHorizontalHeaderLabels(["Read", "Update", "Delete"]) self.moreObjects.append(self.groupstabel) - - #create Gui surrounding the tabs + # create Gui surrounding the tabs self.editCheckBox = QtGui.QCheckBox(self) self.editCheckBox.setGeometry(QRect(420, 10, 50, 17)) - self.editCheckBox.setText('Edit') + self.editCheckBox.setText("Edit") self.pushButtonOk = QtGui.QPushButton(self) self.pushButtonOk.setGeometry(QRect(420, 500, 85, 27)) self.pushButtonCancel = QtGui.QPushButton(self) self.pushButtonCancel.setGeometry(QRect(320, 500, 85, 27)) - self.pushButtonCancel.setText('Cancel') + self.pushButtonCancel.setText("Cancel") self.warnLabel = QtGui.QLabel(self) self.warnLabel.setGeometry(QRect(300, 505, 80, 15)) - self.warnLabel.setText('Please fill out all mandatory fields') + self.warnLabel.setText("Please fill out all mandatory fields") self.warnLabel.setHidden(True) - self.warnLabel.setStyleSheet('QLabel { color : #ff6666; }') + self.warnLabel.setStyleSheet("QLabel { color : #ff6666; }") - def setEditState(self, b): #b = true or false + def setEditState(self, b): # b = true or false if b: - self.pushButtonOk.setText('Save Changes') + self.pushButtonOk.setText("Save Changes") self.pushButtonCancel.setHidden(False) for editable in self.getAllEditables(): editable.setEnabled(b) - else: self.pushButtonCancel.setHidden(True) - self.pushButtonOk.setText('OK') + self.pushButtonOk.setText("OK") for editable in self.getAllEditables(): editable.setEnabled(b) def getAllEditables(self): - list = [] + editable_list = [] for edit in self.tabedits: - list.append(edit) + editable_list.append(edit) for box in self.tabboxes: - list.append(box) - for object in self.moreObjects: - list.append(object) + editable_list.append(box) + for obj in self.moreObjects: + editable_list.append(obj) for box in self.tools.values(): - list.append(box) - list.append(self.layerlistwidget) - list.append(self.layertreewidget) - return list - + editable_list.append(box) + editable_list.append(self.layerlistwidget) + editable_list.append(self.layertreewidget) + return editable_list def populateTable(self, table, usersList): - if table == 'users': + if table == "users": table = self.usertabel else: table = self.groupstabel @@ -567,9 +582,11 @@ def populateTable(self, table, usersList): for row in range(tableRowCount): user = usersList[row] - table.setVerticalHeaderItem(row, QtGui.QTableWidgetItem(user['displayTitle'])) - permissions = user['permissions'] - permList = ['Read', 'Update', 'Delete'] + table.setVerticalHeaderItem( + row, QtGui.QTableWidgetItem(user["displayTitle"]) + ) + permissions = user["permissions"] + permList = ["Read", "Update", "Delete"] if not permissions: for x in permList: item = QtGui.QTableWidgetItem(x) @@ -578,7 +595,7 @@ def populateTable(self, table, usersList): else: for x in permList: item = QtGui.QTableWidgetItem(x) - if x.upper() in permissions['permissions']: + if x.upper() in permissions["permissions"]: item.setCheckState(Qt.Checked) else: item.setCheckState(Qt.Unchecked) @@ -589,24 +606,28 @@ def noPermissionAccess(self): self.groupstabel.setHidden(True) self.noPermissionAccessLabel = QtGui.QLabel(self.tabs[4]) self.noPermissionAccessLabel.setGeometry(QRect(50, 50, 420, 50)) - self.noPermissionAccessLabel.setText('Could not access application ' - 'permissions. User permission is not high enough') + self.noPermissionAccessLabel.setText( + "Could not access application " + "permissions. User permission is not high enough" + ) def newAppCreation(self): self.usertabel.setHidden(True) self.groupstabel.setHidden(True) self.noPermissionAccessLabel = QtGui.QLabel(self.tabs[4]) self.noPermissionAccessLabel.setGeometry(QRect(50, 50, 420, 100)) - self.noPermissionAccessLabel.setText('You have to save and upload the ' - 'new application once\nfirst, then you can edit it\'s permissions') + self.noPermissionAccessLabel.setText( + "You have to save and upload the " + "new application once\nfirst, then you can edit it's permissions" + ) def showEpsgWarning(self, currentCrs, applicationCrs): self.homeviewEpsgWarning.setHidden(False) - txt = 'Note: The coordinate reference system CRS of the current QGIS \n' - txt += 'project: ' + currentCrs +' is different from this application\'s' - txt += ' CRS: ' + applicationCrs + '\nIt is strongly recommended to ' - txt += 'change the QGIS Project CRS\nto ' + applicationCrs + ' before ' - txt += 'working with the homeview' + txt = "Note: The coordinate reference system CRS of the current QGIS \n" + txt += "project: " + currentCrs + " is different from this application's" + txt += " CRS: " + applicationCrs + "\nIt is strongly recommended to " + txt += "change the QGIS Project CRS\nto " + applicationCrs + " before " + txt += "working with the homeview" self.homeviewEpsgWarning.setText(txt) def hideEpsgWarning(self): diff --git a/gui/dialog_bases/connectdlg.py b/gui/dialog_bases/connectdlg.py index 68ac192..55cf0db 100644 --- a/gui/dialog_bases/connectdlg.py +++ b/gui/dialog_bases/connectdlg.py @@ -1,11 +1,8 @@ # -*- coding: utf-8 -*- -''' +""" (c) 2025 terrestris GmbH & Co. KG, https://www.terrestris.de/en/ This code is licensed under the GPL 2.0 license. -''' - -__author__ = 'ntreff' -__date__ = 'July 2025' +""" import sys @@ -16,31 +13,35 @@ from PyQt4.QtCore import QRect from PyQt4.QtGui import QDialog, QLabel, QLineEdit, QPushButton +__author__ = "ntreff" +__date__ = "July 2025" + + class ConnectDialog(QDialog): - def __init__(self): + def __init__(self): QDialog.__init__(self) self.resize(400, 300) - self.setWindowTitle('Connection Dialog') + self.setWindowTitle("Connection Dialog") self.label = QLabel(self) self.label.setGeometry(QRect(140, 150, 81, 20)) - self.label.setText('username') + self.label.setText("username") self.label2 = QLabel(self) self.label2.setGeometry(QRect(140, 190, 81, 20)) - self.label2.setText('password') + self.label2.setText("password") self.label3 = QLabel(self) self.label3.setGeometry(QRect(40, 32, 151, 20)) - self.label3.setText('Name of the Shogun Client') + self.label3.setText("Name of the Shogun Client") self.label4 = QLabel(self) self.label4.setGeometry(QRect(40, 82, 151, 20)) - self.label4.setText('URL:') + self.label4.setText("URL:") self.nameIn = QLineEdit(self) self.nameIn.setGeometry(QRect(200, 30, 180, 27)) - self.nameIn.setText('Default Shogun Client') + self.nameIn.setText("Default Shogun Client") self.urlIn = QLineEdit(self) self.urlIn.setGeometry(QRect(122, 80, 258, 27)) - self.urlIn.setPlaceholderText('i. e.: http(s)://.../shogun2-webapp') + self.urlIn.setPlaceholderText("i. e.: http(s)://.../shogun2-webapp") self.userIn = QLineEdit(self) self.userIn.setGeometry(QRect(230, 150, 150, 27)) self.passwordIn = QLineEdit(self) @@ -48,7 +49,7 @@ def __init__(self): self.passwordIn.setEchoMode(QLineEdit.Password) self.okButton = QPushButton(self) self.okButton.setGeometry(QRect(270, 240, 85, 27)) - self.okButton.setText('OK') + self.okButton.setText("OK") self.cancelButton = QPushButton(self) self.cancelButton.setGeometry(QRect(180, 240, 85, 27)) - self.cancelButton.setText('Cancel') + self.cancelButton.setText("Cancel") diff --git a/gui/dialog_bases/dockwidget.py b/gui/dialog_bases/dockwidget.py index b7995f8..84b0334 100644 --- a/gui/dialog_bases/dockwidget.py +++ b/gui/dialog_bases/dockwidget.py @@ -1,25 +1,26 @@ # -*- coding: utf-8 -*- -''' +""" (c) 2025 terrestris GmbH & Co. KG, https://www.terrestris.de/en/ This code is licensed under the GPL 2.0 license. -''' - -__author__ = 'ntreff' -__date__ = 'July 2025' +""" import sys if sys.version_info[0] >= 3: from qgis.PyQt.QtCore import QRect, Qt - from qgis.PyQt.QtWidgets import QWidget, QPushButton, QDockWidget, QTreeWidget + from qgis.PyQt.QtWidgets import QDockWidget, QPushButton, QTreeWidget, QWidget else: from PyQt4.QtCore import QRect, Qt - from PyQt4.QtGui import QWidget, QPushButton, QDockWidget, QTreeWidget + from PyQt4.QtGui import QDockWidget, QPushButton, QTreeWidget, QWidget + +__author__ = "ntreff" +__date__ = "July 2025" + class DockWidget(QDockWidget): - def __init__(self): + def __init__(self): QDockWidget.__init__(self) - self.setWindowTitle('Shogun Editor') + self.setWindowTitle("Shogun Editor") self.setContextMenuPolicy(Qt.DefaultContextMenu) self.setLayoutDirection(Qt.LeftToRight) self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) @@ -29,7 +30,7 @@ def __init__(self): self.dockWidgetContents.setGeometry(QRect(20, 30, 320, 700)) self.newConnectionButton = QPushButton(self.dockWidgetContents) self.newConnectionButton.setGeometry(QRect(10, 0, 141, 27)) - self.newConnectionButton.setText('New Connection') + self.newConnectionButton.setText("New Connection") self.treeWidget = QTreeWidget(self.dockWidgetContents) self.treeWidget.setGeometry(QRect(10, 40, 300, 650)) self.treeWidget.setContextMenuPolicy(Qt.CustomContextMenu) diff --git a/gui/dialog_bases/layerSettings.py b/gui/dialog_bases/layerSettings.py index a9f499b..fcc9d91 100644 --- a/gui/dialog_bases/layerSettings.py +++ b/gui/dialog_bases/layerSettings.py @@ -1,55 +1,70 @@ # -*- coding: utf-8 -*- -''' +""" (c) 2025 terrestris GmbH & Co. KG, https://www.terrestris.de/en/ This code is licensed under the GPL 2.0 license. -''' - -__author__ = 'ntreff' -__date__ = 'July 2025' +""" import sys +from qgis.gui import QgsMapLayerComboBox + if sys.version_info[0] >= 3: - from qgis.PyQt.QtCore import QRect, Qt - from qgis.PyQt.QtGui import QDoubleValidator # we are faking the old way of QtGui, not the best style, but makes it easier # for switching betweeng version 2 and 3 from qgis.PyQt import QtWidgets as QtGui + from qgis.PyQt.QtCore import QRect, Qt + from qgis.PyQt.QtGui import QDoubleValidator else: - from PyQt4.QtCore import QRect, Qt from PyQt4 import QtGui + from PyQt4.QtCore import QRect, Qt + +__author__ = "ntreff" +__date__ = "July 2025" -from qgis.gui import QgsMapLayerComboBox class LayerSettingsDialog(QtGui.QDialog): - def __init__(self): + def __init__(self): QtGui.QDialog.__init__(self) - self.tabs = [] #All child-tabWidgets - self.tabedits = [] #All QLineEdits and QComboBox per tabWidget in a list - self.tabboxes = [] #All QCheckBoxes per tabWidget in a list + self.tabs = [] # All child-tabWidgets + self.tabedits = [] # All QLineEdits and QComboBox per tabWidget in a list + self.tabboxes = [] # All QCheckBoxes per tabWidget in a list self.moreObjects = [] self.setupUi() def setupUi(self): self.resize(550, 550) - self.setWindowTitle('Settings') + self.setWindowTitle("Settings") - #create tabWidget that holds the tabs + # create tabWidget that holds the tabs self.tabWidget = QtGui.QTabWidget(self) self.tabWidget.setGeometry(QRect(10, 20, 500, 480)) - self.tabWidget.setObjectName('tabWidget') - tab0labels = [['Name', (50, 50, 56, 17)],['Layer Opacity',(50,100,80,25)], ['Hover Template', (50, 150, 120, 17)]] - tab1labels = [['Until now "Metadata" has to be edited in the shogun2-webapp', (50, 50, 300, 17)]] - tab2labels = [['explanation', (50, 50, 400, 200)]] - tab3labels = [['Users', (100, 10, 50, 20)], ['Groups', (320, 10, 50, 20)]] - tabwidgets = [['General', tab0labels], ['Metadata', tab1labels], ['Style', tab2labels], ['Permissions', tab3labels]] - - expl = 'To edit the style of layer in shogun, first add the layer to QGIS.\n' - expl += 'Then style the layer via the QGIS layer properties.\nWhen finished, ' - expl += 'you can upload the current layer style \nto this layer in Shogun by ' - expl += 'right-clicking it in \nthe Shogun Editor menu' - - #first set the labes for all tabwwidgets in a loop: + self.tabWidget.setObjectName("tabWidget") + tab0labels = [ + ["Name", (50, 50, 56, 17)], + ["Layer Opacity", (50, 100, 80, 25)], + ["Hover Template", (50, 150, 120, 17)], + ] + tab1labels = [ + [ + 'Until now "Metadata" has to be edited in the shogun2-webapp', + (50, 50, 300, 17), + ] + ] + tab2labels = [["explanation", (50, 50, 400, 200)]] + tab3labels = [["Users", (100, 10, 50, 20)], ["Groups", (320, 10, 50, 20)]] + tabwidgets = [ + ["General", tab0labels], + ["Metadata", tab1labels], + ["Style", tab2labels], + ["Permissions", tab3labels], + ] + + expl = "To edit the style of layer in shogun, first add the layer to QGIS.\n" + expl += "Then style the layer via the QGIS layer properties.\nWhen finished, " + expl += "you can upload the current layer style \nto this layer in Shogun by " + expl += "right-clicking it in \nthe Shogun Editor menu" + + # first set the labes for all tabwwidgets in a loop: for tab in tabwidgets: t = QtGui.QWidget() t.setObjectName(tab[0]) @@ -57,27 +72,25 @@ def setupUi(self): self.tabWidget.addTab(t, tab[0]) for label in tab[1]: - l = QtGui.QLabel(t) - l.setGeometry(QRect(label[1][0],label[1][1],label[1][2],label[1][3])) - if label[0] == 'explanation': - l.setText(expl) - l.setAlignment(Qt.AlignTop) + l2 = QtGui.QLabel(t) + l2.setGeometry(QRect(label[1][0], label[1][1], label[1][2], label[1][3])) + if label[0] == "explanation": + l2.setText(expl) + l2.setAlignment(Qt.AlignTop) else: - l.setText(label[0]) - + l2.setText(label[0]) self.tabWidget.setCurrentIndex(0) - - #then populate the specific tabwidgets with other QObjects: - #tab 0 = 'General': + # then populate the specific tabwidgets with other QObjects: + # tab 0 = 'General': self.nameEdit = QtGui.QLineEdit(self.tabs[0]) self.nameEdit.setGeometry(QRect(180, 40, 113, 27)) self.tabedits.append(self.nameEdit) self.sliderEdit = QtGui.QLineEdit(self.tabs[0]) self.sliderEdit.setGeometry(QRect(400, 90, 30, 23)) - self.sliderEdit.setInputMask('9.99') + self.sliderEdit.setInputMask("9.99") if sys.version_info[0] >= 3: validator = QDoubleValidator(-0.01, 1.01, 2) else: @@ -86,7 +99,7 @@ def setupUi(self): self.tabedits.append(self.sliderEdit) self.hoverEdit = QtGui.QLineEdit(self.tabs[0]) - self.hoverEdit.setGeometry(QRect(180, 140, 113,27)) + self.hoverEdit.setGeometry(QRect(180, 140, 113, 27)) self.tabedits.append(self.hoverEdit) self.hoverBox = QtGui.QComboBox(self.tabs[0]) @@ -95,7 +108,7 @@ def setupUi(self): self.hoverAddButton = QtGui.QPushButton(self.tabs[0]) self.hoverAddButton.setGeometry(QRect(410, 140, 30, 27)) - self.hoverAddButton.setText('Add') + self.hoverAddButton.setText("Add") self.tabedits.append(self.hoverAddButton) self.slider = QtGui.QSlider(self.tabs[0]) @@ -105,69 +118,67 @@ def setupUi(self): self.slider.setMinimum(-1) self.slider.setEnabled(False) self.moreObjects.append(self.slider) - self.slider.valueChanged.connect(lambda: self.sliderEdit.setText(str(float(self.slider.value())/100))) - self.sliderEdit.textEdited.connect(lambda: self.slider.setValue(int(float(self.sliderEdit.text())*100))) + self.slider.valueChanged.connect( + lambda: self.sliderEdit.setText(str(float(self.slider.value()) / 100)) + ) + self.sliderEdit.textEdited.connect( + lambda: self.slider.setValue(int(float(self.sliderEdit.text()) * 100)) + ) self.hoverAddButton.clicked.connect(self.addHoverAttribute) - - - #tab 3 = 'Permissions': + # tab 3 = 'Permissions': self.usertabel = QtGui.QTableWidget(self.tabs[3]) self.usertabel.setGeometry(QRect(10, 30, 230, 300)) self.usertabel.setColumnCount(3) - self.usertabel.setHorizontalHeaderLabels(['Read', 'Update', 'Delete']) + self.usertabel.setHorizontalHeaderLabels(["Read", "Update", "Delete"]) self.moreObjects.append(self.usertabel) self.groupstabel = QtGui.QTableWidget(self.tabs[3]) self.groupstabel.setGeometry(QRect(250, 30, 230, 300)) self.groupstabel.setColumnCount(3) - self.groupstabel.setHorizontalHeaderLabels(['Read', 'Update', 'Delete']) + self.groupstabel.setHorizontalHeaderLabels(["Read", "Update", "Delete"]) self.moreObjects.append(self.groupstabel) - - #create Gui surrounding the tabs + # create Gui surrounding the tabs self.editCheckBox = QtGui.QCheckBox(self) self.editCheckBox.setGeometry(QRect(420, 10, 50, 17)) - self.editCheckBox.setText('Edit') + self.editCheckBox.setText("Edit") self.pushButtonOk = QtGui.QPushButton(self) self.pushButtonOk.setGeometry(QRect(420, 500, 85, 27)) self.pushButtonCancel = QtGui.QPushButton(self) self.pushButtonCancel.setGeometry(QRect(320, 500, 85, 27)) - self.pushButtonCancel.setText('Cancel') - + self.pushButtonCancel.setText("Cancel") def addHoverAttribute(self): attribute = self.hoverBox.currentText() if len(attribute) > 0: - attribute = '{' + attribute + '}' + attribute = "{" + attribute + "}" text = self.hoverEdit.text() + attribute self.hoverEdit.setText(text) - - def setEditState(self, b): #b = true or false + def setEditState(self, b): # b = true or false if b: - self.pushButtonOk.setText('Save Changes') + self.pushButtonOk.setText("Save Changes") self.pushButtonCancel.setHidden(False) for editable in self.getAllEditables(): editable.setEnabled(b) else: self.pushButtonCancel.setHidden(True) - self.pushButtonOk.setText('OK') + self.pushButtonOk.setText("OK") for editable in self.getAllEditables(): editable.setEnabled(b) def getAllEditables(self): - list = [] + editable_list = [] for edit in self.tabedits: - list.append(edit) + editable_list.append(edit) for box in self.tabboxes: - list.append(box) - for object in self.moreObjects: - list.append(object) - return list - + editable_list.append(box) + for obj in self.moreObjects: + editable_list.append(obj) + return editable_list def deactivateHoverEditing(self): self.hoverBox.setHidden(True) @@ -175,82 +186,85 @@ def deactivateHoverEditing(self): self.hoverAddButton.setHidden(True) self.infoEdit = QtGui.QLineEdit(self.tabs[0]) self.infoEdit.setEnabled(False) - self.infoEdit.setText('only available for vector layers') - self.infoEdit.setGeometry(QRect(180, 143, 200 ,27)) - + self.infoEdit.setText("only available for vector layers") + self.infoEdit.setGeometry(QRect(180, 143, 200, 27)) def populateTable(self, table, usersList): - if table == 'users': + if table == "users": table = self.usertabel else: table = self.groupstabel - usersList = sorted(usersList, key = lambda x : x['displayTitle']) + usersList = sorted(usersList, key=lambda x: x["displayTitle"]) tableRowCount = len(usersList) table.setRowCount(tableRowCount) for row in range(tableRowCount): user = usersList[row] - table.setVerticalHeaderItem(row, QtGui.QTableWidgetItem(user['displayTitle'])) - permissions = user['permissions'] - permList = ['Read', 'Update', 'Delete'] + table.setVerticalHeaderItem( + row, QtGui.QTableWidgetItem(user["displayTitle"]) + ) + permissions = user["permissions"] + permList = ["Read", "Update", "Delete"] if not permissions: for x in permList: item = QtGui.QTableWidgetItem(x) item.setCheckState(Qt.Unchecked) table.setItem(row, permList.index(x), item) else: - perms = permissions['permissions'] + perms = permissions["permissions"] for x in permList: item = QtGui.QTableWidgetItem(x) - if perms[0] == 'ADMIN' or x.upper() in perms: + if perms[0] == "ADMIN" or x.upper() in perms: item.setCheckState(Qt.Checked) else: item.setCheckState(Qt.Unchecked) table.setItem(row, permList.index(x), item) table.sortItems(0, Qt.AscendingOrder) - def noPermissionAccess(self): self.usertabel.setHidden(True) self.groupstabel.setHidden(True) self.noPermissionAccessLabel = QtGui.QLabel(self.tabs[3]) self.noPermissionAccessLabel.setGeometry(QRect(50, 50, 420, 50)) - self.noPermissionAccessLabel.setText('Could not access application ' - 'permissions. User permission is not high enough') - + self.noPermissionAccessLabel.setText( + "Could not access application " + "permissions. User permission is not high enough" + ) class UploadLayerDialog(QtGui.QDialog): - def __init__(self): + def __init__(self): QtGui.QDialog.__init__(self) self.setupUi() def setupUi(self): self.resize(400, 400) - self.setWindowTitle('Upload layer to Shogun') + self.setWindowTitle("Upload layer to Shogun") title = QtGui.QLabel(self) - title.setGeometry(50,30,300,70) - title.setText('Please select the layer you wish to upload to the \n Shogun Server') + title.setGeometry(50, 30, 300, 70) + title.setText( + "Please select the layer you wish to upload to the \n Shogun Server" + ) self.layerBox = QgsMapLayerComboBox(self) self.layerBox.setGeometry(QRect(50, 100, 300, 30)) self.uploadButton = QtGui.QPushButton(self) self.uploadButton.setGeometry(QRect(250, 160, 100, 35)) - self.uploadButton.setText('Upload Layer') + self.uploadButton.setText("Upload Layer") self.cancelButton = QtGui.QPushButton(self) self.cancelButton.setGeometry(QRect(140, 160, 100, 35)) - self.cancelButton.setText('Cancel') + self.cancelButton.setText("Cancel") self.cancelButton.clicked.connect(self.hide) self.logWindow = QtGui.QTextEdit(self) self.logWindow.setGeometry(QRect(50, 200, 300, 180)) self.logWindow.setReadOnly(True) - self.logWindow.setText('Upload Log:') + self.logWindow.setText("Upload Log:") def log(self, message): - msg = ' - ' + message + msg = " - " + message self.logWindow.append(msg) diff --git a/gui/editor.py b/gui/editor.py index ac7fe8f..ad6b0e8 100644 --- a/gui/editor.py +++ b/gui/editor.py @@ -1,39 +1,36 @@ # -*- coding: utf-8 -*- -''' +""" (c) 2025 terrestris GmbH & Co. KG, https://www.terrestris.de/en/ This code is licensed under the GPL 2.0 license. -''' +""" -__author__ = 'ntreff' -__date__ = 'July 2025' - -import sys - -from qgis.PyQt.QtCore import QObject, Qt, QTimer -from qgis.PyQt.QtWidgets import QMenu, QAction, QMessageBox -from qgis.PyQt.QtWidgets import QTreeWidgetItemIterator - -from qgis.gui import QgsMessageBar from qgis.core import QgsNetworkAccessManager +from qgis.PyQt.QtCore import QObject, Qt, QTimer +from qgis.PyQt.QtWidgets import QAction, QMenu, QMessageBox, QTreeWidgetItemIterator +from ..connection.shogunressource import ShogunRessource from .dialog_bases.connectdlg import ConnectDialog from .dialog_bases.dockwidget import DockWidget -from .editoritems import EditorItem, EditorTopItem, QgisLayerItem, ApplicationItem, LayerItem -from ..connection.shogunressource import ShogunRessource +from .editoritems import ApplicationItem, EditorItem, EditorTopItem, LayerItem, QgisLayerItem + +__author__ = "ntreff" +__date__ = "July 2025" class Editor(QObject): - '''This class controls all plugin-related GUI elements.''' + """This class controls all plugin-related GUI elements.""" - def __init__ (self, iface): - '''initialize the GUI control''' + def __init__(self, iface): + """initialize the GUI control""" QObject.__init__(self) self.iface = iface self.dock = DockWidget() self.connectdlg = ConnectDialog() - self.iface.addDockWidget( Qt.RightDockWidgetArea, self.dock) - self.dock.newConnectionButton.clicked.connect(lambda: self.showDialog(self.connectdlg)) + self.iface.addDockWidget(Qt.RightDockWidgetArea, self.dock) + self.dock.newConnectionButton.clicked.connect( + lambda: self.showDialog(self.connectdlg) + ) self.connectdlg.okButton.clicked.connect(self.setupNewConnection) self.topitem = EditorTopItem() self.connections = [] @@ -44,7 +41,7 @@ def __init__ (self, iface): self.timer = QTimer() - ''' + """ WORKAROUND: When performing requests with self.http, it will call the QgsNetworkAccessManager.instance(). For some reason @@ -52,15 +49,15 @@ def __init__ (self, iface): 'authenticationRequired' (inherited from QNetworkAccessManager) to a method where a dialog in QGIS pops up asking for the users credentials (when working with Basic Auth). We disable the signal here as the - case of wrong identifacation credentials is treated separately + case of wrong identification credentials is treated separately in def: checkConnection(self) - ''' + """ try: QgsNetworkAccessManager.instance().authenticationRequired.disconnect() - except: + except Exception as e: + print('Error occurred: ' + str(e)) pass - def on_context_menu(self, point): item = self.dock.treeWidget.itemAt(point) @@ -68,19 +65,21 @@ def on_context_menu(self, point): return if item.actiontype is None: return - actionDict = {'application': - ('Copy Application', 'Load all layers to QGIS', - 'Application Settings', 'View Application in web browser', - 'Delete Application'), - 'layer': - ('Add Layer to QGIS','Layer Settings', 'Delete Layer'), - 'qgisLayerReference': - ('Upload New Style', 'Apply Original Style'), - 'applicationsItem': - ('Create New Application', 'Refresh Applications'), - 'layersItem':('Upload New Layer from QGIS', 'Refresh Layers'), - 'connection':('Refresh Connection', 'Remove Connection'), - 'topitem':['New Connection']} + actionDict = { + "application": ( + "Copy Application", + "Load all layers to QGIS", + "Application Settings", + "View Application in web browser", + "Delete Application", + ), + "layer": ("Add Layer to QGIS", "Layer Settings", "Delete Layer"), + "qgisLayerReference": ("Upload New Style", "Apply Original Style"), + "applicationsItem": ("Create New Application", "Refresh Applications"), + "layersItem": ("Upload New Layer from QGIS", "Refresh Layers"), + "connection": ("Refresh Connection", "Remove Connection"), + "topitem": ["New Connection"], + } actions = actionDict[item.actiontype] menu = QMenu() @@ -95,50 +94,51 @@ def on_context_menu(self, point): # this could be re-written when refactoring: def connectAction(self, action, actionName, item): - if actionName == 'Copy Application': + if actionName == "Copy Application": action.triggered.connect(item.copyApplication) - elif actionName == 'View Application in web browser': - action.triggered.connect(lambda: item.ressource.viewApplicationOnline(item.id)) - elif actionName == 'New Connection': + elif actionName == "View Application in web browser": + action.triggered.connect( + lambda: item.ressource.viewApplicationOnline(item.id) + ) + elif actionName == "New Connection": action.triggered.connect(lambda: self.showDialog(self.connectdlg)) - elif actionName == 'Application Settings': + elif actionName == "Application Settings": action.triggered.connect(lambda: self.showDialog(item)) - elif actionName == 'Layer Settings': + elif actionName == "Layer Settings": action.triggered.connect(lambda: self.showDialog(item)) - elif actionName == 'Remove Connection': + elif actionName == "Remove Connection": action.triggered.connect(lambda: self.removeConnection(item)) - elif actionName == 'Refresh Connection': + elif actionName == "Refresh Connection": action.triggered.connect(lambda: self.refreshConnection(item)) - elif actionName == 'Add Layer to QGIS': + elif actionName == "Add Layer to QGIS": action.triggered.connect(lambda: item.addQgsLayer(self.iface)) - elif actionName == 'Upload New Style': + elif actionName == "Upload New Style": action.triggered.connect(lambda: self.uploadStyle(item)) - elif actionName == 'Apply Original Style': + elif actionName == "Apply Original Style": action.triggered.connect(lambda: self.downloadStyle(item)) - elif actionName == 'Load all layers to QGIS': + elif actionName == "Load all layers to QGIS": action.triggered.connect(lambda: self.loadAllAppLayers(item)) - elif actionName == 'Create New Application': + elif actionName == "Create New Application": action.triggered.connect(lambda: item.createNewApplication(self.iface)) - elif actionName == 'Upload New Layer from QGIS': + elif actionName == "Upload New Layer from QGIS": action.triggered.connect(lambda: item.createNewLayer(self.iface)) - elif actionName == 'Delete Layer': + elif actionName == "Delete Layer": action.triggered.connect(item.deleteLayer) - elif actionName == 'Delete Application': + elif actionName == "Delete Application": action.triggered.connect(item.deleteApplication) - elif actionName == 'Refresh Applications': + elif actionName == "Refresh Applications": action.triggered.connect(item.update) - elif actionName == 'Refresh Layers': + elif actionName == "Refresh Layers": action.triggered.connect(item.update) - def on_tree_item_double_clicked(self, item): if isinstance(item, QgisLayerItem): try: self.iface.showLayerProperties(item.layer) - except: + except Exception as e: + print('Error occurred: ' + str(e)) pass - def showDialog(self, item): if not isinstance(item, ConnectDialog): if item.dlg is None: @@ -156,10 +156,10 @@ def showDialog(self, item): try: dialog.setWindowState(Qt.WindowActive) dialog.activateWindow() - except: + except Exception as e: + print('Error occurred: ' + str(e)) pass - def setupNewConnection(self): # if the connect button gets clicked two times quickly it calls a new # test request before the old one is finished and making qgis crash @@ -174,28 +174,31 @@ def setupNewConnection(self): pw = self.connectdlg.passwordIn.text() if len(url) == 0 or len(name) == 0: - self.showWarning(self.connectdlg, 'Please fill in all necessary ' - 'fields') + self.showWarning(self.connectdlg, "Please fill in all necessary fields") self.connectdlg.show() return if name in self.connections: - self.showWarning(self.connectdlg, 'A connection with that name ' - 'exists already, please fill in a different name') + self.showWarning( + self.connectdlg, + "A connection with that name " + "exists already, please fill in a different name", + ) self.connectdlg.show() return - newRessource = ShogunRessource(self.iface, url, name, user, pw) + newRessource = ShogunRessource(self.iface, url, name, user, pw) connectionOk = newRessource.checkConnection() if not connectionOk[0]: self.showWarning(self.connectdlg, connectionOk[1]) self.connectdlg.show() return - bool = newRessource.updateData() - if not bool: - self.showWarning(self.connectdlg, 'Error: Could not retrieve all ' - 'data from Shogun') + result = newRessource.updateData() + if not result: + self.showWarning( + self.connectdlg, "Error: Could not retrieve all data from Shogun" + ) self.connectdlg.hide() @@ -219,25 +222,24 @@ def refreshConnection(self, item): self.expandEditorTree(item) def showWarning(self, parent, text): - warn = QMessageBox.warning(parent, 'Warning', - text, QMessageBox.Ok) + QMessageBox.warning(parent, "Warning", text, QMessageBox.Ok) def uploadStyle(self, item): success = item.uploadStyle() if success: - msg = 'New style of layer '+ item.parentShogunLayer.name - msg += ' was successfully uploaded to Shogun.' - self.iface.messageBar().pushSuccess('Success', msg) + msg = "New style of layer " + item.parentShogunLayer.name + msg += " was successfully uploaded to Shogun." + self.iface.messageBar().pushSuccess("Success", msg) else: - msg = 'New style of layer '+ item.parentShogunLayer.name - msg += ' could not be uploaded to Shogun.' - self.iface.messageBar().pushCritical('Error',msg) + msg = "New style of layer " + item.parentShogunLayer.name + msg += " could not be uploaded to Shogun." + self.iface.messageBar().pushCritical("Error", msg) def downloadStyle(self, item): success = item.downloadStyle() if not success: - msg = 'Could not download Style for layer' - self.iface.messageBar().pushCritical('Error',msg) + msg = "Could not download Style for layer" + self.iface.messageBar().pushCritical("Error", msg) def loadAllAppLayers(self, item): layerIds, shogunConnectionItem = item.getAllAppLayersById() @@ -246,9 +248,9 @@ def loadAllAppLayers(self, item): layer.addQgsLayer(self.iface) def expandEditorTree(self, connectionItem): - iter = QTreeWidgetItemIterator(connectionItem) - val = iter.value() + iterator = QTreeWidgetItemIterator(connectionItem) + val = iterator.value() while val: val.setExpanded(True) - iter += 1 - val = iter.value() + iterator += 1 + val = iterator.value() diff --git a/gui/editoritems.py b/gui/editoritems.py index 26d730b..cb18df6 100644 --- a/gui/editoritems.py +++ b/gui/editoritems.py @@ -1,85 +1,83 @@ # -*- coding: utf-8 -*- -''' +""" (c) 2025 terrestris GmbH & Co. KG, https://www.terrestris.de/en/ This code is licensed under the GPL 2.0 license. -''' - -__author__ = 'ntreff' -__date__ = 'July 2025' +""" import os import sys -from qgis.PyQt.QtWidgets import QLabel, QLineEdit, QLabel, QMessageBox, QTreeWidgetItem +from qgis.core import QgsLayerItem, QgsMapLayer, QgsPointXY, QgsProject, QgsRectangle, QgsWkbTypes +from qgis.PyQt.QtCore import Qt from qgis.PyQt.QtGui import QFont, QIcon -from qgis.PyQt.QtCore import QRect, Qt -from qgis.core import QgsPointXY, QgsProject, QgsWkbTypes - -from qgis.core import QgsMapLayer, QgsProject, QgsLayerItem -from qgis.core import QgsRectangle +from qgis.PyQt.QtWidgets import QMessageBox, QTreeWidgetItem -from ..layerutils import prepareLayerForUpload, createLayer +from ..layerutils import createLayer, prepareLayerForUpload from .dialog_bases.applicationSettings import ApplicationSettingsDialog from .dialog_bases.layerSettings import LayerSettingsDialog, UploadLayerDialog +__author__ = "ntreff" +__date__ = "July 2025" PYTHON_VERSION = sys.version_info[0] -'''This file contains all classes used in the QTreeWidget representating the -structure of the connected shogun2-webapp ressource''' +"""This file contains all classes used in the QTreeWidget representating the +structure of the connected shogun2-webapp ressource""" -## TODO: the classes LayerItem and ApplicationItem could be merged or inherit +# TODO: the classes LayerItem and ApplicationItem could be merged or inherit # from a common abstract class, the same is LayerSettingsDialog and # ApplicationSettingsDialog. Note when refactoring for future release + class TreeItem(QTreeWidgetItem): - ''' This class works as a kind of 'abstract class' from which all other - classes in this module inherit''' - def __init__(self, icon = None, text = None): + """This class works as a kind of 'abstract class' from which all other + classes in this module inherit""" + + def __init__(self, icon=None, text=None): QTreeWidgetItem.__init__(self) self.setText(0, text) if icon is not None: if isinstance(icon, QIcon): self.setIcon(0, icon) elif isinstance(icon, str): - iconPath = ':/plugins/shoguneditor/' + icon + iconPath = ":/plugins/shoguneditor/" + icon self.setIcon(0, QIcon(iconPath)) self.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) self.actiontype = None - class EditorTopItem(TreeItem): def __init__(self): TreeItem.__init__( - self, 'shogun-logo-50x50px-round-blue.png', 'Shogun Connections') - self.actiontype = 'topitem' - font = QFont('Arial',14) + self, "shogun-logo-50x50px-round-blue.png", "Shogun Connections" + ) + self.actiontype = "topitem" + font = QFont("Arial", 14) font.setBold(True) - self.setFont(0,font) - + self.setFont(0, font) class EditorItem(TreeItem): def __init__(self, shogunRessource): self.ressource = shogunRessource self.name = shogunRessource.name - TreeItem.__init__(self, 'shogun-logo-25x-25px.png', self.name) + TreeItem.__init__(self, "shogun-logo-25x-25px.png", self.name) self.isConnected = False - self.actiontype = 'connection' - font = QFont('Arial',12) + self.actiontype = "connection" + font = QFont("Arial", 12) font.setBold(True) - self.setFont(0,font) + self.setFont(0, font) self.populateTree(shogunRessource) def disconnectSignals(self): if self.layersitem is not None: - for layer in self.layersitem.layerlist: - try: - QgsProject.instance().layerRemoved.disconnect(layer.updateLayerList) - except: - pass - + print("disconnecting signals for layersitem") + # TODO: Check - code seems to be unreachable + # for layer in self.layersitem.layerlist: + # try: + # QgsProject.instance().layerRemoved.disconnect(layer.updateLayerList) + # except: + # pass def update(self): for x in [self.applicationsitem, self.layersitem]: @@ -93,27 +91,25 @@ def populateTree(self, shogunRessource): self.addChildren([self.applicationsitem, self.layersitem]) - class ApplicationsItem(TreeItem): def __init__(self, ressource): - TreeItem.__init__(self, 'applications-logo.png', 'Applications') + TreeItem.__init__(self, "applications-logo.png", "Applications") self.ressource = ressource self.applications = self.ressource.getApplicationIdsAndNames() self.applicationlist = [] - self.actiontype = 'applicationsItem' - font = QFont('Arial',10) + self.actiontype = "applicationsItem" + font = QFont("Arial", 10) font.setBold(True) - self.setFont(0,font) + self.setFont(0, font) self.populate() - def createNewApplication(self, iface): - self.newApplication = ApplicationItem(None, '', self.ressource) + self.newApplication = ApplicationItem(None, "", self.ressource) self.newApplication.createStaticSettings(iface) self.dlg = self.newApplication.dlg self.dlg.setEditState(True) self.dlg.editCheckBox.setHidden(True) - self.dlg.pushButtonOk.setText('Create') + self.dlg.pushButtonOk.setText("Create") self.dlg.pushButtonOk.clicked.disconnect(self.dlg.hide) self.dlg.pushButtonOk.clicked.connect(self.checkNewApplicationSettings) self.dlg.pushButtonCancel.clicked.disconnect(self.newApplication.stopEditing) @@ -123,15 +119,32 @@ def createNewApplication(self, iface): for toolbox in self.dlg.tools.values(): toolbox.setChecked(True) - exampleHomeview = {'mapconfig' : { - 'id' : None, - 'center' : {'x' : 0.0, 'y' : 0.0}, - 'zoom' : 18, - 'resolutions': [156543.03390625, 78271.516953125, 39135.7584765625, - 19567.87923828125, 9783.939619140625, 4891.9698095703125, 2445.9849047851562, - 1222.9924523925781, 611.4962261962891, 305.74811309814453, 152.87405654907226, - 76.43702827453613, 38.218514137268066, 19.109257068634033, 9.554628534317017, - 4.777314267158508, 2.388657133579254, 1.194328566789627, 0.5971642833948135] + exampleHomeview = { + "mapconfig": { + "id": None, + "center": {"x": 0.0, "y": 0.0}, + "zoom": 18, + "resolutions": [ + 156543.03390625, + 78271.516953125, + 39135.7584765625, + 19567.87923828125, + 9783.939619140625, + 4891.9698095703125, + 2445.9849047851562, + 1222.9924523925781, + 611.4962261962891, + 305.74811309814453, + 152.87405654907226, + 76.43702827453613, + 38.218514137268066, + 19.109257068634033, + 9.554628534317017, + 4.777314267158508, + 2.388657133579254, + 1.194328566789627, + 0.5971642833948135, + ], } } @@ -153,29 +166,34 @@ def checkNewApplicationSettings(self): name = self.dlg.nameEdit.text() if len(name) == 0: - self.dlg.nameEdit.setStyleSheet('QLineEdit { background-color: #ff3333; }') + self.dlg.nameEdit.setStyleSheet("QLineEdit { background-color: #ff3333; }") self.dlg.warnLabel.setHidden(False) return else: - self.dlg.nameEdit.setStyleSheet('QLineEdit { border-color : #000000; }') + self.dlg.nameEdit.setStyleSheet("QLineEdit { border-color : #000000; }") self.dlg.warnLabel.setHidden(True) newhomeview = self.newApplication.getHomeViewChanges() if newhomeview is None: - newhomeview = self.newApplication.homeview['mapconfig'] + newhomeview = self.newApplication.homeview["mapconfig"] data = { - 'id' : None, - 'name' : name, - 'description' : self.dlg.descriptionEdit.text(), - 'language' : self.dlg.languageBox.currentText(), - 'isPublic' : self.dlg.boxPublic.isChecked(), - 'isActive' : self.dlg.boxActive.isChecked(), - 'activeTools' : [x['id'] for x in self.newApplication.getActiveToolsChanges()], - 'projection' : 'EPSG:3857', - 'center' : {'x' : newhomeview['center']['x'], 'y' : newhomeview['center']['y']}, - 'zoom' : newhomeview['zoom'], - 'layerTree': 4535 - } + "id": None, + "name": name, + "description": self.dlg.descriptionEdit.text(), + "language": self.dlg.languageBox.currentText(), + "isPublic": self.dlg.boxPublic.isChecked(), + "isActive": self.dlg.boxActive.isChecked(), + "activeTools": [ + x["id"] for x in self.newApplication.getActiveToolsChanges() + ], + "projection": "EPSG:3857", + "center": { + "x": newhomeview["center"]["x"], + "y": newhomeview["center"]["y"], + }, + "zoom": newhomeview["zoom"], + "layerTree": 4535, + } self.dlg.hide() uploaded = self.ressource.uploadNewApplication(data) @@ -190,7 +208,7 @@ def populate(self): self.sortChildren(0, Qt.AscendingOrder) def update(self): - self.applications = self.ressource.getApplicationIdsAndNames(reload = True) + self.applications = self.ressource.getApplicationIdsAndNames(reload=True) for item in self.applicationlist: self.removeChild(item) self.applicationlist = [] @@ -201,26 +219,25 @@ def update(self): self.sortChildren(0, Qt.AscendingOrder) - class ApplicationItem(TreeItem): - def __init__(self, id, name, ressource): + def __init__(self, _id, name, ressource): TreeItem.__init__(self, None, name) - self.actiontype = 'application' - self.id = id + self.actiontype = "application" + self.id = _id self.name = name self.dlg = None self.ressource = ressource self.activeTools = [] self.homeview = None self.settings = { - 'id' : 0, - 'name' : '', - 'description' : '', - 'language' : '', - 'open' : False, - 'active' : False, - 'activeTools' : [], - } + "id": 0, + "name": "", + "description": "", + "language": "", + "open": False, + "active": False, + "activeTools": [], + } def createStaticSettings(self, iface): self.dlg = ApplicationSettingsDialog() @@ -240,7 +257,6 @@ def createStaticSettings(self, iface): self.dlg.homeviewCenterEditY.textEdited.connect(lambda: self.zoomToNewExtent()) self.dlg.jumpButtonNew.clicked.connect(lambda: self.zoomToNewExtent()) - def setOriginalExtent(self): disableSignals = [self.dlg.homeviewZoomBox, self.dlg.homeviewCenterEditX] disableSignals.append(self.dlg.homeviewCenterEditY) @@ -249,22 +265,24 @@ def setOriginalExtent(self): for qobject in disableSignals: qobject.blockSignals(True) - self.dlg.homeviewCenterEditX.setText(str(self.homeview['mapconfig']['center']['x'])) - self.dlg.homeviewCenterEditY.setText(str(self.homeview['mapconfig']['center']['y'])) - self.dlg.homeviewZoomBox.setValue(self.homeview['mapconfig']['zoom']) + self.dlg.homeviewCenterEditX.setText( + str(self.homeview["mapconfig"]["center"]["x"]) + ) + self.dlg.homeviewCenterEditY.setText( + str(self.homeview["mapconfig"]["center"]["y"]) + ) + self.dlg.homeviewZoomBox.setValue(self.homeview["mapconfig"]["zoom"]) self.populateExtentEdits(self.origExtRect) for qobject in disableSignals: qobject.blockSignals(False) - def populateExtentEdits(self, extent): self.dlg.extentEdits[0].setText(str(extent.xMinimum())) self.dlg.extentEdits[1].setText(str(extent.yMinimum())) self.dlg.extentEdits[2].setText(str(extent.xMaximum())) self.dlg.extentEdits[3].setText(str(extent.yMinimum())) - def setQgsExtent(self): rect = self.iface.mapCanvas().extent() self.populateExtentEdits(rect) @@ -273,8 +291,8 @@ def setQgsExtent(self): self.dlg.homeviewCenterEditY.setText(str(center.y())) zoom = self.iface.mapCanvas().mapUnitsPerPixel() - #get shogun resolutions as enumerated list - resolutions = list(enumerate(self.homeview['mapconfig']['resolutions'])) + # get shogun resolutions as enumerated list + resolutions = list(enumerate(self.homeview["mapconfig"]["resolutions"])) cursor = resolutions[0] for res in resolutions: if abs(zoom - res[1]) < abs(zoom - cursor[1]): @@ -283,24 +301,22 @@ def setQgsExtent(self): self.dlg.homeviewZoomBox.setValue(cursor[0]) - def zoomToOrigExtent(self): - #first set the original homeview center as the new canvas center - center = self.homeview['mapconfig']['center'] - point = QgsPointXY(center['x'],center['y']) + # first set the original homeview center as the new canvas center + center = self.homeview["mapconfig"]["center"] + point = QgsPointXY(center["x"], center["y"]) self.iface.mapCanvas().setCenter(point) - zoomlvl = self.homeview['mapconfig']['zoom'] - zoom = self.homeview['mapconfig']['resolutions'][zoomlvl] + zoomlvl = self.homeview["mapconfig"]["zoom"] + zoom = self.homeview["mapconfig"]["resolutions"][zoomlvl] currentResolution = self.iface.mapCanvas().mapUnitsPerPixel() if currentResolution != zoom: - diff = zoom/currentResolution + diff = zoom / currentResolution self.iface.mapCanvas().zoomByFactor(diff) self.iface.mapCanvas().refresh() - def zoomToNewExtent(self): x = float(self.dlg.homeviewCenterEditX.text()) y = float(self.dlg.homeviewCenterEditY.text()) @@ -308,17 +324,15 @@ def zoomToNewExtent(self): self.iface.mapCanvas().setCenter(point) zoomlvl = self.dlg.homeviewZoomBox.value() - zoom = self.homeview['mapconfig']['resolutions'][zoomlvl] + zoom = self.homeview["mapconfig"]["resolutions"][zoomlvl] currentResolution = self.iface.mapCanvas().mapUnitsPerPixel() if currentResolution != zoom: - diff = zoom/currentResolution + diff = zoom / currentResolution self.iface.mapCanvas().zoomByFactor(diff) self.iface.mapCanvas().refresh() - - def populateSettings(self): # get application settings as a dict representing the json settings = self.ressource.getApplicationAttrsById(self.id) @@ -326,30 +340,42 @@ def populateSettings(self): self.settings[attr] = settings[attr] # set the general settings - self.dlg.nameEdit.setText(settings['name']) - self.dlg.descriptionEdit.setText(settings['description']) - self.dlg.languageBox.setCurrentIndex(self.dlg.languageBox.findText( - settings['language'])) - self.dlg.boxPublic.setChecked(settings['open']) - self.dlg.boxActive.setChecked(settings['active']) - + self.dlg.nameEdit.setText(settings["name"]) + self.dlg.descriptionEdit.setText(settings["description"]) + self.dlg.languageBox.setCurrentIndex( + self.dlg.languageBox.findText(settings["language"]) + ) + self.dlg.boxPublic.setChecked(settings["open"]) + self.dlg.boxActive.setChecked(settings["active"]) # set the configured homeview - self.mapConfigId = settings['viewport']['subModules'][0]['subModules'][0]['mapConfig']['id'] - self.extentId = settings['viewport']['subModules'][0]['subModules'][0]['mapConfig']['extent']['id'] + self.mapConfigId = settings["viewport"]["subModules"][0]["subModules"][0][ + "mapConfig" + ]["id"] + self.extentId = settings["viewport"]["subModules"][0]["subModules"][0][ + "mapConfig" + ]["extent"]["id"] # getHomeviewByIds returns homeview as dict {'mapconfig':XY,'extent':XY} self.homeview = self.ressource.getHomeviewByIds(self.mapConfigId, self.extentId) - self.dlg.homeviewZoomBox.setMaximum(len(self.homeview['mapconfig']['resolutions'])-1) - origExtent = list(self.homeview['extent']['lowerLeft'].values())+list(self.homeview['extent']['upperRight'].values()) - self.origExtRect = QgsRectangle(origExtent[0],origExtent[1],origExtent[2],origExtent[3]) + self.dlg.homeviewZoomBox.setMaximum( + len(self.homeview["mapconfig"]["resolutions"]) - 1 + ) + origExtent = list(self.homeview["extent"]["lowerLeft"].values()) + list( + self.homeview["extent"]["upperRight"].values() + ) + self.origExtRect = QgsRectangle( + origExtent[0], origExtent[1], origExtent[2], origExtent[3] + ) self.setOriginalExtent() - self.epsg = self.homeview['mapconfig']['projection'] + self.epsg = self.homeview["mapconfig"]["projection"] if PYTHON_VERSION >= 3: currentQgsCrs = QgsProject.instance().crs().authid() else: - currentQgsCrs = self.iface.mapCanvas().mapRenderer().destinationCrs().authid() + currentQgsCrs = ( + self.iface.mapCanvas().mapRenderer().destinationCrs().authid() + ) if self.epsg != currentQgsCrs: self.dlg.showEpsgWarning(currentQgsCrs, self.epsg) @@ -357,146 +383,147 @@ def populateSettings(self): self.dlg.hideEpsgWarning() # set the application tools - self.activeTools = [{'id' : tool['id']} for tool in settings['activeTools']] + self.activeTools = [{"id": tool["id"]} for tool in settings["activeTools"]] for tool in self.activeTools: - self.dlg.tools[tool['id']].setChecked(True) + self.dlg.tools[tool["id"]].setChecked(True) # populate the layer tree and the table of available layers - layerTree = settings['layerTree'] + layerTree = settings["layerTree"] self.dlg.layertreewidget.populateTree(layerTree) allLayers = self.ressource.getLayerIdsAndNames() self.dlg.layerlistwidget.populateList(allLayers) # populate the permissions tables - self.userPermissions = self.ressource.getObjectPermissions(self.id, 'Application', 'User') - self.groupPermissions = self.ressource.getObjectPermissions(self.id, 'Application', 'UserGroup') - - if not self.userPermissions['success'] or not self.groupPermissions['success']: + self.userPermissions = self.ressource.getObjectPermissions( + self.id, "Application", "User" + ) + self.groupPermissions = self.ressource.getObjectPermissions( + self.id, "Application", "UserGroup" + ) + + if not self.userPermissions["success"] or not self.groupPermissions["success"]: self.dlg.noPermissionAccess() else: - self.dlg.populateTable('users', self.userPermissions['data']['permissions']) - self.dlg.populateTable('groups', self.groupPermissions['data']['permissions']) + self.dlg.populateTable("users", self.userPermissions["data"]["permissions"]) + self.dlg.populateTable( + "groups", self.groupPermissions["data"]["permissions"] + ) def getAllAppLayersById(self): rootConnectionItem = self.parent().parent() settings = self.ressource.getApplicationAttrsById(self.id) listOfIds = [] - layerTree = settings['layerTree'] + layerTree = settings["layerTree"] # for reasons of simplicity we only search for belonging layers down to the # third level of the tree - if layerTree['leaf']: - listOfIds.append(layerTree['layer']['id']) + if layerTree["leaf"]: + listOfIds.append(layerTree["layer"]["id"]) return listOfIds, rootConnectionItem else: - children = layerTree['children'] + children = layerTree["children"] for child in children: - if child['leaf']: - listOfIds.append(child['layer']['id']) + if child["leaf"]: + listOfIds.append(child["layer"]["id"]) else: - children = child['children'] + children = child["children"] for child in children: - if child['leaf']: - listOfIds.append(child['layer']['id']) + if child["leaf"]: + listOfIds.append(child["layer"]["id"]) else: - children = child['children'] + children = child["children"] for child in children: - if child['leaf']: - listOfIds.append(child['layer']['id']) + if child["leaf"]: + listOfIds.append(child["layer"]["id"]) return listOfIds, rootConnectionItem - def getAllChanges(self): allChanges = {} if self.getGeneralChanges() is not None: - allChanges['general'] = self.getGeneralChanges() + allChanges["general"] = self.getGeneralChanges() if self.getActiveToolsChanges() is not None: - if not 'general' in allChanges: - allChanges['general'] = {} - allChanges['general']['activeTools'] = self.getActiveToolsChanges() + if "general" not in allChanges: + allChanges["general"] = {} + allChanges["general"]["activeTools"] = self.getActiveToolsChanges() if self.getHomeViewChanges() is not None: - allChanges['homeview'] = self.getHomeViewChanges() + allChanges["homeview"] = self.getHomeViewChanges() layerTreeChanges = self.dlg.layertreewidget.getLayerTreeChanges() if layerTreeChanges is not None: - allChanges['layerTree'] = layerTreeChanges + allChanges["layerTree"] = layerTreeChanges - userPermissionChanges = self.getPermissionChanges('User') + userPermissionChanges = self.getPermissionChanges("User") if userPermissionChanges is not None: - allChanges['permissions'] = {'User' : userPermissionChanges} - groupPermissionChanges = self.getPermissionChanges('UserGroup') + allChanges["permissions"] = {"User": userPermissionChanges} + groupPermissionChanges = self.getPermissionChanges("UserGroup") if groupPermissionChanges is not None: - if not 'permissions' in allChanges: - allChanges['permissions'] = {} - allChanges['permissions']['UserGroup'] = groupPermissionChanges + if "permissions" not in allChanges: + allChanges["permissions"] = {} + allChanges["permissions"]["UserGroup"] = groupPermissionChanges return allChanges - def getGeneralChanges(self): changes = {} - if self.dlg.nameEdit.text() != self.settings['name']: - changes['name'] = self.dlg.nameEdit.text() - if self.dlg.descriptionEdit.text() != self.settings['description']: - changes['description'] = self.dlg.descriptionEdit.text() - if self.dlg.languageBox.currentText() != self.settings['language']: - changes['languae'] = self.dlg.languageBox.currentText() - if self.dlg.boxPublic.isChecked() != self.settings['open']: - changes['open'] = self.dlg.boxPublic.isChecked() - if self.dlg.boxActive.isChecked() != self.settings['active']: - changes['active'] = self.dlg.boxActive.isChecked() + if self.dlg.nameEdit.text() != self.settings["name"]: + changes["name"] = self.dlg.nameEdit.text() + if self.dlg.descriptionEdit.text() != self.settings["description"]: + changes["description"] = self.dlg.descriptionEdit.text() + if self.dlg.languageBox.currentText() != self.settings["language"]: + changes["languae"] = self.dlg.languageBox.currentText() + if self.dlg.boxPublic.isChecked() != self.settings["open"]: + changes["open"] = self.dlg.boxPublic.isChecked() + if self.dlg.boxActive.isChecked() != self.settings["active"]: + changes["active"] = self.dlg.boxActive.isChecked() if len(changes) == 0: return None else: return changes - def getActiveToolsChanges(self): toolsCheckedInGui = [] for toolid in self.dlg.tools.keys(): if self.dlg.tools[toolid].isChecked(): toolsCheckedInGui.append(toolid) - #convert the lists to set for in case they have different orders - if set(toolsCheckedInGui) != set([tool['id'] for tool in self.activeTools]): - return [{'id': toolid} for toolid in toolsCheckedInGui] + # convert the lists to set for in case they have different orders + if set(toolsCheckedInGui) != set([tool["id"] for tool in self.activeTools]): + return [{"id": toolid} for toolid in toolsCheckedInGui] else: return None - def getHomeViewChanges(self): zoom = self.dlg.homeviewZoomBox.value() x = float(self.dlg.homeviewCenterEditX.text()) y = float(self.dlg.homeviewCenterEditY.text()) - conf = self.homeview['mapconfig'] + conf = self.homeview["mapconfig"] newhomeview = None - if (conf['zoom'] != zoom or - conf['center']['x'] != x or - conf['center']['y'] != y): + if conf["zoom"] != zoom or conf["center"]["x"] != x or conf["center"]["y"] != y: newhomeview = { - 'id': self.homeview['mapconfig']['id'], 'zoom': zoom, - 'center':{'x':x, 'y':y} } + "id": self.homeview["mapconfig"]["id"], + "zoom": zoom, + "center": {"x": x, "y": y}, + } return newhomeview - - def getPermissionChanges(self, type): - if type == 'User': - permissions = self.userPermissions['data'] + def getPermissionChanges(self, _type): + if _type == "User": + permissions = self.userPermissions["data"] table = self.dlg.usertabel - elif type == 'UserGroup': - permissions = self.groupPermissions['data'] + elif _type == "UserGroup": + permissions = self.groupPermissions["data"] table = self.dlg.groupstabel else: return - oldPermissionList = permissions['permissions'] + oldPermissionList = permissions["permissions"] newPermissionList = [] for entry in oldPermissionList: - name = entry['displayTitle'] + name = entry["displayTitle"] row = None for x in range(table.rowCount()): if table.verticalHeaderItem(x).text() == name: @@ -506,33 +533,32 @@ def getPermissionChanges(self, type): return currentPermissionsInTable = [] - for p in enumerate(['READ', 'UPDATE', 'DELETE']): + for p in enumerate(["READ", "UPDATE", "DELETE"]): if table.item(row, p[0]).checkState() == 2: currentPermissionsInTable.append(p[1]) - if entry['permissions'] == None: + if entry["permissions"] is None: oldPermissions = [] else: - oldPermissions = entry['permissions']['permissions'] - if oldPermissions == ['ADMIN']: - oldPermissions = ['READ', 'UPDATE', 'DELETE'] + oldPermissions = entry["permissions"]["permissions"] + if oldPermissions == ["ADMIN"]: + oldPermissions = ["READ", "UPDATE", "DELETE"] if set(currentPermissionsInTable) != set(oldPermissions): newEntry = { - 'targetEntity' : permissions['targetEntity'], - 'permissions' : [{ - 'permissions' : { - 'permissions' : currentPermissionsInTable - }, - 'targetEntity' : entry['targetEntity'] - }] - } + "targetEntity": permissions["targetEntity"], + "permissions": [ + { + "permissions": {"permissions": currentPermissionsInTable}, + "targetEntity": entry["targetEntity"], + } + ], + } newPermissionList.append(newEntry) if len(newPermissionList) == 0: newPermissionList = None return newPermissionList - def startEditing(self): self.dlg.setEditState(True) self.dlg.editCheckBox.clicked.disconnect(self.startEditing) @@ -540,12 +566,16 @@ def startEditing(self): self.dlg.editCheckBox.clicked.connect(self.stopEditing) self.dlg.pushButtonOk.clicked.connect(self.saveChanges) - def stopEditing(self): changes = self.getAllChanges() if len(changes) > 0: - warn = QMessageBox.warning(self.dlg, 'Warning','All changes will be' - ' lost. Continue?', QMessageBox.Cancel, QMessageBox.Ok) + warn = QMessageBox.warning( + self.dlg, + "Warning", + "All changes will be" " lost. Continue?", + QMessageBox.Cancel, + QMessageBox.Ok, + ) if warn == QMessageBox.Ok: self.populateSettings() else: @@ -553,86 +583,94 @@ def stopEditing(self): return self.stoppedEditing() - def saveChanges(self): changes = self.getAllChanges() if len(changes) > 0: - conf = QMessageBox.warning(self.dlg, 'Confirm', 'Please confirm you' - ' want to save the changes', QMessageBox.Cancel, QMessageBox.Ok) + conf = QMessageBox.warning( + self.dlg, + "Confirm", + "Please confirm you" " want to save the changes", + QMessageBox.Cancel, + QMessageBox.Ok, + ) if conf == QMessageBox.Ok: - if 'general' in changes: - #'changes' will be sent as the data, therefore append the id - #as a key, so that the data will be recognized - changes['general']['id'] = self.id - responseCode = self.ressource.editApplication(self.id, - changes['general']) - #update the name in the tree view: - if 'name' in changes['general'] and responseCode > 199 and responseCode < 299: - self.name = changes['general']['name'] + if "general" in changes: + # 'changes' will be sent as the data, therefore append the id + # as a key, so that the data will be recognized + changes["general"]["id"] = self.id + responseCode = self.ressource.editApplication( + self.id, changes["general"] + ) + # update the name in the tree view: + if ("name" in changes["general"] and responseCode > 199 and responseCode < 299): + self.name = changes["general"]["name"] self.setText(0, self.name) - if 'homeview' in changes: - self.ressource.editMapConfig( - self.mapConfigId, changes['homeview']) + if "homeview" in changes: + self.ressource.editMapConfig(self.mapConfigId, changes["homeview"]) - #update the homeview after the changes + # update the homeview after the changes self.ressource.updateExtentsAndMapConfigs() self.homeview = self.ressource.getHomeviewByIds( - self.mapConfigId, self.extentId) + self.mapConfigId, self.extentId + ) - if 'layerTree' in changes: - deletedItemIds = changes['layerTree']['deleteItems'] + if "layerTree" in changes: + deletedItemIds = changes["layerTree"]["deleteItems"] responses = [] if len(deletedItemIds) > 0: - for id in deletedItemIds: - responses.append(self.ressource.deleteLayerTreeItem(id)) - newItems = changes['layerTree']['newItems'] + for _id in deletedItemIds: + responses.append(self.ressource.deleteLayerTreeItem(_id)) + newItems = changes["layerTree"]["newItems"] if len(newItems) > 0: for item in newItems: responses.append(self.ressource.createLayerTreeItem(item)) - changeItems = changes['layerTree']['changeItems'] + changeItems = changes["layerTree"]["changeItems"] if len(changeItems) > 0: for item in changeItems: - id = item['id'] + _id = item["id"] responses.append( - self.ressource.updateLayerTreeItem(id, item)) + self.ressource.updateLayerTreeItem(_id, item) + ) res = 200 for x in responses: if not 199 < x < 210: res = x - self.ressource.userInfo(res, 'Layertree', 'updated') + self.ressource.userInfo(res, "Layertree", "updated") - if 'permissions' in changes: + if "permissions" in changes: responses = 200 - for x in changes['permissions'].keys(): - for entry in changes['permissions'][x]: + for x in changes["permissions"].keys(): + for entry in changes["permissions"][x]: responseBool = self.ressource.editObjectPermission( - self.id, 'ProjectApplication', x, entry) + self.id, "ProjectApplication", x, entry + ) if not responseBool: responses = 400 self.userPermissions = self.ressource.getObjectPermissions( - self.id, 'Application', 'User') + self.id, "Application", "User" + ) self.groupPermissions = self.ressource.getObjectPermissions( - self.id, 'Application', 'UserGroup') - self.ressource.userInfo(responses, - 'Application permissions', 'updated') + self.id, "Application", "UserGroup" + ) + self.ressource.userInfo( + responses, "Application permissions", "updated" + ) - #update the class internal copy of the general settings + # update the class internal copy of the general settings newSettings = self.ressource.updateSingleApplication(self.id) for attr in self.settings: self.settings[attr] = newSettings[attr] - self.dlg.layertreewidget.populateTree(newSettings['layerTree']) + self.dlg.layertreewidget.populateTree(newSettings["layerTree"]) # if the user clicked 'cancel', just return and stay in edit mode: else: return else: - info = QMessageBox.information(self.dlg, 'Note', - 'No changes were found', QMessageBox.Ok) + QMessageBox.information(self.dlg, "Note", "No changes were found", QMessageBox.Ok) self.stoppedEditing() - def stoppedEditing(self): self.dlg.editCheckBox.clicked.disconnect(self.stopEditing) self.dlg.pushButtonOk.clicked.disconnect(self.saveChanges) @@ -641,17 +679,17 @@ def stoppedEditing(self): self.dlg.editCheckBox.setChecked(False) self.dlg.setEditState(False) - def deleteApplication(self): - txt = 'Please confirm you want to permanently delete ' - txt += 'the selected application in Shogun' - conf = QMessageBox.warning(self.dlg, 'Confirm',txt , QMessageBox.Cancel, QMessageBox.Ok) + txt = "Please confirm you want to permanently delete " + txt += "the selected application in Shogun" + conf = QMessageBox.warning( + self.dlg, "Confirm", txt, QMessageBox.Cancel, QMessageBox.Ok + ) if conf == QMessageBox.Ok: self.ressource.deleteApplication(self.id) self.ressource.updateApplications() self.parent().update() - def copyApplication(self): self.ressource.copyApplication(self.id, self.name) self.parent().update() @@ -659,14 +697,14 @@ def copyApplication(self): class LayersItem(TreeItem): def __init__(self, ressource): - TreeItem.__init__(self, 'layers-logo.png', 'Layers') + TreeItem.__init__(self, "layers-logo.png", "Layers") self.ressource = ressource self.layers = self.ressource.getLayerIdsAndNames() self.layerlist = [] - self.actiontype = 'layersItem' - font = QFont('Arial',10) + self.actiontype = "layersItem" + font = QFont("Arial", 10) font.setBold(True) - self.setFont(0,font) + self.setFont(0, font) self.populate() def populate(self): @@ -677,16 +715,16 @@ def populate(self): self.sortChildren(0, Qt.AscendingOrder) def update(self): - #update the list of current shogun layers: - self.layers = self.ressource.getLayerIdsAndNames(reload = True) + # update the list of current shogun layers: + self.layers = self.ressource.getLayerIdsAndNames(reload=True) layerIdList = [layer[0] for layer in self.layers] - #remove every item that is not in the updated list: + # remove every item that is not in the updated list: for layer in self.layerlist: if layer.id not in layerIdList: self.removeChild(layer) self.layerlist.remove(layer) - #check if new layers appeared and append them: + # check if new layers appeared and append them: currentLayerIdList = [layer.id for layer in self.layerlist] for layer in self.layers: if layer[0] not in currentLayerIdList: @@ -695,12 +733,10 @@ def update(self): self.layerlist.append(item) self.sortChildren(0, Qt.AscendingOrder) - - def createNewLayer(self, iface): self.uploadDialog = UploadLayerDialog() - #mapLayers = QgsMapLayerRegistry.instance().mapLayers().values() - #self.uploadDialog.layerBox.addItems([layer.name() for layer in mapLayers]) + # mapLayers = QgsMapLayerRegistry.instance().mapLayers().values() + # self.uploadDialog.layerBox.addItems([layer.name() for layer in mapLayers]) self.uploadDialog.uploadButton.clicked.connect(self.uploadLayerAction) self.uploadDialog.show() @@ -710,54 +746,61 @@ def uploadLayerAction(self): pathToZipFile, pathToTempDir = prepareLayerForUpload(layer, self.uploadDialog) if pathToZipFile: if layer.type() == QgsMapLayer.VectorLayer: - type = 'Vector' + layer_type = "Vector" else: - if layer.providerType() == 'wms': + if layer.providerType() == "wms": success = self.ressource.publishWmsLayer() if success: - self.uploadDialog.log('WMS layer ' + layer.name() + '' - 'was successfully published') + self.uploadDialog.log( + "WMS layer " + layer.name() + "" + "was successfully published" + ) self.update() else: - self.uploadDialog.log('Publishing WMS ' - 'layer ' + layer.name() + ' was not successfull') + self.uploadDialog.log( + "Publishing WMS " + "layer " + layer.name() + " was not successfull" + ) return - else: - type = 'Raster' - success = self.ressource.uploadLayer(pathToZipFile, type) + layer_type = "Raster" + success = self.ressource.uploadLayer(pathToZipFile, layer_type) if success: - self.uploadDialog.log('Layer ' + layer.name() + ' was successfully uploaded') + self.uploadDialog.log( + "Layer " + layer.name() + " was successfully uploaded" + ) self.update() else: - self.uploadDialog.log('Uploading layer ' + layer.name() + ' was not successfull') + self.uploadDialog.log( + "Uploading layer " + layer.name() + " was not successfull" + ) # after the process has finished we delete the created zipfile and # temporary directory for cleaning up try: os.remove(pathToZipFile) os.rmdir(pathToTempDir) - except: + except Exception as e: + print('Error occurred: ' + str(e)) pass - class LayerItem(TreeItem): - def __init__(self, id, name, datatype, source, ressource): + def __init__(self, _id, name, datatype, source, ressource): # unfortunately there is no option to retrieve the layer geometry (i. e. # point/line/polygon for vectorlayers) just from the json object. # Therefore they get a default polygon icon which will be updated as soon as # layer is added as WFS to QGIS - see def addQgsLayer() - if datatype == 'Raster': + if datatype == "Raster": self.icon = QgsLayerItem.iconRaster() - elif datatype == 'Vector': + elif datatype == "Vector": self.icon = QgsLayerItem.iconPolygon() else: self.icon = QgsLayerItem.iconDefault() TreeItem.__init__(self, self.icon, name) - self.id = id - self.actiontype = 'layer' + self.id = _id + self.actiontype = "layer" self.ressource = ressource self.dlg = None self.name = name @@ -765,15 +808,11 @@ def __init__(self, id, name, datatype, source, ressource): self.datatype = datatype self.qgisLayers = [] self.settings = { - 'name' : '', - 'appearance' : { - 'hoverTemplate' : None, - 'opacity' : 1 - } + "name": "", + "appearance": {"hoverTemplate": None, "opacity": 1}, } - - QgsProject.instance().layerRemoved.connect(self.updateLayerList) + QgsProject.instance().layerRemoved.connect(self.updateLayerList) def updateLayerList(self): if self.qgisLayers == []: @@ -784,13 +823,12 @@ def updateLayerList(self): self.qgisLayers.remove(qgisLayerItem) self.removeChild(qgisLayerItem) - def addQgsLayer(self, iface): currentCrs = iface.mapCanvas().mapSettings().destinationCrs() if currentCrs.isValid(): epsg = currentCrs.authid() else: - epsg = 'EPSG:3857' + epsg = "EPSG:3857" # layerutils layer = createLayer(self, epsg) if not layer: @@ -800,16 +838,18 @@ def addQgsLayer(self, iface): self.addChild(qgisLayerItem) self.setExpanded(True) - #for an odd reason there appears a warning below the layer if it is a - #wms layer from shogun - workaround to hide this: - if layer.dataProvider().name() == 'wms' and self.source['url'].startswith('/shogun2-webapp/'): + # for an odd reason there appears a warning below the layer if it is a + # wms layer from shogun - workaround to hide this: + if layer.dataProvider().name() == "wms" and self.source["url"].startswith( + "/shogun2-webapp/" + ): root = QgsProject.instance().layerTreeRoot() layerNode = root.findLayer(layer.id()) layerNode.setExpanded(False) # when the layer has loaded for the first time and it's icon # is still on iconDefault, update the icon with the correct geometry - if not self.datatype == 'Raster': + if not self.datatype == "Raster": if layer.type() == QgsMapLayer.VectorLayer: geom = layer.geometryType() @@ -823,59 +863,62 @@ def addQgsLayer(self, iface): self.icon = QgsLayerItem.iconRaster() self.setIcon(0, self.icon) - def createStaticSettings(self): self.dlg = LayerSettingsDialog() self.dlg.setEditState(False) self.dlg.pushButtonOk.clicked.connect(self.normalClose) self.dlg.editCheckBox.clicked.connect(self.startEditing) self.dlg.pushButtonCancel.clicked.connect(self.stopEditing) - if self.datatype == 'Vector' or self.datatype == '': - fieldNames = self.ressource.getFieldNamesFromWfs(self.source['layerNames']) + if self.datatype == "Vector" or self.datatype == "": + fieldNames = self.ressource.getFieldNamesFromWfs(self.source["layerNames"]) if fieldNames: self.dlg.hoverBox.addItems(fieldNames) else: self.dlg.deactivateHoverEditing() - def populateSettings(self): settings = self.ressource.getLayerAttrsById(self.id) for attr in self.settings: self.settings[attr] = settings[attr] - self.dlg.nameEdit.setText(settings['name']) + self.dlg.nameEdit.setText(settings["name"]) - opac = settings['appearance']['opacity'] + opac = settings["appearance"]["opacity"] if opac is None: opac = 0 self.dlg.sliderEdit.setText(str(opac)) self.dlg.slider.setValue(int(opac * 100)) - if settings['appearance']['hoverTemplate'] is not None: - self.dlg.hoverEdit.setText(settings['appearance']['hoverTemplate']) + if settings["appearance"]["hoverTemplate"] is not None: + self.dlg.hoverEdit.setText(settings["appearance"]["hoverTemplate"]) - self.userPermissions = self.ressource.getObjectPermissions(self.id, 'Layer', 'User') - self.groupPermissions = self.ressource.getObjectPermissions(self.id, 'Layer', 'UserGroup') + self.userPermissions = self.ressource.getObjectPermissions( + self.id, "Layer", "User" + ) + self.groupPermissions = self.ressource.getObjectPermissions( + self.id, "Layer", "UserGroup" + ) - if not self.userPermissions['success'] or not self.groupPermissions['success']: + if not self.userPermissions["success"] or not self.groupPermissions["success"]: self.dlg.noPermissionAccess() else: - self.dlg.populateTable('users', self.userPermissions['data']['permissions']) - self.dlg.populateTable('groups', self.groupPermissions['data']['permissions']) - - - def getPermissionChanges(self, type): - if type == 'User': - permissions = self.userPermissions['data'] + self.dlg.populateTable("users", self.userPermissions["data"]["permissions"]) + self.dlg.populateTable( + "groups", self.groupPermissions["data"]["permissions"] + ) + + def getPermissionChanges(self, _type): + if _type == "User": + permissions = self.userPermissions["data"] table = self.dlg.usertabel - elif type == 'UserGroup': - permissions = self.groupPermissions['data'] + elif _type == "UserGroup": + permissions = self.groupPermissions["data"] table = self.dlg.groupstabel else: return - oldPermissionList = permissions['permissions'] + oldPermissionList = permissions["permissions"] newPermissionList = [] for entry in oldPermissionList: - name = entry['displayTitle'] + name = entry["displayTitle"] row = None for x in range(table.rowCount()): if table.verticalHeaderItem(x).text() == name: @@ -885,69 +928,67 @@ def getPermissionChanges(self, type): return currentPermissionsInTable = [] - for p in enumerate(['READ', 'UPDATE', 'DELETE']): + for p in enumerate(["READ", "UPDATE", "DELETE"]): if table.item(row, p[0]).checkState() == 2: currentPermissionsInTable.append(p[1]) - if entry['permissions'] == None: + if entry["permissions"] is None: oldPermissions = [] else: - oldPermissions = entry['permissions']['permissions'] - if oldPermissions == ['ADMIN']: - oldPermissions = ['READ', 'UPDATE', 'DELETE'] + oldPermissions = entry["permissions"]["permissions"] + if oldPermissions == ["ADMIN"]: + oldPermissions = ["READ", "UPDATE", "DELETE"] if set(currentPermissionsInTable) != set(oldPermissions): newEntry = { - 'targetEntity' : permissions['targetEntity'], - 'permissions' : [{ - 'permissions' : { - 'permissions' : currentPermissionsInTable - }, - 'targetEntity' : entry['targetEntity'] - }] - } + "targetEntity": permissions["targetEntity"], + "permissions": [ + { + "permissions": {"permissions": currentPermissionsInTable}, + "targetEntity": entry["targetEntity"], + } + ], + } newPermissionList.append(newEntry) if len(newPermissionList) == 0: newPermissionList = None return newPermissionList - def getAllChanges(self): changes = {} generalChanges = self.getGeneralChanges() if generalChanges is not None: - changes['general'] = generalChanges - userPermissionChanges = self.getPermissionChanges('User') + changes["general"] = generalChanges + userPermissionChanges = self.getPermissionChanges("User") if userPermissionChanges is not None: - changes['permissions'] = {'User' : userPermissionChanges} - groupPermissionChanges = self.getPermissionChanges('UserGroup') + changes["permissions"] = {"User": userPermissionChanges} + groupPermissionChanges = self.getPermissionChanges("UserGroup") if groupPermissionChanges is not None: - if not 'permissions' in changes: - changes['permissions'] = {} - changes['permissions']['UserGroup'] = groupPermissionChanges + if "permissions" not in changes: + changes["permissions"] = {} + changes["permissions"]["UserGroup"] = groupPermissionChanges if len(changes) == 0: return None else: return changes - def getGeneralChanges(self): changes = {} opacity = self.dlg.sliderEdit.text() - if self.dlg.nameEdit.text() != self.settings['name']: - changes['name'] = self.dlg.nameEdit.text() - if type(opacity) == float or type(opacity) == int: - if float(opacity) != self.settings['appearance']['opacity']: - changes['appearance'] = {} - changes['appearance']['opacity'] = float(opacity) - hoverInput = self.dlg.hoverEdit.text() - if len(hoverInput) == 0: - hoverInput = None - if hoverInput != self.settings['appearance']['hoverTemplate']: - if not 'appearance' in changes: - changes['appearance'] = {} - changes['appearance']['hoverTemplate'] = self.dlg.hoverEdit.text() + if self.dlg.nameEdit.text() != self.settings["name"]: + changes["name"] = self.dlg.nameEdit.text() + if type(opacity) is float or type(opacity) is int: + if float(opacity) != self.settings["appearance"]["opacity"]: + changes["appearance"] = {} + changes["appearance"]["opacity"] = float(opacity) + hover_input = self.dlg.hoverEdit.text() + if len(hover_input) == 0: + hover_input = None + if hover_input != self.settings["appearance"]["hoverTemplate"]: + if "appearance" not in changes: + changes["appearance"] = {} + changes["appearance"]["hoverTemplate"] = self.dlg.hoverEdit.text() if len(changes) == 0: return None @@ -964,8 +1005,13 @@ def startEditing(self): def stopEditing(self): changes = self.getAllChanges() if changes is not None: - warn = QMessageBox.warning(self.dlg, 'Warning','All changes will be' - ' lost. Continue?', QMessageBox.Cancel, QMessageBox.Ok) + warn = QMessageBox.warning( + self.dlg, + "Warning", + "All changes will be" " lost. Continue?", + QMessageBox.Cancel, + QMessageBox.Ok, + ) if warn == QMessageBox.Ok: self.populateSettings() else: @@ -976,43 +1022,52 @@ def stopEditing(self): def saveChanges(self): changes = self.getAllChanges() if changes is not None: - conf = QMessageBox.warning(self.dlg, 'Confirm','Please confirm you ' - 'want to save the changes', QMessageBox.Cancel, QMessageBox.Ok) + conf = QMessageBox.warning( + self.dlg, + "Confirm", + "Please confirm you " "want to save the changes", + QMessageBox.Cancel, + QMessageBox.Ok, + ) if conf == QMessageBox.Ok: - if 'general' in changes: - data = changes['general'] - data['id'] = self.id + if "general" in changes: + data = changes["general"] + data["id"] = self.id responseCode = self.ressource.editLayer(self.id, data) - if 'name' in data and responseCode > 199 and responseCode < 299: - self.name = data['name'] + if "name" in data and responseCode > 199 and responseCode < 299: + self.name = data["name"] self.setText(0, self.name) - #update the settings of the class: + # update the settings of the class: newSettings = self.ressource.updateSingleLayer(self.id) for attr in self.settings: self.settings[attr] = newSettings[attr] - if 'permissions' in changes: + if "permissions" in changes: responses = 200 - for x in changes['permissions'].keys(): - for entry in changes['permissions'][x]: + for x in changes["permissions"].keys(): + for entry in changes["permissions"][x]: responseBool = self.ressource.editObjectPermission( - self.id, 'ProjectLayer', x, entry) + self.id, "ProjectLayer", x, entry + ) if not responseBool: responses = 400 # update the class variable copy of the permissions self.userPermissions = self.ressource.getObjectPermissions( - self.id, 'Layer', 'User') + self.id, "Layer", "User" + ) self.groupPermissions = self.ressource.getObjectPermissions( - self.id, 'Layer', 'UserGroup') - self.ressource.userInfo(responses, 'Layer permissions', 'updated') + self.id, "Layer", "UserGroup" + ) + self.ressource.userInfo(responses, "Layer permissions", "updated") # if the user clicked 'cancel' just return and stay in edit mode: else: return else: - QMessageBox.information(self.dlg, 'Note','No changes were found', - QMessageBox.Ok) + QMessageBox.information( + self.dlg, "Note", "No changes were found", QMessageBox.Ok + ) self.stoppedEditing() def stoppedEditing(self): @@ -1027,14 +1082,17 @@ def normalClose(self): self.dlg.hide() def deleteLayer(self): - txt = 'Please confirm you want to permanently delete the selected layer in Shogun' - conf = QMessageBox.warning(self.dlg, 'Confirm',txt , QMessageBox.Cancel, QMessageBox.Ok) + txt = ( + "Please confirm you want to permanently delete the selected layer in Shogun" + ) + conf = QMessageBox.warning( + self.dlg, "Confirm", txt, QMessageBox.Cancel, QMessageBox.Ok + ) if conf == QMessageBox.Ok: self.ressource.deleteLayer(self.id) self.parent().update() - class QgisLayerItem(TreeItem): def __init__(self, qgislayer, shogunLayerItem, ressource): TreeItem.__init__(self, None, qgislayer.name()) @@ -1042,30 +1100,30 @@ def __init__(self, qgislayer, shogunLayerItem, ressource): self.id = self.layer.id() self.type = self.layer.type() self.stylename = None - #make upload and download Style only available for vector layers: + # make upload and download Style only available for vector layers: if self.type == 0: - self.actiontype = 'qgisLayerReference' + self.actiontype = "qgisLayerReference" else: self.actiontype = None self.parentShogunLayer = shogunLayerItem self.ressource = ressource - + QgsProject.instance().addMapLayer(self.layer) if self.layer.type() == QgsMapLayer.VectorLayer: self.downloadStyle() self.layer.nameChanged.connect(self.on_name_changed) - def uploadStyle(self): - msg = 'Please confirm that you want to upload the style of the selected' - msg += ' layer and overwrite the style of the corresponding layer in Shogun' - confirm = QMessageBox.warning(None, 'Confirm', msg, QMessageBox.Cancel, QMessageBox.Ok) + msg = "Please confirm that you want to upload the style of the selected" + msg += " layer and overwrite the style of the corresponding layer in Shogun" + confirm = QMessageBox.warning( + None, "Confirm", msg, QMessageBox.Cancel, QMessageBox.Ok + ) if not confirm: return else: return self.ressource.uploadStyle(self) - def downloadStyle(self): sld, geoServerStyleName = self.ressource.downloadStyle(self) self.stylename = geoServerStyleName @@ -1076,7 +1134,6 @@ def downloadStyle(self): else: return False - def uploadIcon(self): self.ressource.uploadCustomIcon(self.layer.rendererV2().symbol().symbolLayer(0)) diff --git a/help/source/conf.py b/help/source/conf.py index de723d3..5ed7701 100644 --- a/help/source/conf.py +++ b/help/source/conf.py @@ -11,17 +11,15 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os - # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. @@ -34,14 +32,14 @@ source_suffix = '.rst' # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'MDIDEMetadataSearch' -copyright = u'2024, terrestris GmbH & Co. KG' +copyrightTerrestris = u'2024, terrestris GmbH & Co. KG' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -54,37 +52,37 @@ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_TemplateModuleNames = True +# add_TemplateModuleNames = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- @@ -96,26 +94,26 @@ # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -124,44 +122,44 @@ # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'TemplateClassdoc' @@ -170,40 +168,40 @@ # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' +# latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' +# latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'GeoportalRlpMetadataSearch.tex', u'GeoportalRlpMetadataSearch Documentation', - u'Armin Retterath', 'manual'), + ('index', 'GeoportalRlpMetadataSearch.tex', u'GeoportalRlpMetadataSearch Documentation', + u'Armin Retterath', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Additional stuff for the LaTeX preamble. -#latex_preamble = '' +# latex_preamble = '' # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output -------------------------------------------- diff --git a/layerutils.py b/layerutils.py index 3744717..b66d02d 100644 --- a/layerutils.py +++ b/layerutils.py @@ -1,106 +1,119 @@ # -*- coding: utf-8 -*- -''' +""" (c) 2025 terrestris GmbH & Co. KG, https://www.terrestris.de/en/ + the last method "getLabelingAsSld()" was taken + from the qgis geoserver explorer plugin by + (C) 2016 Boundless, http://boundlessgeo.com + see: https://github.com/boundlessgeo/qgis-geoserver-plugin -''' +""" -__author__ = 'ntreff' -__date__ = 'July 2025' - -import sys import os.path -import zipfile +import sys import tempfile +import urllib.parse +import urllib.request +import zipfile -from qgis.PyQt.QtWidgets import QMessageBox, QDialog, QLabel, QPushButton -from qgis.PyQt.QtCore import QRect +from qgis.core import ( + QgsWkbTypes, + QgsMapLayer, + QgsRasterFileWriter, + QgsRasterLayer, + QgsRasterPipe, + QgsVectorFileWriter, + QgsVectorLayer, +) +from qgis.PyQt.QtWidgets import QMessageBox from qgis.PyQt.QtXml import QDomDocument -import urllib.request, urllib.parse - -from qgis.core import QgsVectorLayer, QgsRasterLayer, QgsMapLayer, QgsCoordinateReferenceSystem -from qgis.core import QgsVectorFileWriter, QgsRasterFileWriter, QgsRasterPipe from .gui.dialog_bases.addraster import AddRasterDialog PYTHON_VERSION = sys.version_info[0] -'''This module contains some helper functions for the shogun-editor plugin''' +__author__ = "ntreff" +__date__ = "July 2025" + +"""This module contains some helper functions for the shogun-editor plugin""" + def createLayer(layerItem, epsg): - #Shogun Webapp saves all layers in the following espg, therefore the - #variable can be static - layerurl = layerItem.source['url'] + # Shogun Webapp saves all layers in the following espg, therefore the + # variable can be static + layerurl = layerItem.source["url"] dataType = layerItem.datatype # every layerItem.source should have an attribute 'dataType' - if dataType == 'vector' or dataType == 'Vector': - url = layerItem.ressource.baseurl.rstrip('/shogun2-webapp/') + layerurl + '?' + if dataType == "vector" or dataType == "Vector": + url = layerItem.ressource.baseurl.rstrip("/shogun2-webapp/") + layerurl + "?" return createWfsLayer(layerItem, url, epsg) - elif dataType == 'Raster': - url = layerItem.ressource.baseurl.rstrip('/shogun2-webapp/') + layerurl + '?' + elif dataType == "Raster": + url = layerItem.ressource.baseurl.rstrip("/shogun2-webapp/") + layerurl + "?" return createRasterLayer(layerItem, url, epsg) - elif dataType == 'WMS': - if layerurl == '/shogun2-webapp/geoserver.action': - url = layerItem.ressource.baseurl.rstrip('/shogun2-webapp/') + layerurl + '?' + elif dataType == "WMS": + if layerurl == "/shogun2-webapp/geoserver.action": + url = ( + layerItem.ressource.baseurl.rstrip("/shogun2-webapp/") + layerurl + "?" + ) return createWmsLayerFromShogun(layerItem, url) else: - return createWmsLayer(layerItem, layerurl, epsg) # TODO + return createWmsLayer(layerItem, layerurl, epsg) # TODO # if for any reason the parameter 'dataType' is not set correctly, we check the url # of the layer to determine if it's a WFS/WCS from the shogun-geoserver # (url has'shogun2-webapp') or if it's a WMS from an outer source (other url) - elif dataType == 'unknown' or dataType == None or dataType == '': - if layerurl.startswith('/shogun2-webapp'): - url = layerItem.ressource.baseurl.rstrip('/shogun2-webapp/') + layerurl + '?' + elif dataType == "unknown" or dataType is None or dataType == "": + if layerurl.startswith("/shogun2-webapp"): + url = ( + layerItem.ressource.baseurl.rstrip("/shogun2-webapp/") + layerurl + "?" + ) try: lyr = createWfsLayer(layerItem, url, epsg) if lyr.isValid(): return lyr - except: + except Exception as e: + print('Could not create WFS layer: ' + str(e)) pass try: - lyr = createWmsLayerFromShogun(layerItem, url, epsg) + lyr = createWmsLayerFromShogun(layerItem, url, epsg) if lyr.isValid(): return lyr - except: + except Exception as e: + print('Could not create WMS layer for SHOGun layer: ' + str(e)) pass try: - lyr = createRasterLayer(layerItem, url, epsg) + lyr = createRasterLayer(layerItem, url, epsg) if lyr.isValid(): return lyr - except: + except Exception as e: + print('Could not create layer: ' + str(e)) pass else: return createWmsLayerNormal(layerItem, layerurl, epsg) else: - info = 'Layer source '+ layerurl + ' could not be loaded' - QMessageBox.warning(None, 'Warning', info, QMessageBox.Ok) - + info = "Layer source " + layerurl + " could not be loaded" + QMessageBox.warning(None, "Warning", info, QMessageBox.Ok) def createWfsLayer(layerItem, url, epsg): params = { - 'service': 'WFS', - 'srsname': epsg, - 'typename': layerItem.source['layerNames'], - 'typenames': layerItem.source['layerNames'], - 'url': url + 'typename=' + layerItem.source['layerNames'], - 'version': 'auto', - 'request': 'GetCapabilities' - } + "service": "WFS", + "srsname": epsg, + "typename": layerItem.source["layerNames"], + "typenames": layerItem.source["layerNames"], + "url": url + "typename=" + layerItem.source["layerNames"], + "version": "auto", + "request": "GetCapabilities", + } if PYTHON_VERSION >= 3: url = url + urllib.parse.unquote(urllib.parse.urlencode(params)) else: url = url + urllib.unquote(urllib.urlencode(params)) - layer = QgsVectorLayer(url, 'SHOGUN-Layer: ' + layerItem.name, 'WFS') + layer = QgsVectorLayer(url, "SHOGUN-Layer: " + layerItem.name, "WFS") return layer @@ -118,75 +131,79 @@ def createRasterLayer(layerItem, url, epsg): elif userSelection == 2: - #workaround... - ## TODO: can this be made using the qgis wcs provider? - layerName = layerItem.source['layerNames'] + # workaround... + # TODO: can this be made using the qgis wcs provider? + layerName = layerItem.source["layerNames"] params = { - 'request' : 'GetCoverage', - 'version' : '2.0.1', - 'coverageId' : layerName, - 'namespace' : 'SHOGUN', - 'identifier' : layerName + "request": "GetCoverage", + "version": "2.0.1", + "coverageId": layerName, + "namespace": "SHOGUN", + "identifier": layerName, } - #build the fitting url from the baseurl ('http...geoserver.action?') - #and the parameters - baseurl = url + 'service=WCS&' + # build the fitting url from the baseurl ('http...geoserver.action?') + # and the parameters + baseurl = url + "service=WCS&" if PYTHON_VERSION >= 3: url = baseurl + urllib.parse.urlencode(params) else: url = baseurl + urllib.urlencode(params) - #urlretrieve stores the downloaded file in /tmp/ and returns it's path: + # urlretrieve stores the downloaded file in /tmp/ and returns it's path: if PYTHON_VERSION >= 3: response = urllib.request.urlretrieve(url) else: response = urllib.urlretrieve(url) file = response[0] - #create a raster layer and return it - return QgsRasterLayer(file, 'QGIS-Layer: ' + layerItem.name) + # create a raster layer and return it + return QgsRasterLayer(file, "QGIS-Layer: " + layerItem.name) def createWmsLayerNormal(layerItem, url, epsg): params = { - 'crs': epsg, - 'dpiMode': '7', - 'format': 'image/png', - 'layers': layerItem.source['layerNames'], - 'styles': '', - 'url': url - } + "crs": epsg, + "dpiMode": "7", + "format": "image/png", + "layers": layerItem.source["layerNames"], + "styles": "", + "url": url, + } if PYTHON_VERSION >= 3: uri = urllib.parse.urlencode(params) else: uri = urllib.urlencode(params) - layer = QgsRasterLayer(uri, 'QGIS-Layer: ' + layerItem.name, 'wms') + layer = QgsRasterLayer(uri, "QGIS-Layer: " + layerItem.name, "wms") if layer.isValid(): return layer else: return False +def createWmsLayer(layerItem, url, epsg): + # TODO + return False + def createWmsLayerFromShogun(layerItem, url, epsg): - layerNames = layerItem.source['layerNames'] + layerNames = layerItem.source["layerNames"] params = { - 'IgnoreGetMapUrl' : 1, - 'crs': epsg, - 'format': 'image/png', - 'layers': layerNames.split(':')[1], - 'styles': '', - 'url': url + 'layers=' + layerNames + "IgnoreGetMapUrl": 1, + "crs": epsg, + "format": "image/png", + "layers": layerNames.split(":")[1], + "styles": "", + "url": url + "layers=" + layerNames, } if PYTHON_VERSION >= 3: uri = urllib.parse.urlencode(params) else: uri = urllib.urlencode(params) - layer = QgsRasterLayer(uri, 'QGIS-Layer: ' + layerItem.name, 'wms') + layer = QgsRasterLayer(uri, "QGIS-Layer: " + layerItem.name, "wms") if layer.isValid(): return layer else: @@ -195,144 +212,150 @@ def createWmsLayerFromShogun(layerItem, url, epsg): def createAndParseSld(qgisLayerItem): document = QDomDocument() - header = document.createProcessingInstruction( 'xml', 'version=\'1.0\' encoding=\'UTF-8\'' ) - document.appendChild( header ) - root = document.createElementNS( 'http://www.opengis.net/sld', 'StyledLayerDescriptor' ) - root.setAttribute( 'version', '1.0.0' ) - root.setAttribute( 'xmlns:ogc', 'http://www.opengis.net/ogc' ) - root.setAttribute( 'xmlns:sld', 'http://www.opengis.net/sld' ) - root.setAttribute( 'xmlns:gml', 'http://www.opengis.net/gml' ) - document.appendChild( root ) - - namedLayerNode = document.createElement( 'sld:NamedLayer' ) - root.appendChild( namedLayerNode ) - - - qgisLayerItem.layer.writeSld(namedLayerNode, document, '') - - nameNode = namedLayerNode.firstChildElement('se:Name') + header = document.createProcessingInstruction( + "xml", "version='1.0' encoding='UTF-8'" + ) + document.appendChild(header) + root = document.createElementNS( + "http://www.opengis.net/sld", "StyledLayerDescriptor" + ) + root.setAttribute("version", "1.0.0") + root.setAttribute("xmlns:ogc", "http://www.opengis.net/ogc") + root.setAttribute("xmlns:sld", "http://www.opengis.net/sld") + root.setAttribute("xmlns:gml", "http://www.opengis.net/gml") + document.appendChild(root) + + namedLayerNode = document.createElement("sld:NamedLayer") + root.appendChild(namedLayerNode) + + qgisLayerItem.layer.writeSld(namedLayerNode, document, "") + + nameNode = namedLayerNode.firstChildElement("se:Name") oldNameText = nameNode.firstChild() - newname = qgisLayerItem.parentShogunLayer.source['layerNames'] + newname = qgisLayerItem.parentShogunLayer.source["layerNames"] newNameText = document.createTextNode(newname) nameNode.appendChild(newNameText) nameNode.removeChild(oldNameText) - userStyleNode = namedLayerNode.firstChildElement('UserStyle') - userStyleNameNode = userStyleNode.firstChildElement('se:Name') + userStyleNode = namedLayerNode.firstChildElement("UserStyle") + userStyleNameNode = userStyleNode.firstChildElement("se:Name") userStyleNameText = userStyleNameNode.firstChild() userStyleNameNode.removeChild(userStyleNameText) userStyleNameNode.appendChild(document.createTextNode(qgisLayerItem.stylename)) - titleNode = document.createElement('sld:Title') - title = document.createTextNode('A QGIS-Style for '+qgisLayerItem.layer.name()) + titleNode = document.createElement("sld:Title") + title = document.createTextNode("A QGIS-Style for " + qgisLayerItem.layer.name()) titleNode.appendChild(title) userStyleNode.appendChild(titleNode) - defaultNode = document.createElement('sld:IsDefault') - defaultNode.appendChild(document.createTextNode('1')) + defaultNode = document.createElement("sld:IsDefault") + defaultNode.appendChild(document.createTextNode("1")) userStyleNode.appendChild(defaultNode) - featureTypeStyleNode = userStyleNode.firstChildElement('se:FeatureTypeStyle') - featureTypeStyleNameNode = document.createElement('sld:Name') - featureTypeStyleNameNode.appendChild(document.createTextNode('name')) + featureTypeStyleNode = userStyleNode.firstChildElement("se:FeatureTypeStyle") + featureTypeStyleNameNode = document.createElement("sld:Name") + featureTypeStyleNameNode.appendChild(document.createTextNode("name")) featureTypeStyleNode.appendChild(featureTypeStyleNameNode) - rules = featureTypeStyleNode.elementsByTagName('se:Rule') + rules = featureTypeStyleNode.elementsByTagName("se:Rule") for x in range(rules.length()): rule = rules.at(x) - rule.removeChild(rule.firstChildElement('se:Description')) + rule.removeChild(rule.firstChildElement("se:Description")) # Check if custom icons are used in symbology and replace the text: # search if tag 'se:OnlineResource' is in the sld document - listOfGraphics = rule.toElement().elementsByTagName('se:OnlineResource') + listOfGraphics = rule.toElement().elementsByTagName("se:OnlineResource") if not listOfGraphics.isEmpty(): for x in range(listOfGraphics.length()): graphicNode = listOfGraphics.at(x) - currentIcon = graphicNode.attributes().namedItem('xlink:href').nodeValue() + currentIcon = graphicNode.attributes().namedItem("xlink:href").nodeValue() iconUrl = qgisLayerItem.ressource.prepareIconForUpload(currentIcon) - graphicNode.toElement().setAttribute('xlink:href', iconUrl) - graphicNode.toElement().setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink') + graphicNode.toElement().setAttribute("xlink:href", iconUrl) + graphicNode.toElement().setAttribute( + "xmlns:xlink", "http://www.w3.org/1999/xlink" + ) sld = document.toString() - # in qgis3 layer.writeSld() also incluedes labeling in the output sld, + # in qgis3 layer.writeSld() also includes labeling in the output sld, # whereas in qgis2 we have to do this manually by using this module's function # getLabelingAsSld - ## TODO: The automatic sld labeling from QGIS 3 produces an extra rule for + # TODO: The automatic sld labeling from QGIS 3 produces an extra rule for # every labeling style, thus leading to a less beautiful viewe in the # shogun2-webapp styler - is this a problem? if qgisLayerItem.layer.labelsEnabled() and PYTHON_VERSION < 3: labelSld = getLabelingAsSld(qgisLayerItem.layer) - sld = sld.replace('', labelSld + '') + sld = sld.replace("", labelSld + "") - sld = sld.replace('ogc:Filter xmlns:ogc="http://www.opengis.net/ogc"', 'ogc:Filter') + sld = sld.replace('ogc:Filter xmlns:ogc="http://www.opengis.net/ogc"', "ogc:Filter") # the following fixes weird problems with the sld compability with the # shogun webapp - sld = sld.replace('', '') - sld = sld.replace('', '') - sld = sld.replace(' ', '') - sld = sld.replace(' ', '') - - sld = sld.replace('StyledLayerDescriptor', 'sld:StyledLayerDescriptor') - sld = sld.replace('UserStyle', 'sld:UserStyle') - sld = sld.replace('se:', 'sld:') - sld = sld.replace('SvgParameter', 'CssParameter') - sld = sld.replace('\n', '') - sld = sld.replace('\t', '') + sld = sld.replace("", "") + sld = sld.replace("", "") + sld = sld.replace(" ", "") + sld = sld.replace(" ", "") + + sld = sld.replace("StyledLayerDescriptor", "sld:StyledLayerDescriptor") + sld = sld.replace("UserStyle", "sld:UserStyle") + sld = sld.replace("se:", "sld:") + sld = sld.replace("SvgParameter", "CssParameter") + sld = sld.replace("\n", "") + sld = sld.replace("\t", "") return sld def prepareLayerForUpload(layer, uploadDialog): tmpdir = tempfile.mkdtemp() - zipfilePath = os.path.join(tmpdir, 'uploadzip.zip') - uploadDialog.log('Writing layer as shapefile...') + zipfilePath = os.path.join(tmpdir, "uploadzip.zip") + uploadDialog.log("Writing layer as shapefile...") if layer.type() == QgsMapLayer.VectorLayer: - if layer.source().endswith('.shp'): + if layer.source().endswith(".shp"): file = layer.source() - uploadDialog.log('...success\nZipping...') - zipSuccess = createZipFromShapefile(file, zipfilePath, delete = False) + uploadDialog.log("...success\nZipping...") + zipSuccess = createZipFromShapefile(file, zipfilePath, delete=False) else: - path = os.path.join(tmpdir, 'VectorlayerFromQGisPlugin.shp') + path = os.path.join(tmpdir, "VectorlayerFromQGisPlugin.shp") file = writeShapefile(layer, path) if not file: - uploadDialog.log('Error: Could not write the shapefile ') + uploadDialog.log("Error: Could not write the shapefile ") return - uploadDialog.log('...success\nZipping...') - zipSuccess = createZipFromShapefile(file, zipfilePath, delete = True) + uploadDialog.log("...success\nZipping...") + zipSuccess = createZipFromShapefile(file, zipfilePath, delete=True) else: - if layer.providerType() == 'wms': + if layer.providerType() == "wms": return True - if layer.source().endswith('.tif'): + if layer.source().endswith(".tif"): rasterfile = layer.source() - uploadDialog.log('Zipping...') - zipSuccess = createZipFromRaster(rasterfile, zipfilePath, delete = False) + uploadDialog.log("Zipping...") + zipSuccess = createZipFromRaster(rasterfile, zipfilePath, delete=False) else: - path = os.path.join(tmpdir, 'RasterlayerFromQGisPlugin.tif') - uploadDialog.log('Creating raster file from layer...') + path = os.path.join(tmpdir, "RasterlayerFromQGisPlugin.tif") + uploadDialog.log("Creating raster file from layer...") rasterfile = writeRasterFile(layer, path) - uploadDialog.log('Zipping...') + uploadDialog.log("Zipping...") if not rasterfile: - uploadDialog.log('Error: Could not write the raster file') + uploadDialog.log("Error: Could not write the raster file") return - zipSuccess = createZipFromRaster(rasterfile, zipfilePath, delete = True) + zipSuccess = createZipFromRaster(rasterfile, zipfilePath, delete=True) uploadDialog.log(zipSuccess) return zipfilePath, tmpdir def writeShapefile(layer, path): - ## TODO: here are some problems with the upload - also shogun-problems + # TODO: here are some problems with the upload - also shogun-problems writeError = QgsVectorFileWriter.writeAsVectorFormat( - layer, path,'utf-8', layer.crs(), 'ESRI Shapefile', False) + layer, path, "utf-8", layer.crs(), "ESRI Shapefile", False + ) if PYTHON_VERSION >= 3: - if writeError[0] != 0: # in QGIS 3 writeAsVectorFormat returns a tuple + if writeError[0] != 0: # in QGIS 3 writeAsVectorFormat returns a tuple return False else: - if writeError != 0: #0 = writing success + if writeError != 0: # 0 = writing success return False return path @@ -340,19 +363,19 @@ def writeShapefile(layer, path): def createZipFromShapefile(filepath, zipfilePath, delete=False): # create a list with '/path/name.shp' as the first item shapefileParts = [filepath] - for extension in ['.dbf', '.shx', '.prj']: + for extension in [".dbf", ".shx", ".prj"]: newFilename = os.path.splitext(filepath)[0] + extension if os.path.isfile(newFilename): # append the further shapefile parts to the list shapefileParts.append(newFilename) else: - return 'Error: Could not find ' + os.path.basename(newFilename) + return "Error: Could not find " + os.path.basename(newFilename) # create a new zip-archive at zipfilePath - newZipfile = zipfile.ZipFile(zipfilePath, 'w', zipfile.ZIP_DEFLATED) + newZipfile = zipfile.ZipFile(zipfilePath, "w", zipfile.ZIP_DEFLATED) for part in shapefileParts: # add the parts of the shapefile to the zip-archive - newZipfile.write(part, arcname = os.path.basename(part)) + newZipfile.write(part, arcname=os.path.basename(part)) # then delete the parts because we do not need them anymore if delete: os.remove(part) @@ -361,11 +384,11 @@ def createZipFromShapefile(filepath, zipfilePath, delete=False): # delete further created files belonging to the shapefile, because we # do not need them if delete: - for extension in ['.cpg', '.qpj']: + for extension in [".cpg", ".qpj"]: newFilename = os.path.splitext(filepath)[0] + extension if os.path.isfile(newFilename): os.remove(newFilename) - return 'Written successfully' + return "Written successfully" def writeRasterFile(layer, filepath): @@ -374,44 +397,59 @@ def writeRasterFile(layer, filepath): pipe.set(provider.clone()) writer = QgsRasterFileWriter(filepath) - writeError = writer.writeRaster(pipe, provider.xSize(), provider.ySize(), - provider.extent(), provider.crs()) + writeError = writer.writeRaster( + pipe, provider.xSize(), provider.ySize(), provider.extent(), provider.crs() + ) + print(writeError) return filepath -def createZipFromRaster(pathToRaster, zipfilePath, delete = False): - newZipfile = zipfile.ZipFile(zipfilePath, 'w', zipfile.ZIP_DEFLATED) - newZipfile.write(pathToRaster, arcname = os.path.basename(pathToRaster)) +def createZipFromRaster(pathToRaster, zipfilePath, delete=False): + newZipfile = zipfile.ZipFile(zipfilePath, "w", zipfile.ZIP_DEFLATED) + newZipfile.write(pathToRaster, arcname=os.path.basename(pathToRaster)) newZipfile.close() if delete: try: os.remove(pathToRaster) - except: + except Exception as e: + print('Could not remove:' + str(e)) pass - return 'Written successfully' - + return "Written successfully" -#the following function (we need it only when the plugin is used on qgis2): +# the following function (we need it only when the plugin is used on qgis2): # (c) 2016 Boundless, http://boundlessgeo.com # This code is licensed under the GPL 2.0 license. def getLabelingAsSld(layer): - SIZE_FACTOR = 4 try: s = "" - s += "" + layer.customProperty("labeling/fieldName") + "" + s += ( + "" + + layer.customProperty("labeling/fieldName") + + "" + ) s += "" r = int(layer.customProperty("labeling/textColorR")) g = int(layer.customProperty("labeling/textColorG")) b = int(layer.customProperty("labeling/textColorB")) - rgb = '#%02x%02x%02x' % (r, g, b) - s += '' + rgb + "" + rgb = "#%02x%02x%02x" % (r, g, b) + s += ( + '' + + rgb + + "" + ) s += "" - s += '' + layer.customProperty("labeling/fontFamily") +'' - s += ('' + - str(int(layer.customProperty("labeling/fontSize"))) - +'') + s += ( + '' + + layer.customProperty("labeling/fontFamily") + + "" + ) + s += ( + '' + + str(int(layer.customProperty("labeling/fontSize"))) + + "" + ) italic = False bold = False @@ -426,41 +464,66 @@ def getLabelingAsSld(layer): s += 'normal' s += "" s += "" - if layer.geometryType() == QGis.Point: - s += ("" + if layer.geometryType() == QgsWkbTypes.PointGeometry: + s += ( + "" "" "0.5" "0.5" - "") + "" + ) s += "" - s += "" + str(int(layer.customProperty("labeling/xOffset"))) + "" - s += "" + str(int(layer.customProperty("labeling/yOffset"))) + "" + s += ( + "" + + str(int(layer.customProperty("labeling/xOffset"))) + + "" + ) + s += ( + "" + + str(int(layer.customProperty("labeling/yOffset"))) + + "" + ) s += "" - s += "" + str(abs(int(layer.customProperty("labeling/angleOffset")))) + "" + s += ( + "" + + str(abs(int(layer.customProperty("labeling/angleOffset")))) + + "" + ) s += "" - elif layer.geometryType() == QGis.Line: + if layer.geometryType() == QgsWkbTypes.LineGeometry: mode = layer.customProperty("labeling/placement") if mode != 4: - follow = 'true' if mode == 3 else '' - s += ''' + follow = ( + 'true' + if mode == 3 + else "" + ) + s += """ %s - %s''' % (str(layer.customProperty("labeling/dist")), follow) + %s""" % ( + str(layer.customProperty("labeling/dist")), + follow, + ) s += "" if layer.customProperty("labeling/bufferDraw") == "true": r = int(layer.customProperty("labeling/bufferColorR")) g = int(layer.customProperty("labeling/bufferColorG")) b = int(layer.customProperty("labeling/bufferColorB")) - rgb = '#%02x%02x%02x' % (r, g, b) + rgb = "#%02x%02x%02x" % (r, g, b) haloSize = str(layer.customProperty("labeling/bufferSize")) opacity = str(float(layer.customProperty("labeling/bufferColorA")) / 255.0) s += "%s" % haloSize - s += '%s' % rgb - s += '%s' % opacity - s +="" + s += '%s' % rgb + s += ( + '%s' + % opacity + ) + s += "" return s - except: - return "" + except Exception as e: + print('Could not create SLD: ' + str(e)) + pass diff --git a/plugin_upload.py b/plugin_upload.py index a88ea2b..83c051c 100644 --- a/plugin_upload.py +++ b/plugin_upload.py @@ -5,8 +5,8 @@ git sha : $TemplateVCSFormat """ -import sys import getpass +import sys import xmlrpc.client from optparse import OptionParser diff --git a/qgis_shogun_editor.py b/qgis_shogun_editor.py index 5fe52a5..842b2e1 100644 --- a/qgis_shogun_editor.py +++ b/qgis_shogun_editor.py @@ -21,22 +21,32 @@ * * ***************************************************************************/ """ -from qgis.PyQt.QtCore import Qt, QSettings, QTranslator, QCoreApplication, QUrl -from qgis.PyQt.QtGui import QIcon, QPixmap, QDesktopServices -from qgis.PyQt.QtWidgets import * +import os.path + +from qgis.core import ( + Qgis, + QgsBrowserModel, + QgsMessageLog, + QgsNetworkAccessManager, + QgsProject, + QgsProviderRegistry, + QgsRasterLayer, + QgsSettings, +) +from qgis.gui import QgsGui, QgsMessageBar, QgsMessageViewer + # some things for doing http requests -from qgis.PyQt.QtCore import QUrl, QEventLoop, QUrlQuery +from qgis.PyQt.QtCore import QCoreApplication, QEventLoop, QSettings, Qt, QTranslator, QUrl, QUrlQuery +from qgis.PyQt.QtGui import QDesktopServices, QIcon, QPixmap from qgis.PyQt.QtNetwork import QNetworkRequest -from qgis.core import Qgis, QgsNetworkAccessManager, QgsMessageLog, QgsSettings, QgsProviderRegistry, QgsBrowserModel -from qgis.core import QgsProject, QgsRasterLayer -from qgis.gui import QgsMessageBar, QgsGui, QgsMessageViewer -# Initialize Qt resources from file resources.py -from .resources import * +from qgis.PyQt.QtWidgets import QAction, QMessageBox + +from .gui.editor import Editor + # Import the code for the dialog from .qgis_shogun_editor_dialog import QgisShogunEditorDialog -import os.path -from .gui.editor import Editor +# Initialize Qt resources from file resources.py class QgisShogunEditor: @@ -55,11 +65,10 @@ def __init__(self, iface): # initialize plugin directory self.plugin_dir = os.path.dirname(__file__) # initialize locale - locale = QSettings().value('locale/userLocale')[0:2] + locale = QSettings().value("locale/userLocale")[0:2] locale_path = os.path.join( - self.plugin_dir, - 'i18n', - 'gprlp_metadata_search_{}.qm'.format(locale)) + self.plugin_dir, "i18n", "gprlp_metadata_search_{}.qm".format(locale) + ) if os.path.exists(locale_path): self.translator = QTranslator() @@ -68,7 +77,7 @@ def __init__(self, iface): # Declare instance attributes self.actions = [] - self.menu = self.tr(u'&Qgis Shogun Editor') + self.menu = self.tr("&Qgis Shogun Editor") # Check if plugin was started the first time in current QGIS session # Must be set in initGui() to survive plugin reloads @@ -80,13 +89,11 @@ def __init__(self, iface): # read actual browser model self.browser_model = QgsBrowserModel() - #network access + # network access self.na_manager = QgsNetworkAccessManager.instance() self.disable_ssl_verification = self.settings.value( - "/MetaSearch/disableSSL", - False, - bool + "/MetaSearch/disableSSL", False, bool ) self.pluginIsActive = False self.editor = None @@ -104,8 +111,7 @@ def tr(self, message): :rtype: QString """ # noinspection PyTypeChecker,PyArgumentList,PyCallByClass - return QCoreApplication.translate('QgisShogunEditor', message) - + return QCoreApplication.translate("QgisShogunEditor", message) def add_action( self, @@ -117,7 +123,8 @@ def add_action( add_to_toolbar=True, status_tip=None, whats_this=None, - parent=None): + parent=None, + ): """Add a toolbar icon to the toolbar. :param icon_path: Path to the icon for this action. Can be a resource @@ -173,9 +180,7 @@ def add_action( self.iface.addToolBarIcon(action) if add_to_menu: - self.iface.addPluginToWebMenu( - self.menu, - action) + self.iface.addPluginToWebMenu(self.menu, action) self.actions.append(action) @@ -186,20 +191,18 @@ def initGui(self): icon_path = os.path.join(os.path.dirname(__file__), "shogun_logo.png") self.add_action( icon_path, - text=self.tr(u'Qgis Shogun Editor'), - callback=self.openEditor, # ehemalig: self.run - parent=self.iface.mainWindow()) + text=self.tr("Qgis Shogun Editor"), + callback=self.openEditor, # ehemalig: self.run + parent=self.iface.mainWindow(), + ) # will be set False in run() # self.first_start = True - def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" for action in self.actions: - self.iface.removePluginWebMenu( - self.tr(u'Qgis Shogun Editor'), - action) + self.iface.removePluginWebMenu(self.tr("Qgis Shogun Editor"), action) self.iface.removeToolBarIcon(action) def run(self): @@ -207,7 +210,7 @@ def run(self): # Create the dialog with elements (after translation) and keep reference # Only create GUI ONCE in callback, so that it will only load when the plugin is started - if self.first_start == True: + if self.first_start: self.first_start = False self.dlg = QgisShogunEditorDialog() # search_catalogues @@ -219,20 +222,23 @@ def run(self): # build pixmap = QPixmap(logo_path) # draw preview - self.dlg.labelLogo.setPixmap(pixmap.scaled(self.dlg.labelLogo.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) + self.dlg.labelLogo.setPixmap( + pixmap.scaled( + self.dlg.labelLogo.size(), + Qt.KeepAspectRatio, + Qt.SmoothTransformation, + ) + ) self.dlg.labelLogo.mousePressEvent = self.open_project_link else: - QgsMessageLog.logMessage("An error occured while try to open url: ", 'GeoPortal.rlp search', - level=Qgis.Critical) + QgsMessageLog.logMessage( + "An error occured while try to open url: ", + "GeoPortal.rlp search", + level=Qgis.Critical, + ) # add link to github for help - help_icon_path = os.path.join(os.path.dirname(__file__), "questionmark.png") - # TODO - #pixmap.load(help_icon_path) - #self.dlg.labelHelp.setPixmap(pixmap.scaled(self.dlg.labelHelp.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) - # TODO: exchange the gprlp_metadata_search repository for the new target repository - #self.dlg.labelHelp.setText('' + - # self.tr("Help") + '') - #self.dlg.labelHelp.setOpenExternalLinks(True) + # help_icon_path = os.path.join(os.path.dirname(__file__), "questionmark.png") + # self.dlg.labelHelp.setOpenExternalLinks(True) # show the dialog self.dlg.show() # Run the dialog event loop @@ -255,8 +261,8 @@ def onClosePlugin(self): try: connection = self.editor.topitem.child(x) connection.disconnectSignals() - except: - pass + except Exception as e: + print('could not disconnect signals for connection' + str(e)) def openEditor(self): if not self.pluginIsActive: diff --git a/qgis_shogun_editor_dialog.py b/qgis_shogun_editor_dialog.py index 7793fa3..93eeabf 100644 --- a/qgis_shogun_editor_dialog.py +++ b/qgis_shogun_editor_dialog.py @@ -24,8 +24,7 @@ import os -from qgis.PyQt import uic -from qgis.PyQt import QtWidgets +from qgis.PyQt import QtWidgets, uic # This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer FORM_CLASS, _ = uic.loadUiType(os.path.join( diff --git a/requirements/development.txt b/requirements/development.txt new file mode 100644 index 0000000..15291dc --- /dev/null +++ b/requirements/development.txt @@ -0,0 +1,8 @@ +flake8 +flake8-builtins +flake8-isort +setuptools +pylint +pycodestyle +autopep8 +black diff --git a/resource_column.py b/resource_column.py index 273ad83..044b0f4 100644 --- a/resource_column.py +++ b/resource_column.py @@ -1,6 +1,7 @@ from enum import Enum + class ResourceColumn(Enum): TITLE = 0 IDENTIFIER = 1 - TYPE = 2 \ No newline at end of file + TYPE = 2 diff --git a/resources.py b/resources.py index 02140d6..cdf51e4 100644 --- a/resources.py +++ b/resources.py @@ -112,7 +112,7 @@ \x00\x00\x01\x7e\xe0\x16\xbc\x03\ " -qt_version = [int(v) for v in QtCore.qVersion().split('.')] +qt_version = [int(v) for v in QtCore.qVersion().split(".")] if qt_version < [5, 8, 0]: rcc_version = 1 qt_resource_struct = qt_resource_struct_v1 @@ -120,10 +120,17 @@ rcc_version = 2 qt_resource_struct = qt_resource_struct_v2 + def qInitResources(): - QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qRegisterResourceData( + rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data + ) + def qCleanupResources(): - QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qUnregisterResourceData( + rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data + ) + qInitResources() diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..cc2c068 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,31 @@ +[metadata] +description_file = README.md + +# -- Code quality ------------------------------------ +[flake8] +count = True +exclude = venv,env,__pycache__,build,dist,.git,.eggs,*.egg-info,site-packages +ignore = T201, W503, W504, C901 +max-complexity = 20 +max-doc-length = 130 +max-line-length = 120 +per-file-ignores = + __init__.py: E501, W505 + plugin_upload.py: E501, W505, F821 + resources.py: E501, W505 + qgis_shogun_editor.py: F401, E501, F403, W505 + qgis_shogun_editor_dialog.py: F401, E501, F403, W505 +per-file-line_length = 21 + +[isort] +ensure_newline_before_comments = True +force_grid_wrap = 0 +include_trailing_comma = True +line_length = 120 +multi_line_output = 3 +profile = black +use_parentheses = True + +[pycodestyle] +exclude = venv,env,__pycache__,build,dist,.git,.eggs,*.egg-info,site-packages +max-line-length = 120 diff --git a/startDocker.sh b/startDocker.sh new file mode 100755 index 0000000..67c9ce0 --- /dev/null +++ b/startDocker.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +xhost +local:docker +docker run --rm -it --name qgis --net host \ + -e DISPLAY=$DISPLAY \ + -v /tmp/.X11-unix:/tmp/.X11-unix \ + -v ./:/root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/shogun_qgis_editor \ + qgis/qgis:3.44 \ + qgis diff --git a/test/__init__.py b/test/__init__.py deleted file mode 100644 index 8feeb0b..0000000 --- a/test/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# import qgis libs so that ve set the correct sip api version -import qgis # pylint: disable=W0611 # NOQA \ No newline at end of file diff --git a/test/qgis_interface.py b/test/qgis_interface.py deleted file mode 100644 index a407052..0000000 --- a/test/qgis_interface.py +++ /dev/null @@ -1,205 +0,0 @@ -# coding=utf-8 -"""QGIS plugin implementation. - -.. note:: This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - -.. note:: This source code was copied from the 'postgis viewer' application - with original authors: - Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk - Copyright (c) 2011 German Carrillo, geotux_tuxman@linuxmail.org - Copyright (c) 2014 Tim Sutton, tim@linfiniti.com - -""" - -__author__ = 'tim@linfiniti.com' -__revision__ = '$Format:%H$' -__date__ = '10/01/2011' -__copyright__ = ( - 'Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk and ' - 'Copyright (c) 2011 German Carrillo, geotux_tuxman@linuxmail.org' - 'Copyright (c) 2014 Tim Sutton, tim@linfiniti.com' -) - -import logging -from qgis.PyQt.QtCore import QObject, pyqtSlot, pyqtSignal -from qgis.core import QgsMapLayerRegistry -from qgis.gui import QgsMapCanvasLayer -LOGGER = logging.getLogger('QGIS') - - -#noinspection PyMethodMayBeStatic,PyPep8Naming -class QgisInterface(QObject): - """Class to expose QGIS objects and functions to plugins. - - This class is here for enabling us to run unit tests only, - so most methods are simply stubs. - """ - currentLayerChanged = pyqtSignal(QgsMapCanvasLayer) - - def __init__(self, canvas): - """Constructor - :param canvas: - """ - QObject.__init__(self) - self.canvas = canvas - # Set up slots so we can mimic the behaviour of QGIS when layers - # are added. - LOGGER.debug('Initialising canvas...') - # noinspection PyArgumentList - QgsMapLayerRegistry.instance().layersAdded.connect(self.addLayers) - # noinspection PyArgumentList - QgsMapLayerRegistry.instance().layerWasAdded.connect(self.addLayer) - # noinspection PyArgumentList - QgsMapLayerRegistry.instance().removeAll.connect(self.removeAllLayers) - - # For processing module - self.destCrs = None - - @pyqtSlot('QStringList') - def addLayers(self, layers): - """Handle layers being added to the registry so they show up in canvas. - - :param layers: list list of map layers that were added - - .. note:: The QgsInterface api does not include this method, - it is added here as a helper to facilitate testing. - """ - #LOGGER.debug('addLayers called on qgis_interface') - #LOGGER.debug('Number of layers being added: %s' % len(layers)) - #LOGGER.debug('Layer Count Before: %s' % len(self.canvas.layers())) - current_layers = self.canvas.layers() - final_layers = [] - for layer in current_layers: - final_layers.append(QgsMapCanvasLayer(layer)) - for layer in layers: - final_layers.append(QgsMapCanvasLayer(layer)) - - self.canvas.setLayerSet(final_layers) - #LOGGER.debug('Layer Count After: %s' % len(self.canvas.layers())) - - @pyqtSlot('QgsMapLayer') - def addLayer(self, layer): - """Handle a layer being added to the registry so it shows up in canvas. - - :param layer: list list of map layers that were added - - .. note: The QgsInterface api does not include this method, it is added - here as a helper to facilitate testing. - - .. note: The addLayer method was deprecated in QGIS 1.8 so you should - not need this method much. - """ - pass - - @pyqtSlot() - def removeAllLayers(self): - """Remove layers from the canvas before they get deleted.""" - self.canvas.setLayerSet([]) - - def newProject(self): - """Create new project.""" - # noinspection PyArgumentList - QgsMapLayerRegistry.instance().removeAllMapLayers() - - # ---------------- API Mock for QgsInterface follows ------------------- - - def zoomFull(self): - """Zoom to the map full extent.""" - pass - - def zoomToPrevious(self): - """Zoom to previous view extent.""" - pass - - def zoomToNext(self): - """Zoom to next view extent.""" - pass - - def zoomToActiveLayer(self): - """Zoom to extent of active layer.""" - pass - - def addVectorLayer(self, path, base_name, provider_key): - """Add a vector layer. - - :param path: Path to layer. - :type path: str - - :param base_name: Base name for layer. - :type base_name: str - - :param provider_key: Provider key e.g. 'ogr' - :type provider_key: str - """ - pass - - def addRasterLayer(self, path, base_name): - """Add a raster layer given a raster layer file name - - :param path: Path to layer. - :type path: str - - :param base_name: Base name for layer. - :type base_name: str - """ - pass - - def activeLayer(self): - """Get pointer to the active layer (layer selected in the legend).""" - # noinspection PyArgumentList - layers = QgsMapLayerRegistry.instance().mapLayers() - for item in layers: - return layers[item] - - def addToolBarIcon(self, action): - """Add an icon to the plugins toolbar. - - :param action: Action to add to the toolbar. - :type action: QAction - """ - pass - - def removeToolBarIcon(self, action): - """Remove an action (icon) from the plugin toolbar. - - :param action: Action to add to the toolbar. - :type action: QAction - """ - pass - - def addToolBar(self, name): - """Add toolbar with specified name. - - :param name: Name for the toolbar. - :type name: str - """ - pass - - def mapCanvas(self): - """Return a pointer to the map canvas.""" - return self.canvas - - def mainWindow(self): - """Return a pointer to the main window. - - In case of QGIS it returns an instance of QgisApp. - """ - pass - - def addDockWidget(self, area, dock_widget): - """Add a dock widget to the main window. - - :param area: Where in the ui the dock should be placed. - :type area: - - :param dock_widget: A dock widget to add to the UI. - :type dock_widget: QDockWidget - """ - pass - - def legendInterface(self): - """Get the legend.""" - return self.canvas diff --git a/test/tenbytenraster.asc b/test/tenbytenraster.asc deleted file mode 100644 index 96a0ee1..0000000 --- a/test/tenbytenraster.asc +++ /dev/null @@ -1,19 +0,0 @@ -NCOLS 10 -NROWS 10 -XLLCENTER 1535380.000000 -YLLCENTER 5083260.000000 -DX 10 -DY 10 -NODATA_VALUE -9999 -0 1 2 3 4 5 6 7 8 9 -0 1 2 3 4 5 6 7 8 9 -0 1 2 3 4 5 6 7 8 9 -0 1 2 3 4 5 6 7 8 9 -0 1 2 3 4 5 6 7 8 9 -0 1 2 3 4 5 6 7 8 9 -0 1 2 3 4 5 6 7 8 9 -0 1 2 3 4 5 6 7 8 9 -0 1 2 3 4 5 6 7 8 9 -0 1 2 3 4 5 6 7 8 9 -CRS -NOTES diff --git a/test/tenbytenraster.asc.aux.xml b/test/tenbytenraster.asc.aux.xml deleted file mode 100644 index cfb1578..0000000 --- a/test/tenbytenraster.asc.aux.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - Point - - - - 9 - 4.5 - 0 - 2.872281323269 - - - diff --git a/test/tenbytenraster.keywords b/test/tenbytenraster.keywords deleted file mode 100644 index 8be3f61..0000000 --- a/test/tenbytenraster.keywords +++ /dev/null @@ -1 +0,0 @@ -title: Tenbytenraster diff --git a/test/tenbytenraster.lic b/test/tenbytenraster.lic deleted file mode 100644 index 8345533..0000000 --- a/test/tenbytenraster.lic +++ /dev/null @@ -1,18 +0,0 @@ - - - - Tim Sutton, Linfiniti Consulting CC - - - - tenbytenraster.asc - 2700044251 - Yes - Tim Sutton - Tim Sutton (QGIS Source Tree) - Tim Sutton - This data is publicly available from QGIS Source Tree. The original - file was created and contributed to QGIS by Tim Sutton. - - - diff --git a/test/tenbytenraster.prj b/test/tenbytenraster.prj deleted file mode 100644 index a30c00a..0000000 --- a/test/tenbytenraster.prj +++ /dev/null @@ -1 +0,0 @@ -GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] \ No newline at end of file diff --git a/test/tenbytenraster.qml b/test/tenbytenraster.qml deleted file mode 100644 index 85247d4..0000000 --- a/test/tenbytenraster.qml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - 0 - diff --git a/test/test_gprlp_metadata_search_dialog.py b/test/test_gprlp_metadata_search_dialog.py deleted file mode 100644 index 16ccdcb..0000000 --- a/test/test_gprlp_metadata_search_dialog.py +++ /dev/null @@ -1,55 +0,0 @@ -# coding=utf-8 -"""Dialog test. - -.. note:: This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - -""" - -__author__ = 'armin.retterath@gmail.com' -__date__ = '2022-02-09' -__copyright__ = 'Copyright 2022, Armin Retterath' - -import unittest - -from qgis.PyQt.QtGui import QDialogButtonBox, QDialog - -from shogun_qgis_configurator_dialog import GeoportalRlpMetadataSearchDialog - -from utilities import get_qgis_app -QGIS_APP = get_qgis_app() - - -class GeoportalRlpMetadataSearchDialogTest(unittest.TestCase): - """Test dialog works.""" - - def setUp(self): - """Runs before each test.""" - self.dialog = GeoportalRlpMetadataSearchDialog(None) - - def tearDown(self): - """Runs after each test.""" - self.dialog = None - - def test_dialog_ok(self): - """Test we can click OK.""" - - button = self.dialog.button_box.button(QDialogButtonBox.Ok) - button.click() - result = self.dialog.result() - self.assertEqual(result, QDialog.Accepted) - - def test_dialog_cancel(self): - """Test we can click cancel.""" - button = self.dialog.button_box.button(QDialogButtonBox.Cancel) - button.click() - result = self.dialog.result() - self.assertEqual(result, QDialog.Rejected) - -if __name__ == "__main__": - suite = unittest.makeSuite(GeoportalRlpMetadataSearchDialogTest) - runner = unittest.TextTestRunner(verbosity=2) - runner.run(suite) - diff --git a/test/test_init.py b/test/test_init.py deleted file mode 100644 index a11ca44..0000000 --- a/test/test_init.py +++ /dev/null @@ -1,64 +0,0 @@ -# coding=utf-8 -"""Tests QGIS plugin init.""" - -__author__ = 'Tim Sutton ' -__revision__ = '$Format:%H$' -__date__ = '17/10/2010' -__license__ = "GPL" -__copyright__ = 'Copyright 2012, Australia Indonesia Facility for ' -__copyright__ += 'Disaster Reduction' - -import os -import unittest -import logging -import configparser - -LOGGER = logging.getLogger('QGIS') - - -class TestInit(unittest.TestCase): - """Test that the plugin init is usable for QGIS. - - Based heavily on the validator class by Alessandro - Passoti available here: - - http://github.com/qgis/qgis-django/blob/master/qgis-app/ - plugins/validator.py - - """ - - def test_read_init(self): - """Test that the plugin __init__ will validate on plugins.qgis.org.""" - - # You should update this list according to the latest in - # https://github.com/qgis/qgis-django/blob/master/qgis-app/ - # plugins/validator.py - - required_metadata = [ - 'name', - 'description', - 'version', - 'qgisMinimumVersion', - 'email', - 'author'] - - file_path = os.path.abspath(os.path.join( - os.path.dirname(__file__), os.pardir, - 'metadata.txt')) - LOGGER.info(file_path) - metadata = [] - parser = configparser.ConfigParser() - parser.optionxform = str - parser.read(file_path) - message = 'Cannot find a section named "general" in %s' % file_path - assert parser.has_section('general'), message - metadata.extend(parser.items('general')) - - for expectation in required_metadata: - message = ('Cannot find metadata "%s" in metadata source (%s).' % ( - expectation, file_path)) - - self.assertIn(expectation, dict(metadata), message) - -if __name__ == '__main__': - unittest.main() diff --git a/test/test_qgis_environment.py b/test/test_qgis_environment.py deleted file mode 100644 index 1becb30..0000000 --- a/test/test_qgis_environment.py +++ /dev/null @@ -1,60 +0,0 @@ -# coding=utf-8 -"""Tests for QGIS functionality. - - -.. note:: This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - -""" -__author__ = 'tim@linfiniti.com' -__date__ = '20/01/2011' -__copyright__ = ('Copyright 2012, Australia Indonesia Facility for ' - 'Disaster Reduction') - -import os -import unittest -from qgis.core import ( - QgsProviderRegistry, - QgsCoordinateReferenceSystem, - QgsRasterLayer) - -from .utilities import get_qgis_app -QGIS_APP = get_qgis_app() - - -class QGISTest(unittest.TestCase): - """Test the QGIS Environment""" - - def test_qgis_environment(self): - """QGIS environment has the expected providers""" - - r = QgsProviderRegistry.instance() - self.assertIn('gdal', r.providerList()) - self.assertIn('ogr', r.providerList()) - self.assertIn('postgres', r.providerList()) - - def test_projection(self): - """Test that QGIS properly parses a wkt string. - """ - crs = QgsCoordinateReferenceSystem() - wkt = ( - 'GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",' - 'SPHEROID["WGS_1984",6378137.0,298.257223563]],' - 'PRIMEM["Greenwich",0.0],UNIT["Degree",' - '0.0174532925199433]]') - crs.createFromWkt(wkt) - auth_id = crs.authid() - expected_auth_id = 'EPSG:4326' - self.assertEqual(auth_id, expected_auth_id) - - # now test for a loaded layer - path = os.path.join(os.path.dirname(__file__), 'tenbytenraster.asc') - title = 'TestRaster' - layer = QgsRasterLayer(path, title) - auth_id = layer.crs().authid() - self.assertEqual(auth_id, expected_auth_id) - -if __name__ == '__main__': - unittest.main() diff --git a/test/test_resources.py b/test/test_resources.py deleted file mode 100644 index 90e1193..0000000 --- a/test/test_resources.py +++ /dev/null @@ -1,44 +0,0 @@ -# coding=utf-8 -"""Resources test. - -.. note:: This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - -""" - -__author__ = 'armin.retterath@gmail.com' -__date__ = '2022-02-09' -__copyright__ = 'Copyright 2022, Armin Retterath' - -import unittest - -from qgis.PyQt.QtGui import QIcon - - - -class GeoportalRlpMetadataSearchDialogTest(unittest.TestCase): - """Test rerources work.""" - - def setUp(self): - """Runs before each test.""" - pass - - def tearDown(self): - """Runs after each test.""" - pass - - def test_icon_png(self): - """Test we can click OK.""" - path = ':/plugins/GeoportalRlpMetadataSearch/icon.png' - icon = QIcon(path) - self.assertFalse(icon.isNull()) - -if __name__ == "__main__": - suite = unittest.makeSuite(GeoportalRlpMetadataSearchResourcesTest) - runner = unittest.TextTestRunner(verbosity=2) - runner.run(suite) - - - diff --git a/test/test_translations.py b/test/test_translations.py deleted file mode 100644 index 035dc62..0000000 --- a/test/test_translations.py +++ /dev/null @@ -1,55 +0,0 @@ -# coding=utf-8 -"""Safe Translations Test. - -.. note:: This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - -""" -from .utilities import get_qgis_app - -__author__ = 'ismailsunni@yahoo.co.id' -__date__ = '12/10/2011' -__copyright__ = ('Copyright 2012, Australia Indonesia Facility for ' - 'Disaster Reduction') -import unittest -import os - -from qgis.PyQt.QtCore import QCoreApplication, QTranslator - -QGIS_APP = get_qgis_app() - - -class SafeTranslationsTest(unittest.TestCase): - """Test translations work.""" - - def setUp(self): - """Runs before each test.""" - if 'LANG' in iter(os.environ.keys()): - os.environ.__delitem__('LANG') - - def tearDown(self): - """Runs after each test.""" - if 'LANG' in iter(os.environ.keys()): - os.environ.__delitem__('LANG') - - def test_qgis_translations(self): - """Test that translations work.""" - parent_path = os.path.join(__file__, os.path.pardir, os.path.pardir) - dir_path = os.path.abspath(parent_path) - file_path = os.path.join( - dir_path, 'i18n', 'af.qm') - translator = QTranslator() - translator.load(file_path) - QCoreApplication.installTranslator(translator) - - expected_message = 'Goeie more' - real_message = QCoreApplication.translate("@default", 'Good morning') - self.assertEqual(real_message, expected_message) - - -if __name__ == "__main__": - suite = unittest.makeSuite(SafeTranslationsTest) - runner = unittest.TextTestRunner(verbosity=2) - runner.run(suite) diff --git a/test/utilities.py b/test/utilities.py deleted file mode 100644 index be7ee3b..0000000 --- a/test/utilities.py +++ /dev/null @@ -1,61 +0,0 @@ -# coding=utf-8 -"""Common functionality used by regression tests.""" - -import sys -import logging - - -LOGGER = logging.getLogger('QGIS') -QGIS_APP = None # Static variable used to hold hand to running QGIS app -CANVAS = None -PARENT = None -IFACE = None - - -def get_qgis_app(): - """ Start one QGIS application to test against. - - :returns: Handle to QGIS app, canvas, iface and parent. If there are any - errors the tuple members will be returned as None. - :rtype: (QgsApplication, CANVAS, IFACE, PARENT) - - If QGIS is already running the handle to that app will be returned. - """ - - try: - from qgis.PyQt import QtGui, QtCore - from qgis.core import QgsApplication - from qgis.gui import QgsMapCanvas - from .qgis_interface import QgisInterface - except ImportError: - return None, None, None, None - - global QGIS_APP # pylint: disable=W0603 - - if QGIS_APP is None: - gui_flag = True # All test will run qgis in gui mode - #noinspection PyPep8Naming - QGIS_APP = QgsApplication(sys.argv, gui_flag) - # Make sure QGIS_PREFIX_PATH is set in your env if needed! - QGIS_APP.initQgis() - s = QGIS_APP.showSettings() - LOGGER.debug(s) - - global PARENT # pylint: disable=W0603 - if PARENT is None: - #noinspection PyPep8Naming - PARENT = QtGui.QWidget() - - global CANVAS # pylint: disable=W0603 - if CANVAS is None: - #noinspection PyPep8Naming - CANVAS = QgsMapCanvas(PARENT) - CANVAS.resize(QtCore.QSize(400, 400)) - - global IFACE # pylint: disable=W0603 - if IFACE is None: - # QgisInterface is a stub implementation of the QGIS plugin interface - #noinspection PyPep8Naming - IFACE = QgisInterface(CANVAS) - - return QGIS_APP, CANVAS, IFACE, PARENT