def testSetActive(self): """ Test setting the search as active - should set active flags to match search widget wrapper's defaults """ layer = QgsVectorLayer("Point?field=fldtext:string&field=fldint:integer", "test", "memory") parent = QWidget() w = QgsDefaultSearchWidgetWrapper(layer, 0, parent) setup = QgsGui.editorWidgetRegistry().findBest(layer, "fldint") wrapper = QgsGui.editorWidgetRegistry().create(layer, 0, None, parent) af = QgsAttributeFormEditorWidget(wrapper, setup.type(), None) af.setSearchWidgetWrapper(w) sb = af.findChild(QWidget, "SearchWidgetToolButton") # start with inactive sb.setActiveFlags(QgsSearchWidgetWrapper.FilterFlags()) # set to inactive sb.setActive() # check that correct default flag was taken from search widget wrapper self.assertTrue(sb.activeFlags() & QgsSearchWidgetWrapper.Contains) # try again with numeric field - default should be "EqualTo" w = QgsDefaultSearchWidgetWrapper(layer, 1, parent) af.setSearchWidgetWrapper(w) # start with inactive sb.setActiveFlags(QgsSearchWidgetWrapper.FilterFlags()) # set to inactive sb.setActive() # check that correct default flag was taken from search widget wrapper self.assertTrue(sb.activeFlags() & QgsSearchWidgetWrapper.EqualTo)
def setUpClass(cls): QgsGui.editorWidgetRegistry().initEditors() # Bring up a simple HTTP server os.chdir(unitTestDataPath() + '') handler = http.server.SimpleHTTPRequestHandler cls.httpd = socketserver.TCPServer(('localhost', 0), handler) cls.port = cls.httpd.server_address[1] cls.httpd_thread = threading.Thread(target=cls.httpd.serve_forever) cls.httpd_thread.setDaemon(True) cls.httpd_thread.start()
def setUpClass(cls): """Run before all tests""" cls.dbconn = 'dbname=\'qgis_test\'' if 'QGIS_PGTEST_DB' in os.environ: cls.dbconn = os.environ['QGIS_PGTEST_DB'] # Create test layers cls.vl = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'pk\' srid=4326 type=POINT table="qgis_test"."someData" (geom) sql=', 'test', 'postgres') assert cls.vl.isValid() cls.source = cls.vl.dataProvider() cls.poly_vl = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'pk\' srid=4326 type=POLYGON table="qgis_test"."some_poly_data" (geom) sql=', 'test', 'postgres') assert cls.poly_vl.isValid() cls.poly_provider = cls.poly_vl.dataProvider() QgsGui.editorWidgetRegistry().initEditors() cls.con = psycopg2.connect(cls.dbconn)
def testStringWithMaxLen(self): """ tests that text edit wrappers correctly handle string fields with a maximum length """ layer = QgsVectorLayer("none?field=fldint:integer", "layer", "memory") self.assertTrue(layer.isValid()) layer.dataProvider().addAttributes([QgsField('max', QVariant.String, 'string', 10), QgsField('nomax', QVariant.String, 'string', 0)]) layer.updateFields() QgsProject.instance().addMapLayer(layer) reg = QgsGui.editorWidgetRegistry() config = {'IsMultiline': 'True'} # first test for field without character limit editor = QTextEdit() editor.setPlainText('this_is_a_long_string') w = reg.create('TextEdit', layer, 2, config, editor, None) self.assertEqual(w.value(), 'this_is_a_long_string') # next test for field with character limit editor = QTextEdit() editor.setPlainText('this_is_a_long_string') w = reg.create('TextEdit', layer, 1, config, editor, None) self.assertEqual(w.value(), 'this_is_a_') QgsProject.instance().removeAllMapLayers()
def __createBinaryWidget(self): """ create a binary widget """ reg = QgsGui.editorWidgetRegistry() configWdg = reg.createConfigWidget('Binary', self.layer, 1, None) config = configWdg.config() binary_widget = reg.create('Binary', self.layer, 1, config, None, None) return binary_widget
def testCurrentFilterExpression(self): """ Test creating an expression using the widget""" layer = QgsVectorLayer("Point?field=fldint:integer", "test", "memory") parent = QWidget() w = QgsDefaultSearchWidgetWrapper(layer, 0, parent) setup = QgsGui.editorWidgetRegistry().findBest(layer, "fldint") wrapper = QgsGui.editorWidgetRegistry().create(layer, 0, None, parent) af = QgsAttributeFormEditorWidget(wrapper, setup.type(), None) af.setSearchWidgetWrapper(w) # test that filter combines both current value in search widget wrapper and flags from search tool button w.lineEdit().setText('5.5') sb = af.findChild(QWidget, "SearchWidgetToolButton") sb.setActiveFlags(QgsSearchWidgetWrapper.EqualTo) self.assertEqual(af.currentFilterExpression(), '"fldint"=5.5') sb.setActiveFlags(QgsSearchWidgetWrapper.NotEqualTo) self.assertEqual(af.currentFilterExpression(), '"fldint"<>5.5')
def setUpClass(cls): """ Setup the involved layers and relations for a n:m relation :return: """ cls.mapCanvas = QgsMapCanvas() QgsGui.editorWidgetRegistry().initEditors(cls.mapCanvas) cls.dbconn = 'service=\'qgis_test\'' if 'QGIS_PGTEST_DB' in os.environ: cls.dbconn = os.environ['QGIS_PGTEST_DB'] # Create test layer cls.vl_b = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."books" sql=', 'books', 'postgres') cls.vl_a = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."authors" sql=', 'authors', 'postgres') cls.vl_link = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."books_authors" sql=', 'books_authors', 'postgres') QgsProject.instance().addMapLayer(cls.vl_b) QgsProject.instance().addMapLayer(cls.vl_a) QgsProject.instance().addMapLayer(cls.vl_link) cls.relMgr = QgsProject.instance().relationManager() cls.rel_a = QgsRelation() cls.rel_a.setReferencingLayer(cls.vl_link.id()) cls.rel_a.setReferencedLayer(cls.vl_a.id()) cls.rel_a.addFieldPair('fk_author', 'pk') cls.rel_a.setId('rel_a') assert(cls.rel_a.isValid()) cls.relMgr.addRelation(cls.rel_a) cls.rel_b = QgsRelation() cls.rel_b.setReferencingLayer(cls.vl_link.id()) cls.rel_b.setReferencedLayer(cls.vl_b.id()) cls.rel_b.addFieldPair('fk_book', 'pk') cls.rel_b.setId('rel_b') assert(cls.rel_b.isValid()) cls.relMgr.addRelation(cls.rel_b) # Our mock QgsVectorLayerTools, that allow injecting data where user input is expected cls.vltools = VlTools() assert(cls.vl_a.isValid()) assert(cls.vl_b.isValid()) assert(cls.vl_link.isValid())
def testEditorWidgetTypes(self): """Test that editor widget types can be fetched from the qgis_editor_widget_styles table""" vl = QgsVectorLayer('%s table="qgis_test"."widget_styles" sql=' % (self.dbconn), "widget_styles", "postgres") self.assertTrue(vl.isValid()) fields = vl.dataProvider().fields() setup1 = fields.field("fld1").editorWidgetSetup() self.assertFalse(setup1.isNull()) self.assertEqual(setup1.type(), "FooEdit") self.assertEqual(setup1.config(), {"param1": "value1", "param2": "2"}) best1 = QgsGui.editorWidgetRegistry().findBest(vl, "fld1") self.assertEqual(best1.type(), "FooEdit") self.assertEqual(best1.config(), setup1.config()) self.assertTrue(fields.field("fld2").editorWidgetSetup().isNull()) best2 = QgsGui.editorWidgetRegistry().findBest(vl, "fld2") self.assertEqual(best2.type(), "TextEdit")
def test_enableDisable(self): reg = QgsGui.editorWidgetRegistry() layer = QgsVectorLayer("none?field=number:integer", "layer", "memory") wrapper = reg.create('ValueRelation', layer, 0, {}, None, None) widget = wrapper.widget() self.assertTrue(widget.isEnabled()) wrapper.setEnabled(False) self.assertFalse(widget.isEnabled()) wrapper.setEnabled(True) self.assertTrue(widget.isEnabled())
def __createRangeWidget(self, allownull=False): """ create a range widget """ reg = QgsGui.editorWidgetRegistry() configWdg = reg.createConfigWidget('Range', self.layer, 1, None) config = configWdg.config() # if null shall be allowed if allownull: config["AllowNull"] = allownull rangewidget = reg.create('Range', self.layer, 1, config, None, None) return rangewidget
def test_ValueMap_set_get(self): layer = QgsVectorLayer("none?field=number:integer", "layer", "memory") self.assertTrue(layer.isValid()) QgsProject.instance().addMapLayer(layer) reg = QgsGui.editorWidgetRegistry() configWdg = reg.createConfigWidget('ValueMap', layer, 0, None) config = {'map': [{'two': '2'}, {'twoandhalf': '2.5'}, {'NULL text': 'NULL'}, {'nothing': self.VALUEMAP_NULL_TEXT}]} # Set a configuration containing values and NULL and check if it # is returned intact. configWdg.setConfig(config) self.assertEqual(configWdg.config(), config) QgsProject.instance().removeAllMapLayers()
def test_enableDisableOnTableWidget(self): reg = QgsGui.editorWidgetRegistry() layer = QgsVectorLayer("none?field=number:integer", "layer", "memory") wrapper = reg.create('ValueRelation', layer, 0, {'AllowMulti': 'True'}, None, None) widget = wrapper.widget() item = QTableWidgetItem('first item') widget.setItem(0, 0, item) # does not change the state the whole widget but the single items instead wrapper.setEnabled(False) # widget still true, but items false self.assertTrue(widget.isEnabled()) self.assertNotEqual(widget.item(0, 0).flags(), widget.item(0, 0).flags() | Qt.ItemIsEnabled) wrapper.setEnabled(True) self.assertTrue(widget.isEnabled()) self.assertEqual(widget.item(0, 0).flags(), widget.item(0, 0).flags() | Qt.ItemIsEnabled)
def doAttributeTest(self, idx, expected): reg = QgsGui.editorWidgetRegistry() configWdg = reg.createConfigWidget('TextEdit', self.layer, idx, None) config = configWdg.config() editwidget = reg.create('TextEdit', self.layer, idx, config, None, None) editwidget.setValue('value') self.assertEqual(editwidget.value(), expected[0]) editwidget.setValue(123) self.assertEqual(editwidget.value(), expected[1]) editwidget.setValue(None) self.assertEqual(editwidget.value(), expected[2]) editwidget.setValue(NULL) self.assertEqual(editwidget.value(), expected[3])
def testBetweenFilter(self): """ Test creating a between type filter """ layer = QgsVectorLayer("Point?field=fldtext:string&field=fldint:integer", "test", "memory") form = QgsAttributeForm(layer) wrapper = QgsGui.editorWidgetRegistry().create(layer, 0, None, form) af = QgsAttributeFormEditorWidget(wrapper, 'DateTime', None) af.createSearchWidgetWrappers() d1 = af.findChildren(QDateTimeEdit)[0] d2 = af.findChildren(QDateTimeEdit)[1] d1.setDateTime(QDateTime(QDate(2013, 5, 6), QTime())) d2.setDateTime(QDateTime(QDate(2013, 5, 16), QTime())) sb = af.findChild(QWidget, "SearchWidgetToolButton") sb.setActiveFlags(QgsSearchWidgetWrapper.Between) self.assertEqual(af.currentFilterExpression(), '"fldtext">=\'2013-05-06\' AND "fldtext"<=\'2013-05-16\'') sb.setActiveFlags(QgsSearchWidgetWrapper.IsNotBetween) self.assertEqual(af.currentFilterExpression(), '"fldtext"<\'2013-05-06\' OR "fldtext">\'2013-05-16\'')
def setUpClass(cls): """Run before all tests""" QgsGui.editorWidgetRegistry().initEditors() # Create test layer for FeatureSourceTestCase cls.source = cls.getSource()
def setUpClass(cls): QgsGui.editorWidgetRegistry().initEditors()
class STDMFieldWidget(): # Instantiate the singleton QgsEditorWidgetRegistry widgetRegistry = QgsGui.editorWidgetRegistry() def __init__(self, plugin): self.entity = None self.widget_mapping = {} self.column_mapping = {} self.layer = None self.feature_models = OrderedDict() self.removed_feature_models = OrderedDict() self.current_feature = None self.editor = None self.plugin = plugin def init_form(self, table, spatial_column, curr_layer): """ Initialize required methods and slots to be used in form initialization. :param table: The table name of the layer :type table: String :param spatial_column: The spatial column name of the layer :type spatial_column: String :param curr_layer: The current layer of form. :type curr_layer: QgsVectorLayer :return: None :rtype: NoneTYpe """ # init form self.set_entity(table) self.set_widget_mapping() self.register_factory() self.set_widget_type(curr_layer) curr_layer.editFormConfig().setSuppress(QgsEditFormConfig.SuppressOn) curr_layer.featureAdded.connect( lambda feature_id: self.load_stdm_form( feature_id, spatial_column ) ) curr_layer.featureDeleted.connect( self.on_feature_deleted ) curr_layer.beforeCommitChanges.connect( self.on_digitizing_saved ) def set_entity(self, source): """ Sets the layer entity of the layer based on a table name. :param source: Table name that acts as a layer source. :type source: String :return: None :rtype: NoneType """ curr_profile = current_profile() self.entity = curr_profile.entity_by_name( source ) def _set_widget_type(self, layer, column, widget_type_id): """ Sets the widget type for each field into QGIS form configuration. :param layer: The layer to which the widget type is set. :type layer: QgsVectorLayer :param column: STDM column object :type column: Object :param widget_type_id: The widget type id which could be the default QGIS or the custom STDM widget id which is based on column.TYPE_INFO. :type widget_type_id: String :return: None :rtype:NoneType """ idx = layer.fields().indexFromName(column.name) # Set Alias/ Display names for the column names layer.setFieldAlias( idx, column.header() ) setup = QgsEditorWidgetSetup(widget_type_id, {}) layer.setEditorWidgetSetup(idx, setup) def set_widget_mapping(self): """ Maps each column to QGIS or STDM editor widgets. :return: None :rtype:NoneType """ self.widget_mapping.clear() self.column_mapping.clear() for c in self.entity.columns.values(): self.column_mapping[c.name] = c if c.TYPE_INFO == 'SERIAL': self.widget_mapping[c.name] = ['Hidden', None] elif c.TYPE_INFO == 'GEOMETRY': self.widget_mapping[c.name] = ['TextEdit', None] else: stdm = QApplication.translate( 'STDMFieldWidget', 'STDM' ) self.widget_mapping[c.name] = [ 'stdm_{}'.format( c.TYPE_INFO.lower() ), '{} {}'.format( stdm, c.display_name() ) ] def register_factory(self): """ Registers each widget type to a QGIS widget factory registry. :return: None :rtype: NoneType """ # The destructor has no effects. It is QGIS bug. # So restarting QGIS is required to destroy # registered stdm widgets. for widget_id_name in self.widget_mapping.values(): # add and register stdm widget type only if not widget_id_name[1] is None: widget_name = widget_id_name[1] if widget_id_name[0] not in \ self.widgetRegistry.factories().keys(): widget_factory = QGISFieldWidgetFactory( widget_name ) self.widgetRegistry.registerWidget( widget_id_name[0], widget_factory ) def set_widget_type(self, layer): """ Sets widget type for each fields in a layer. :param layer: The layer to which the widget type is set. :type layer: QgsVectorLayer :return: None :rtype: NoneType """ self.layer = layer for col_name, widget_id_name in \ self.widget_mapping.items(): col = self.column_mapping[col_name] self._set_widget_type( layer, col, widget_id_name[0] ) def feature_to_model(self, feature_id): """ Converts feature to db model. :param feature_id: The feature id :type feature_id: Integer :return: The model and number of columns with data. :rtype: Tuple """ ent_model = entity_model(self.entity) model_obj = ent_model() iterator = self.layer.getFeatures( QgsFeatureRequest().setFilterFid(feature_id)) feature = next(iterator) field_names = [field.name() for field in self.layer.fields()] attribute = feature.attributes() if isinstance(attribute[0], QgsField): return None, 0 mapped_data = OrderedDict(list(zip(field_names, feature.attributes()))) col_with_data = [] for col, value in mapped_data.items(): if col == 'id': continue if value is None: continue if value == NULL: continue setattr(model_obj, col, value) col_with_data.append(col) return model_obj, len(col_with_data) def load_stdm_form(self, feature_id, spatial_column): """ Loads STDM Form and collects the model added into the form so that it is saved later. :param feature_id: the ID of a feature that is last added :type feature_id: Integer :param spatial_column: The spatial column name of the layer :type spatial_column: String :return: None :rtype:NoneType """ srid = None self.current_feature = feature_id # If the digitizing save button is clicked, # the featureAdded signal is called but the # feature ids value is over 0. Return to prevent # the dialog from popping up for every feature. if feature_id > 0: return # if the feature is already in the OrderedDict don't # show the form as the model of the feature is # already populated by the form if feature_id in self.feature_models.keys(): return # If the feature is removed by the undo button, don't # load the form for it but add it # back to feature_models and don't show the form. # This happens when redo button(add feature back) is # clicked after an undo button(remove feature) if feature_id in self.removed_feature_models.keys(): self.feature_models[feature_id] = \ self.removed_feature_models[feature_id] return # If the feature is not valid, geom_wkt will be None # So don't launch form for invalid feature and delete feature geom_wkt = self.get_wkt(spatial_column, feature_id) if geom_wkt is None: title = QApplication.translate( 'STDMFieldWidget', 'Spatial Entity Form Error', None ) msg = QApplication.translate( 'STDMFieldWidget', 'The feature you have added is invalid. \n' 'To fix this issue, check if the feature ' 'is digitized correctly. \n' 'Make sure you have added a base layer to digitize on.', None ) # Message: Spatial column information # could not be found QMessageBox.critical( iface.mainWindow(), title, msg ) return # init form feature_model, col_with_data = self.feature_to_model(feature_id) if col_with_data == 0: feature_model = None self.editor = EntityEditorDialog( self.entity, model=feature_model, parent=iface.mainWindow(), manage_documents=True, collect_model=True, plugin=self.plugin ) self.model = self.editor.model() self.editor.addedModel.connect(self.on_form_saved) # get srid with EPSG text full_srid = self.layer.crs().authid().split(':') if len(full_srid) > 0: # Only extract the number srid = full_srid[1] if not geom_wkt is None: # add geometry into the model setattr( self.model, spatial_column, 'SRID={};{}'.format(srid, geom_wkt) ) # open editor result = self.editor.exec_() if result < 1: self.removed_feature_models[feature_id] = None self.layer.deleteFeature(feature_id) def get_wkt(self, spatial_column, feature_id): """ Gets feature geometry in Well-Known Text format and returns it. :param spatial_column: The spatial column name. :type spatial_column: String :param feature_id: Feature id :type feature_id: Integer :return: Well-Known Text format of a geometry :rtype: WKT """ geom_wkt = None fid = feature_id request = QgsFeatureRequest() request.setFilterFid(fid) features = self.layer.getFeatures(request) geom_col_obj = self.entity.columns[spatial_column] geom_type = geom_col_obj.geometry_type() # get the wkt of the geometry for feature in features: geometry = feature.geometry() if geometry.isGeosValid(): if geom_type in ['MULTIPOLYGON', 'MULTILINESTRING']: geometry.convertToMultiType() geom_wkt = geometry.asWkt() return geom_wkt def on_form_saved(self, model): """ A slot raised when the save button is clicked in spatial unit form. It adds the feature model in feature_models ordered dictionary to be saved later. :param model: The model holding feature geometry and attributes obtained from the form :type model: SQLAlchemy Model :return: None :rtype: NoneType """ if not model is None: self.feature_models[self.current_feature] = model if self.editor.is_valid: self.editor.accept() def on_feature_deleted(self, feature_id): """ A slot raised when a feature is deleted in QGIS map canvas via the undo button. It deletes the associated model of the feature. :param feature_id: The id that is removed. :type feature_id: Integer :return: None :rtype: NoneType """ if feature_id in self.feature_models.keys(): self.removed_feature_models[feature_id] = \ self.feature_models[feature_id] del self.feature_models[feature_id] def on_digitizing_saved(self): """ A slot raised when the save button is clicked on Digitizing Toolbar of QGIS. It saves feature models created by the digitizer and STDM form to the Database. :return: None :rtype: NoneType """ ent_model = entity_model(self.entity) entity_obj = ent_model() entity_obj.saveMany( list(self.feature_models.values()) ) for model in self.feature_models.values(): STDMDb.instance().session.flush() for attrMapper in self.editor._attrMappers: control = attrMapper.valueHandler().control if isinstance(control, ExpressionLineEdit): value = control.on_expression_triggered(model) print(attrMapper._attrName, value) setattr(model, attrMapper._attrName, value) model.update() # Save child models if self.editor is not None: self.editor.save_children() # undo each feature created so that qgis # don't try to save the same feature again. # It will also clear all the models from # self.feature_models as on_feature_deleted # is raised when a feature is removed. for f_id in self.feature_models.keys(): iface.mainWindow().blockSignals(True) self.layer.deleteFeature(f_id) self.on_feature_deleted(f_id) iface.mainWindow().blockSignals(True) for i in range(len(self.feature_models)): self.layer.undoStack().undo()
def setUpClass(cls): cls.mCanvas = QgsMapCanvas() QgsGui.editorWidgetRegistry().initEditors(cls.mCanvas)
__copyright__ = 'Copyright 2016, The QGIS Project' # This will get replaced with a git SHA1 when you do a git archive __revision__ = '248bc5db032cf4a5f7b1b762dd265181105004e2' import qgis # NOQA from qgis.gui import (QgsSearchWidgetWrapper, QgsAttributeFormEditorWidget, QgsDefaultSearchWidgetWrapper, QgsAttributeForm, QgsSearchWidgetToolButton, QgsGui) from qgis.core import (QgsVectorLayer) from qgis.PyQt.QtWidgets import QWidget, QDateTimeEdit from qgis.PyQt.QtCore import QDateTime, QDate, QTime from qgis.testing import start_app, unittest start_app() QgsGui.editorWidgetRegistry().initEditors() class PyQgsAttributeFormEditorWidget(unittest.TestCase): def testCurrentFilterExpression(self): """ Test creating an expression using the widget""" layer = QgsVectorLayer("Point?field=fldint:integer", "test", "memory") parent = QWidget() w = QgsDefaultSearchWidgetWrapper(layer, 0, parent) setup = QgsGui.editorWidgetRegistry().findBest(layer, "fldint") wrapper = QgsGui.editorWidgetRegistry().create(layer, 0, None, parent) af = QgsAttributeFormEditorWidget(wrapper, setup.type(), None) af.setSearchWidgetWrapper(w) # test that filter combines both current value in search widget wrapper and flags from search tool button
import qgis # NOQA from qgis.gui import (QgsSearchWidgetWrapper, QgsAttributeFormEditorWidget, QgsDefaultSearchWidgetWrapper, QgsAttributeForm, QgsSearchWidgetToolButton, QgsGui ) from qgis.core import (QgsVectorLayer) from qgis.PyQt.QtWidgets import QWidget, QDateTimeEdit from qgis.PyQt.QtCore import QDateTime, QDate, QTime from qgis.testing import start_app, unittest start_app() QgsGui.editorWidgetRegistry().initEditors() class PyQgsAttributeFormEditorWidget(unittest.TestCase): def testCurrentFilterExpression(self): """ Test creating an expression using the widget""" layer = QgsVectorLayer("Point?field=fldint:integer", "test", "memory") parent = QWidget() w = QgsDefaultSearchWidgetWrapper(layer, 0, parent) setup = QgsGui.editorWidgetRegistry().findBest(layer, "fldint") wrapper = QgsGui.editorWidgetRegistry().create(layer, 0, None, parent) af = QgsAttributeFormEditorWidget(wrapper, setup.type(), None) af.setSearchWidgetWrapper(w)
def get_widgets_for_field(cls, vl): """Get compatible widget names""" return [k for k, v in QgsGui.editorWidgetRegistry().factories().items() if v.supportsField(vl, 0)]