class QgisCloudPluginDialog(QDockWidget): COLUMN_LAYERS = 0 COLUMN_DATA_SOURCE = 1 COLUMN_TABLE_NAME = 2 COLUMN_GEOMETRY_TYPE = 3 COLUMN_SRID = 4 if QGis.QGIS_VERSION_INT < 21400: GEOMETRY_TYPES = { QGis.WKBUnknown: "Unknown", QGis.WKBPoint: "Point", QGis.WKBMultiPoint: "MultiPoint", QGis.WKBLineString: "LineString", QGis.WKBMultiLineString: "MultiLineString", QGis.WKBPolygon: "Polygon", QGis.WKBMultiPolygon: "MultiPolygon", # Workaround (missing Python binding?): QGis.WKBNoGeometry / # ogr.wkbNone 100: "No geometry", QGis.WKBPoint25D: "Point", QGis.WKBLineString25D: "LineString", QGis.WKBPolygon25D: "Polygon", QGis.WKBMultiPoint25D: "MultiPoint", QGis.WKBMultiLineString25D: "MultiLineString", QGis.WKBMultiPolygon25D: "MultiPolygon", QGis.WKBMultiPolygon25D: "MultiPolygon", QGis.WKBMultiPolygon25D: "MultiPolygon", } else: GEOMETRY_TYPES = { QgsWKBTypes.Unknown: "Unknown", QgsWKBTypes.NoGeometry: "No geometry", QgsWKBTypes.Point: "Point", QgsWKBTypes.MultiPoint: "MultiPoint", QgsWKBTypes.PointZ: "PointZ", QgsWKBTypes.MultiPointZ: "MultiPointZ", QgsWKBTypes.PointM: "PointM", QgsWKBTypes.MultiPointM: "MultiPointM", QgsWKBTypes.PointZM: "PointZM", QgsWKBTypes.MultiPointZM: "MultiPointZM", QgsWKBTypes.Point25D: "Point25D", QgsWKBTypes.MultiPoint25D: "MultiPoint25D", QgsWKBTypes.LineString: "LineString", QgsWKBTypes.MultiLineString: "LineString", QgsWKBTypes.LineStringZ: "LineStringZ", QgsWKBTypes.MultiLineStringZ: "LineStringZ", QgsWKBTypes.LineStringM: "LineStringM", QgsWKBTypes.MultiLineStringM: "LineStringM", QgsWKBTypes.LineStringZM: "LineStringZM", QgsWKBTypes.MultiLineStringZM: "LineStringZM", QgsWKBTypes.LineString25D: "LineString25D", QgsWKBTypes.MultiLineString25D: "MultiLineString25D", QgsWKBTypes.Polygon: "Polygon", QgsWKBTypes.MultiPolygon: "MultiPolygon", QgsWKBTypes.PolygonZ: "PolygonZ", QgsWKBTypes.MultiPolygonZ: "MultiPolygonZ", QgsWKBTypes.PolygonM: "PolygonM", QgsWKBTypes.MultiPolygonM: "MultiPolygonM", QgsWKBTypes.PolygonZM: "PolygonZM", QgsWKBTypes.MultiPolygonZM: "MultiPolygonZM", QgsWKBTypes.Polygon25D: "Polygon25D", QgsWKBTypes.MultiPolygon25D: "MultiPolygon25D", QgsWKBTypes.CircularString: "CircularString", QgsWKBTypes.CompoundCurve: "CompoundCurve", QgsWKBTypes.CurvePolygon: "CurvePolygon", QgsWKBTypes.MultiCurve: "MultiCurve", QgsWKBTypes.MultiSurface: "MultiSurface", QgsWKBTypes.CircularStringZ: "CircularStringZ", QgsWKBTypes.CompoundCurveZ: "CompoundCurveZ", QgsWKBTypes.CurvePolygonZ: "CurvePolygonZ", QgsWKBTypes.MultiCurveZ: "MultiCurveZ", QgsWKBTypes.MultiSurfaceZ: "MultiSurfaceZ", QgsWKBTypes.CircularStringM: "CircularStringM", QgsWKBTypes.CompoundCurveM: "CompoundCurveM", QgsWKBTypes.CurvePolygonM: "CurvePolygonM", QgsWKBTypes.MultiCurveM: "MultiCurveM", QgsWKBTypes.MultiSurfaceM: "MultiSurfaceM", QgsWKBTypes.CircularStringZM: "CircularStringZM", QgsWKBTypes.CompoundCurveZM: "CompoundCurveZM", QgsWKBTypes.CurvePolygonZM: "CurvePolygonZM", QgsWKBTypes.MultiCurveZM: "MultiCurveZM", QgsWKBTypes.MultiSurfaceZM: "MultiSurfaceZM", } def __init__(self, iface, version): QDockWidget.__init__(self, None) self.iface = iface self.clouddb = True self.version = version # Set up the user interface from Designer. self.ui = Ui_QgisCloudPlugin() self.ui.setupUi(self) self.storage_exceeded = True myAbout = DlgAbout() self.ui.aboutText.setText( myAbout.aboutString() + myAbout.contribString() + myAbout.licenseString() + "<p>Versions:<ul>" + "<li>QGIS: %s</li>" % unicode(QGis.QGIS_VERSION).encode("utf-8") + "<li>Python: %s</li>" % sys.version.replace("\n", " ") + "<li>OS: %s</li>" % platform.platform() + "</ul></p>") self.ui.lblVersionPlugin.setText(self.version) self.ui.tblLocalLayers.setColumnCount(5) header = ["Layers", "Data source", "Table name", "Geometry type", "SRID"] self.ui.tblLocalLayers.setHorizontalHeaderLabels(header) self.ui.tblLocalLayers.resizeColumnsToContents() self.ui.tblLocalLayers.setEditTriggers(QAbstractItemView.NoEditTriggers) self.ui.btnUploadData.setEnabled(False) self.ui.btnPublishMap.setEnabled(False) self.ui.progressWidget.hide() self.ui.btnLogout.hide() self.ui.lblLoginStatus.hide() self.ui.widgetServices.hide() self.ui.widgetDatabases.setEnabled(False) self.ui.labelOpenLayersPlugin.hide() try: if QGis.QGIS_VERSION_INT >= 20300: from openlayers_menu import OpenlayersMenu else: # QGIS 1.x - QGIS-2.2 from openlayers_menu_compat import OpenlayersMenu self.ui.btnBackgroundLayer.setMenu(OpenlayersMenu(self.iface)) except: self.ui.btnBackgroundLayer.hide() self.ui.labelOpenLayersPlugin.show() # map<data source, table name> self.data_sources_table_names = {} # flag to disable update of local data sources during upload self.do_update_local_data_sources = True QObject.connect(self.ui.btnLogin, SIGNAL("clicked()"), self.check_login) QObject.connect( self.ui.btnDbCreate, SIGNAL("clicked()"), self.create_database) QObject.connect( self.ui.btnDbDelete, SIGNAL("clicked()"), self.delete_database) QObject.connect( self.ui.btnDbRefresh, SIGNAL("clicked()"), self.refresh_databases) QObject.connect(self.ui.tabDatabases, SIGNAL( "itemSelectionChanged()"), self.select_database) QObject.connect( self.ui.btnPublishMap, SIGNAL("clicked()"), self.publish_map) QObject.connect(self.ui.btnRefreshLocalLayers, SIGNAL( "clicked()"), self.refresh_local_data_sources) QObject.connect( self.iface, SIGNAL("newProjectCreated()"), self.reset_load_data) QObject.connect(QgsMapLayerRegistry.instance(), SIGNAL( "layerWillBeRemoved(QString)"), self.remove_layer) QObject.connect(QgsMapLayerRegistry.instance(), SIGNAL( "layerWasAdded(QgsMapLayer *)"), self.add_layer) QObject.connect(self.ui.cbUploadDatabase, SIGNAL( "currentIndexChanged(int)"), lambda idx: self.activate_upload_button()) QObject.connect( self.ui.btnUploadData, SIGNAL("clicked()"), self.upload_data) self.ui.editServer.textChanged.connect(self.serverURL) self.ui.resetUrlBtn.clicked.connect(self.resetApiUrl) self.read_settings() self.api = API() self.db_connections = DbConnections() self.local_data_sources = LocalDataSources() self.data_upload = DataUpload( self.iface, self.statusBar(), self.ui.lblProgress, self.api, self.db_connections) if self.URL == "": self.ui.editServer.setText(self.api.api_url()) else: self.ui.editServer.setText(self.URL) self.palette_red = QPalette(self.ui.lblVersionPlugin.palette()) self.palette_red.setColor(QPalette.WindowText, Qt.red) def unload(self): self.do_update_local_data_sources = False if self.iface: QObject.disconnect( self.iface, SIGNAL("newProjectCreated()"), self.reset_load_data) if QgsMapLayerRegistry.instance(): QObject.disconnect(QgsMapLayerRegistry.instance(), SIGNAL( "layerWillBeRemoved(QString)"), self.remove_layer) QObject.disconnect(QgsMapLayerRegistry.instance(), SIGNAL( "layerWasAdded(QgsMapLayer *)"), self.add_layer) def statusBar(self): return self.iface.mainWindow().statusBar() def map(self): project = QgsProject.instance() name = os.path.splitext( os.path.basename(unicode(project.fileName())))[0] # Allowed chars for QGISCloud map name: /\A[A-Za-z0-9\_\-]*\Z/ name = unicode(name).encode( 'ascii', 'replace') # Replace non-ascii chars # Replace withespace name = re.compile("\W+", re.UNICODE).sub("_", name) return name def resetApiUrl(self): self.ui.editServer.setText(self.api.api_url()) def serverURL(self, URL): self.URL = URL self.store_settings() def store_settings(self): s = QSettings() s.setValue("qgiscloud/user", self.user) s.setValue("qgiscloud/URL", self.URL) def read_settings(self): s = QSettings() self.user = s.value("qgiscloud/user", "", type=str) self.URL = s.value("qgiscloud/URL", "", type=str) def _update_clouddb_mode(self, clouddb): self.clouddb = clouddb self.ui.widgetDatabases.setVisible(self.clouddb) tab_index = 1 tab_name = QApplication.translate("QgisCloudPlugin", "Upload Data") visible = (self.ui.tabWidget.indexOf(self.ui.uploadTab) == tab_index) if visible and not self.clouddb: self.ui.tabWidget.removeTab(tab_index) elif not visible and self.clouddb: self.ui.tabWidget.insertTab(tab_index, self.ui.uploadTab, tab_name) def _version_info(self): return { 'versions': { 'plugin': self.version, 'QGIS': QGis.QGIS_VERSION, 'OS': platform.platform(), 'Python': sys.version } } def check_login(self): version_ok = True if not self.api.check_auth(): login_dialog = QDialog(self) login_dialog.ui = Ui_LoginDialog() login_dialog.ui.setupUi(login_dialog) login_dialog.ui.editUser.setText(self.user) login_ok = False while not login_ok and version_ok: self.api.set_url(self.api_url()) if not login_dialog.exec_(): self.api.set_auth( user=login_dialog.ui.editUser.text(), password=None) return login_ok self.api.set_auth( user=login_dialog.ui.editUser.text(), password=login_dialog.ui.editPassword.text()) try: login_info = self.api.check_login( version_info=self._version_info()) self.user = login_dialog.ui.editUser.text() self._update_clouddb_mode(login_info['clouddb']) version_ok = StrictVersion(self.version) >= StrictVersion(login_info['current_plugin']) if not version_ok: self.ui.lblVersionPlugin.setPalette(self.palette_red) QMessageBox.information(None, self.tr('New Version'), self.tr('New plugin release {version} is available! Please upgrade the QGIS Cloud plugin.').format(version=login_info['current_plugin'])) self.store_settings() self.ui.btnLogin.hide() self.ui.lblSignup.hide() self.ui.btnLogout.show() self.ui.widgetDatabases.setEnabled(True) self.ui.lblLoginStatus.setText( self.tr_uni("Logged in as {0} ({1})").format(self.user, login_info['plan'])) self.ui.lblLoginStatus.show() self._push_message( self.tr("QGIS Cloud"), self.tr_uni("Logged in as {0}").format(self.user), level=0, duration=2) self.refresh_databases() if not version_ok: self._push_message(self.tr("QGIS Cloud"), self.tr( "Unsupported versions detected. Please check your versions first!"), level=1) version_ok = False self.ui.tabWidget.setCurrentWidget(self.ui.aboutTab) login_ok = True self.update_local_layers() except ForbiddenError: QMessageBox.critical( self, self.tr("Account Disabled"), self.tr("Account {username} is disabled! Please contact [email protected]").format(username=login_dialog.ui.editUser.text())) login_ok = False except UnauthorizedError: QMessageBox.critical( self, self.tr("Login for user {username} failed").format(username=login_dialog.ui.editUser.text()), self.tr("Wrong user name or password")) login_ok = False except (TokenRequiredError, ConnectionException) as e: QMessageBox.critical( self, self.tr("Login failed"), self.tr("Login failed: %s") % str(e)) login_ok = False return version_ok def create_database(self): if self.numDbs < self.maxDBs: db = self.api.create_database() self.show_api_error(db) self.refresh_databases() else: QMessageBox.warning(None, self.tr('Warning!'), self.tr('Number of %s permitted databases exceeded! Please upgrade your account!') % self.maxDBs) def delete_database(self): name = self.ui.tabDatabases.currentItem().text() answer = False for layer in QgsMapLayerRegistry.instance().mapLayers().values(): if QgsDataSourceURI(layer.publicSource()).database() == name: if not answer: answer = QMessageBox.question( self, self.tr("Warning"), self.tr("You have layers from database {name} loaded in your project! Do you want to remove them before you delete database {name}.").format(name=name), QMessageBox.StandardButtons( QMessageBox.Cancel | QMessageBox.Yes)) if answer == QMessageBox.Yes: QgsMapLayerRegistry.instance().removeMapLayer(layer.id()) if answer == QMessageBox.Cancel: QMessageBox.warning(None, self.tr('Warning'), self.tr('Deletion of database {name} interrupted!').format(name=name)) return msgBox = QMessageBox() msgBox.setText(self.tr("Delete QGIS Cloud database.")) msgBox.setInformativeText( self.tr_uni("Do you want to delete the database \"%s\"?") % name) msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) msgBox.setDefaultButton(QMessageBox.Cancel) msgBox.setIcon(QMessageBox.Question) ret = msgBox.exec_() if ret == QMessageBox.Ok: self.setCursor(Qt.WaitCursor) result = self.api.delete_database(name) self.show_api_error(result) self.ui.btnDbDelete.setEnabled(False) time.sleep(2) self.refresh_databases() self.unsetCursor() def select_database(self): self.ui.btnDbDelete.setEnabled( len(self.ui.tabDatabases.selectedItems()) > 0) @pyqtSignature('') def on_btnLogout_clicked(self): self.api.reset_auth() self.ui.btnLogout.hide() self.ui.lblLoginStatus.hide() self.ui.btnLogin.show() self.ui.widgetServices.hide() self.ui.tabDatabases.clear() self.ui.lblDbSize.setText("") self.ui.lblDbSizeUpload.setText("") self.ui.cbUploadDatabase.clear() self.ui.widgetDatabases.setEnabled(False) self.activate_upload_button() def refresh_databases(self): QApplication.setOverrideCursor(Qt.WaitCursor) if self.clouddb: db_list = self.api.read_databases() if self.show_api_error(db_list): QApplication.restoreOverrideCursor() return self.db_connections = DbConnections() for db in db_list: self.db_connections.add_from_json(db) self.ui.tabDatabases.clear() self.ui.btnDbDelete.setEnabled(False) self.ui.cbUploadDatabase.clear() self.ui.cbUploadDatabase.setEditable(True) self.ui.cbUploadDatabase.lineEdit().setReadOnly(True) if self.db_connections.count() == 0: self.ui.cbUploadDatabase.setEditText(self.tr("No databases")) else: for name, db in self.db_connections.iteritems(): it = QListWidgetItem(name) it.setToolTip(db.description()) self.ui.tabDatabases.addItem(it) self.ui.cbUploadDatabase.addItem(name) if self.ui.cbUploadDatabase.count() > 1: # Display the "Select database" text if more than one db is available self.ui.cbUploadDatabase.setCurrentIndex(-1) self.ui.cbUploadDatabase.setEditText(self.tr("Select database")) self.db_connections.refresh(self.user) self.db_size(self.db_connections) QApplication.restoreOverrideCursor() def api_url(self): return unicode(self.ui.editServer.text()) def update_urls(self): self.update_url(self.ui.lblWebmap, self.api_url(), 'http://', u'{0}/{1}'.format(self.user, self.map())) #QWC2 (beta) update link self.update_url(self.ui.lblQwc2, self.api_url(), 'http://', u'{0}/{1}/qwc2/'.format(self.user, self.map())) if self.clouddb: self.update_url( self.ui.lblMobileMap, self.api_url(), 'http://m.', u'{0}/{1}'.format(self.user, self.map())) self.update_url( self.ui.lblWMS, self.api_url(), 'http://wms.', u'{0}/{1}'.format(self.user, self.map())) else: self.update_url(self.ui.lblMobileMap, self.api_url( ), 'http://', u'{0}/{1}/mobile'.format(self.user, self.map())) self.update_url(self.ui.lblWMS, self.api_url( ), 'http://', u'{0}/{1}/wms'.format(self.user, self.map())) self.update_url(self.ui.lblMaps, self.api_url(), 'http://', 'maps') self.ui.widgetServices.show() def update_url(self, label, api_url, prefix, path): base_url = string.replace(api_url, 'https://api.', prefix) url = u'{0}/{1}'.format(base_url, path) text = re.sub(r'http[^"]+', url, unicode(label.text())) label.setText(text) def read_maps(self): #map = self.api.read_map("1") if self.check_login(): self.api.read_maps() def check_project_saved(self): project = QgsProject.instance() fname = project.fileName() if project.isDirty() or fname == '': msgBox = QMessageBox() msgBox.setWindowTitle(self.tr("Project Modified")) msgBox.setText(self.tr("The project has been modified.")) msgBox.setInformativeText( self.tr("The project needs to be saved before it can be published. Proceed?")) msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Cancel) msgBox.setDefaultButton(QMessageBox.Save) if msgBox.exec_() == QMessageBox.Save: self.iface.actionSaveProject().trigger() return not project.isDirty() else: return False return True def publish_map(self): canvas = self.iface.mapCanvas() mapRenderer = canvas.mapRenderer() srs=mapRenderer.destinationCrs() if "USER" in srs.authid(): QMessageBox.warning(None, self.tr('Warning!'), self.tr("The project has a user defined CRS. The use of user defined CRS is not supported. Please correct the project CRS before publishing!")) return layers = self.iface.legendInterface().layers() layerList = '' for layer in layers: if "USER" in layer.crs().authid(): layerList += "'"+layer.name()+"' " if len(layerList) > 0: QMessageBox.warning(None, self.tr('Warning!'), self.tr("The layer(s) {layerlist}have user defined CRS. The use of user defined CRS is not supported. Please correct the CRS before publishing!").format(layerlist=layerList)) return saved = self.check_project_saved() if not saved: self.statusBar().showMessage(self.tr("Cancelled")) return if self.check_login() and self.check_layers(): self.statusBar().showMessage(self.tr("Publishing map")) try: fullExtent = self.iface.mapCanvas().fullExtent() config = { 'fullExtent': { 'xmin': fullExtent.xMinimum(), 'ymin': fullExtent.yMinimum(), 'xmax': fullExtent.xMaximum(), 'ymax': fullExtent.yMaximum() #}, # 'svgPaths': QgsApplication.svgPaths() #For resolving absolute symbol paths in print composer } } fname = unicode(QgsProject.instance().fileName()) map = self.api.create_map(self.map(), fname, config)['map'] self.show_api_error(map) if map['config']['missingSvgSymbols']: self.publish_symbols(map['config']['missingSvgSymbols']) self.update_urls() self._push_message(self.tr("QGIS Cloud"), self.tr( "Map successfully published"), level=0, duration=2) self.statusBar().showMessage( self.tr("Map successfully published")) except Exception as e: self.statusBar().showMessage("") ErrorReportDialog(self.tr("Error uploading project"), self.tr("An error occured."), str(e) + "\n" + traceback.format_exc(), self.user, self).exec_() def publish_symbols(self, missingSvgSymbols): self.statusBar().showMessage(self.tr("Uploading SVG symbols")) search_paths = QgsApplication.svgPaths() if hasattr(QgsProject.instance(), 'homePath'): search_paths += [QgsProject.instance().homePath()] # Search and upload symbol files for sym in missingSvgSymbols: # Absolute custom path if os.path.isfile(sym): self.api.create_graphic(sym, sym) else: for path in search_paths: fullpath = os.path.join(unicode(path), sym) if os.path.isfile(fullpath): self.api.create_graphic(sym, fullpath) self.statusBar().showMessage("") def reset_load_data(self): self.update_local_data_sources([], []) self.ui.btnUploadData.setEnabled(False) def remove_layer(self, layer_id): if self.do_update_local_data_sources: # skip layer if layer will be removed self.update_local_layers(layer_id) self.activate_upload_button() def add_layer(self): if self.do_update_local_data_sources: self.update_local_layers() self.activate_upload_button() def update_local_layers(self, skip_layer_id=None): local_layers, unsupported_layers, local_raster_layers = self.local_data_sources.local_layers(skip_layer_id) try: self.update_local_data_sources(local_layers, local_raster_layers) except Exception as e: ErrorReportDialog(self.tr("Error checking local data sources"), self.tr("An error occured."), str(e) + "\n" + traceback.format_exc(), self.user, self).exec_() return local_layers, unsupported_layers, local_raster_layers def check_layers(self): local_layers, unsupported_layers, local_raster_layers = self.update_local_layers() if ((local_layers or local_raster_layers) and self.clouddb) or unsupported_layers: message = "" if local_layers or local_raster_layers: title = self.tr("Local layers found") message += self.tr( "Some layers are using local data. You can upload local layers to your cloud database in the 'Upload Data' tab.\n\n") if unsupported_layers: title = self.tr("Unsupported layers found") message += self.tr( "Raster, plugin or geometryless layers are not supported:\n\n") layer_types = ["No geometry", "Raster", "Plugin"] for layer in sorted(unsupported_layers, key=lambda layer: layer.name()): message += self.tr_uni(" - %s (%s)\n") % ( layer.name(), layer_types[layer.type()]) message += self.tr( "\nPlease remove or replace above layers before publishing your map.\n") message += self.tr( "For raster data you can use public WMS layers or the OpenLayers Plugin.") QMessageBox.information(self, title, message) self.refresh_databases() self.ui.tabWidget.setCurrentWidget(self.ui.uploadTab) return False return True def update_local_data_sources(self, local_layers, local_raster_layers): # update table names lookup local_layers += local_raster_layers self.update_data_sources_table_names() self.local_data_sources.update_local_data_sources(local_layers) # update GUI self.ui.tblLocalLayers.setRowCount(0) for data_source, layers in self.local_data_sources.iteritems(): layer_names = [] for layer in layers: layer_names.append(unicode(layer.name())) layers_item = QTableWidgetItem(", ".join(layer_names)) layers_item.setToolTip("\n".join(layer_names)) data_source_item = QTableWidgetItem(data_source) data_source_item.setToolTip(data_source) # find a better table name if there are multiple layers with same # data source? table_name = layers[0].name() if data_source in self.data_sources_table_names: # use current table name if available to keep changes by user table_name = self.data_sources_table_names[data_source] table_name_item = QTableWidgetItem(QgisCloudPluginDialog.launder_pg_name(table_name)) if layers[0].providerType() == 'gdal': geometry_type_item = QTableWidgetItem('Raster') else: wkbType = layers[0].wkbType() if wkbType not in self.GEOMETRY_TYPES: QMessageBox.warning(self.iface.mainWindow(), self.tr("Unsupported geometry type"), pystring(self.tr( "Unsupported geometry type '{type}' in layer '{layer}'")).format(type=self.__wkbTypeString(wkbType), layer=layers[0].name())) continue geometry_type_item = QTableWidgetItem(self.GEOMETRY_TYPES[wkbType]) if layers[0].providerType() == "ogr": geometry_type_item.setToolTip( self.tr("Note: OGR features will be converted to MULTI-type")) srid_item = QTableWidgetItem(layers[0].crs().authid()) row = self.ui.tblLocalLayers.rowCount() self.ui.tblLocalLayers.insertRow(row) self.ui.tblLocalLayers.setItem( row, self.COLUMN_LAYERS, layers_item) self.ui.tblLocalLayers.setItem( row, self.COLUMN_DATA_SOURCE, data_source_item) self.ui.tblLocalLayers.setItem( row, self.COLUMN_TABLE_NAME, table_name_item) self.ui.tblLocalLayers.setItem( row, self.COLUMN_GEOMETRY_TYPE, geometry_type_item) self.ui.tblLocalLayers.setItem(row, self.COLUMN_SRID, srid_item) if self.local_data_sources.count() > 0: self.ui.tblLocalLayers.resizeColumnsToContents() self.ui.tblLocalLayers.setColumnWidth(self.COLUMN_LAYERS, 100) self.ui.tblLocalLayers.setColumnWidth(self.COLUMN_DATA_SOURCE, 100) self.ui.tblLocalLayers.sortItems(self.COLUMN_DATA_SOURCE) self.ui.tblLocalLayers.setSortingEnabled(False) else: self.ui.btnUploadData.setEnabled(False) self.statusBar().showMessage(self.tr("Updated local data sources")) def __wkbTypeString(self, wkbType): if wkbType == QGis.WKBUnknown: return "WKBUnknown" elif wkbType == QGis.WKBPoint: return "WKBPoint" elif wkbType == QGis.WKBLineString: return "WKBLineString" elif wkbType == QGis.WKBMultiLineString: return "WKBMultiLineString" elif wkbType == QGis.WKBPolygon: return "WKBPolygon" elif wkbType == QGis.WKBMultiPoint: return "WKBMultiPoint" elif wkbType == QGis.WKBMultiPolygon: return "WKBMultiPolygon" elif wkbType == QGis.WKBNoGeometry: return "WKBNoGeometry" elif wkbType == QGis.WKBPoint25D: return "WKBPoint25D" elif wkbType == QGis.WKBLineString25D: return "WKBLineString25D" elif wkbType == QGis.WKBPolygon25D: return "WKBPolygon25D" elif wkbType == QGis.WKBMultiPoint25D: return "WKBMultiPoint25D" elif wkbType == QGis.WKBMultiLineString25D: return "WKBMultiLineString25D" elif wkbType == QGis.WKBMultiPolygon25D: return "WKBMultiPolygon25D" elif QGis.QGIS_VERSION_INT >= 21400: if wkbType == QgsWKBTypes.LineStringZM: return "WKBLineStringZM" elif wkbType == QgsWKBTypes.MultiLineStringZM: return "WKBMultiLineStringZM" return self.tr("Unknown type") @staticmethod def launder_pg_name(name): # OGRPGDataSource::LaunderName # return re.sub(r"[#'-]", '_', unicode(name).lower()) input_string = unicode(name).lower().encode('ascii', 'replace') input_string = re.compile("\W+", re.UNICODE).sub("_", input_string) # check if tabke_name starts with number if re.search("^\d", input_string): input_string = '_'+input_string return input_string def refresh_local_data_sources(self): self.do_update_local_data_sources = True self.update_local_layers() self.activate_upload_button() def update_data_sources_table_names(self): if self.local_data_sources.count() == 0: self.data_sources_table_names.clear() else: # remove table names without data sources keys_to_remove = [] for key in self.data_sources_table_names.iterkeys(): if self.local_data_sources.layers(key) is None: keys_to_remove.append(key) for key in keys_to_remove: del self.data_sources_table_names[key] # update table names for row in range(0, self.ui.tblLocalLayers.rowCount()): data_source = unicode( self.ui.tblLocalLayers.item(row, self.COLUMN_DATA_SOURCE).text()) table_name = unicode( self.ui.tblLocalLayers.item(row, self.COLUMN_TABLE_NAME).text()) self.data_sources_table_names[data_source] = table_name def activate_upload_button(self): if not self.storage_exceeded: self.ui.btnUploadData.setEnabled(self.local_data_sources.count() > 0) self.ui.btnPublishMap.setDisabled(self.storage_exceeded) else: self.ui.btnUploadData.setDisabled(self.storage_exceeded) self.ui.btnPublishMap.setDisabled(self.storage_exceeded) def upload_data(self): if self.check_login(): if self.local_data_sources.count() == 0: return if self.db_connections.count() == 0: QMessageBox.warning(self, self.tr("No database available"), self.tr("Please create a database in the 'Account' tab.")) return if not self.ui.cbUploadDatabase.currentIndex() >= 0: QMessageBox.warning(self, self.tr("No database selected"), self.tr("Please select a database to upload data.")) return db_name = self.ui.cbUploadDatabase.currentText() if not self.db_connections.isPortOpen(db_name): uri = self.db_connections.cloud_layer_uri(db_name, "", "") host = str(uri.host()) port = uri.port() QMessageBox.critical(self, self.tr("Network Error"), self.tr("Could not connect to database server ({0}) on port {1}. Please contact your system administrator or internet provider to open port {1} in the firewall".format(host, port))) return # disable update of local data sources during upload, as there are # temporary layers added and removed self.do_update_local_data_sources = False self.statusBar().showMessage(self.tr("Uploading data...")) self.setCursor(Qt.WaitCursor) self.ui.btnUploadData.hide() self.ui.spinner.start() self.ui.progressWidget.show() # Map<data_source, {table: table, layers: layers}> data_sources_items = {} for row in range(0, self.ui.tblLocalLayers.rowCount()): data_source = unicode( self.ui.tblLocalLayers.item( row, self.COLUMN_DATA_SOURCE).text()) layers = self.local_data_sources.layers(data_source) if layers is not None: table_name = unicode( self.ui.tblLocalLayers.item( row, self.COLUMN_TABLE_NAME).text()) data_sources_items[data_source] = { 'table': table_name, 'layers': layers} login_info = self.api.check_login(version_info=self._version_info()) try: self.maxSize = login_info['max_storage'] self.maxDBs = login_info['max_dbs'] except: self.maxSize = 50 self.maxDBs = 5 try: self.data_upload.upload( self.db_connections.db(db_name), data_sources_items, self.maxSize) upload_ok = True except Exception as e: ErrorReportDialog(self.tr("Upload errors occurred"), self.tr("Upload errors occurred. Not all data could be uploaded."), str(e) + "\n" + traceback.format_exc(), self.user, self).exec_() upload_ok = False self.ui.spinner.stop() self.ui.progressWidget.hide() self.ui.btnUploadData.show() self.unsetCursor() self.statusBar().showMessage("") # Refresh local layers self.do_update_local_data_sources = True self.update_local_layers() # Refresh used space after upload self.db_size(self.db_connections) if upload_ok: # Show save project dialog save_dialog = QDialog(self) save_dialog.setWindowTitle(self.tr("Save Project")) save_dialog.setLayout(QVBoxLayout()) header = QWidget() header.setLayout(QVBoxLayout()) label = QLabel(self.tr("Upload complete. The local layers in the project were replaced with the layers uploaded to the qgiscloud database.")) label.setWordWrap(True) header.layout().addWidget(label) label = QLabel(self.tr("Choose were to save the modified project:")) label.setWordWrap(True) header.layout().addWidget(label) save_dialog.layout().setContentsMargins(0, 0, 0, 0) save_dialog.layout().addWidget(header) initialPath = QgsProject.instance().fileName() if not initialPath: initialPath = QSettings().value("/UI/lastProjectDir", ".") fd = QFileDialog(None, self.tr("Save Project"), initialPath, "%s (*.qgs)" % self.tr("QGIS Project Files")) fd.setParent(save_dialog, Qt.Widget) fd.setOption(QFileDialog.DontUseNativeDialog) fd.setAcceptMode(QFileDialog.AcceptSave) save_dialog.layout().addWidget(fd) header.layout().setContentsMargins(fd.layout().contentsMargins()) fd.accepted.connect(save_dialog.accept) fd.rejected.connect(save_dialog.reject) if save_dialog.exec_() == QDialog.Accepted: files = list(fd.selectedFiles()) if files: QgsProject.instance().setFileName(files[0]) self.iface.actionSaveProject().trigger() # Switch to map tab self.ui.tabWidget.setCurrentWidget(self.ui.mapTab) def _push_message(self, title, text, level=0, duration=0): # QGIS >= 2.0 if hasattr(self.iface, 'messageBar') and hasattr(self.iface.messageBar(), 'pushMessage'): self.iface.messageBar().pushMessage(title, text, level, duration) else: QMessageBox.information(self, title, text) def show_api_error(self, result): if 'error' in result: QMessageBox.critical( self, self.tr("QGIS Cloud Error"), "%s" % result['error']) self.statusBar().showMessage(self.tr("Error")) return True else: return False def tr_uni(self, str): return unicode(self.tr(str)) def db_size(self, db_connections): usedSpace = 0 self.numDbs = len(db_connections._dbs.keys()) for db in db_connections._dbs.keys(): try: conn = db_connections.db(db).psycopg_connection() except: continue cursor = conn.cursor() sql = "SELECT pg_database_size('" + str(db) + "')" cursor.execute(sql) usedSpace += int(cursor.fetchone()[0])-(11*1024*1024) cursor.close() conn.close # Used space in MB usedSpace /= 1024 * 1024 login_info = self.api.check_login(version_info=self._version_info()) try: self.maxSize = login_info['max_storage'] self.maxDBs = login_info['max_dbs'] except: self.maxSize = 50 self.maxDBs = 5 lblPalette = QPalette(self.ui.lblDbSize.palette()) usage = usedSpace / float(self.maxSize) self.storage_exceeded = False if usage < 0.8: bg_color = QColor(255, 0, 0, 0) text_color = QColor(Qt.black) elif usage >= 0.8 and usage < 1: bg_color = QColor(255, 0, 0, 100) text_color = QColor(Qt.white) elif usage >= 1: bg_color = QColor(255, 0, 0, 255) text_color = QColor(Qt.white) self.storage_exceeded = True lblPalette.setColor(QPalette.Window, QColor(bg_color)) lblPalette.setColor(QPalette.Foreground,QColor(text_color)) self.ui.lblDbSize.setAutoFillBackground(True) self.ui.lblDbSize.setPalette(lblPalette) self.ui.lblDbSize.setText( self.tr("Used DB Storage: ") + "%d / %d MB" % (usedSpace, self.maxSize)) self.ui.lblDbSizeUpload.setAutoFillBackground(True) self.ui.lblDbSizeUpload.setPalette(lblPalette) self.ui.lblDbSizeUpload.setText( self.tr("Used DB Storage: ") + "%d / %d MB" % (usedSpace, self.maxSize)) self.ui.btnUploadData.setDisabled(self.storage_exceeded) self.ui.btnPublishMap.setDisabled(self.storage_exceeded)
class QgisCloudPluginDialog(QDockWidget): COLUMN_LAYERS = 0 COLUMN_DATA_SOURCE = 1 COLUMN_TABLE_NAME = 2 COLUMN_GEOMETRY_TYPE = 3 COLUMN_SRID = 4 GEOMETRY_TYPES = { QGis.WKBUnknown: "Unknown", QGis.WKBPoint: "Point", QGis.WKBMultiPoint: "MultiPoint", QGis.WKBLineString: "LineString", QGis.WKBMultiLineString: "MultiLineString", QGis.WKBPolygon: "Polygon", QGis.WKBMultiPolygon: "MultiPolygon", 100: "No geometry", # Workaround (missing Python binding?): QGis.WKBNoGeometry / ogr.wkbNone QGis.WKBPoint25D: "Point", QGis.WKBLineString25D: "LineString", QGis.WKBPolygon25D: "Polygon", QGis.WKBMultiPoint25D: "MultiPoint", QGis.WKBMultiLineString25D: "MultiLineString", QGis.WKBMultiPolygon25D: "MultiPolygon" } def __init__(self, iface, version): QDockWidget.__init__(self, None) self.iface = iface self.clouddb = True self.version = version # Set up the user interface from Designer. self.ui = Ui_QgisCloudPlugin() self.ui.setupUi(self) myAbout = DlgAbout() self.ui.aboutText.setText(myAbout.aboutString() + myAbout.contribString() + myAbout.licenseString() + "<p>Version: " + version + "</p>") self.ui.tblLocalLayers.setColumnCount(5) header = ["Layers", "Data source", "Table name", "Geometry type", "SRID"] self.ui.tblLocalLayers.setHorizontalHeaderLabels(header) self.ui.tblLocalLayers.resizeColumnsToContents() # TODO; delegate for read only columns self.ui.btnUploadData.setEnabled(False) self.ui.uploadProgressBar.hide() self.ui.btnPublishMapUpload.hide() self.ui.btnLogout.hide() self.ui.lblLoginStatus.hide() # map<data source, table name> self.data_sources_table_names = {} # flag to disable update of local data sources during upload self.do_update_local_data_sources = True QObject.connect(self.ui.btnLogin, SIGNAL("clicked()"), self.login) QObject.connect(self.ui.btnDbCreate, SIGNAL("clicked()"), self.create_database) QObject.connect(self.ui.btnDbDelete, SIGNAL("clicked()"), self.delete_database) QObject.connect(self.ui.btnDbRefresh, SIGNAL("clicked()"), self.refresh_databases) QObject.connect(self.ui.tabDatabases, SIGNAL("itemSelectionChanged()"), self.select_database) QObject.connect(self.ui.btnPublishMap, SIGNAL("clicked()"), self.publish_map) QObject.connect(self.ui.btnRefreshLocalLayers, SIGNAL("clicked()"), self.refresh_local_data_sources) QObject.connect(self.iface, SIGNAL("newProjectCreated()"), self.reset_load_data) QObject.connect(QgsMapLayerRegistry.instance(), SIGNAL("layerWillBeRemoved(QString)"), self.remove_layer) QObject.connect(QgsMapLayerRegistry.instance(), SIGNAL("layerWasAdded(QgsMapLayer *)"), self.add_layer) QObject.connect(self.ui.cbUploadDatabase, SIGNAL("currentIndexChanged(int)"), self.upload_database_selected) QObject.connect(self.ui.btnUploadData, SIGNAL("clicked()"), self.upload_data) QObject.connect(self.ui.btnPublishMapUpload, SIGNAL("clicked()"), self.publish_map) self.ui.editServer.textChanged.connect(self.serverURL) self.ui.resetUrlBtn.clicked.connect(self.resetApiUrl) self.read_settings() self.api = API() self.db_connections = DbConnections() self.local_data_sources = LocalDataSources() self.data_upload = DataUpload(self.iface, self.statusBar(), self.ui.uploadProgressBar, self.api, self.db_connections) if self.URL == "": self.ui.editServer.setText(self.api.api_url()) else: self.ui.editServer.setText(self.URL) self.palette_red = QPalette(self.ui.serviceLinks.palette()) self.palette_red.setColor(QPalette.WindowText, QColor('red')) def __del__(self): QObject.disconnect(self.iface, SIGNAL("newProjectCreated()"), self.reset_load_data) QObject.disconnect(QgsMapLayerRegistry.instance(), SIGNAL("layerWillBeRemoved(QString)"), self.remove_layer) QObject.disconnect(QgsMapLayerRegistry.instance(), SIGNAL("layerWasAdded(QgsMapLayer *)"), self.add_layer) def statusBar(self): return self.iface.mainWindow().statusBar() def map(self): project = QgsProject.instance() name = os.path.splitext(os.path.basename(unicode(project.fileName())))[0] #Allowed chars for QGISCloud map name: /\A[A-Za-z0-9\_\-]*\Z/ name = unicode(name).lower().encode('ascii', 'replace') # Replace non-ascii chars name = re.compile("\W+", re.UNICODE).sub("_", name) # Replace withespace return name def resetApiUrl(self): self.ui.editServer.setText(self.api.api_url()) def serverURL(self, URL): self.URL = URL self.store_settings() def store_settings(self): s = QSettings() s.setValue("qgiscloud/user", self.user) s.setValue("qgiscloud/URL", self.URL) def read_settings(self): s = QSettings() self.user = s.value("qgiscloud/user", "", type=str) self.URL = s.value("qgiscloud/URL", "", type=str) def _update_clouddb_mode(self, clouddb): self.clouddb = clouddb self.ui.groupBoxDatabases.setVisible(self.clouddb) tab_index = 1 tab_name = QApplication.translate("QgisCloudPlugin", "Upload Data") visible = (self.ui.tabWidget.indexOf(self.ui.upload) == tab_index) if visible and not self.clouddb: self.ui.tabWidget.removeTab(tab_index) elif not visible and self.clouddb: self.ui.tabWidget.insertTab(tab_index, self.ui.upload, tab_name) def _version_info(self): return {'versions': {'plugin': self.version, 'QGIS': QGis.QGIS_VERSION, 'OGR': ogr_version_info(), 'OS': platform.platform(), 'Python': sys.version}} def _update_versions(self, current_plugin_version): version_ok = True self.ui.lblVersionQGIS.setText(QGis.QGIS_VERSION) self.ui.lblVersionPlugin.setText(self.version) if StrictVersion(self.version) < StrictVersion(current_plugin_version): self.ui.lblVersionPlugin.setPalette(self.palette_red) version_ok = False self.ui.lblVersionOGR.setText(ogr_version_info()) if ogr_version_num() < 1900: self.ui.lblVersionOGR.setPalette(self.palette_red) version_ok = False self.ui.lblVersionPython.setText(sys.version) self.ui.lblVersionOS.setText(platform.platform()) return version_ok def check_login(self): version_ok = True if not self.api.check_auth(): login_dialog = QDialog(self) login_dialog.ui = Ui_LoginDialog() login_dialog.ui.setupUi(login_dialog) login_dialog.ui.editUser.setText(self.user) login_ok = False while not login_ok and version_ok: self.api.set_url(self.api_url()) if not login_dialog.exec_(): self.api.set_auth(user=login_dialog.ui.editUser.text(), password=None) return login_ok self.api.set_auth(user=login_dialog.ui.editUser.text(), password=login_dialog.ui.editPassword.text()) try: login_info = self.api.check_login(version_info=self._version_info()) #{u'paid_until': None, u'plan': u'Free', u'current_plugin': u'0.8.0'} self.user = login_dialog.ui.editUser.text() self._update_clouddb_mode(login_info['clouddb']) version_ok = self._update_versions(login_info['current_plugin']) self.ui.serviceLinks.setCurrentWidget(self.ui.pageVersions) self.store_settings() self.ui.btnLogin.hide() self.ui.lblSignup.hide() self.ui.btnLogout.show() self.ui.lblLoginStatus.setText(self.tr_uni("Logged in as {0} ({1})").format(self.user, login_info['plan'])) self.ui.lblLoginStatus.show() self._push_message(self.tr("QGIS Cloud"), self.tr_uni("Logged in as {0}").format(self.user), level=0, duration=2) if not version_ok: self._push_message(self.tr("QGIS Cloud"), self.tr("Unsupported versions detected. Please check your versions first!"), level=1) version_ok = False self.ui.tabWidget.setCurrentWidget(self.ui.services) login_ok = True except (UnauthorizedError, TokenRequiredError, ConnectionException): QMessageBox.critical(self, self.tr("Login failed"), self.tr("Wrong user name or password")) login_ok = False return version_ok def create_database(self): if self.check_login(): db = self.api.create_database() # {u'username': u'jhzgpfwi_qgiscloud', u'host': u'beta.spacialdb.com', u'password': u'11d7338c', u'name': u'jhzgpfwi_qgiscloud', u'port': 9999} self.show_api_error(db) self.refresh_databases() def delete_database(self): if self.check_login(): name = self.ui.tabDatabases.currentItem().text() msgBox = QMessageBox() msgBox.setText(self.tr("Delete QGIS Cloud database.")) msgBox.setInformativeText(self.tr_uni("Do you want to delete the database \"%s\"?") % name) msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) msgBox.setDefaultButton(QMessageBox.Cancel) msgBox.setIcon(QMessageBox.Question) ret = msgBox.exec_() if ret == QMessageBox.Ok: self.setCursor(Qt.WaitCursor) result = self.api.delete_database(name) self.show_api_error(result) self.ui.btnDbDelete.setEnabled(False) time.sleep(2) self.refresh_databases() self.unsetCursor() def select_database(self): self.ui.btnDbDelete.setEnabled(len(self.ui.tabDatabases.selectedItems()) > 0) def login(self): if self.check_login(): self.refresh_databases() @pyqtSignature('') def on_btnLogout_clicked(self): self.api.reset_auth() self.ui.btnLogout.hide() self.ui.lblLoginStatus.hide() self.ui.btnLogin.show() def refresh_databases(self): if self.clouddb and self.check_login(): db_list = self.api.read_databases() if self.show_api_error(db_list): return self.db_connections = DbConnections() for db in db_list: #db example: {"host":"spacialdb.com","connection_string":"postgres://*****:*****@spacialdb.com:9999/sekpjr_jpyled","name":"sekpjr_jpyled","username":"******","port":9999,"password":"******"} self.db_connections.add_from_json(db) self.ui.tabDatabases.clear() self.ui.btnDbDelete.setEnabled(False) self.ui.cbUploadDatabase.clear() if self.db_connections.count() == 0: self.ui.cbUploadDatabase.addItem(self.tr("Create new database")) elif self.db_connections.count() > 1: self.ui.cbUploadDatabase.addItem(self.tr("Select database")) for name, db in self.db_connections.iteritems(): it = QListWidgetItem(name) it.setToolTip(db.description()) self.ui.tabDatabases.addItem(it) self.ui.cbUploadDatabase.addItem(name) self.db_connections.refresh(self.user) def api_url(self): return unicode(self.ui.editServer.text()) def update_urls(self): self.update_url(self.ui.lblWebmap, self.api_url(), 'http://', u'{0}/{1}'.format(self.user, self.map())) if self.clouddb: self.update_url(self.ui.lblMobileMap, self.api_url(), 'http://m.', u'{0}/{1}'.format(self.user, self.map())) self.update_url(self.ui.lblWMS, self.api_url(), 'http://wms.', u'{0}/{1}'.format(self.user, self.map())) else: self.update_url(self.ui.lblMobileMap, self.api_url(), 'http://', u'{0}/{1}/mobile'.format(self.user, self.map())) self.update_url(self.ui.lblWMS, self.api_url(), 'http://', u'{0}/{1}/wms'.format(self.user, self.map())) self.update_url(self.ui.lblMaps, self.api_url(), 'http://', 'maps') def update_url(self, label, api_url, prefix, path): base_url = string.replace(api_url, 'https://api.', prefix) url = u'{0}/{1}'.format(base_url, path) text = re.sub(r'http[^"]+', url, unicode(label.text())) label.setText(text) def read_maps(self): #map = self.api.read_map("1") if self.check_login(): self.api.read_maps() def check_project_saved(self): cancel = False project = QgsProject.instance() fname = unicode(project.fileName()) if project.isDirty() or fname == '': msgBox = QMessageBox() msgBox.setText(self.tr("The project has been modified.")) msgBox.setInformativeText(self.tr("Do you want to save your changes?")) if not fname: msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Cancel) else: msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Ignore | QMessageBox.Cancel) msgBox.setDefaultButton(QMessageBox.Save) ret = msgBox.exec_() if ret == QMessageBox.Save: if not fname: project.setFileName(QFileDialog.getSaveFileName(self, "Save Project", "", "QGIS Project Files (*.qgs)")) if not unicode(project.fileName()): cancel = True else: project.write() elif ret == QMessageBox.Cancel: cancel = True return cancel def publish_map(self): cancel = self.check_project_saved() if cancel: self.statusBar().showMessage(self.tr("Cancelled")) return if self.check_login() and self.check_layers(): self.statusBar().showMessage(self.tr("Publishing map")) try: fullExtent = self.iface.mapCanvas().fullExtent() config = { 'fullExtent': { 'xmin': fullExtent.xMinimum(), 'ymin': fullExtent.yMinimum(), 'xmax': fullExtent.xMaximum(), 'ymax': fullExtent.yMaximum() #}, #'svgPaths': QgsApplication.svgPaths() #For resolving absolute symbol paths in print composer } } fname = unicode(QgsProject.instance().fileName()) map = self.api.create_map(self.map(), fname, config)['map'] #QMessageBox.information(self, "create_map", str(map['config'])) self.show_api_error(map) if map['config']['missingSvgSymbols']: self.publish_symbols(map['config']['missingSvgSymbols']) self.update_urls() self.ui.serviceLinks.setCurrentWidget(self.ui.pageLinks) self.ui.btnPublishMapUpload.hide() self._push_message(self.tr("QGIS Cloud"), self.tr("Map successfully published"), level=0, duration=2) self.statusBar().showMessage(self.tr("Map successfully published")) except Exception: self.statusBar().showMessage("") self._exception_message(self.tr("Error uploading project")) def _exception_message(self, title): stack = traceback.format_exc().splitlines() msgBox = QMessageBox() msgBox.setText(self.tr_uni("An error occurred: %s") % stack[-1]) msgBox.setInformativeText(self.tr("Do you want to send the exception info to qgiscloud.com?")) msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) msgBox.setIcon(QMessageBox.Question) ret = msgBox.exec_() if ret == QMessageBox.Ok: project_fname = unicode(QgsProject.instance().fileName()) self.api.create_exception(str(traceback.format_exc()), self._version_info(), project_fname) def publish_symbols(self, missingSvgSymbols): self.statusBar().showMessage(self.tr("Uploading SVG symbols")) qDebug("publish_symbols: %s" % missingSvgSymbols) search_paths = QgsApplication.svgPaths() if hasattr(QgsProject.instance(), 'homePath'): search_paths += [QgsProject.instance().homePath()] #Search and upload symbol files for sym in missingSvgSymbols: #Absolute custom path if os.path.isfile(sym): self.api.create_graphic(sym, sym) else: for path in search_paths: fullpath = os.path.join(unicode(path), sym) if os.path.isfile(fullpath): qDebug("api.create_graphic: %s" % fullpath) self.api.create_graphic(sym, fullpath) self.statusBar().showMessage("") def reset_load_data(self): self.update_local_data_sources([]) self.ui.btnUploadData.setEnabled(False) self.ui.btnPublishMapUpload.hide() def remove_layer(self, layer_id): if self.db_connections.refreshed() and self.do_update_local_data_sources: # skip layer if layer will be removed self.update_local_layers(layer_id) self.activate_upload_button() def add_layer(self): if self.db_connections.refreshed() and self.do_update_local_data_sources: self.update_local_layers() self.activate_upload_button() def update_local_layers(self, skip_layer_id=None): local_layers, unsupported_layers = self.local_data_sources.local_layers(skip_layer_id) try: self.update_local_data_sources(local_layers) except: self._exception_message(self.tr("Error checking local data sources")) return local_layers, unsupported_layers def check_layers(self): local_layers, unsupported_layers = self.update_local_layers() if (local_layers and self.clouddb) or unsupported_layers: message = "" if local_layers: title = self.tr("Local layers found") message += self.tr("Some layers are using local data. You can upload local layers to your cloud database in the 'Upload Data' tab.\n\n") if unsupported_layers: title = self.tr("Unsupported layers found") message += self.tr("Raster, plugin or geometryless layers are not supported:\n\n") layer_types = ["No geometry", "Raster", "Plugin"] for layer in sorted(unsupported_layers, key=lambda layer: layer.name()): message += self.tr_uni(" - %s (%s)\n") % (layer.name(), layer_types[layer.type()]) message += self.tr("\nPlease remove or replace above layers before publishing your map.\n") message += self.tr("For raster data you can use public WMS layers or the OpenLayers Plugin.") QMessageBox.information(self, title, message) self.refresh_databases() self.ui.tabWidget.setCurrentWidget(self.ui.upload) return False return True def update_local_data_sources(self, local_layers): # update table names lookup self.update_data_sources_table_names() self.local_data_sources.update_local_data_sources(local_layers) # update GUI while self.ui.tblLocalLayers.rowCount() > 0: self.ui.tblLocalLayers.removeRow(0) for data_source, layers in self.local_data_sources.iteritems(): layer_names = [] for layer in layers: layer_names.append(unicode(layer.name())) layers_item = QTableWidgetItem(", ".join(layer_names)) layers_item.setToolTip("\n".join(layer_names)) data_source_item = QTableWidgetItem(data_source) data_source_item.setToolTip(data_source) table_name = layers[0].name() # find a better table name if there are multiple layers with same data source? if data_source in self.data_sources_table_names: # use current table name if available to keep changes by user table_name = self.data_sources_table_names[data_source] table_name_item = QTableWidgetItem(QgisCloudPluginDialog.launder_pg_name(table_name)) wkbType = layers[0].wkbType() if wkbType not in self.GEOMETRY_TYPES: raise Exception(self.tr("Unsupported geometry type '%s' in layer '%s'") % (wkbType, layers[0].name())) geometry_type_item = QTableWidgetItem(self.GEOMETRY_TYPES[wkbType]) if layers[0].providerType() == "ogr": geometry_type_item.setToolTip(self.tr("Note: OGR features will be converted to MULTI-type")) srid_item = QTableWidgetItem(layers[0].crs().authid()) row = self.ui.tblLocalLayers.rowCount() self.ui.tblLocalLayers.insertRow(row) self.ui.tblLocalLayers.setItem(row, self.COLUMN_LAYERS, layers_item) self.ui.tblLocalLayers.setItem(row, self.COLUMN_DATA_SOURCE, data_source_item) self.ui.tblLocalLayers.setItem(row, self.COLUMN_TABLE_NAME, table_name_item) self.ui.tblLocalLayers.setItem(row, self.COLUMN_GEOMETRY_TYPE, geometry_type_item) self.ui.tblLocalLayers.setItem(row, self.COLUMN_SRID, srid_item) if self.local_data_sources.count() > 0: self.ui.tblLocalLayers.resizeColumnsToContents() self.ui.tblLocalLayers.setColumnWidth(self.COLUMN_LAYERS, 100) self.ui.tblLocalLayers.setColumnWidth(self.COLUMN_DATA_SOURCE, 100) self.ui.tblLocalLayers.sortItems(self.COLUMN_DATA_SOURCE) self.ui.tblLocalLayers.setSortingEnabled(False) else: self.ui.btnUploadData.setEnabled(False) self.statusBar().showMessage(self.tr("Updated local data sources")) @staticmethod def launder_pg_name(name): #OGRPGDataSource::LaunderName #return re.sub(r"[#'-]", '_', unicode(name).lower()) input_string = unicode(name).lower().encode('ascii', 'replace') return re.compile("\W+", re.UNICODE).sub("_", input_string) def refresh_local_data_sources(self): if not self.db_connections.refreshed(): # get dbs on first refresh self.refresh_databases() self.do_update_local_data_sources = True self.update_local_layers() self.activate_upload_button() def update_data_sources_table_names(self): if self.local_data_sources.count() == 0: self.data_sources_table_names.clear() else: # remove table names without data sources keys_to_remove = [] for key in self.data_sources_table_names.iterkeys(): if self.local_data_sources.layers(key) is None: keys_to_remove.append(key) for key in keys_to_remove: del self.data_sources_table_names[key] # update table names for row in range(0, self.ui.tblLocalLayers.rowCount()): data_source = unicode(self.ui.tblLocalLayers.item(row, self.COLUMN_DATA_SOURCE).text()) table_name = unicode(self.ui.tblLocalLayers.item(row, self.COLUMN_TABLE_NAME).text()) self.data_sources_table_names[data_source] = table_name def upload_database_selected(self, index): self.activate_upload_button() def activate_upload_button(self): self.ui.btnUploadData.setEnabled((self.db_connections.count() <= 1 or self.ui.cbUploadDatabase.currentIndex() > 0) and self.local_data_sources.count() > 0) self.ui.btnPublishMapUpload.hide() def upload_data(self): if self.check_login(): if self.local_data_sources.count() == 0: return if self.db_connections.count() == 0: # create db self.statusBar().showMessage(self.tr("Create new database...")) QApplication.processEvents() # refresh status bar self.create_database() self.statusBar().showMessage("") db_name = self.ui.cbUploadDatabase.currentText() if not self.db_connections.isPortOpen(db_name): uri = self.db_connections.cloud_layer_uri(db_name, "", "") host = str(uri.host()) port = uri.port() QMessageBox.critical(self, self.tr("Network Error"), self.tr("Could not connect to database server ({0}) on port {1}. Please contact your system administrator or internet provider".format(host, port))) return # disable update of local data sources during upload, as there are temporary layers added and removed self.do_update_local_data_sources = False self.statusBar().showMessage(self.tr("Uploading data...")) self.setCursor(Qt.WaitCursor) # Map<data_source, {table: table, layers: layers}> data_sources_items = {} for row in range(0, self.ui.tblLocalLayers.rowCount()): data_source = unicode(self.ui.tblLocalLayers.item(row, self.COLUMN_DATA_SOURCE).text()) layers = self.local_data_sources.layers(data_source) if layers is not None: table_name = unicode(self.ui.tblLocalLayers.item(row, self.COLUMN_TABLE_NAME).text()) data_sources_items[data_source] = {'table': table_name, 'layers': layers} try: success = self.data_upload.upload(self.db_connections.db(db_name), data_sources_items, self.ui.cbReplaceLocalLayers.isChecked()) except Exception: success = False QgsMessageLog.logMessage(str(traceback.format_exc()), 'QGISCloud') if not success: self._show_log_window() QMessageBox.warning(self, self.tr("Upload data"), self.tr("Data upload error.\nSee Log Messages for more information.")) self.unsetCursor() self.statusBar().showMessage("") self.do_update_local_data_sources = True if success and self.ui.cbReplaceLocalLayers.isChecked(): self.update_local_layers() # show save project dialog msgBox = QMessageBox() msgBox.setWindowTitle(self.tr("QGIS Cloud")) msgBox.setText(self.tr("The project is ready for publishing.")) msgBox.setInformativeText(self.tr("Do you want to save your changes?")) msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Cancel) msgBox.setDefaultButton(QMessageBox.Save) ret = msgBox.exec_() if ret == QMessageBox.Save: self.iface.actionSaveProjectAs().trigger() self.ui.btnPublishMapUpload.show() def _show_log_window(self): logDock = self.iface.mainWindow().findChild(QDockWidget, 'MessageLog') logDock.show() def _push_message(self, title, text, level=0, duration=0): if hasattr(self.iface, 'messageBar') and hasattr(self.iface.messageBar(), 'pushMessage'): # QGIS >= 2.0 self.iface.messageBar().pushMessage(title, text, level, duration) else: QMessageBox.information(self, title, text) def show_api_error(self, result): if 'error' in result: QMessageBox.critical(self, self.tr("QGIS Cloud Error"), "%s" % result['error']) self.statusBar().showMessage(self.tr("Error")) return True else: return False def tr_uni(self, str): return unicode(self.tr(str))
class QgisCloudPluginDialog(QDockWidget): COLUMN_LAYERS = 0 COLUMN_DATA_SOURCE = 1 COLUMN_TABLE_NAME = 2 COLUMN_GEOMETRY_TYPE = 3 COLUMN_SRID = 4 def __init__(self, iface, version): QDockWidget.__init__(self, None) self.iface = iface self.version = version # Set up the user interface from Designer. self.ui = Ui_QgisCloudPlugin() self.ui.setupUi(self) self.ui.tblLocalLayers.setColumnCount(5) header = [ "Layers", "Data source", "Table name", "Geometry type", "SRID" ] self.ui.tblLocalLayers.setHorizontalHeaderLabels(header) self.ui.tblLocalLayers.resizeColumnsToContents() # TODO; delegate for read only columns self.ui.btnUploadData.setEnabled(False) self.ui.uploadProgressBar.hide() self.ui.btnPublishMapUpload.hide() self.ui.lblLoginStatus.hide() # map<data source, table name> self.data_sources_table_names = {} self.dbs_refreshed = False # flag to disable update of local data sources during upload self.do_update_local_data_sources = True QObject.connect(self.ui.btnLogin, SIGNAL("clicked()"), self.refresh_databases) QObject.connect(self.ui.btnDbCreate, SIGNAL("clicked()"), self.create_database) QObject.connect(self.ui.btnDbDelete, SIGNAL("clicked()"), self.delete_database) QObject.connect(self.ui.btnDbRefresh, SIGNAL("clicked()"), self.refresh_databases) QObject.connect(self.ui.tabDatabases, SIGNAL("itemSelectionChanged()"), self.select_database) QObject.connect(self.ui.btnPublishMap, SIGNAL("clicked()"), self.publish_map) QObject.connect(self.ui.btnRefreshLocalLayers, SIGNAL("clicked()"), self.refresh_local_data_sources) QObject.connect(self.iface, SIGNAL("newProjectCreated()"), self.reset_load_data) QObject.connect(QgsMapLayerRegistry.instance(), SIGNAL("layerWillBeRemoved(QString)"), self.remove_layer) QObject.connect(QgsMapLayerRegistry.instance(), SIGNAL("layerWasAdded(QgsMapLayer *)"), self.add_layer) QObject.connect(self.ui.cbUploadDatabase, SIGNAL("currentIndexChanged(int)"), self.upload_database_selected) QObject.connect(self.ui.btnUploadData, SIGNAL("clicked()"), self.upload_data) QObject.connect(self.ui.btnPublishMapUpload, SIGNAL("clicked()"), self.publish_map) self.read_settings() self.update_urls() self.api = API() self.db_connections = DbConnections() self.local_data_sources = LocalDataSources() self.data_upload = DataUpload(self.iface, self.statusBar(), self.ui.uploadProgressBar, self.api, self.db_connections) self.ui.editServer.setText(self.api.api_url()) def __del__(self): QObject.disconnect(self.iface, SIGNAL("newProjectCreated()"), self.reset_load_data) QObject.disconnect(QgsMapLayerRegistry.instance(), SIGNAL("layerWillBeRemoved(QString)"), self.remove_layer) QObject.disconnect(QgsMapLayerRegistry.instance(), SIGNAL("layerWasAdded(QgsMapLayer *)"), self.add_layer) def statusBar(self): return self.iface.mainWindow().statusBar() def map(self): project = QgsProject.instance() name = '' try: name = re.search(r'.*/(.+).qgs', project.fileName()).group(1) except: name = '' return name def store_settings(self): s = QSettings() s.setValue("qgiscloud/user", self.user) def read_settings(self): s = QSettings() self.user = s.value("qgiscloud/user").toString() def _version_info(self): return { 'versions': { 'plugin': self.version, 'QGIS': QGis.QGIS_VERSION, 'OS': platform.platform() } } def check_login(self): if not self.api.check_auth(): login_dialog = QDialog(self) login_dialog.ui = Ui_LoginDialog() login_dialog.ui.setupUi(login_dialog) login_dialog.ui.editUser.setText(self.user) login_ok = False while not login_ok: if not login_dialog.exec_(): self.api.set_auth(user=login_dialog.ui.editUser.text(), password=None) return login_ok self.api.set_auth(user=login_dialog.ui.editUser.text(), password=login_dialog.ui.editPassword.text()) try: self.api.check_login(version_info=self._version_info()) self.user = login_dialog.ui.editUser.text() self.ui.serviceLinks.setCurrentIndex(1) self.update_urls() self.store_settings() self.ui.btnLogin.hide() self.ui.lblSignup.hide() self.ui.lblLoginStatus.setText("Logged in as %s" % self.user) self.ui.lblLoginStatus.show() QMessageBox.information(self, "Login successful", "Logged in as %s" % self.user) login_ok = True except (UnauthorizedError, TokenRequiredError, ConnectionException): QMessageBox.critical(self, "Login failed", "Wrong user name or password") login_ok = False return True def create_database(self): if self.check_login(): db = self.api.create_database() # {u'username': u'jhzgpfwi_qgiscloud', u'host': u'beta.spacialdb.com', u'password': u'11d7338c', u'name': u'jhzgpfwi_qgiscloud', u'port': 9999} self.show_api_error(db) self.refresh_databases() def delete_database(self): if self.check_login(): name = self.ui.tabDatabases.currentItem().text() msgBox = QMessageBox() msgBox.setText("Delete QGIS Cloud database.") msgBox.setInformativeText( "Do you want to delete the database \"%s\"?" % name) msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) msgBox.setDefaultButton(QMessageBox.Cancel) msgBox.setIcon(QMessageBox.Question) ret = msgBox.exec_() if ret == QMessageBox.Ok: self.setCursor(Qt.WaitCursor) result = self.api.delete_database(name) self.show_api_error(result) self.ui.btnDbDelete.setEnabled(False) self.refresh_databases() while name in self.dbs.keys(): # wait until db is deleted on server time.sleep(2) self.refresh_databases() self.unsetCursor() def select_database(self): self.ui.btnDbDelete.setEnabled( len(self.ui.tabDatabases.selectedItems()) > 0) def refresh_databases(self): if self.check_login(): db_list = self.api.read_databases() if self.show_api_error(db_list): return self.dbs = {} # Map<dbname, {dbattributes}> for db in db_list: #db example: {"host":"spacialdb.com","connection_string":"postgres://*****:*****@spacialdb.com:9999/sekpjr_jpyled","name":"sekpjr_jpyled","username":"******","port":9999,"password":"******"} self.dbs[db['name']] = { 'host': db['host'], 'port': db['port'], 'username': db['username'], 'password': db['password'] } self.ui.tabDatabases.clear() self.ui.btnDbDelete.setEnabled(False) self.ui.cbUploadDatabase.clear() if len(self.dbs) == 0: self.ui.cbUploadDatabase.addItem("Create new database") elif len(self.dbs) > 1: self.ui.cbUploadDatabase.addItem("Select database") #import rpdb2; rpdb2.start_embedded_debugger("dbg") for name, db in self.dbs.iteritems(): it = QListWidgetItem(name) it.setToolTip( "host: %s port: %s database: %s username: %s password: %s" % (db['host'], db['port'], name, db['username'], db['password'])) self.ui.tabDatabases.addItem(it) self.ui.cbUploadDatabase.addItem(name) self.db_connections.refresh(self.dbs, self.user) self.dbs_refreshed = True def update_urls(self): self.update_url(self.ui.lblWMS, self.user, self.map()) self.update_url(self.ui.lblWebmap, self.user, self.map()) self.update_url(self.ui.lblMobileMap, self.user, self.map()) #self.update_url(self.ui.lblWFS, self.user, self.map()) def update_url(self, label, user, map): text = re.sub(r'qgiscloud.com/.*?/[^/"<>]*', 'qgiscloud.com/%s/%s' % (user, map), unicode(label.text())) label.setText(text) def read_maps(self): #map = self.api.read_map("1") if self.check_login(): maps = self.api.read_maps() def check_project_saved(self): cancel = False project = QgsProject.instance() if project.isDirty(): msgBox = QMessageBox() msgBox.setText("The project has been modified.") msgBox.setInformativeText("Do you want to save your changes?") msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Ignore | QMessageBox.Cancel) msgBox.setDefaultButton(QMessageBox.Save) ret = msgBox.exec_() if ret == QMessageBox.Save: project.write() elif ret == QMessageBox.Cancel: cancel = True return cancel def publish_map(self): cancel = self.check_project_saved() if cancel: return fname = unicode(QgsProject.instance().fileName()) if self.check_login() and self.check_layers(): self.statusBar().showMessage(u"Publishing map") try: fullExtent = self.iface.mapCanvas().fullExtent() config = { 'fullExtent': { 'xmin': fullExtent.xMinimum(), 'ymin': fullExtent.yMinimum(), 'xmax': fullExtent.xMaximum(), 'ymax': fullExtent.yMaximum() } } map = self.api.create_map(self.map(), fname, config)['map'] #QMessageBox.information(self, "create_map", str(map['config'])) self.show_api_error(map) if map['config']['missingSvgSymbols']: self.publish_symbols(map['config']['missingSvgSymbols']) self.update_urls() self.ui.btnPublishMapUpload.hide() self.statusBar().showMessage(u"Map successfully published") except Exception: self.statusBar().showMessage("") self._exception_message("Error uploading project") def _exception_message(self, title): exc_type, exc_value, exc_traceback = sys.exc_info() stack = traceback.format_exc().splitlines() msgBox = QMessageBox(QMessageBox.Critical, title, "") msgBox.setTextFormat(Qt.RichText) msgBox.setText("<b>%s</b><br/>%s" % (stack[-1], stack[1])) maillink = "<a href=\"mailto:%s?subject=QGISCloud exception: %s&body=%s\">Mail to support</a>" % \ ("*****@*****.**", title, urllib.quote(traceback.format_exc() + str(self._version_info()))) msgBox.setInformativeText(maillink) msgBox.exec_() def publish_symbols(self, missingSvgSymbols): self.statusBar().showMessage(u"Uploading SVG symbols") #Search and upload symbol files for sym in missingSvgSymbols: for path in QgsApplication.svgPaths(): fullpath = path + sym if os.path.isfile(fullpath): self.api.create_graphic(sym, fullpath) def reset_load_data(self): self.update_local_data_sources([]) self.ui.btnUploadData.setEnabled(False) self.ui.btnPublishMapUpload.hide() def remove_layer(self, layer_id): if self.dbs_refreshed and self.do_update_local_data_sources: # skip layer if layer will be removed self.update_local_layers(layer_id) self.activate_upload_button() def add_layer(self): if self.dbs_refreshed and self.do_update_local_data_sources: self.update_local_layers() self.activate_upload_button() def update_local_layers(self, skip_layer_id=None): local_layers, unsupported_layers = self.local_data_sources.local_layers( skip_layer_id) self.update_local_data_sources(local_layers) return local_layers, unsupported_layers def check_layers(self): local_layers, unsupported_layers = self.update_local_layers() if local_layers or unsupported_layers: message = "" if local_layers: title = "Local layers found" message += "Some layers are using local data. You can upload local layers to your cloud database in the 'Upload Data' tab.\n\n" if unsupported_layers: title = "Unsupported layers found" message += "Raster, plugin or geometryless layers are not supported:\n\n" layer_types = ["No geometry", "Raster", "Plugin"] for layer in sorted(unsupported_layers, key=lambda layer: layer.name()): message += " - %s (%s)\n" % (layer.name(), layer_types[layer.type()]) message += "\nPlease remove or replace above layers before publishing your map.\n" message += "For raster data you can use public WMS layers or the OpenLayers Plugin." QMessageBox.information(self, title, message) self.refresh_databases() self.ui.tabWidget.setCurrentIndex(1) return False return True def update_local_data_sources(self, local_layers): # update table names lookup self.update_data_sources_table_names() self.local_data_sources.update_local_data_sources(local_layers) # update GUI while self.ui.tblLocalLayers.rowCount() > 0: self.ui.tblLocalLayers.removeRow(0) geometry_types = { QGis.WKBUnknown: "Unknown", QGis.WKBPoint: "Point", QGis.WKBMultiPoint: "MultiPoint", QGis.WKBLineString: "LineString", QGis.WKBMultiLineString: "MultiLineString", QGis.WKBPolygon: "Polygon", QGis.WKBMultiPolygon: "MultiPolygon", 100: "No geometry" # FIXME: QGis.WKBNoGeometry # FIXME: 2.5d } for data_source, layers in self.local_data_sources.iteritems(): layer_names = [] for layer in layers: layer_names.append(str(layer.name())) layers_item = QTableWidgetItem(", ".join(layer_names)) layers_item.setToolTip("\n".join(layer_names)) data_source_item = QTableWidgetItem(data_source) data_source_item.setToolTip(data_source) table_name = layers[0].name( ) # find a better table name if there are multiple layers with same data source? if data_source in self.data_sources_table_names: # use current table name if available to keep changes by user table_name = self.data_sources_table_names[data_source] table_name_item = QTableWidgetItem( QgisCloudPluginDialog.launder_pg_name(table_name)) wkbType = layers[0].wkbType( ) #FIXME: message for unsupported types (2.5D) geometry_type_item = QTableWidgetItem(geometry_types[wkbType]) if layers[0].providerType() == "ogr": geometry_type_item.setToolTip( "Note: OGR features will be converted to MULTI-type") srid_item = QTableWidgetItem(layers[0].crs().authid()) row = self.ui.tblLocalLayers.rowCount() self.ui.tblLocalLayers.insertRow(row) self.ui.tblLocalLayers.setItem(row, self.COLUMN_LAYERS, layers_item) self.ui.tblLocalLayers.setItem(row, self.COLUMN_DATA_SOURCE, data_source_item) self.ui.tblLocalLayers.setItem(row, self.COLUMN_TABLE_NAME, table_name_item) self.ui.tblLocalLayers.setItem(row, self.COLUMN_GEOMETRY_TYPE, geometry_type_item) self.ui.tblLocalLayers.setItem(row, self.COLUMN_SRID, srid_item) if self.local_data_sources.count() > 0: self.ui.tblLocalLayers.resizeColumnsToContents() self.ui.tblLocalLayers.setColumnWidth(self.COLUMN_LAYERS, 100) self.ui.tblLocalLayers.setColumnWidth(self.COLUMN_DATA_SOURCE, 100) self.ui.tblLocalLayers.sortItems(self.COLUMN_DATA_SOURCE) self.ui.tblLocalLayers.setSortingEnabled(False) else: self.ui.btnUploadData.setEnabled(False) self.statusBar().showMessage(u"Updated local data sources") @staticmethod def launder_pg_name(name): return re.sub(r"[#'-]", '_', str(name).lower()) #OGRPGDataSource::LaunderName def refresh_local_data_sources(self): if not self.dbs_refreshed: # get dbs on first refresh self.refresh_databases() self.do_update_local_data_sources = True self.update_local_layers() self.activate_upload_button() def update_data_sources_table_names(self): if self.local_data_sources.count() == 0: self.data_sources_table_names.clear() else: # remove table names without data sources keys_to_remove = [] for key in self.data_sources_table_names.iterkeys(): if self.local_data_sources.layers(key) == None: keys_to_remove.append(key) for key in keys_to_remove: del self.data_sources_table_names[key] # update table names for row in range(0, self.ui.tblLocalLayers.rowCount()): data_source = str( self.ui.tblLocalLayers.item( row, self.COLUMN_DATA_SOURCE).text()) table_name = str( self.ui.tblLocalLayers.item(row, self.COLUMN_TABLE_NAME).text()) self.data_sources_table_names[data_source] = table_name def upload_database_selected(self, index): self.activate_upload_button() def activate_upload_button(self): self.ui.btnUploadData.setEnabled( (len(self.dbs) <= 1 or self.ui.cbUploadDatabase.currentIndex() > 0) and self.local_data_sources.count() > 0) self.ui.btnPublishMapUpload.hide() def upload_data(self): if self.check_login(): if self.local_data_sources.count() == 0: return if len(self.dbs) == 0: # create db self.statusBar().showMessage(u"Create new database...") QApplication.processEvents() # refresh status bar self.create_database() db_name = self.ui.cbUploadDatabase.currentText() # disable update of local data sources during upload, as there are temporary layers added and removed self.do_update_local_data_sources = False self.setCursor(Qt.WaitCursor) # Map<data_source, {table: table, layers: layers}> data_sources_items = {} for row in range(0, self.ui.tblLocalLayers.rowCount()): data_source = str( self.ui.tblLocalLayers.item( row, self.COLUMN_DATA_SOURCE).text()) layers = self.local_data_sources.layers(data_source) if layers != None: table_name = str( self.ui.tblLocalLayers.item( row, self.COLUMN_TABLE_NAME).text()) data_sources_items[data_source] = { 'table': table_name, 'layers': layers } try: #Via QGIS providers: #success = self.data_upload.upload_data(db_name, data_sources_items, self.ui.cbReplaceLocalLayers.isChecked()) #Via OGR: success = self.data_upload.ogr2ogr( db_name, data_sources_items, self.ui.cbReplaceLocalLayers.isChecked()) except Exception: success = False self._exception_message("Data upload error") self.unsetCursor() self.statusBar().showMessage("") self.do_update_local_data_sources = True if success and self.ui.cbReplaceLocalLayers.isChecked(): self.update_local_layers() # show save project dialog msgBox = QMessageBox() msgBox.setWindowTitle("QGIS Cloud") msgBox.setText("The project is ready for publishing.") msgBox.setInformativeText("Do you want to save your changes?") msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Cancel) msgBox.setDefaultButton(QMessageBox.Save) ret = msgBox.exec_() if ret == QMessageBox.Save: self.iface.actionSaveProjectAs().trigger() self.ui.btnPublishMapUpload.show() def show_api_error(self, result): if 'error' in result: QMessageBox.critical(self, "QGIS Cloud Error", "%s" % result['error']) self.statusBar().showMessage(u"Error") return True else: return False
class QgisCloudPluginDialog(QDockWidget): COLUMN_LAYERS = 0 COLUMN_DATA_SOURCE = 1 COLUMN_TABLE_NAME = 2 COLUMN_GEOMETRY_TYPE = 3 COLUMN_SRID = 4 if QGis.QGIS_VERSION_INT < 21400: GEOMETRY_TYPES = { QGis.WKBUnknown: "Unknown", QGis.WKBPoint: "Point", QGis.WKBMultiPoint: "MultiPoint", QGis.WKBLineString: "LineString", QGis.WKBMultiLineString: "MultiLineString", QGis.WKBPolygon: "Polygon", QGis.WKBMultiPolygon: "MultiPolygon", # Workaround (missing Python binding?): QGis.WKBNoGeometry / # ogr.wkbNone 100: "No geometry", QGis.WKBPoint25D: "Point", QGis.WKBLineString25D: "LineString", QGis.WKBPolygon25D: "Polygon", QGis.WKBMultiPoint25D: "MultiPoint", QGis.WKBMultiLineString25D: "MultiLineString", QGis.WKBMultiPolygon25D: "MultiPolygon", QGis.WKBMultiPolygon25D: "MultiPolygon", QGis.WKBMultiPolygon25D: "MultiPolygon", } else: GEOMETRY_TYPES = { QgsWKBTypes.Unknown: "Unknown", QgsWKBTypes.NoGeometry: "No geometry", QgsWKBTypes.Point: "Point", QgsWKBTypes.MultiPoint: "MultiPoint", QgsWKBTypes.PointZ: "PointZ", QgsWKBTypes.MultiPointZ: "MultiPointZ", QgsWKBTypes.PointM: "PointM", QgsWKBTypes.MultiPointM: "MultiPointM", QgsWKBTypes.PointZM: "PointZM", QgsWKBTypes.MultiPointZM: "MultiPointZM", QgsWKBTypes.Point25D: "Point25D", QgsWKBTypes.MultiPoint25D: "MultiPoint25D", QgsWKBTypes.LineString: "LineString", QgsWKBTypes.MultiLineString: "LineString", QgsWKBTypes.LineStringZ: "LineStringZ", QgsWKBTypes.MultiLineStringZ: "LineStringZ", QgsWKBTypes.LineStringM: "LineStringM", QgsWKBTypes.MultiLineStringM: "LineStringM", QgsWKBTypes.LineStringZM: "LineStringZM", QgsWKBTypes.MultiLineStringZM: "LineStringZM", QgsWKBTypes.LineString25D: "LineString25D", QgsWKBTypes.MultiLineString25D: "MultiLineString25D", QgsWKBTypes.Polygon: "Polygon", QgsWKBTypes.MultiPolygon: "MultiPolygon", QgsWKBTypes.PolygonZ: "PolygonZ", QgsWKBTypes.MultiPolygonZ: "MultiPolygonZ", QgsWKBTypes.PolygonM: "PolygonM", QgsWKBTypes.MultiPolygonM: "MultiPolygonM", QgsWKBTypes.PolygonZM: "PolygonZM", QgsWKBTypes.MultiPolygonZM: "MultiPolygonZM", QgsWKBTypes.Polygon25D: "Polygon25D", QgsWKBTypes.MultiPolygon25D: "MultiPolygon25D", QgsWKBTypes.CircularString: "CircularString", QgsWKBTypes.CompoundCurve: "CompoundCurve", QgsWKBTypes.CurvePolygon: "CurvePolygon", QgsWKBTypes.MultiCurve: "MultiCurve", QgsWKBTypes.MultiSurface: "MultiSurface", QgsWKBTypes.CircularStringZ: "CircularStringZ", QgsWKBTypes.CompoundCurveZ: "CompoundCurveZ", QgsWKBTypes.CurvePolygonZ: "CurvePolygonZ", QgsWKBTypes.MultiCurveZ: "MultiCurveZ", QgsWKBTypes.MultiSurfaceZ: "MultiSurfaceZ", QgsWKBTypes.CircularStringM: "CircularStringM", QgsWKBTypes.CompoundCurveM: "CompoundCurveM", QgsWKBTypes.CurvePolygonM: "CurvePolygonM", QgsWKBTypes.MultiCurveM: "MultiCurveM", QgsWKBTypes.MultiSurfaceM: "MultiSurfaceM", QgsWKBTypes.CircularStringZM: "CircularStringZM", QgsWKBTypes.CompoundCurveZM: "CompoundCurveZM", QgsWKBTypes.CurvePolygonZM: "CurvePolygonZM", QgsWKBTypes.MultiCurveZM: "MultiCurveZM", QgsWKBTypes.MultiSurfaceZM: "MultiSurfaceZM", } def __init__(self, iface, version): QDockWidget.__init__(self, None) self.iface = iface self.clouddb = True self.version = version # Set up the user interface from Designer. self.ui = Ui_QgisCloudPlugin() self.ui.setupUi(self) self.storage_exceeded = True myAbout = DlgAbout() self.ui.aboutText.setText( myAbout.aboutString() + myAbout.contribString() + myAbout.licenseString() + "<p>Versions:<ul>" + "<li>QGIS: %s</li>" % unicode(QGis.QGIS_VERSION).encode("utf-8") + "<li>Python: %s</li>" % sys.version.replace("\n", " ") + "<li>OS: %s</li>" % platform.platform() + "</ul></p>") self.ui.lblVersionPlugin.setText(self.version) self.ui.tblLocalLayers.setColumnCount(5) header = [ "Layers", "Data source", "Table name", "Geometry type", "SRID" ] self.ui.tblLocalLayers.setHorizontalHeaderLabels(header) self.ui.tblLocalLayers.resizeColumnsToContents() self.ui.tblLocalLayers.setEditTriggers( QAbstractItemView.NoEditTriggers) self.ui.btnUploadData.setEnabled(False) self.ui.btnPublishMap.setEnabled(False) self.ui.progressWidget.hide() self.ui.btnLogout.hide() self.ui.lblLoginStatus.hide() self.ui.widgetServices.hide() self.ui.widgetDatabases.setEnabled(False) self.ui.labelOpenLayersPlugin.hide() try: if QGis.QGIS_VERSION_INT >= 20300: from openlayers_menu import OpenlayersMenu else: # QGIS 1.x - QGIS-2.2 from openlayers_menu_compat import OpenlayersMenu self.ui.btnBackgroundLayer.setMenu(OpenlayersMenu(self.iface)) except: self.ui.btnBackgroundLayer.hide() self.ui.labelOpenLayersPlugin.show() # map<data source, table name> self.data_sources_table_names = {} # flag to disable update of local data sources during upload self.do_update_local_data_sources = True QObject.connect(self.ui.btnLogin, SIGNAL("clicked()"), self.check_login) QObject.connect(self.ui.btnDbCreate, SIGNAL("clicked()"), self.create_database) QObject.connect(self.ui.btnDbDelete, SIGNAL("clicked()"), self.delete_database) QObject.connect(self.ui.btnDbRefresh, SIGNAL("clicked()"), self.refresh_databases) QObject.connect(self.ui.tabDatabases, SIGNAL("itemSelectionChanged()"), self.select_database) QObject.connect(self.ui.btnPublishMap, SIGNAL("clicked()"), self.publish_map) QObject.connect(self.ui.btnRefreshLocalLayers, SIGNAL("clicked()"), self.refresh_local_data_sources) QObject.connect(self.iface, SIGNAL("newProjectCreated()"), self.reset_load_data) QObject.connect(QgsMapLayerRegistry.instance(), SIGNAL("layerWillBeRemoved(QString)"), self.remove_layer) QObject.connect(QgsMapLayerRegistry.instance(), SIGNAL("layerWasAdded(QgsMapLayer *)"), self.add_layer) QObject.connect(self.ui.cbUploadDatabase, SIGNAL("currentIndexChanged(int)"), lambda idx: self.activate_upload_button()) QObject.connect(self.ui.btnUploadData, SIGNAL("clicked()"), self.upload_data) self.ui.editServer.textChanged.connect(self.serverURL) self.ui.resetUrlBtn.clicked.connect(self.resetApiUrl) self.read_settings() self.api = API() self.db_connections = DbConnections() self.local_data_sources = LocalDataSources() self.data_upload = DataUpload(self.iface, self.statusBar(), self.ui.lblProgress, self.api, self.db_connections) if self.URL == "": self.ui.editServer.setText(self.api.api_url()) else: self.ui.editServer.setText(self.URL) self.palette_red = QPalette(self.ui.lblVersionPlugin.palette()) self.palette_red.setColor(QPalette.WindowText, Qt.red) def unload(self): self.do_update_local_data_sources = False if self.iface: QObject.disconnect(self.iface, SIGNAL("newProjectCreated()"), self.reset_load_data) if QgsMapLayerRegistry.instance(): QObject.disconnect(QgsMapLayerRegistry.instance(), SIGNAL("layerWillBeRemoved(QString)"), self.remove_layer) QObject.disconnect(QgsMapLayerRegistry.instance(), SIGNAL("layerWasAdded(QgsMapLayer *)"), self.add_layer) def statusBar(self): return self.iface.mainWindow().statusBar() def map(self): project = QgsProject.instance() name = os.path.splitext(os.path.basename(unicode( project.fileName())))[0] # Allowed chars for QGISCloud map name: /\A[A-Za-z0-9\_\-]*\Z/ name = unicode(name).encode('ascii', 'replace') # Replace non-ascii chars # Replace withespace name = re.compile("\W+", re.UNICODE).sub("_", name) return name def resetApiUrl(self): self.ui.editServer.setText(self.api.api_url()) def serverURL(self, URL): self.URL = URL self.store_settings() def store_settings(self): s = QSettings() s.setValue("qgiscloud/user", self.user) s.setValue("qgiscloud/URL", self.URL) def read_settings(self): s = QSettings() self.user = s.value("qgiscloud/user", "", type=str) self.URL = s.value("qgiscloud/URL", "", type=str) def _update_clouddb_mode(self, clouddb): self.clouddb = clouddb self.ui.widgetDatabases.setVisible(self.clouddb) tab_index = 1 tab_name = QApplication.translate("QgisCloudPlugin", "Upload Data") visible = (self.ui.tabWidget.indexOf(self.ui.uploadTab) == tab_index) if visible and not self.clouddb: self.ui.tabWidget.removeTab(tab_index) elif not visible and self.clouddb: self.ui.tabWidget.insertTab(tab_index, self.ui.uploadTab, tab_name) def _version_info(self): return { 'versions': { 'plugin': self.version, 'QGIS': QGis.QGIS_VERSION, 'OS': platform.platform(), 'Python': sys.version } } def check_login(self): version_ok = True if not self.api.check_auth(): login_dialog = QDialog(self) login_dialog.ui = Ui_LoginDialog() login_dialog.ui.setupUi(login_dialog) login_dialog.ui.editUser.setText(self.user) login_ok = False while not login_ok and version_ok: self.api.set_url(self.api_url()) if not login_dialog.exec_(): self.api.set_auth(user=login_dialog.ui.editUser.text(), password=None) return login_ok self.api.set_auth(user=login_dialog.ui.editUser.text(), password=login_dialog.ui.editPassword.text()) try: login_info = self.api.check_login( version_info=self._version_info()) self.user = login_dialog.ui.editUser.text() self._update_clouddb_mode(login_info['clouddb']) version_ok = StrictVersion(self.version) >= StrictVersion( login_info['current_plugin']) if not version_ok: self.ui.lblVersionPlugin.setPalette(self.palette_red) QMessageBox.information( None, self.tr('New Version'), self. tr('New plugin release {version} is available! Please upgrade the QGIS Cloud plugin.' ).format(version=login_info['current_plugin'])) self.store_settings() self.ui.btnLogin.hide() self.ui.lblSignup.hide() self.ui.btnLogout.show() self.ui.widgetDatabases.setEnabled(True) self.ui.lblLoginStatus.setText( self.tr_uni("Logged in as {0} ({1})").format( self.user, login_info['plan'])) self.ui.lblLoginStatus.show() self._push_message(self.tr("QGIS Cloud"), self.tr_uni("Logged in as {0}").format( self.user), level=0, duration=2) self.refresh_databases() if not version_ok: self._push_message( self.tr("QGIS Cloud"), self. tr("Unsupported versions detected. Please check your versions first!" ), level=1) version_ok = False self.ui.tabWidget.setCurrentWidget(self.ui.aboutTab) login_ok = True self.update_local_layers() except ForbiddenError: QMessageBox.critical( self, self.tr("Account Disabled"), self. tr("Account {username} is disabled! Please contact [email protected]" ).format(username=login_dialog.ui.editUser.text())) login_ok = False except UnauthorizedError: QMessageBox.critical( self, self.tr("Login for user {username} failed").format( username=login_dialog.ui.editUser.text()), self.tr("Wrong user name or password")) login_ok = False except (TokenRequiredError, ConnectionException) as e: QMessageBox.critical(self, self.tr("Login failed"), self.tr("Login failed: %s") % str(e)) login_ok = False return version_ok def create_database(self): if self.numDbs < self.maxDBs: db = self.api.create_database() self.show_api_error(db) self.refresh_databases() else: QMessageBox.warning( None, self.tr('Warning!'), self. tr('Number of %s permitted databases exceeded! Please upgrade your account!' ) % self.maxDBs) def delete_database(self): name = self.ui.tabDatabases.currentItem().text() answer = False for layer in QgsMapLayerRegistry.instance().mapLayers().values(): if QgsDataSourceURI(layer.publicSource()).database() == name: if not answer: answer = QMessageBox.question( self, self.tr("Warning"), self. tr("You have layers from database {name} loaded in your project! Do you want to remove them before you delete database {name}." ).format(name=name), QMessageBox.StandardButtons(QMessageBox.Cancel | QMessageBox.Yes)) if answer == QMessageBox.Yes: QgsMapLayerRegistry.instance().removeMapLayer(layer.id()) if answer == QMessageBox.Cancel: QMessageBox.warning( None, self.tr('Warning'), self.tr('Deletion of database {name} interrupted!').format( name=name)) return msgBox = QMessageBox() msgBox.setText(self.tr("Delete QGIS Cloud database.")) msgBox.setInformativeText( self.tr_uni("Do you want to delete the database \"%s\"?") % name) msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) msgBox.setDefaultButton(QMessageBox.Cancel) msgBox.setIcon(QMessageBox.Question) ret = msgBox.exec_() if ret == QMessageBox.Ok: self.setCursor(Qt.WaitCursor) result = self.api.delete_database(name) self.show_api_error(result) self.ui.btnDbDelete.setEnabled(False) time.sleep(2) self.refresh_databases() self.unsetCursor() def select_database(self): self.ui.btnDbDelete.setEnabled( len(self.ui.tabDatabases.selectedItems()) > 0) @pyqtSignature('') def on_btnLogout_clicked(self): self.api.reset_auth() self.ui.btnLogout.hide() self.ui.lblLoginStatus.hide() self.ui.btnLogin.show() self.ui.widgetServices.hide() self.ui.tabDatabases.clear() self.ui.lblDbSize.setText("") self.ui.lblDbSizeUpload.setText("") self.ui.cbUploadDatabase.clear() self.ui.widgetDatabases.setEnabled(False) self.activate_upload_button() def refresh_databases(self): QApplication.setOverrideCursor(Qt.WaitCursor) if self.clouddb: db_list = self.api.read_databases() if self.show_api_error(db_list): QApplication.restoreOverrideCursor() return self.db_connections = DbConnections() for db in db_list: self.db_connections.add_from_json(db) self.ui.tabDatabases.clear() self.ui.btnDbDelete.setEnabled(False) self.ui.cbUploadDatabase.clear() self.ui.cbUploadDatabase.setEditable(True) self.ui.cbUploadDatabase.lineEdit().setReadOnly(True) if self.db_connections.count() == 0: self.ui.cbUploadDatabase.setEditText(self.tr("No databases")) else: for name, db in self.db_connections.iteritems(): it = QListWidgetItem(name) it.setToolTip(db.description()) self.ui.tabDatabases.addItem(it) self.ui.cbUploadDatabase.addItem(name) if self.ui.cbUploadDatabase.count() > 1: # Display the "Select database" text if more than one db is available self.ui.cbUploadDatabase.setCurrentIndex(-1) self.ui.cbUploadDatabase.setEditText( self.tr("Select database")) self.db_connections.refresh(self.user) self.db_size(self.db_connections) QApplication.restoreOverrideCursor() def api_url(self): return unicode(self.ui.editServer.text()) def update_urls(self): self.update_url(self.ui.lblWebmap, self.api_url(), 'http://', u'{0}/{1}'.format(self.user, self.map())) #QWC2 (beta) update link self.update_url(self.ui.lblQwc2, self.api_url(), 'http://', u'{0}/{1}/qwc2/'.format(self.user, self.map())) if self.clouddb: self.update_url(self.ui.lblMobileMap, self.api_url(), 'http://m.', u'{0}/{1}'.format(self.user, self.map())) self.update_url(self.ui.lblWMS, self.api_url(), 'http://wms.', u'{0}/{1}'.format(self.user, self.map())) else: self.update_url(self.ui.lblMobileMap, self.api_url(), 'http://', u'{0}/{1}/mobile'.format(self.user, self.map())) self.update_url(self.ui.lblWMS, self.api_url(), 'http://', u'{0}/{1}/wms'.format(self.user, self.map())) self.update_url(self.ui.lblMaps, self.api_url(), 'http://', 'maps') self.ui.widgetServices.show() def update_url(self, label, api_url, prefix, path): base_url = string.replace(api_url, 'https://api.', prefix) url = u'{0}/{1}'.format(base_url, path) text = re.sub(r'http[^"]+', url, unicode(label.text())) label.setText(text) def read_maps(self): #map = self.api.read_map("1") if self.check_login(): self.api.read_maps() def check_project_saved(self): project = QgsProject.instance() fname = project.fileName() if project.isDirty() or fname == '': msgBox = QMessageBox() msgBox.setWindowTitle(self.tr("Project Modified")) msgBox.setText(self.tr("The project has been modified.")) msgBox.setInformativeText( self. tr("The project needs to be saved before it can be published. Proceed?" )) msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Cancel) msgBox.setDefaultButton(QMessageBox.Save) if msgBox.exec_() == QMessageBox.Save: self.iface.actionSaveProject().trigger() return not project.isDirty() else: return False return True def publish_map(self): canvas = self.iface.mapCanvas() mapRenderer = canvas.mapRenderer() srs = mapRenderer.destinationCrs() if "USER" in srs.authid(): QMessageBox.warning( None, self.tr('Warning!'), self. tr("The project has a user defined CRS. The use of user defined CRS is not supported. Please correct the project CRS before publishing!" )) return layers = self.iface.legendInterface().layers() layerList = '' for layer in layers: if "USER" in layer.crs().authid(): layerList += "'" + layer.name() + "' " if len(layerList) > 0: QMessageBox.warning( None, self.tr('Warning!'), self. tr("The layer(s) {layerlist}have user defined CRS. The use of user defined CRS is not supported. Please correct the CRS before publishing!" ).format(layerlist=layerList)) return saved = self.check_project_saved() if not saved: self.statusBar().showMessage(self.tr("Cancelled")) return if self.check_login() and self.check_layers(): self.statusBar().showMessage(self.tr("Publishing map")) try: fullExtent = self.iface.mapCanvas().fullExtent() config = { 'fullExtent': { 'xmin': fullExtent.xMinimum(), 'ymin': fullExtent.yMinimum(), 'xmax': fullExtent.xMaximum(), 'ymax': fullExtent.yMaximum() #}, # 'svgPaths': QgsApplication.svgPaths() #For resolving absolute symbol paths in print composer } } fname = unicode(QgsProject.instance().fileName()) map = self.api.create_map(self.map(), fname, config)['map'] self.show_api_error(map) if map['config']['missingSvgSymbols']: self.publish_symbols(map['config']['missingSvgSymbols']) self.update_urls() self._push_message(self.tr("QGIS Cloud"), self.tr("Map successfully published"), level=0, duration=2) self.statusBar().showMessage( self.tr("Map successfully published")) except Exception as e: self.statusBar().showMessage("") ErrorReportDialog(self.tr("Error uploading project"), self.tr("An error occured."), str(e) + "\n" + traceback.format_exc(), self.user, self).exec_() def publish_symbols(self, missingSvgSymbols): self.statusBar().showMessage(self.tr("Uploading SVG symbols")) search_paths = QgsApplication.svgPaths() if hasattr(QgsProject.instance(), 'homePath'): search_paths += [QgsProject.instance().homePath()] # Search and upload symbol files for sym in missingSvgSymbols: # Absolute custom path if os.path.isfile(sym): self.api.create_graphic(sym, sym) else: for path in search_paths: fullpath = os.path.join(unicode(path), sym) if os.path.isfile(fullpath): self.api.create_graphic(sym, fullpath) self.statusBar().showMessage("") def reset_load_data(self): self.update_local_data_sources([], []) self.ui.btnUploadData.setEnabled(False) def remove_layer(self, layer_id): if self.do_update_local_data_sources: # skip layer if layer will be removed self.update_local_layers(layer_id) self.activate_upload_button() def add_layer(self): if self.do_update_local_data_sources: self.update_local_layers() self.activate_upload_button() def update_local_layers(self, skip_layer_id=None): local_layers, unsupported_layers, local_raster_layers = self.local_data_sources.local_layers( skip_layer_id) try: self.update_local_data_sources(local_layers, local_raster_layers) except Exception as e: ErrorReportDialog(self.tr("Error checking local data sources"), self.tr("An error occured."), str(e) + "\n" + traceback.format_exc(), self.user, self).exec_() return local_layers, unsupported_layers, local_raster_layers def check_layers(self): local_layers, unsupported_layers, local_raster_layers = self.update_local_layers( ) if ((local_layers or local_raster_layers) and self.clouddb) or unsupported_layers: message = "" if local_layers or local_raster_layers: title = self.tr("Local layers found") message += self.tr( "Some layers are using local data. You can upload local layers to your cloud database in the 'Upload Data' tab.\n\n" ) if unsupported_layers: title = self.tr("Unsupported layers found") message += self.tr( "Raster, plugin or geometryless layers are not supported:\n\n" ) layer_types = ["No geometry", "Raster", "Plugin"] for layer in sorted(unsupported_layers, key=lambda layer: layer.name()): message += self.tr_uni(" - %s (%s)\n") % ( layer.name(), layer_types[layer.type()]) message += self.tr( "\nPlease remove or replace above layers before publishing your map.\n" ) message += self.tr( "For raster data you can use public WMS layers or the OpenLayers Plugin." ) QMessageBox.information(self, title, message) self.refresh_databases() self.ui.tabWidget.setCurrentWidget(self.ui.uploadTab) return False return True def update_local_data_sources(self, local_layers, local_raster_layers): # update table names lookup local_layers += local_raster_layers self.update_data_sources_table_names() self.local_data_sources.update_local_data_sources(local_layers) # update GUI self.ui.tblLocalLayers.setRowCount(0) for data_source, layers in self.local_data_sources.iteritems(): layer_names = [] for layer in layers: layer_names.append(unicode(layer.name())) layers_item = QTableWidgetItem(", ".join(layer_names)) layers_item.setToolTip("\n".join(layer_names)) data_source_item = QTableWidgetItem(data_source) data_source_item.setToolTip(data_source) # find a better table name if there are multiple layers with same # data source? table_name = layers[0].name() if data_source in self.data_sources_table_names: # use current table name if available to keep changes by user table_name = self.data_sources_table_names[data_source] table_name_item = QTableWidgetItem( QgisCloudPluginDialog.launder_pg_name(table_name)) if layers[0].providerType() == 'gdal': geometry_type_item = QTableWidgetItem('Raster') else: wkbType = layers[0].wkbType() if wkbType not in self.GEOMETRY_TYPES: QMessageBox.warning( self.iface.mainWindow(), self.tr("Unsupported geometry type"), pystring( self. tr("Unsupported geometry type '{type}' in layer '{layer}'" )).format(type=self.__wkbTypeString(wkbType), layer=layers[0].name())) continue geometry_type_item = QTableWidgetItem( self.GEOMETRY_TYPES[wkbType]) if layers[0].providerType() == "ogr": geometry_type_item.setToolTip( self. tr("Note: OGR features will be converted to MULTI-type" )) srid_item = QTableWidgetItem(layers[0].crs().authid()) row = self.ui.tblLocalLayers.rowCount() self.ui.tblLocalLayers.insertRow(row) self.ui.tblLocalLayers.setItem(row, self.COLUMN_LAYERS, layers_item) self.ui.tblLocalLayers.setItem(row, self.COLUMN_DATA_SOURCE, data_source_item) self.ui.tblLocalLayers.setItem(row, self.COLUMN_TABLE_NAME, table_name_item) self.ui.tblLocalLayers.setItem(row, self.COLUMN_GEOMETRY_TYPE, geometry_type_item) self.ui.tblLocalLayers.setItem(row, self.COLUMN_SRID, srid_item) if self.local_data_sources.count() > 0: self.ui.tblLocalLayers.resizeColumnsToContents() self.ui.tblLocalLayers.setColumnWidth(self.COLUMN_LAYERS, 100) self.ui.tblLocalLayers.setColumnWidth(self.COLUMN_DATA_SOURCE, 100) self.ui.tblLocalLayers.sortItems(self.COLUMN_DATA_SOURCE) self.ui.tblLocalLayers.setSortingEnabled(False) else: self.ui.btnUploadData.setEnabled(False) self.statusBar().showMessage(self.tr("Updated local data sources")) def __wkbTypeString(self, wkbType): if wkbType == QGis.WKBUnknown: return "WKBUnknown" elif wkbType == QGis.WKBPoint: return "WKBPoint" elif wkbType == QGis.WKBLineString: return "WKBLineString" elif wkbType == QGis.WKBMultiLineString: return "WKBMultiLineString" elif wkbType == QGis.WKBPolygon: return "WKBPolygon" elif wkbType == QGis.WKBMultiPoint: return "WKBMultiPoint" elif wkbType == QGis.WKBMultiPolygon: return "WKBMultiPolygon" elif wkbType == QGis.WKBNoGeometry: return "WKBNoGeometry" elif wkbType == QGis.WKBPoint25D: return "WKBPoint25D" elif wkbType == QGis.WKBLineString25D: return "WKBLineString25D" elif wkbType == QGis.WKBPolygon25D: return "WKBPolygon25D" elif wkbType == QGis.WKBMultiPoint25D: return "WKBMultiPoint25D" elif wkbType == QGis.WKBMultiLineString25D: return "WKBMultiLineString25D" elif wkbType == QGis.WKBMultiPolygon25D: return "WKBMultiPolygon25D" elif QGis.QGIS_VERSION_INT >= 21400: if wkbType == QgsWKBTypes.LineStringZM: return "WKBLineStringZM" elif wkbType == QgsWKBTypes.MultiLineStringZM: return "WKBMultiLineStringZM" return self.tr("Unknown type") @staticmethod def launder_pg_name(name): # OGRPGDataSource::LaunderName # return re.sub(r"[#'-]", '_', unicode(name).lower()) input_string = unicode(name).lower().encode('ascii', 'replace') input_string = re.compile("\W+", re.UNICODE).sub("_", input_string) # check if tabke_name starts with number if re.search("^\d", input_string): input_string = '_' + input_string return input_string def refresh_local_data_sources(self): self.do_update_local_data_sources = True self.update_local_layers() self.activate_upload_button() def update_data_sources_table_names(self): if self.local_data_sources.count() == 0: self.data_sources_table_names.clear() else: # remove table names without data sources keys_to_remove = [] for key in self.data_sources_table_names.iterkeys(): if self.local_data_sources.layers(key) is None: keys_to_remove.append(key) for key in keys_to_remove: del self.data_sources_table_names[key] # update table names for row in range(0, self.ui.tblLocalLayers.rowCount()): data_source = unicode( self.ui.tblLocalLayers.item( row, self.COLUMN_DATA_SOURCE).text()) table_name = unicode( self.ui.tblLocalLayers.item(row, self.COLUMN_TABLE_NAME).text()) self.data_sources_table_names[data_source] = table_name def activate_upload_button(self): if not self.storage_exceeded: self.ui.btnUploadData.setEnabled( self.local_data_sources.count() > 0) self.ui.btnPublishMap.setDisabled(self.storage_exceeded) else: self.ui.btnUploadData.setDisabled(self.storage_exceeded) self.ui.btnPublishMap.setDisabled(self.storage_exceeded) def upload_data(self): if self.check_login(): if self.local_data_sources.count() == 0: return if self.db_connections.count() == 0: QMessageBox.warning( self, self.tr("No database available"), self.tr("Please create a database in the 'Account' tab.")) return if not self.ui.cbUploadDatabase.currentIndex() >= 0: QMessageBox.warning( self, self.tr("No database selected"), self.tr("Please select a database to upload data.")) return db_name = self.ui.cbUploadDatabase.currentText() if not self.db_connections.isPortOpen(db_name): uri = self.db_connections.cloud_layer_uri(db_name, "", "") host = str(uri.host()) port = uri.port() QMessageBox.critical( self, self.tr("Network Error"), self. tr("Could not connect to database server ({0}) on port {1}. Please contact your system administrator or internet provider to open port {1} in the firewall" .format(host, port))) return # disable update of local data sources during upload, as there are # temporary layers added and removed self.do_update_local_data_sources = False self.statusBar().showMessage(self.tr("Uploading data...")) self.setCursor(Qt.WaitCursor) self.ui.btnUploadData.hide() self.ui.spinner.start() self.ui.progressWidget.show() # Map<data_source, {table: table, layers: layers}> data_sources_items = {} for row in range(0, self.ui.tblLocalLayers.rowCount()): data_source = unicode( self.ui.tblLocalLayers.item( row, self.COLUMN_DATA_SOURCE).text()) layers = self.local_data_sources.layers(data_source) if layers is not None: table_name = unicode( self.ui.tblLocalLayers.item( row, self.COLUMN_TABLE_NAME).text()) data_sources_items[data_source] = { 'table': table_name, 'layers': layers } login_info = self.api.check_login( version_info=self._version_info()) try: self.maxSize = login_info['max_storage'] self.maxDBs = login_info['max_dbs'] except: self.maxSize = 50 self.maxDBs = 5 try: self.data_upload.upload(self.db_connections.db(db_name), data_sources_items, self.maxSize) upload_ok = True except Exception as e: ErrorReportDialog( self.tr("Upload errors occurred"), self. tr("Upload errors occurred. Not all data could be uploaded." ), str(e) + "\n" + traceback.format_exc(), self.user, self).exec_() upload_ok = False self.ui.spinner.stop() self.ui.progressWidget.hide() self.ui.btnUploadData.show() self.unsetCursor() self.statusBar().showMessage("") # Refresh local layers self.do_update_local_data_sources = True self.update_local_layers() # Refresh used space after upload self.db_size(self.db_connections) if upload_ok: # Show save project dialog save_dialog = QDialog(self) save_dialog.setWindowTitle(self.tr("Save Project")) save_dialog.setLayout(QVBoxLayout()) header = QWidget() header.setLayout(QVBoxLayout()) label = QLabel( self. tr("Upload complete. The local layers in the project were replaced with the layers uploaded to the qgiscloud database." )) label.setWordWrap(True) header.layout().addWidget(label) label = QLabel( self.tr("Choose were to save the modified project:")) label.setWordWrap(True) header.layout().addWidget(label) save_dialog.layout().setContentsMargins(0, 0, 0, 0) save_dialog.layout().addWidget(header) initialPath = QgsProject.instance().fileName() if not initialPath: initialPath = QSettings().value("/UI/lastProjectDir", ".") fd = QFileDialog(None, self.tr("Save Project"), initialPath, "%s (*.qgs)" % self.tr("QGIS Project Files")) fd.setParent(save_dialog, Qt.Widget) fd.setOption(QFileDialog.DontUseNativeDialog) fd.setAcceptMode(QFileDialog.AcceptSave) save_dialog.layout().addWidget(fd) header.layout().setContentsMargins( fd.layout().contentsMargins()) fd.accepted.connect(save_dialog.accept) fd.rejected.connect(save_dialog.reject) if save_dialog.exec_() == QDialog.Accepted: files = list(fd.selectedFiles()) if files: QgsProject.instance().setFileName(files[0]) self.iface.actionSaveProject().trigger() # Switch to map tab self.ui.tabWidget.setCurrentWidget(self.ui.mapTab) def _push_message(self, title, text, level=0, duration=0): # QGIS >= 2.0 if hasattr(self.iface, 'messageBar') and hasattr( self.iface.messageBar(), 'pushMessage'): self.iface.messageBar().pushMessage(title, text, level, duration) else: QMessageBox.information(self, title, text) def show_api_error(self, result): if 'error' in result: QMessageBox.critical(self, self.tr("QGIS Cloud Error"), "%s" % result['error']) self.statusBar().showMessage(self.tr("Error")) return True else: return False def tr_uni(self, str): return unicode(self.tr(str)) def db_size(self, db_connections): usedSpace = 0 self.numDbs = len(db_connections._dbs.keys()) for db in db_connections._dbs.keys(): try: conn = db_connections.db(db).psycopg_connection() except: continue cursor = conn.cursor() sql = "SELECT pg_database_size('" + str(db) + "')" cursor.execute(sql) usedSpace += int(cursor.fetchone()[0]) - (11 * 1024 * 1024) cursor.close() conn.close # Used space in MB usedSpace /= 1024 * 1024 login_info = self.api.check_login(version_info=self._version_info()) try: self.maxSize = login_info['max_storage'] self.maxDBs = login_info['max_dbs'] except: self.maxSize = 50 self.maxDBs = 5 lblPalette = QPalette(self.ui.lblDbSize.palette()) usage = usedSpace / float(self.maxSize) self.storage_exceeded = False if usage < 0.8: bg_color = QColor(255, 0, 0, 0) text_color = QColor(Qt.black) elif usage >= 0.8 and usage < 1: bg_color = QColor(255, 0, 0, 100) text_color = QColor(Qt.white) elif usage >= 1: bg_color = QColor(255, 0, 0, 255) text_color = QColor(Qt.white) self.storage_exceeded = True lblPalette.setColor(QPalette.Window, QColor(bg_color)) lblPalette.setColor(QPalette.Foreground, QColor(text_color)) self.ui.lblDbSize.setAutoFillBackground(True) self.ui.lblDbSize.setPalette(lblPalette) self.ui.lblDbSize.setText( self.tr("Used DB Storage: ") + "%d / %d MB" % (usedSpace, self.maxSize)) self.ui.lblDbSizeUpload.setAutoFillBackground(True) self.ui.lblDbSizeUpload.setPalette(lblPalette) self.ui.lblDbSizeUpload.setText( self.tr("Used DB Storage: ") + "%d / %d MB" % (usedSpace, self.maxSize)) self.ui.btnUploadData.setDisabled(self.storage_exceeded) self.ui.btnPublishMap.setDisabled(self.storage_exceeded)
class QgisCloudPluginDialog(QDockWidget): COLUMN_LAYERS = 0 COLUMN_DATA_SOURCE = 1 COLUMN_TABLE_NAME = 2 COLUMN_GEOMETRY_TYPE = 3 COLUMN_SRID = 4 def __init__(self, iface, version): QDockWidget.__init__(self, None) self.iface = iface self.version = version # Set up the user interface from Designer. self.ui = Ui_QgisCloudPlugin() self.ui.setupUi(self) self.ui.tblLocalLayers.setColumnCount(5) header = ["Layers", "Data source", "Table name", "Geometry type", "SRID"] self.ui.tblLocalLayers.setHorizontalHeaderLabels(header) self.ui.tblLocalLayers.resizeColumnsToContents() # TODO; delegate for read only columns self.ui.btnUploadData.setEnabled(False) self.ui.uploadProgressBar.hide() self.ui.btnPublishMapUpload.hide() self.ui.lblLoginStatus.hide() # map<data source, table name> self.data_sources_table_names = {} self.dbs_refreshed = False # flag to disable update of local data sources during upload self.do_update_local_data_sources = True QObject.connect(self.ui.btnLogin, SIGNAL("clicked()"), self.refresh_databases) QObject.connect(self.ui.btnDbCreate, SIGNAL("clicked()"), self.create_database) QObject.connect(self.ui.btnDbDelete, SIGNAL("clicked()"), self.delete_database) QObject.connect(self.ui.btnDbRefresh, SIGNAL("clicked()"), self.refresh_databases) QObject.connect(self.ui.tabDatabases, SIGNAL("itemSelectionChanged()"), self.select_database) QObject.connect(self.ui.btnPublishMap, SIGNAL("clicked()"), self.publish_map) QObject.connect(self.ui.btnRefreshLocalLayers, SIGNAL("clicked()"), self.refresh_local_data_sources) QObject.connect(self.iface, SIGNAL("newProjectCreated()"), self.reset_load_data) QObject.connect(QgsMapLayerRegistry.instance(), SIGNAL("layerWillBeRemoved(QString)"), self.remove_layer) QObject.connect(QgsMapLayerRegistry.instance(), SIGNAL("layerWasAdded(QgsMapLayer *)"), self.add_layer) QObject.connect(self.ui.cbUploadDatabase, SIGNAL("currentIndexChanged(int)"), self.upload_database_selected) QObject.connect(self.ui.btnUploadData, SIGNAL("clicked()"), self.upload_data) QObject.connect(self.ui.btnPublishMapUpload, SIGNAL("clicked()"), self.publish_map) self.read_settings() self.update_urls() self.api = API() self.db_connections = DbConnections() self.local_data_sources = LocalDataSources() self.data_upload = DataUpload(self.iface, self.statusBar(), self.ui.uploadProgressBar, self.api, self.db_connections) self.ui.editServer.setText(self.api.api_url()) def __del__(self): QObject.disconnect(self.iface, SIGNAL("newProjectCreated()"), self.reset_load_data) QObject.disconnect(QgsMapLayerRegistry.instance(), SIGNAL("layerWillBeRemoved(QString)"), self.remove_layer) QObject.disconnect(QgsMapLayerRegistry.instance(), SIGNAL("layerWasAdded(QgsMapLayer *)"), self.add_layer) def statusBar(self): return self.iface.mainWindow().statusBar() def map(self): project = QgsProject.instance() name = '' try: name = re.search(r'.*/(.+).qgs', project.fileName()).group(1) except: name = '' return name def store_settings(self): s = QSettings() s.setValue("qgiscloud/user", self.user) def read_settings(self): s = QSettings() self.user = s.value("qgiscloud/user").toString() def _version_info(self): return {'versions': {'plugin': self.version, 'QGIS': QGis.QGIS_VERSION, 'OS': platform.platform()}} def check_login(self): if not self.api.check_auth(): login_dialog = QDialog(self) login_dialog.ui = Ui_LoginDialog() login_dialog.ui.setupUi(login_dialog) login_dialog.ui.editUser.setText(self.user) login_ok = False while not login_ok: if not login_dialog.exec_(): self.api.set_auth(user=login_dialog.ui.editUser.text(), password=None) return login_ok self.api.set_auth(user=login_dialog.ui.editUser.text(), password=login_dialog.ui.editPassword.text()) try: self.api.check_login(version_info=self._version_info()) self.user = login_dialog.ui.editUser.text() self.ui.serviceLinks.setCurrentIndex(1) self.update_urls() self.store_settings() self.ui.btnLogin.hide() self.ui.lblSignup.hide() self.ui.lblLoginStatus.setText("Logged in as %s" % self.user) self.ui.lblLoginStatus.show() QMessageBox.information(self, "Login successful", "Logged in as %s" % self.user) login_ok = True except (UnauthorizedError, TokenRequiredError, ConnectionException): QMessageBox.critical(self, "Login failed", "Wrong user name or password") login_ok = False return True def create_database(self): if self.check_login(): db = self.api.create_database() # {u'username': u'jhzgpfwi_qgiscloud', u'host': u'beta.spacialdb.com', u'password': u'11d7338c', u'name': u'jhzgpfwi_qgiscloud', u'port': 9999} self.show_api_error(db) self.refresh_databases() def delete_database(self): if self.check_login(): name = self.ui.tabDatabases.currentItem().text() msgBox = QMessageBox() msgBox.setText("Delete QGIS Cloud database.") msgBox.setInformativeText("Do you want to delete the database \"%s\"?" % name) msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) msgBox.setDefaultButton(QMessageBox.Cancel) msgBox.setIcon(QMessageBox.Question) ret = msgBox.exec_() if ret == QMessageBox.Ok: self.setCursor(Qt.WaitCursor) result = self.api.delete_database(name) self.show_api_error(result) self.ui.btnDbDelete.setEnabled(False) self.refresh_databases() while name in self.dbs.keys(): # wait until db is deleted on server time.sleep(2) self.refresh_databases() self.unsetCursor() def select_database(self): self.ui.btnDbDelete.setEnabled(len(self.ui.tabDatabases.selectedItems()) > 0) def refresh_databases(self): if self.check_login(): db_list = self.api.read_databases() if self.show_api_error(db_list): return self.dbs = {} # Map<dbname, {dbattributes}> for db in db_list: #db example: {"host":"spacialdb.com","connection_string":"postgres://*****:*****@spacialdb.com:9999/sekpjr_jpyled","name":"sekpjr_jpyled","username":"******","port":9999,"password":"******"} self.dbs[db['name']] = {'host': db['host'], 'port': db['port'], 'username': db['username'], 'password': db['password']} self.ui.tabDatabases.clear() self.ui.btnDbDelete.setEnabled(False) self.ui.cbUploadDatabase.clear() if len(self.dbs) == 0: self.ui.cbUploadDatabase.addItem("Create new database") elif len(self.dbs) > 1: self.ui.cbUploadDatabase.addItem("Select database") #import rpdb2; rpdb2.start_embedded_debugger("dbg") for name, db in self.dbs.iteritems(): it = QListWidgetItem(name) it.setToolTip("host: %s port: %s database: %s username: %s password: %s" % (db['host'], db['port'], name, db['username'], db['password'])) self.ui.tabDatabases.addItem(it) self.ui.cbUploadDatabase.addItem(name) self.db_connections.refresh(self.dbs, self.user) self.dbs_refreshed = True def update_urls(self): self.update_url(self.ui.lblWMS, self.user, self.map()) self.update_url(self.ui.lblWebmap, self.user, self.map()) self.update_url(self.ui.lblMobileMap, self.user, self.map()) #self.update_url(self.ui.lblWFS, self.user, self.map()) def update_url(self, label, user, map): text = re.sub(r'qgiscloud.com/.*?/[^/"<>]*', 'qgiscloud.com/%s/%s' % (user, map), unicode(label.text())) label.setText(text) def read_maps(self): #map = self.api.read_map("1") if self.check_login(): maps = self.api.read_maps() def check_project_saved(self): cancel = False project = QgsProject.instance() if project.isDirty(): msgBox = QMessageBox() msgBox.setText("The project has been modified.") msgBox.setInformativeText("Do you want to save your changes?") msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Ignore | QMessageBox.Cancel) msgBox.setDefaultButton(QMessageBox.Save) ret = msgBox.exec_() if ret == QMessageBox.Save: project.write() elif ret == QMessageBox.Cancel: cancel = True return cancel def publish_map(self): cancel = self.check_project_saved() if cancel: return fname = unicode(QgsProject.instance().fileName()) if self.check_login() and self.check_layers(): self.statusBar().showMessage(u"Publishing map") try: fullExtent = self.iface.mapCanvas().fullExtent() config = { 'fullExtent': { 'xmin': fullExtent.xMinimum(), 'ymin': fullExtent.yMinimum(), 'xmax': fullExtent.xMaximum(), 'ymax': fullExtent.yMaximum() }} map = self.api.create_map(self.map(), fname, config)['map'] #QMessageBox.information(self, "create_map", str(map['config'])) self.show_api_error(map) if map['config']['missingSvgSymbols']: self.publish_symbols(map['config']['missingSvgSymbols']) self.update_urls() self.ui.btnPublishMapUpload.hide() self.statusBar().showMessage(u"Map successfully published") except Exception: self.statusBar().showMessage("") self._exception_message("Error uploading project") def _exception_message(self, title): exc_type, exc_value, exc_traceback = sys.exc_info() stack = traceback.format_exc().splitlines() msgBox = QMessageBox(QMessageBox.Critical, title, "") msgBox.setTextFormat(Qt.RichText) msgBox.setText("<b>%s</b><br/>%s" % (stack[-1], stack[1])) maillink = "<a href=\"mailto:%s?subject=QGISCloud exception: %s&body=%s\">Mail to support</a>" % \ ("*****@*****.**", title, urllib.quote(traceback.format_exc() + str(self._version_info()))) msgBox.setInformativeText(maillink) msgBox.exec_() def publish_symbols(self, missingSvgSymbols): self.statusBar().showMessage(u"Uploading SVG symbols") #Search and upload symbol files for sym in missingSvgSymbols: for path in QgsApplication.svgPaths(): fullpath = path + sym if os.path.isfile(fullpath): self.api.create_graphic(sym, fullpath) def reset_load_data(self): self.update_local_data_sources([]) self.ui.btnUploadData.setEnabled(False) self.ui.btnPublishMapUpload.hide() def remove_layer(self, layer_id): if self.dbs_refreshed and self.do_update_local_data_sources: # skip layer if layer will be removed self.update_local_layers(layer_id) self.activate_upload_button() def add_layer(self): if self.dbs_refreshed and self.do_update_local_data_sources: self.update_local_layers() self.activate_upload_button() def update_local_layers(self, skip_layer_id=None): local_layers, unsupported_layers = self.local_data_sources.local_layers(skip_layer_id) self.update_local_data_sources(local_layers) return local_layers, unsupported_layers def check_layers(self): local_layers, unsupported_layers = self.update_local_layers() if local_layers or unsupported_layers: message = "" if local_layers: title = "Local layers found" message += "Some layers are using local data. You can upload local layers to your cloud database in the 'Upload Data' tab.\n\n" if unsupported_layers: title = "Unsupported layers found" message += "Raster, plugin or geometryless layers are not supported:\n\n" layer_types = ["No geometry", "Raster", "Plugin"] for layer in sorted(unsupported_layers, key=lambda layer: layer.name()): message += " - %s (%s)\n" % (layer.name(), layer_types[layer.type()]) message += "\nPlease remove or replace above layers before publishing your map.\n" message += "For raster data you can use public WMS layers or the OpenLayers Plugin." QMessageBox.information(self, title, message) self.refresh_databases() self.ui.tabWidget.setCurrentIndex(1) return False return True def update_local_data_sources(self, local_layers): # update table names lookup self.update_data_sources_table_names() self.local_data_sources.update_local_data_sources(local_layers) # update GUI while self.ui.tblLocalLayers.rowCount() > 0: self.ui.tblLocalLayers.removeRow(0) geometry_types = { QGis.WKBUnknown: "Unknown", QGis.WKBPoint: "Point", QGis.WKBMultiPoint: "MultiPoint", QGis.WKBLineString: "LineString", QGis.WKBMultiLineString: "MultiLineString", QGis.WKBPolygon: "Polygon", QGis.WKBMultiPolygon: "MultiPolygon", 100: "No geometry" # FIXME: QGis.WKBNoGeometry # FIXME: 2.5d } for data_source, layers in self.local_data_sources.iteritems(): layer_names = [] for layer in layers: layer_names.append(str(layer.name())) layers_item = QTableWidgetItem(", ".join(layer_names)) layers_item.setToolTip("\n".join(layer_names)) data_source_item = QTableWidgetItem(data_source) data_source_item.setToolTip(data_source) table_name = layers[0].name() # find a better table name if there are multiple layers with same data source? if data_source in self.data_sources_table_names: # use current table name if available to keep changes by user table_name = self.data_sources_table_names[data_source] table_name_item = QTableWidgetItem(QgisCloudPluginDialog.launder_pg_name(table_name)) wkbType = layers[0].wkbType() #FIXME: message for unsupported types (2.5D) geometry_type_item = QTableWidgetItem(geometry_types[wkbType]) if layers[0].providerType() == "ogr": geometry_type_item.setToolTip("Note: OGR features will be converted to MULTI-type") srid_item = QTableWidgetItem(layers[0].crs().authid()) row = self.ui.tblLocalLayers.rowCount() self.ui.tblLocalLayers.insertRow(row) self.ui.tblLocalLayers.setItem(row, self.COLUMN_LAYERS, layers_item) self.ui.tblLocalLayers.setItem(row, self.COLUMN_DATA_SOURCE, data_source_item) self.ui.tblLocalLayers.setItem(row, self.COLUMN_TABLE_NAME, table_name_item) self.ui.tblLocalLayers.setItem(row, self.COLUMN_GEOMETRY_TYPE, geometry_type_item) self.ui.tblLocalLayers.setItem(row, self.COLUMN_SRID, srid_item) if self.local_data_sources.count() > 0: self.ui.tblLocalLayers.resizeColumnsToContents() self.ui.tblLocalLayers.setColumnWidth(self.COLUMN_LAYERS, 100) self.ui.tblLocalLayers.setColumnWidth(self.COLUMN_DATA_SOURCE, 100) self.ui.tblLocalLayers.sortItems(self.COLUMN_DATA_SOURCE) self.ui.tblLocalLayers.setSortingEnabled(False) else: self.ui.btnUploadData.setEnabled(False) self.statusBar().showMessage(u"Updated local data sources") @staticmethod def launder_pg_name(name): return re.sub(r"[#'-]", '_', str(name).lower()) #OGRPGDataSource::LaunderName def refresh_local_data_sources(self): if not self.dbs_refreshed: # get dbs on first refresh self.refresh_databases() self.do_update_local_data_sources = True self.update_local_layers() self.activate_upload_button() def update_data_sources_table_names(self): if self.local_data_sources.count() == 0: self.data_sources_table_names.clear() else: # remove table names without data sources keys_to_remove = [] for key in self.data_sources_table_names.iterkeys(): if self.local_data_sources.layers(key) == None: keys_to_remove.append(key) for key in keys_to_remove: del self.data_sources_table_names[key] # update table names for row in range(0, self.ui.tblLocalLayers.rowCount()): data_source = str(self.ui.tblLocalLayers.item(row, self.COLUMN_DATA_SOURCE).text()) table_name = str(self.ui.tblLocalLayers.item(row, self.COLUMN_TABLE_NAME).text()) self.data_sources_table_names[data_source] = table_name def upload_database_selected(self, index): self.activate_upload_button() def activate_upload_button(self): self.ui.btnUploadData.setEnabled((len(self.dbs) <= 1 or self.ui.cbUploadDatabase.currentIndex() > 0) and self.local_data_sources.count() > 0) self.ui.btnPublishMapUpload.hide() def upload_data(self): if self.check_login(): if self.local_data_sources.count() == 0: return if len(self.dbs) == 0: # create db self.statusBar().showMessage(u"Create new database...") QApplication.processEvents() # refresh status bar self.create_database() db_name = self.ui.cbUploadDatabase.currentText() # disable update of local data sources during upload, as there are temporary layers added and removed self.do_update_local_data_sources = False self.setCursor(Qt.WaitCursor) # Map<data_source, {table: table, layers: layers}> data_sources_items = {} for row in range(0, self.ui.tblLocalLayers.rowCount()): data_source = str(self.ui.tblLocalLayers.item(row, self.COLUMN_DATA_SOURCE).text()) layers = self.local_data_sources.layers(data_source) if layers != None: table_name = str(self.ui.tblLocalLayers.item(row, self.COLUMN_TABLE_NAME).text()) data_sources_items[data_source] = {'table': table_name, 'layers': layers} try: #Via QGIS providers: #success = self.data_upload.upload_data(db_name, data_sources_items, self.ui.cbReplaceLocalLayers.isChecked()) #Via OGR: success = self.data_upload.ogr2ogr(db_name, data_sources_items, self.ui.cbReplaceLocalLayers.isChecked()) except Exception: success = False self._exception_message("Data upload error") self.unsetCursor() self.statusBar().showMessage("") self.do_update_local_data_sources = True if success and self.ui.cbReplaceLocalLayers.isChecked(): self.update_local_layers() # show save project dialog msgBox = QMessageBox() msgBox.setWindowTitle("QGIS Cloud") msgBox.setText("The project is ready for publishing.") msgBox.setInformativeText("Do you want to save your changes?") msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Cancel) msgBox.setDefaultButton(QMessageBox.Save) ret = msgBox.exec_() if ret == QMessageBox.Save: self.iface.actionSaveProjectAs().trigger() self.ui.btnPublishMapUpload.show() def show_api_error(self, result): if 'error' in result: QMessageBox.critical(self, "QGIS Cloud Error", "%s" % result['error']) self.statusBar().showMessage(u"Error") return True else: return False