diff --git a/CHANGELOG.md b/CHANGELOG.md index 335317d..247fe6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,13 @@ * Default symbology for not supported symbols. * Add messages for not supported symbols. -* Add uploaded message +* Add uploaded message. +* Change icons. + +#### Fixes + +* Fix #29 Error updating layer single user account. +* Fix #27 Error getting columns in SQL dialog. @@ -13,7 +19,7 @@ #### Enhancements -* Remove connection controls in SQL Editor +* Remove connection controls in SQL Editor. * Add composite mode in create maps. * Add dash style to lines in create maps. * Add join style to lines in create maps. @@ -23,9 +29,9 @@ #### Fixes -* Fix #19 Do not allow to edit read-only layers -* Show owner in shared layers, issue #18 -* Fix #26 Encoding error creating map +* Fix #19 Do not allow to edit read-only layers. +* Show owner in shared layers, issue #18. +* Fix #26 Encoding error creating map. @@ -39,7 +45,7 @@ #### Fixes -* Fix #23 Error conecting multiuser account +* Fix #23 Error conecting multiuser account. ### 0.1.5 (2015-06-16) @@ -48,9 +54,9 @@ * Upload layers to CartoDB (More formats). * Create basic visualization. Support: - * Borders - * Fill - * Label + * Borders. + * Fill. + * Label. ### 0.1.4 (2015-06-05) @@ -65,7 +71,7 @@ #### Fixes -* Fix avatar size +* Fix avatar size. * Fix #21 Insert data without complete fields. * Enable buttons only if there is at least one created connection. @@ -101,6 +107,7 @@ ### 0.1.1 (2014-11-27) +* Fix error when repeat layer name at spatialite database. #### New Features * Load cartodb layers from SQL Queries. @@ -108,7 +115,6 @@ #### Fixes. * New connection dialog is now a modal window. -* Fix error when repeat layer name at spatialite database. ### 0.1.0 (2014-09-23) diff --git a/CartoDBPlugin.py b/CartoDBPlugin.py index efa7d44..e6bdd2c 100644 --- a/CartoDBPlugin.py +++ b/CartoDBPlugin.py @@ -81,11 +81,11 @@ def initGui(self): self._mainAction = QAction(self.tr('Add CartoDB Layer'), self.iface.mainWindow()) self._mainAction.setIcon(QIcon(":/plugins/qgis-cartodb/images/icons/add.png")) self._loadDataAction = QAction(self.tr('Upload layers to CartoDB'), self.iface.mainWindow()) - self._loadDataAction.setIcon(QIcon(":/plugins/qgis-cartodb/images/icons/polygon.png")) + self._loadDataAction.setIcon(QIcon(":/plugins/qgis-cartodb/images/icons/upload.png")) self._createVizAction = QAction(self.tr('Create New Map'), self.iface.mainWindow()) - self._createVizAction.setIcon(QIcon(":/plugins/qgis-cartodb/images/icons/rectangle.png")) + self._createVizAction.setIcon(QIcon(":/plugins/qgis-cartodb/images/icons/map.png")) self._addSQLAction = QAction(self.tr('Add SQL CartoDB Layer'), self.iface.mainWindow()) - self._addSQLAction.setIcon(QIcon(":/plugins/qgis-cartodb/images/icons/add_sql.png")) + self._addSQLAction.setIcon(QIcon(":/plugins/qgis-cartodb/images/icons/sql.png")) self.toolbar = CartoDBToolbar() self.toolbar.setClick(self.connectionManager) @@ -182,7 +182,8 @@ def run(self): for i, table in enumerate(selectedItems): widget = dlg.getItemWidget(table) worker = CartoDBLayerWorker(self.iface, widget.tableName, widget.tableOwner, dlg, - filterByExtent=dlg.filterByExtent(), readonly=widget.readonly) + filterByExtent=dlg.filterByExtent(), readonly=widget.readonly, + multiuser=widget.multiuser) worker.finished.connect(self.addLayer) self.worker = worker worker.load() diff --git a/README.md b/README.md index ed088c5..6394d36 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ Voilá !!!! #### Adding SQL CartoDB layer -Click on the icon: ![Icon](images/add_sql.png?raw=true "Icon") or on the web menu item "CartoDB Plugin" => "Add SQL CartoDB Layer" +Click on the icon: ![Icon](images/icons/sql.png?raw=true "Icon") or on the web menu item "CartoDB Plugin" => "Add SQL CartoDB Layer" ![Dialog 3](images/sql_dialog.png?raw=true "Adding SQL layer") diff --git a/dialogs/Main.py b/dialogs/Main.py index 422548f..b3f7fda 100644 --- a/dialogs/Main.py +++ b/dialogs/Main.py @@ -86,7 +86,7 @@ def updateList(self, visualizations): widget = CartoDBDatasetsListItem( visualization['name'], owner, visualization['table']['size'], visualization['table']['row_count'], - shared=(owner != self.currentUser)) + shared=(owner != self.currentUser), multiuser=self.currentMultiuser) # item.setText(visualization['name']) readonly = False # qDebug('Vis:' + json.dumps(visualization, sort_keys=True, indent=2, separators=(',', ': '))) diff --git a/dialogs/NewSQL.py b/dialogs/NewSQL.py index 3f1cdc1..901af85 100644 --- a/dialogs/NewSQL.py +++ b/dialogs/NewSQL.py @@ -115,13 +115,12 @@ def findTables(self): if not str(self.currentMultiuser) in ['true', '1', 'True']: sqlTables = "SELECT CDB_UserTables() table_name" res = cl.sql( - "SELECT *, CDB_ColumnType(table_name, column_name) column_type \ - FROM ( \ - SELECT *, CDB_ColumnNames(table_name) column_name \ - FROM (" + sqlTables + ") t1 \ - ) t2 \ - WHERE CDB_ColumnType(table_name, column_name) != 'USER-DEFINED' \ - ORDER BY table_name, column_name") + "WITH usertables AS (" + sqlTables + ") \ + SELECT ut.table_name, c.column_name, c.data_type column_type \ + FROM usertables ut \ + JOIN information_schema.columns c ON c.table_name = ut.table_name \ + WHERE c.data_type != 'USER-DEFINED' \ + ORDER BY ut.table_name, c.column_name") else: sqlTables = "SELECT string_agg(privilege_type, ', ') AS privileges, table_schema, table_name \ FROM information_schema.role_table_grants tg \ @@ -208,3 +207,12 @@ def getQuery(self): def showEvent(self, event): worker = CartoDBPluginWorker(self, 'findTables') worker.start() + +''' +WITH usertables AS (SELECT CDB_UserTables() table_name) +SELECT ut.table_name, c.column_name, c.data_type column_type, ut.privileges + FROM usertables ut + JOIN information_schema.columns c ON c.table_name = ut.table_name +WHERE c.data_type != 'USER-DEFINED' +ORDER BY ut.table_name, c.column_name +''' diff --git a/images/icons/add.png b/images/icons/add.png index 898afe9..2c1d66f 100644 Binary files a/images/icons/add.png and b/images/icons/add.png differ diff --git a/images/icons/add_sql.png b/images/icons/add_sql.png deleted file mode 100644 index e5d394f..0000000 Binary files a/images/icons/add_sql.png and /dev/null differ diff --git a/images/icons/avatar.png b/images/icons/avatar.png new file mode 100644 index 0000000..3e079e2 Binary files /dev/null and b/images/icons/avatar.png differ diff --git a/images/icons/error.png b/images/icons/error.png new file mode 100644 index 0000000..cc07f30 Binary files /dev/null and b/images/icons/error.png differ diff --git a/images/icons/map.png b/images/icons/map.png new file mode 100644 index 0000000..78994f2 Binary files /dev/null and b/images/icons/map.png differ diff --git a/images/icons/sql.png b/images/icons/sql.png index 29a3bd0..8b2c422 100644 Binary files a/images/icons/sql.png and b/images/icons/sql.png differ diff --git a/images/icons/upload.png b/images/icons/upload.png new file mode 100644 index 0000000..de70c73 Binary files /dev/null and b/images/icons/upload.png differ diff --git a/layers/CartoDBLayer.py b/layers/CartoDBLayer.py index 6a5d87c..9608fce 100644 --- a/layers/CartoDBLayer.py +++ b/layers/CartoDBLayer.py @@ -44,13 +44,15 @@ class CartoDBLayer(QgsVectorLayer): LAYER_TNAME_PROPERTY = 'tableName' LAYER_SQL_PROPERTY = 'cartoSQL' - def __init__(self, iface, tableName, user, apiKey, owner=None, sql=None, geoJSON=None, filterByExtent=False, spatiaLite=None, readonly=False): + def __init__(self, iface, tableName, user, apiKey, owner=None, sql=None, geoJSON=None, + filterByExtent=False, spatiaLite=None, readonly=False, multiuser=False): # SQLite available? self.iface = iface self.user = user self._apiKey = apiKey self.layerType = 'ogr' self.owner = owner + self.multiuser = multiuser driverName = "SQLite" sqLiteDrv = ogr.GetDriverByName(driverName) self.databasePath = QgisCartoDB.CartoDBPlugin.PLUGIN_DIR + '/db/database.sqlite' @@ -61,7 +63,7 @@ def __init__(self, iface, tableName, user, apiKey, owner=None, sql=None, geoJSON self.forceReadOnly = False or readonly if sql is None: - sql = 'SELECT * FROM ' + ((owner + '.') if owner is not None else '') + self.cartoTable + sql = 'SELECT * FROM ' + self._schema() + self.cartoTable if filterByExtent: extent = self.iface.mapCanvas().extent() sql = sql + " WHERE ST_Intersects(ST_GeometryFromText('{}', 4326), the_geom)".format(extent.asWktPolygon()) @@ -169,7 +171,7 @@ def _loadData(self, sql, geoJSON=None, spatiaLite=None): self.setCustomProperty(CartoDBLayer.LAYER_SQL_PROPERTY, sql) def _uneditableFields(self): - schema = 'public' if self.owner is None else self.owner + schema = 'public' if not self.multiuser else self.owner sql = "SELECT table_name, column_name, column_default, is_nullable, data_type, table_schema \ FROM information_schema.columns \ WHERE data_type != 'USER-DEFINED' AND table_schema = '" + schema + "' AND table_name = '" + self.cartoTable + "' \ @@ -224,11 +226,10 @@ def _beforeCommitChanges(self): self._deleteFeatures(editBuffer.deletedFeatureIds()) def _updateAttributes(self, changedAttributeValues): - schema = '' if self.owner is None else (self.owner + '.') provider = self.dataProvider() for featureID, v in changedAttributeValues.iteritems(): QgsMessageLog.logMessage('Update attributes for feature ID: ' + str(featureID), 'CartoDB Plugin', QgsMessageLog.INFO) - sql = "UPDATE " + schema + self.cartoTable + " SET " + sql = "UPDATE " + self._schema() + self.cartoTable + " SET " request = QgsFeatureRequest().setFilterFid(featureID) try: feature = self.getFeatures(request).next() @@ -263,12 +264,11 @@ def _updateAttributes(self, changedAttributeValues): level=self.iface.messageBar().WARNING, duration=10) def _updateGeometries(self, changedGeometries): - schema = '' if self.owner is None else (self.owner + '.') for featureID, geom in changedGeometries.iteritems(): QgsMessageLog.logMessage('Update geometry for feature ID: ' + str(featureID), 'CartoDB Plugin', QgsMessageLog.INFO) request = QgsFeatureRequest().setFilterFid(featureID) try: - sql = "UPDATE " + schema + self.cartoTable + " SET the_geom = " + sql = "UPDATE " + self._schema() + self.cartoTable + " SET the_geom = " feature = self.getFeatures(request).next() sql = sql + "ST_GeomFromText('" + geom.exportToWkt() + "', ST_SRID(the_geom)) WHERE cartodb_id = " + unicode(feature['cartodb_id']) sql = sql.encode('utf-8') @@ -285,11 +285,10 @@ def _updateGeometries(self, changedGeometries): level=self.iface.messageBar().WARNING, duration=10) def _addFeatures(self, addedFeatures): - schema = '' if self.owner is None else (self.owner + '.') provider = self.dataProvider() for featureID, feature in addedFeatures.iteritems(): QgsMessageLog.logMessage('Add feature with feature ID: ' + str(featureID), 'CartoDB Plugin', QgsMessageLog.INFO) - sql = "INSERT INTO " + schema + self.cartoTable + " (" + sql = "INSERT INTO " + self._schema() + self.cartoTable + " (" addComma = False for field in feature.fields(): if unicode(feature[field.name()]) == 'NULL' or feature[field.name()] is None: @@ -311,26 +310,29 @@ def _addFeatures(self, addedFeatures): addComma = True if addComma: sql = sql + ", " - sql = sql + "ST_GeomFromText('" + feature.geometry().exportToWkt() + "', 4326)) RETURNING cartodb_id" + sql = sql + "ST_GeomFromText('" + feature.geometry().exportToWkt() + "', 4326)) RETURNING cartodb_id, created_at, updated_at" sql = sql.encode('utf-8') res = self._updateSQL(sql, 'Some error ocurred inserting feature') if isinstance(res, dict) and res['total_rows'] == 1: self.iface.messageBar().pushMessage('Info', 'Feature inserted at CartoDB servers', level=self.iface.messageBar().INFO, duration=10) - self.setFieldEditable(self.fieldNameIndex('cartodb_id'), True) - self.editBuffer().changeAttributeValue(featureID, self.fieldNameIndex('cartodb_id'), res['rows'][0]['cartodb_id'], None) - self.setFieldEditable(self.fieldNameIndex('cartodb_id'), False) + for f in ['cartodb_id', 'created_at', 'updated_at']: + self._updateNullableFields(featureID, f, res['rows'][0][f]) + + def _updateNullableFields(self, featureID, fieldName, value): + self.setFieldEditable(self.fieldNameIndex(fieldName), True) + self.editBuffer().changeAttributeValue(featureID, self.fieldNameIndex(fieldName), value, None) + self.setFieldEditable(self.fieldNameIndex(fieldName), False) def _deleteFeatures(self, deletedFeatureIds): - schema = '' if self.owner is None else (self.owner + '.') provider = self.dataProvider() for featureID in deletedFeatureIds: QgsMessageLog.logMessage('Delete feature with feature ID: ' + str(featureID), 'CartoDB Plugin', QgsMessageLog.INFO) request = QgsFeatureRequest().setFilterFid(featureID) try: feature = provider.getFeatures(request).next() - sql = "DELETE FROM " + schema + self.cartoTable + " WHERE cartodb_id = " + unicode(feature['cartodb_id']) + sql = "DELETE FROM " + self._schema() + self.cartoTable + " WHERE cartodb_id = " + unicode(feature['cartodb_id']) res = self._updateSQL(sql, 'Some error ocurred deleting feature') if isinstance(res, dict) and res['total_rows'] == 1: self.iface.messageBar().pushMessage('Info', @@ -354,6 +356,13 @@ def _updateSQL(self, sql, errorMsg): self.iface.messageBar().pushMessage('Error!!', errorMsg, level=self.iface.messageBar().CRITICAL, duration=10) return e + def _schema(self): + schema = self.schema() + return schema + ('.' if schema != '' else '') + + def schema(self): + return '' if not self.multiuser else self.owner + def tableName(self): return self.cartoTable @@ -377,12 +386,13 @@ class CartoDBLayerWorker(QObject): finished = pyqtSignal(CartoDBLayer) error = pyqtSignal(Exception, basestring) - def __init__(self, iface, tableName, owner=None, dlg=None, sql=None, filterByExtent=False, readonly=False): + def __init__(self, iface, tableName, owner=None, dlg=None, sql=None, filterByExtent=False, readonly=False, multiuser=False): QObject.__init__(self) self.iface = iface self.owner = owner self.tableName = tableName self.readonly = readonly + self.multiuser = multiuser self.dlg = dlg self.sql = sql self.filterByExtent = filterByExtent @@ -398,7 +408,7 @@ def load(self): @pyqtSlot(str) def _loadData(self, spatiaLite): layer = CartoDBLayer(self.iface, self.tableName, self.dlg.currentUser, self.dlg.currentApiKey, - self.owner, self.sql, spatiaLite=spatiaLite, readonly=self.readonly) + self.owner, self.sql, spatiaLite=spatiaLite, readonly=self.readonly, multiuser=self.multiuser) self.finished.emit(layer) @pyqtSlot() diff --git a/metadata.txt b/metadata.txt index da50bdd..74bcbb6 100644 --- a/metadata.txt +++ b/metadata.txt @@ -26,7 +26,10 @@ email=michaelsalgado@gkudos.com changelog=0.1.9 - Default symbology for not supported symbols. - Add messages for not supported symbols. - - Add uploaded message + - Add uploaded message. + - Change icons. + - Fix error updating layer single user account. + - Fix error getting columns in SQL dialog. 0.1.8 - Remove connection controls in SQL Editor. - Add composite mode in create maps. diff --git a/resources.qrc b/resources.qrc index 05b973d..980d6ba 100644 --- a/resources.qrc +++ b/resources.qrc @@ -12,7 +12,7 @@ images/icons/rectangle.png - images/icons/add_sql.png + images/icons/sql.png images/icons/text.png @@ -29,4 +29,10 @@ images/icons/layers.png + + images/icons/upload.png + + + images/icons/map.png + diff --git a/toolbars/CartoDBToolbar.py b/toolbars/CartoDBToolbar.py index 1ef0890..65740bb 100644 --- a/toolbars/CartoDBToolbar.py +++ b/toolbars/CartoDBToolbar.py @@ -39,6 +39,7 @@ def __init__(self, parent=None, flags=Qt.WindowFlags(0)): if self.currentUser: self.currentApiKey = self.settings.value('/CartoDBPlugin/%s/api' % self.currentUser) self.currentMultiuser = self.settings.value('/CartoDBPlugin/%s/multiuser' % self.currentUser, False) + self.currentMultiuser = self.currentMultiuser in ['True', 'true', True] else: self.currentApiKey = None self.currentMultiuser = None diff --git a/widgets/ListItemWidgets.py b/widgets/ListItemWidgets.py index ee5dc9e..1b4ecc1 100644 --- a/widgets/ListItemWidgets.py +++ b/widgets/ListItemWidgets.py @@ -26,12 +26,13 @@ class CartoDBDatasetsListItem(QWidget): - def __init__(self, tableName=None, tableOwner=None, size=None, rows=None, shared=False): + def __init__(self, tableName=None, tableOwner=None, size=None, rows=None, multiuser=False, shared=False): QWidget.__init__(self) self.ui = Ui_ListItem() self.ui.setupUi(self) self.shared = shared + self.multiuser = multiuser self.readonly = False self.tableOwner = tableOwner