pnguadeloupe / db-style-manager Goto Github PK
View Code? Open in Web Editor NEWImprove the workflow for managing styles in PostgreSQL
Improve the workflow for managing styles in PostgreSQL
Great Work !!!
I can try making it work with qgis3, maybe a qgis3
Branch ? Can I submit a PR ?
Hey @Gustry! I just found your plugin. I was kinda hoping it would do what the title of this ticket suggests - iterate over all my project layers, saving the style of each layer as the default in the PostgreSQL database.
On the "Load a Style Summary from PostgreSQL" button, could we have one for "Loading a style summary from Oracle" that does the same thing, but over an Oracle connection?
Est-il possible d'ajouter la fonctionnalité "sauvegarder par défaut" sur de multiples couches d'un coup ?
Hi,
I am unable to run the plugin. Immediatly after install, an error is raised :
Impossible de charger l'extension 'db-style-manager' provoque une erreur lors de l'appel à sa méthode classFactory()
TypeError: 'QVariant' object is not subscriptable
Traceback (most recent call last):
File "/usr/lib/python3/dist-packages/qgis/utils.py", line 334, in _startPlugin
plugins[packageName] = package.classFactory(iface)
File "/home/jb/.local/share/QGIS/QGIS3/profiles/default/python/plugins/db-style-manager/init.py", line 34, in classFactory
return DbStyleManager(iface)
File "/home/jb/.local/share/QGIS/QGIS3/profiles/default/python/plugins/db-style-manager/plugin.py", line 61, in init
locale = QSettings().value('locale/userLocale')[0:2]
TypeError: 'QVariant' object is not subscriptableVersion de Python : 3.8.5 (default, Jul 28 2020, 12:59:40) [GCC 9.3.0]
Version de QGIS : 3.16.1-Hannover Hannover, b381a90dcaChemin Python :
/usr/share/qgis/python
/home/jb/.local/share/QGIS/QGIS3/profiles/default/python
/home/jb/.local/share/QGIS/QGIS3/profiles/default/python/plugins
/usr/share/qgis/python/plugins
/usr/lib/python38.zip
/usr/lib/python3.8
/usr/lib/python3.8/lib-dynload
/home/jb/.local/lib/python3.8/site-packages
/usr/local/lib/python3.8/dist-packages
/usr/lib/python3/dist-packages
/usr/lib/python3.8/dist-packages
/home/jb/.local/share/QGIS/QGIS3/profiles/default/python
/mnt/20fcc09a-8a1d-4cfb-bd76-916b293d8d21/jb
Est-ce possible d'ajouter une fonctionnalité qui fait un export de tous les fichiers XML de la base ?
I think there's a small bug in the code.
The problem is in the load_style_from_database(self, layer)
function, starting from row #368:
369 manager = layer.styleManager()
370 existing_styles = manager.styles()
371 for s in existing_styles:
372 manager.removeStyle(s)
373
374 manager.currentStyle()
375 manager.renameStyle(manager.currentStyle(), '')
376 manager.renameStyle('', 'default')
377 styles = layer.listStylesInDatabase()
378 if len(styles) == 0:
379 # No style for all layers in the database, we do nothing
380 return
381
382 number_styles = styles[0]
383 if number_styles == 0:
384 # No style for this layer in the database, we do nothing
385 return
386
387
388 related_styles_idx = styles[1][0:number_styles]
389 related_styles_names = styles[2][0:number_styles]
390 related_styles_description = styles[3][0:number_styles]
391 related_styles = zip(related_styles_idx, related_styles_names, related_styles_description)
392 for style in related_styles:
393 if Qgis.QGIS_VERSION_INT < 30000:
394 xml_style = layer.getStyleFromDatabase(style[0], '')
395 else:
396 xml_style = layer.getStyleFromDatabase(style[0])[0]
397 # description = style[2]
398 manager.addStyle(style[1], QgsMapLayerStyle(xml_style))
399
400 # Deactivated in 0.3, because in QGIS 2.18 we can't know which one is the default style
401 # if len(number_styles) > 0:
402 # # If we have at least one style, we take the first one for the title and name
403 # layer.setTitle(related_styles[0][2])
404 # layer.setName(related_styles[0][2])
405
406 # len(zip object) do not exist on Python 3
407 if len(list(related_styles)) >= 1:
408 # We got one layer, we can set it by default in QGIS
409 manager.setCurrentStyle(related_styles[0][1])
410 manager.removeStyle('default')
411
412 self.iface.messageBar().pushInfo(
413 tr('Style Loaded'),
414 tr('{layer_name} has {number} styles loaded successfully.').format(
415 layer_name=layer.name(), number=len(list(related_styles))))
If I already have got styles associated to the layer (like when I reopen the project and the Load styles automatically button is toggled), it removes all but the last one.
>>>from qgis.core import Qgis
>>>from qgis.PyQt.QtCore import QSettings, QTranslator, qVersion, QCoreApplication
>>>from qgis.core import QgsMapLayer, QgsVectorLayer, QgsMapLayerStyle, QgsProject
>>>from qgis.PyQt.QtGui import QIcon
>>>layer = iface.activeLayer()
layer
<qgis._core.QgsVectorLayer object at 0x000001E88E636CA8>
>>>manager = layer.styleManager()
>>>manager.styles()
['ALGHERO', 'DMA', 'HIDI_DMA', 'Leakage_Index', 'Leakage_Index_via', 'MAT+VALVE', 'MURAVERA', 'OLBIA', 'OZIERI', 'PLOAGHE', 'PORTO TORRES', 'QSE', 'SASSARI', 'SERBATOIO', 'SORSO', 'STG', 'default', 'idx_sost_leakage']
>>>existing_styles = manager.styles()
>>>for s in existing_styles:
... manager.removeStyle(s)
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
False
>>>manager.styles()
['idx_sost_leakage']
That code leaves the last saved style (not ever the default one). In fact it is confirmed by the definition of the removeStyle()
function:
https://qgis.org/api/classQgsMapLayerStyleManager.html#a23ab0b557dc0c1ad6a12e1d17798ba63
removeStyle()
bool QgsMapLayerStyleManager::removeStyle ( const QString & name )
Remove a stored style.
Returns
true on success (style exists and it is not the last one)
Definition at line 128 of file qgsmaplayerstylemanager.cpp.
The first problem is that this last style is then renamed as 'default':
>>>manager.currentStyle()
'idx_sost_leakage'
>>>manager.renameStyle(manager.currentStyle(), '')
True
>>>manager.renameStyle('', 'default')
True
The second problem is that it stays there "as is" when the code goes on to retrieve the styles from the database:
>>>styles = layer.listStylesInDatabase()
>>>styles[0]
18
>>>number_styles = styles[0]
>>>styles[1][0:number_styles]
['27', '93', '92', '91', '89', '87', '84', '82', '77', '74', '70', '66', '64', '62', '48', '44', '29', '28']
>>>related_styles_idx = styles[1][0:number_styles]
>>>styles[2][0:number_styles]
['default', 'idx_sost_leakage', 'Leakage_Index_via', 'STG', 'Leakage_Index', 'OLBIA', 'PLOAGHE', 'SASSARI', 'QSE', 'MURAVERA', 'PORTO TORRES', 'SORSO', 'OZIERI', 'ALGHERO', 'MAT+VALVE', 'HIDI_DMA', 'DMA', 'SERBATOIO']
>>>related_styles_names = styles[2][0:number_styles]
>>>styles[3][0:number_styles]
['ACQ_RETE', 'ACQ_RETE', 'ACQ_RETE', 'ACQ_RETE', 'ACQ_RETE', 'ACQ_RETE', 'ACQ_RETE', 'ACQ_RETE', 'ACQ_RETE', 'ACQ_RETE', 'ACQ_RETE', 'ACQ_RETE', 'ACQ_RETE', 'ACQ_RETE', 'ACQ_RETE', 'ACQ_RETE', 'ACQ_RETE', 'ACQ_RETE']
>>>related_styles_description = styles[3][0:number_styles]
>>>related_styles = zip(related_styles_idx, related_styles_names, related_styles_description)
>>>related_styles
<zip object at 0x000001E886AD8548>
>>>list(related_styles)
[('27', 'default', 'ACQ_RETE'), ('93', 'idx_sost_leakage', 'ACQ_RETE'), ('92', 'Leakage_Index_via', 'ACQ_RETE'), ('91', 'STG', 'ACQ_RETE'), ('89', 'Leakage_Index', 'ACQ_RETE'), ('87', 'OLBIA', 'ACQ_RETE'), ('84', 'PLOAGHE', 'ACQ_RETE'), ('82', 'SASSARI', 'ACQ_RETE'), ('77', 'QSE', 'ACQ_RETE'), ('74', 'MURAVERA', 'ACQ_RETE'), ('70', 'PORTO TORRES', 'ACQ_RETE'), ('66', 'SORSO', 'ACQ_RETE'), ('64', 'OZIERI', 'ACQ_RETE'), ('62', 'ALGHERO', 'ACQ_RETE'), ('48', 'MAT+VALVE', 'ACQ_RETE'), ('44', 'HIDI_DMA', 'ACQ_RETE'), ('29', 'DMA', 'ACQ_RETE'), ('28', 'SERBATOIO', 'ACQ_RETE')]
>>>for style in related_styles:
>>> xml_style = layer.getStyleFromDatabase(style[0])[0]
>>> manager.addStyle(style[1], QgsMapLayerStyle(xml_style))
False
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
According to me one third problem (not of the plugin) is that QGIS re-sorts the added styles in the style selection popup and in the project file according to the default Python sorting, i.e. taking care of the case (uppercase first and then lowercase). Well, it isn't a big problem, but I would like to sort my layers as I want...:
>>>related_styles = zip(related_styles_idx, related_styles_names, related_styles_description)
>>>list(related_styles)
[('27', 'default', 'ACQ_RETE'), ('93', 'idx_sost_leakage', 'ACQ_RETE'), ('92', 'Leakage_Index_via', 'ACQ_RETE'), ('91', 'STG', 'ACQ_RETE'), ('89', 'Leakage_Index', 'ACQ_RETE'), ('87', 'OLBIA', 'ACQ_RETE'), ('84', 'PLOAGHE', 'ACQ_RETE'), ('82', 'SASSARI', 'ACQ_RETE'), ('77', 'QSE', 'ACQ_RETE'), ('74', 'MURAVERA', 'ACQ_RETE'), ('70', 'PORTO TORRES', 'ACQ_RETE'), ('66', 'SORSO', 'ACQ_RETE'), ('64', 'OZIERI', 'ACQ_RETE'), ('62', 'ALGHERO', 'ACQ_RETE'), ('48', 'MAT+VALVE', 'ACQ_RETE'), ('44', 'HIDI_DMA', 'ACQ_RETE'), ('29', 'DMA', 'ACQ_RETE'), ('28', 'SERBATOIO', 'ACQ_RETE')]
# note that in python3 the iteration cancels the zip :-(
>>>list(related_styles)
[]
>>>for style in related_styles:
... print(style)
('27', 'default', 'ACQ_RETE')
('93', 'idx_sost_leakage', 'ACQ_RETE')
('92', 'Leakage_Index_via', 'ACQ_RETE')
('91', 'STG', 'ACQ_RETE')
('89', 'Leakage_Index', 'ACQ_RETE')
('87', 'OLBIA', 'ACQ_RETE')
('84', 'PLOAGHE', 'ACQ_RETE')
('82', 'SASSARI', 'ACQ_RETE')
('77', 'QSE', 'ACQ_RETE')
('74', 'MURAVERA', 'ACQ_RETE')
('70', 'PORTO TORRES', 'ACQ_RETE')
('66', 'SORSO', 'ACQ_RETE')
('64', 'OZIERI', 'ACQ_RETE')
('62', 'ALGHERO', 'ACQ_RETE')
('48', 'MAT+VALVE', 'ACQ_RETE')
('44', 'HIDI_DMA', 'ACQ_RETE')
('29', 'DMA', 'ACQ_RETE')
('28', 'SERBATOIO', 'ACQ_RETE')
>>>manager.styles()
['ALGHERO', 'DMA', 'HIDI_DMA', 'Leakage_Index', 'Leakage_Index_via', 'MAT+VALVE', 'MURAVERA', 'OLBIA', 'OZIERI', 'PLOAGHE', 'PORTO TORRES', 'QSE', 'SASSARI', 'SERBATOIO', 'SORSO', 'STG', 'default', 'idx_sost_leakage']
One workaround for the first problem can be a "selective" style removing in line #371, but if the user has changed the 'default' style it will stay the same also after the style reset:
>>>for s in (x for x in existing_styles if x!='default'):
... manager.removeStyle(s)
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
>>>manager.styles()
['default']
A second (and I think more valid) workaround can be:
The function code will change in the following manner:
def load_style_from_database(self, layer):
styles = layer.listStylesInDatabase()
if len(styles) == 0:
# No style for all layers in the database, we do nothing
return
number_styles = styles[0]
if number_styles == 0:
# No style for this layer in the database, we do nothing
return
manager = layer.styleManager()
existing_styles = manager.styles()
for s in existing_styles:
manager.removeStyle(s)
manager.currentStyle()
manager.renameStyle(manager.currentStyle(), 'garbage')
# manager.renameStyle('', 'default')
related_styles_idx = styles[1][0:number_styles]
related_styles_names = styles[2][0:number_styles]
related_styles_description = styles[3][0:number_styles]
related_styles = zip(related_styles_idx, related_styles_names, related_styles_description)
for style in related_styles:
if Qgis.QGIS_VERSION_INT < 30000:
xml_style = layer.getStyleFromDatabase(style[0], '')
else:
xml_style = layer.getStyleFromDatabase(style[0])[0]
# description = style[2]
manager.addStyle(style[1], QgsMapLayerStyle(xml_style))
loaded_styles = manager.styles()
for s in (x for x in loaded_styles if x=='default'):
manager.setCurrentStyle(s)
manager.removeStyle('garbage')
# Deactivated in 0.3, because in QGIS 2.18 we can't know which one is the default style
# if len(number_styles) > 0:
# # If we have at least one style, we take the first one for the title and name
# layer.setTitle(related_styles[0][2])
# layer.setName(related_styles[0][2])
# len(zip object) do not exist on Python 3
if len(list(related_styles)) >= 1:
# We got one layer, we can set it by default in QGIS
manager.setCurrentStyle(related_styles[0][1])
manager.removeStyle('default')
self.iface.messageBar().pushInfo(
tr('Style Loaded'),
tr('{layer_name} has {number} styles loaded successfully.').format(
layer_name=layer.name(), number=len(list(related_styles))))
As said some lines up, in python3 any iteration (or listing) cancels the contents of the zip so the code from lines #406 to #415 won't work ( len(list(related_styles))
= 0 after the iteration).
The remaining problem is that if a user has created a new style and, after that, he wants to load also the styles from the db, the procedure will erase his newly created style (if he hasn't saved it in the db)...
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.