def testEditGeoJsonAddFieldAndThenAddFeatures(self): """ Test bugfix of https://issues.qgis.org/issues/18596 (adding a new field)""" datasource = os.path.join(self.basetestpath, 'testEditGeoJsonAddField.json') with open(datasource, 'wt') as f: f.write("""{ "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "x": 1 }, "geometry": { "type": "Point", "coordinates": [ 0, 0 ] } } ] }""") vl = QgsVectorLayer(datasource, 'test', 'ogr') self.assertTrue(vl.isValid()) self.assertTrue(vl.startEditing()) self.assertTrue(vl.addAttribute(QgsField('strfield', QVariant.String))) self.assertTrue(vl.commitChanges()) self.assertEqual(len(vl.dataProvider().fields()), 1 + 1) self.assertEqual([f.name() for f in vl.dataProvider().fields()], ['x', 'strfield']) f = QgsFeature() self.assertTrue(vl.getFeatures(QgsFeatureRequest()).nextFeature(f)) self.assertIsNone(f['strfield']) self.assertEqual([field.name() for field in f.fields()], ['x', 'strfield']) self.assertTrue(vl.startEditing()) vl.changeAttributeValue(f.id(), 1, 'x') self.assertTrue(vl.commitChanges()) f = QgsFeature() self.assertTrue(vl.getFeatures(QgsFeatureRequest()).nextFeature(f)) self.assertEqual(f['strfield'], 'x') self.assertEqual([field.name() for field in f.fields()], ['x', 'strfield']) # Completely reload file vl = QgsVectorLayer(datasource, 'test', 'ogr') self.assertEqual(len(vl.fields()), 2)
def addFeatures(self, flist, flags=None): added = False f_added = [] for f in flist: if f.hasGeometry() and (f.geometry().wkbType() != self.wkbType()): return added, f_added for f in flist: _f = QgsFeature(self.fields()) _f.setGeometry(f.geometry()) attrs = [None for i in range(_f.fields().count())] for i in range(min(len(attrs), len(f.attributes()))): attrs[i] = f.attributes()[i] _f.setAttributes(attrs) _f.setId(self.next_feature_id) self._features[self.next_feature_id] = _f self.next_feature_id += 1 added = True f_added.append(_f) if self._spatialindex is not None: self._spatialindex.insertFeature(_f) if len(f_added): self.clearMinMaxCache() self.updateExtents() return added, f_added
def testCreateFeature(self): """ test creating a feature respecting defaults and constraints """ layer = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer&field=flddbl:double", "addfeat", "memory") # add a bunch of features f = QgsFeature() f.setAttributes(["test", 123, 1.0]) f1 = QgsFeature(2) f1.setAttributes(["test_1", 124, 1.1]) f2 = QgsFeature(3) f2.setAttributes(["test_2", 125, 2.4]) f3 = QgsFeature(4) f3.setAttributes(["test_3", 126, 1.7]) f4 = QgsFeature(5) f4.setAttributes(["superpig", 127, 0.8]) self.assertTrue(layer.dataProvider().addFeatures([f, f1, f2, f3, f4])) # no layer self.assertFalse(QgsVectorLayerUtils.createFeature(None).isValid()) # basic tests f = QgsVectorLayerUtils.createFeature(layer) self.assertTrue(f.isValid()) self.assertEqual(f.fields(), layer.fields()) self.assertFalse(f.hasGeometry()) self.assertEqual(f.attributes(), [NULL, NULL, NULL]) # set geometry g = QgsGeometry.fromPoint(QgsPoint(100, 200)) f = QgsVectorLayerUtils.createFeature(layer, g) self.assertTrue(f.hasGeometry()) self.assertEqual(f.geometry().exportToWkt(), g.exportToWkt()) # using attribute map f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'a', 2: 6.0}) self.assertEqual(f.attributes(), ['a', NULL, 6.0]) # layer with default value expression layer.setDefaultValueExpression(2, '3*4') f = QgsVectorLayerUtils.createFeature(layer) self.assertEqual(f.attributes(), [NULL, NULL, 12.0]) # we expect the default value expression to take precedence over the attribute map f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'a', 2: 6.0}) self.assertEqual(f.attributes(), ['a', NULL, 12.0]) # layer with default value expression based on geometry layer.setDefaultValueExpression(2, '3*$x') f = QgsVectorLayerUtils.createFeature(layer, g) self.assertEqual(f.attributes(), [NULL, NULL, 300.0]) layer.setDefaultValueExpression(2, None) # test with violated unique constraints layer.setFieldConstraint(1, QgsFieldConstraints.ConstraintUnique) f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 123}) self.assertEqual(f.attributes(), ['test_1', 128, NULL]) layer.setFieldConstraint(0, QgsFieldConstraints.ConstraintUnique) f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 123}) self.assertEqual(f.attributes(), ['test_4', 128, NULL])
def layerData(self, layer, request={}, offset=0): # Retrieve the data for a layer first = True data = {} fields = [] fieldTypes = [] fr = QgsFeatureRequest() if request: if 'exact' in request and request['exact']: fr.setFlags(QgsFeatureRequest.ExactIntersect) if 'nogeom' in request and request['nogeom']: fr.setFlags(QgsFeatureRequest.NoGeometry) if 'fid' in request: fr.setFilterFid(request['fid']) elif 'extents' in request: fr.setFilterRect(QgsRectangle(*request['extents'])) if 'attributes' in request: fr.setSubsetOfAttributes(request['attributes']) # IMPORTANT - we do not use `for f in layer.getFeatures(fr):` as we need # to verify that existing attributes and geometry are correctly cleared # from the feature when calling nextFeature() it = layer.getFeatures(fr) f = QgsFeature() while it.nextFeature(f): if first: first = False for field in f.fields(): fields.append(str(field.name())) fieldTypes.append(str(field.typeName())) if sys.version_info.major == 2: fielddata = dict((name, str(f[name])) for name in fields) else: fielddata = dict((name, str(f[name])) for name in fields) g = f.geometry() if not g.isEmpty(): fielddata[geomkey] = str(g.exportToWkt()) else: fielddata[geomkey] = "None" fielddata[fidkey] = f.id() id = fielddata[fields[0]] description = fielddata[fields[1]] fielddata['id'] = id fielddata['description'] = description data[f.id() + offset] = fielddata if 'id' not in fields: fields.insert(0, 'id') if 'description' not in fields: fields.insert(1, 'description') fields.append(fidkey) fields.append(geomkey) return fields, fieldTypes, data
def layerData(self, layer, request={}, offset=0): # Retrieve the data for a layer first = True data = {} fields = [] fieldTypes = [] fr = QgsFeatureRequest() if request: if 'exact' in request and request['exact']: fr.setFlags(QgsFeatureRequest.ExactIntersect) if 'nogeom' in request and request['nogeom']: fr.setFlags(QgsFeatureRequest.NoGeometry) if 'fid' in request: fr.setFilterFid(request['fid']) elif 'extents' in request: fr.setFilterRect(QgsRectangle(*request['extents'])) if 'attributes' in request: fr.setSubsetOfAttributes(request['attributes']) # IMPORTANT - we do not use `for f in layer.getFeatures(fr):` as we need # to verify that existing attributes and geometry are correctly cleared # from the feature when calling nextFeature() it = layer.getFeatures(fr) f = QgsFeature() while it.nextFeature(f): if first: first = False for field in f.fields(): fields.append(str(field.name())) fieldTypes.append(str(field.typeName())) fielddata = dict((name, str(f[name])) for name in fields) g = f.geometry() if not g.isNull(): fielddata[geomkey] = str(g.asWkt()) else: fielddata[geomkey] = "None" fielddata[fidkey] = f.id() id = fielddata[fields[0]] description = fielddata[fields[1]] fielddata['id'] = id fielddata['description'] = description data[f.id() + offset] = fielddata if 'id' not in fields: fields.insert(0, 'id') if 'description' not in fields: fields.insert(1, 'description') fields.append(fidkey) fields.append(geomkey) return fields, fieldTypes, data
def addNewFeature(self, form, geometry): """ Add a new new feature to the given layer """ layer = form.QGISLayer fields = layer.pendingFields() feature = QgsFeature(fields) roam.utils.log(feature.fields().toList()) feature.setGeometry(geometry) for indx in xrange(fields.count()): feature[indx] = layer.dataProvider().defaultValue(indx) self.openForm(form, feature)
def numericFields(self, layer): # get attributes of a sample feature and create numeric field name list numeric_fields = [] f = QgsFeature() layer.getFeatures().nextFeature(f) for field in f.fields(): isNumeric = False try: float(f.attribute(field.name())) isNumeric = True except ValueError: pass if isNumeric: numeric_fields.append(field.name()) return numeric_fields
def _get_attributes_for_feature(self, feature: QgsFeature) -> List[any]: attributes = { i: feature[field.name()] for i, field in enumerate(feature.fields().toList()) } if self.symbol_type == SymbolType.graduatedSymbol: feature_value = feature[self.mapped_col] matched = None if feature_value is not None: for index, s in self.symbols.items(): if s['range_lower'] <= feature_value < s['range_upper']: matched = index break if matched is not None: for field_name in self.field_template.keys(): style: Style = self.symbols[matched]['style'] style.evaluate_data_defined_expressions(feature) attributes[self.fields.names().index( field_name)] = style.to_dict()[field_name] elif self.symbol_type == SymbolType.categorizedSymbol: feature_value = feature[self.mapped_col] matched = None if feature_value is not None: for index, s in self.symbols.items(): if str(feature_value) == str(s['value']): matched = index if matched is not None: for field_name in self.field_template.keys(): style = self.symbols[matched]['style'] style.evaluate_data_defined_expressions(feature) attributes[self.fields.names().index( field_name)] = style.to_dict()[field_name] elif self.symbol_type == SymbolType.singleSymbol: for field_name in self.field_template.keys(): style = self.symbols[0]['style'] style.evaluate_data_defined_expressions(feature) attributes[self.fields.names().index( field_name)] = style.to_dict()[field_name] # TODO: Add more return [attributes[key] for key in sorted(attributes.keys())]
def __addFeatureResult(self, feature: QgsFeature, feature2: QgsFeature, score: float): """Add result feature to result layer. :param feature QgsFeature: The feature will be added on first layer result :param feature QgsFeature: The feature will be added on second layer result :param score float: the feature score result """ featDup = QgsFeature() featDup.setGeometry(feature.geometry()) featDup.setFields(feature.fields()) # print("set attribute") featDup.setAttributes(feature.attributes()) featDup.setAttribute(feature.fields().indexOf(self.scoreName), score) # print("score 1 set") featDup.setAttribute(feature.fields().indexOf("id"), feature.id()) # print("id 1 set") featDup.setAttribute(feature.fields().indexOf("match"), feature2.id()) # print("match 1 set") featDup2 = QgsFeature() featDup2.setGeometry(feature2.geometry()) featDup2.setFields(feature2.fields()) featDup2.setAttributes(feature2.attributes()) featDup2.setAttribute(feature2.fields().indexOf(self.scoreName), score) # print("score 2 set") featDup2.setAttribute(feature2.fields().indexOf('id'), feature2.id()) # print("id 2 set") featDup2.setAttribute(feature2.fields().indexOf('match'), feature.id()) # print("match 2 set") # print(featDup) # print(featDup.attributes()) # print(featDup2) # print(featDup2.attributes()) self.layerResult.dataProvider().addFeature(featDup) # print(self.layerResult.dataProvider().addFeature(featDup)) self.layerResult2.dataProvider().addFeature(featDup2)
def update_feature_fields(feat: QgsFeature, fields: QgsFields): """ Update fields of feature and its data (QgsAttributes) :param feat: QgsFeature :param fields: new QgsFields :return: new QgsFeature with updated fields """ old_fields = feat.fields() try: assert set(fields.names()).issuperset( set(old_fields.names()) ), "new fields must be a super set of existing fields of feature" except AssertionError as e: print_error(e) return ft = QgsFeature(fields) for k in old_fields.names(): ft.setAttribute(k, feat.attribute(k)) ft.setGeometry(feat.geometry()) return ft
def evaluate(params: Dict[str, str], response: QgsServerResponse, project: QgsProject) -> None: """ Evaluate expressions against layer or features In parameters: LAYER=wms-layer-name EXPRESSION=An expression to evaluate or EXPRESSIONS=["first expression", "second expression"] or EXPRESSIONS={"key1": "first expression", "key2": "second expression"} // optionals FEATURE={"type": "Feature", "geometry": {}, "properties": {}} or FEATURES=[{"type": "Feature", "geometry": {}, "properties": {}}, {"type": "Feature", "geometry": {}, "properties": {}}] FORM_SCOPE=boolean to add formScope based on provided features """ layername = params.get('LAYER', '') if not layername: raise ExpressionServiceError( "Bad request error", "Invalid 'Evaluate' REQUEST: LAYER parameter is mandatory", 400) # get layer layer = find_vector_layer(layername, project) # layer not found if not layer: raise ExpressionServiceError( "Bad request error", "Invalid LAYER parameter for 'Evaluate': {} provided".format(layername), 400) # get expressions expressions = params.get('EXPRESSIONS', '') if not expressions: expression = params.get('EXPRESSION', '') if not expression: raise ExpressionServiceError( "Bad request error", "Invalid 'Evaluate' REQUEST: EXPRESSION or EXPRESSIONS parameter is mandatory", 400) expressions = '["{}"]'.format(expression) # try to load expressions list or dict try: exp_json = json.loads(expressions) except Exception: QgsMessageLog.logMessage( "JSON loads expressions '{}' exception:\n{}".format(expressions, traceback.format_exc()), "lizmap", Qgis.Critical) raise ExpressionServiceError( "Bad request error", "Invalid 'Evaluate' REQUEST: EXPRESSIONS '{}' are not well formed".format(expressions), 400) # create expression context exp_context = QgsExpressionContext() exp_context.appendScope(QgsExpressionContextUtils.globalScope()) exp_context.appendScope(QgsExpressionContextUtils.projectScope(project)) exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer)) # create distance area context da = QgsDistanceArea() da.setSourceCrs(layer.crs(), project.transformContext()) da.setEllipsoid(project.ellipsoid()) # parse expressions exp_map = {} exp_parser_errors = [] exp_items = [] if isinstance(exp_json, list): exp_items = enumerate(exp_json) elif isinstance(exp_json, dict): exp_items = exp_json.items() for k, e in exp_items: exp = QgsExpression(e) exp.setGeomCalculator(da) exp.setDistanceUnits(project.distanceUnits()) exp.setAreaUnits(project.areaUnits()) if exp.hasParserError(): exp_parser_errors.append('Error "{}": {}'.format(e, exp.parserErrorString())) continue if not exp.isValid(): exp_parser_errors.append('Expression not valid "{}"'.format(e)) continue exp.prepare(exp_context) exp_map[k] = exp # expression parser errors found if exp_parser_errors: raise ExpressionServiceError( "Bad request error", "Invalid EXPRESSIONS for 'Evaluate':\n{}".format('\n'.join(exp_parser_errors)), 400) # get features features = params.get('FEATURES', '') if not features: feature = params.get('FEATURE', '') if feature: features = '[' + feature + ']' # create the body body = { 'status': 'success', 'results': [], 'errors': [], 'features': 0 } # without features just evaluate expression with layer context if not features: result = {} error = {} for k, exp in exp_map.items(): value = exp.evaluate(exp_context) if exp.hasEvalError(): result[k] = None error[k] = exp.evalErrorString() else: result[k] = json.loads(QgsJsonUtils.encodeValue(value)) body['results'].append(result) body['errors'].append(error) write_json_response(body, response) return # Check features try: geojson = json.loads(features) except Exception: QgsMessageLog.logMessage( "JSON loads features '{}' exception:\n{}".format(features, traceback.format_exc()), "lizmap", Qgis.Critical) raise ExpressionServiceError( "Bad request error", "Invalid 'Evaluate' REQUEST: FEATURES '{}' are not well formed".format(features), 400) if not geojson or not isinstance(geojson, list) or len(geojson) == 0: raise ExpressionServiceError( "Bad request error", "Invalid 'Evaluate' REQUEST: FEATURES '{}' are not well formed".format(features), 400) if 'type' not in geojson[0] or geojson[0]['type'] != 'Feature': raise ExpressionServiceError( "Bad request error", ("Invalid 'Evaluate' REQUEST: FEATURES '{}' are not well formed: type not defined or not " "Feature.").format(features), 400) # try to load features # read fields feature_fields = QgsJsonUtils.stringToFields( '{ "type": "FeatureCollection","features":' + features + '}', QTextCodec.codecForName("UTF-8")) # read features feature_list = QgsJsonUtils.stringToFeatureList( '{ "type": "FeatureCollection","features":' + features + '}', feature_fields, QTextCodec.codecForName("UTF-8")) # features not well formed if not feature_list: raise ExpressionServiceError( "Bad request error", "Invalid FEATURES for 'Evaluate': not GeoJSON features array provided\n{}".format(features), 400) # Extend layer fields with this provided in GeoJSON Features feat_fields = QgsFields(layer.fields()) feat_fields.extend(feature_fields) # form scope add_form_scope = params.get('FORM_SCOPE', '').lower() in ['true', '1', 't'] # loop through provided features to evaluate expressions for f in feature_list: # clone the features with all attributes # those defined in layer + fields from GeoJSON Features feat = QgsFeature(feat_fields) feat.setGeometry(f.geometry()) for field in f.fields(): fname = field.name() if feat_fields.indexOf(fname) != -1: feat.setAttribute(fname, f[fname]) # Add form scope to expression context if add_form_scope: exp_context.appendScope(QgsExpressionContextUtils.formScope(feat)) exp_context.setFeature(feat) exp_context.setFields(feat.fields()) # Evaluate expressions with the new feature result = {} error = {} for k, exp in exp_map.items(): if add_form_scope: # need to prepare the expression because the context has been updated with a new scope exp.prepare(exp_context) value = exp.evaluate(exp_context) if exp.hasEvalError(): result[k] = None error[k] = exp.evalErrorString() else: result[k] = json.loads(QgsJsonUtils.encodeValue(value)) error[k] = exp.expression() body['results'].append(result) body['errors'].append(error) write_json_response(body, response) return
def run_checks(): self.assertEqual([f.name() for f in vl.fields()], ['fid', 'type', 'value']) # expression req = QgsFeatureRequest() req.setFilterExpression("value=16") it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes(), [5, 2, 16]) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # filter fid req = QgsFeatureRequest() req.setFilterFid(5) it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes(), [5, 2, 16]) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # filter fids req = QgsFeatureRequest() req.setFilterFids([5]) it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes(), [5, 2, 16]) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # check with subset of attributes req = QgsFeatureRequest() req.setFilterFids([5]) req.setSubsetOfAttributes([2]) it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes()[2], 16) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # filter rect and expression req = QgsFeatureRequest() req.setFilterExpression("value=16 or value=14") req.setFilterRect(QgsRectangle(4.5, 4.5, 5.5, 5.5)) it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes(), [5, 2, 16]) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # filter rect and fids req = QgsFeatureRequest() req.setFilterFids([3, 5]) req.setFilterRect(QgsRectangle(4.5, 4.5, 5.5, 5.5)) it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes(), [5, 2, 16]) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # Ensure that orig_ogc_fid is still retrieved even if attribute subset is passed req = QgsFeatureRequest() req.setSubsetOfAttributes([]) it = vl.getFeatures(req) ids = [] geoms = {} while it.nextFeature(f): ids.append(f.id()) geoms[f.id()] = f.geometry().asWkt() self.assertCountEqual(ids, [3, 4, 5]) self.assertEqual(geoms, { 3: 'Point (3 3)', 4: 'Point (4 4)', 5: 'Point (5 5)' })
def testCreateFeature(self): """ test creating a feature respecting defaults and constraints """ layer = QgsVectorLayer( "Point?field=fldtxt:string&field=fldint:integer&field=flddbl:double", "addfeat", "memory") # add a bunch of features f = QgsFeature() f.setAttributes(["test", 123, 1.0]) f1 = QgsFeature(2) f1.setAttributes(["test_1", 124, 1.1]) f2 = QgsFeature(3) f2.setAttributes(["test_2", 125, 2.4]) f3 = QgsFeature(4) f3.setAttributes(["test_3", 126, 1.7]) f4 = QgsFeature(5) f4.setAttributes(["superpig", 127, 0.8]) self.assertTrue(layer.dataProvider().addFeatures([f, f1, f2, f3, f4])) # no layer self.assertFalse(QgsVectorLayerUtils.createFeature(None).isValid()) # basic tests f = QgsVectorLayerUtils.createFeature(layer) self.assertTrue(f.isValid()) self.assertEqual(f.fields(), layer.fields()) self.assertFalse(f.hasGeometry()) self.assertEqual(f.attributes(), [NULL, NULL, NULL]) # set geometry g = QgsGeometry.fromPointXY(QgsPointXY(100, 200)) f = QgsVectorLayerUtils.createFeature(layer, g) self.assertTrue(f.hasGeometry()) self.assertEqual(f.geometry().asWkt(), g.asWkt()) # using attribute map f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'a', 2: 6.0 }) self.assertEqual(f.attributes(), ['a', NULL, 6.0]) # layer with default value expression layer.setDefaultValueDefinition(2, QgsDefaultValue('3*4')) f = QgsVectorLayerUtils.createFeature(layer) self.assertEqual(f.attributes(), [NULL, NULL, 12]) # we do not expect the default value expression to take precedence over the attribute map f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'a', 2: 6.0 }) self.assertEqual(f.attributes(), ['a', NULL, 6.0]) # layer with default value expression based on geometry layer.setDefaultValueDefinition(2, QgsDefaultValue('3*$x')) f = QgsVectorLayerUtils.createFeature(layer, g) #adjusted so that input value and output feature are the same self.assertEqual(f.attributes(), [NULL, NULL, 300.0]) layer.setDefaultValueDefinition(2, QgsDefaultValue(None)) # test with violated unique constraints layer.setFieldConstraint(1, QgsFieldConstraints.ConstraintUnique) f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'test_1', 1: 123 }) # since field 1 has Unique Constraint, it ignores value 123 that already has been set and sets to 128 self.assertEqual(f.attributes(), ['test_1', 128, NULL]) layer.setFieldConstraint(0, QgsFieldConstraints.ConstraintUnique) # since field 0 and 1 already have values test_1 and 123, the output must be a new unique value f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'test_1', 1: 123 }) self.assertEqual(f.attributes(), ['test_4', 128, NULL]) # test with violated unique constraints and default value expression providing unique value layer.setDefaultValueDefinition(1, QgsDefaultValue('130')) f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'test_1', 1: 123 }) # since field 1 has Unique Constraint, it ignores value 123 that already has been set and adds the default value self.assertEqual(f.attributes(), ['test_4', 130, NULL]) # fallback: test with violated unique constraints and default value expression providing already existing value # add the feature with the default value: self.assertTrue(layer.dataProvider().addFeatures([f])) f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'test_1', 1: 123 }) # since field 1 has Unique Constraint, it ignores value 123 that already has been set and adds the default value # and since the default value providing an already existing value (130) it generates a unique value (next int: 131) self.assertEqual(f.attributes(), ['test_5', 131, NULL]) layer.setDefaultValueDefinition(1, QgsDefaultValue(None)) # test with manually correct unique constraint f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'test_1', 1: 132 }) self.assertEqual(f.attributes(), ['test_5', 132, NULL]) """ test creating a feature respecting unique values of postgres provider """ layer = QgsVectorLayer( "Point?field=fldtxt:string&field=fldint:integer&field=flddbl:double", "addfeat", "memory") # init connection string dbconn = 'dbname=\'qgis_test\'' if 'QGIS_PGTEST_DB' in os.environ: dbconn = os.environ['QGIS_PGTEST_DB'] # create a vector layer pg_layer = QgsVectorLayer( '{} table="qgis_test"."authors" sql='.format(dbconn), "authors", "postgres") self.assertTrue(pg_layer.isValid()) # check the default clause default_clause = 'nextval(\'qgis_test.authors_pk_seq\'::regclass)' self.assertEqual(pg_layer.dataProvider().defaultValueClause(0), default_clause) # though default_clause is after the first create not unique (until save), it should fill up all the features with it pg_layer.startEditing() f = QgsVectorLayerUtils.createFeature(pg_layer) self.assertEqual(f.attributes(), [default_clause, NULL]) self.assertTrue(pg_layer.addFeatures([f])) self.assertTrue( QgsVectorLayerUtils.valueExists(pg_layer, 0, default_clause)) f = QgsVectorLayerUtils.createFeature(pg_layer) self.assertEqual(f.attributes(), [default_clause, NULL]) self.assertTrue(pg_layer.addFeatures([f])) f = QgsVectorLayerUtils.createFeature(pg_layer) self.assertEqual(f.attributes(), [default_clause, NULL]) self.assertTrue(pg_layer.addFeatures([f])) # if a unique value is passed, use it f = QgsVectorLayerUtils.createFeature(pg_layer, attributes={ 0: 40, 1: NULL }) self.assertEqual(f.attributes(), [40, NULL]) # and if a default value is configured use it as well pg_layer.setDefaultValueDefinition(0, QgsDefaultValue('11*4')) f = QgsVectorLayerUtils.createFeature(pg_layer) self.assertEqual(f.attributes(), [44, NULL]) pg_layer.rollBack()
def testCreateFeature(self): """ test creating a feature respecting defaults and constraints """ layer = QgsVectorLayer( "Point?field=fldtxt:string&field=fldint:integer&field=flddbl:double", "addfeat", "memory") # add a bunch of features f = QgsFeature() f.setAttributes(["test", 123, 1.0]) f1 = QgsFeature(2) f1.setAttributes(["test_1", 124, 1.1]) f2 = QgsFeature(3) f2.setAttributes(["test_2", 125, 2.4]) f3 = QgsFeature(4) f3.setAttributes(["test_3", 126, 1.7]) f4 = QgsFeature(5) f4.setAttributes(["superpig", 127, 0.8]) self.assertTrue(layer.dataProvider().addFeatures([f, f1, f2, f3, f4])) # no layer self.assertFalse(QgsVectorLayerUtils.createFeature(None).isValid()) # basic tests f = QgsVectorLayerUtils.createFeature(layer) self.assertTrue(f.isValid()) self.assertEqual(f.fields(), layer.fields()) self.assertFalse(f.hasGeometry()) self.assertEqual(f.attributes(), [NULL, NULL, NULL]) # set geometry g = QgsGeometry.fromPointXY(QgsPointXY(100, 200)) f = QgsVectorLayerUtils.createFeature(layer, g) self.assertTrue(f.hasGeometry()) self.assertEqual(f.geometry().asWkt(), g.asWkt()) # using attribute map f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'a', 2: 6.0 }) self.assertEqual(f.attributes(), ['a', NULL, 6.0]) # layer with default value expression layer.setDefaultValueDefinition(2, QgsDefaultValue('3*4')) f = QgsVectorLayerUtils.createFeature(layer) self.assertEqual(f.attributes(), [NULL, NULL, 12.0]) # we expect the default value expression to take precedence over the attribute map f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'a', 2: 6.0 }) self.assertEqual(f.attributes(), ['a', NULL, 12.0]) # layer with default value expression based on geometry layer.setDefaultValueDefinition(2, QgsDefaultValue('3*$x')) f = QgsVectorLayerUtils.createFeature(layer, g) self.assertEqual(f.attributes(), [NULL, NULL, 300.0]) layer.setDefaultValueDefinition(2, QgsDefaultValue(None)) # test with violated unique constraints layer.setFieldConstraint(1, QgsFieldConstraints.ConstraintUnique) f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'test_1', 1: 123 }) self.assertEqual(f.attributes(), ['test_1', 128, NULL]) layer.setFieldConstraint(0, QgsFieldConstraints.ConstraintUnique) f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'test_1', 1: 123 }) self.assertEqual(f.attributes(), ['test_4', 128, NULL])
def run_checks(): self.assertEqual([f.name() for f in vl.fields()], ['fid', 'type', 'value']) # expression req = QgsFeatureRequest() req.setFilterExpression("value=16") it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes(), [5, 2, 16]) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # filter fid req = QgsFeatureRequest() req.setFilterFid(5) it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes(), [5, 2, 16]) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # filter fids req = QgsFeatureRequest() req.setFilterFids([5]) it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes(), [5, 2, 16]) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # check with subset of attributes req = QgsFeatureRequest() req.setFilterFids([5]) req.setSubsetOfAttributes([2]) it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes()[2], 16) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # filter rect and expression req = QgsFeatureRequest() req.setFilterExpression("value=16 or value=14") req.setFilterRect(QgsRectangle(4.5, 4.5, 5.5, 5.5)) it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes(), [5, 2, 16]) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # filter rect and fids req = QgsFeatureRequest() req.setFilterFids([3, 5]) req.setFilterRect(QgsRectangle(4.5, 4.5, 5.5, 5.5)) it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes(), [5, 2, 16]) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # Ensure that orig_ogc_fid is still retrieved even if attribute subset is passed req = QgsFeatureRequest() req.setSubsetOfAttributes([]) it = vl.getFeatures(req) ids = [] geoms = {} while it.nextFeature(f): ids.append(f.id()) geoms[f.id()] = f.geometry().asWkt() self.assertCountEqual(ids, [3, 4, 5]) self.assertEqual(geoms, {3: 'Point (3 3)', 4: 'Point (4 4)', 5: 'Point (5 5)'})
def testCreateFeature(self): """ test creating a feature respecting defaults and constraints """ layer = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer&field=flddbl:double", "addfeat", "memory") # add a bunch of features f = QgsFeature() f.setAttributes(["test", 123, 1.0]) f1 = QgsFeature(2) f1.setAttributes(["test_1", 124, 1.1]) f2 = QgsFeature(3) f2.setAttributes(["test_2", 125, 2.4]) f3 = QgsFeature(4) f3.setAttributes(["test_3", 126, 1.7]) f4 = QgsFeature(5) f4.setAttributes(["superpig", 127, 0.8]) self.assertTrue(layer.dataProvider().addFeatures([f, f1, f2, f3, f4])) # no layer self.assertFalse(QgsVectorLayerUtils.createFeature(None).isValid()) # basic tests f = QgsVectorLayerUtils.createFeature(layer) self.assertTrue(f.isValid()) self.assertEqual(f.fields(), layer.fields()) self.assertFalse(f.hasGeometry()) self.assertEqual(f.attributes(), [NULL, NULL, NULL]) # set geometry g = QgsGeometry.fromPointXY(QgsPointXY(100, 200)) f = QgsVectorLayerUtils.createFeature(layer, g) self.assertTrue(f.hasGeometry()) self.assertEqual(f.geometry().asWkt(), g.asWkt()) # using attribute map f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'a', 2: 6.0}) self.assertEqual(f.attributes(), ['a', NULL, 6.0]) # layer with default value expression layer.setDefaultValueDefinition(2, QgsDefaultValue('3*4')) f = QgsVectorLayerUtils.createFeature(layer) self.assertEqual(f.attributes(), [NULL, NULL, 12]) # we do not expect the default value expression to take precedence over the attribute map f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'a', 2: 6.0}) self.assertEqual(f.attributes(), ['a', NULL, 6.0]) # layer with default value expression based on geometry layer.setDefaultValueDefinition(2, QgsDefaultValue('3*$x')) f = QgsVectorLayerUtils.createFeature(layer, g) #adjusted so that input value and output feature are the same self.assertEqual(f.attributes(), [NULL, NULL, 300.0]) layer.setDefaultValueDefinition(2, QgsDefaultValue(None)) # test with violated unique constraints layer.setFieldConstraint(1, QgsFieldConstraints.ConstraintUnique) f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 123}) # since field 1 has Unique Constraint, it ignores value 123 that already has been set and sets to 128 self.assertEqual(f.attributes(), ['test_1', 128, NULL]) layer.setFieldConstraint(0, QgsFieldConstraints.ConstraintUnique) # since field 0 and 1 already have values test_1 and 123, the output must be a new unique value f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 123}) self.assertEqual(f.attributes(), ['test_4', 128, NULL]) # test with violated unique constraints and default value expression providing unique value layer.setDefaultValueDefinition(1, QgsDefaultValue('130')) f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 123}) # since field 1 has Unique Constraint, it ignores value 123 that already has been set and adds the default value self.assertEqual(f.attributes(), ['test_4', 130, NULL]) # fallback: test with violated unique constraints and default value expression providing already existing value # add the feature with the default value: self.assertTrue(layer.dataProvider().addFeatures([f])) f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 123}) # since field 1 has Unique Constraint, it ignores value 123 that already has been set and adds the default value # and since the default value providing an already existing value (130) it generates a unique value (next int: 131) self.assertEqual(f.attributes(), ['test_5', 131, NULL]) layer.setDefaultValueDefinition(1, QgsDefaultValue(None)) # test with manually correct unique constraint f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 132}) self.assertEqual(f.attributes(), ['test_5', 132, NULL]) """ test creating a feature respecting unique values of postgres provider """ layer = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer&field=flddbl:double", "addfeat", "memory") # init connection string dbconn = 'dbname=\'qgis_test\'' if 'QGIS_PGTEST_DB' in os.environ: dbconn = os.environ['QGIS_PGTEST_DB'] # create a vector layer pg_layer = QgsVectorLayer('{} table="qgis_test"."authors" sql='.format(dbconn), "authors", "postgres") self.assertTrue(pg_layer.isValid()) # check the default clause default_clause = 'nextval(\'qgis_test.authors_pk_seq\'::regclass)' self.assertEqual(pg_layer.dataProvider().defaultValueClause(0), default_clause) # though default_clause is after the first create not unique (until save), it should fill up all the features with it pg_layer.startEditing() f = QgsVectorLayerUtils.createFeature(pg_layer) self.assertEqual(f.attributes(), [default_clause, NULL]) self.assertTrue(pg_layer.addFeatures([f])) self.assertTrue(QgsVectorLayerUtils.valueExists(pg_layer, 0, default_clause)) f = QgsVectorLayerUtils.createFeature(pg_layer) self.assertEqual(f.attributes(), [default_clause, NULL]) self.assertTrue(pg_layer.addFeatures([f])) f = QgsVectorLayerUtils.createFeature(pg_layer) self.assertEqual(f.attributes(), [default_clause, NULL]) self.assertTrue(pg_layer.addFeatures([f])) # if a unique value is passed, use it f = QgsVectorLayerUtils.createFeature(pg_layer, attributes={0: 40, 1: NULL}) self.assertEqual(f.attributes(), [40, NULL]) # and if a default value is configured use it as well pg_layer.setDefaultValueDefinition(0, QgsDefaultValue('11*4')) f = QgsVectorLayerUtils.createFeature(pg_layer) self.assertEqual(f.attributes(), [44, NULL]) pg_layer.rollBack()
def save_vector_data(self, metadata_layer, post_layer_data, has_transactions, post_save_signal=True, **kwargs): """Save vector editing data :param metadata_layer: metadata of the layer being edited :type metadata_layer: MetadataVectorLayer :param post_layer_data: post data with 'add', 'delete' etc. :type post_layer_data: dict :param has_transactions: true if the layer support transactions :type has_transactions: bool :param post_save_signal: if this is a post_save_signal call, defaults to True :type post_save_signal: bool, optional """ # Check atomic capabilities for validation # ----------------------------------------------- #for mode_editing in (EDITING_POST_DATA_ADDED, EDITING_POST_DATA_UPDATED, EDITING_POST_DATA_DELETED): # try to get layer model object from metatada_layer layer = getattr(metadata_layer, 'layer', self.layer) if EDITING_POST_DATA_ADDED in post_layer_data and len( post_layer_data[EDITING_POST_DATA_ADDED]) > 0: if not self.request.user.has_perm('qdjango.add_feature', layer): raise ValidationError( _('Sorry but your user doesn\'t has \'Add Feature\' capability' )) if EDITING_POST_DATA_DELETED in post_layer_data and len( post_layer_data[EDITING_POST_DATA_DELETED]) > 0: if not self.request.user.has_perm('qdjango.delete_feature', layer): raise ValidationError( _('Sorry but your user doesn\'t has \'Delete Feature\' capability' )) if EDITING_POST_DATA_UPDATED in post_layer_data and len( post_layer_data[EDITING_POST_DATA_UPDATED]) > 0: if not self.request.user.has_perm('qdjango.change_feature', layer) and \ not self.request.user.has_perm('qdjango.change_attr_feature', layer): raise ValidationError( _('Sorry but your user doesn\'t has \'Change or Change Attributes Features\' capability' )) # get initial featurelocked metadata_layer.lock.getInitialFeatureLockedIds() # get lockids from client metadata_layer.lock.setLockeFeaturesFromClient( post_layer_data['lockids']) # data for response insert_ids = list() lock_ids = list() # FIXME: check this out # for add check if is a metadata_layer and referenced field is a pk is_referenced_field_is_pk = 'referenced_layer_insert_ids' in kwargs and kwargs['referenced_layer_insert_ids'] \ and hasattr(metadata_layer, 'referenced_field_is_pk') \ and metadata_layer.referenced_field_is_pk # Get the layer qgis_layer = metadata_layer.qgis_layer for mode_editing in (EDITING_POST_DATA_ADDED, EDITING_POST_DATA_UPDATED): if mode_editing in post_layer_data: for geojson_feature in post_layer_data[mode_editing]: data_extra_fields = {'feature': geojson_feature} # Clear any old error qgis_layer.dataProvider().clearErrors() # add media data self.add_media_property(geojson_feature, metadata_layer) # for GEOSGeometry of Django 2.2 it must add crs to feature if is not set if a geo feature if metadata_layer.geometry_type != QGIS_LAYER_TYPE_NO_GEOM: if geojson_feature[ 'geometry'] and 'crs' not in geojson_feature[ 'geometry']: geojson_feature['geometry'][ 'crs'] = "{}:{}".format( self.layer.project.group.srid.auth_name, self.layer.project.group.srid.auth_srid) # reproject data if necessary if kwargs[ 'reproject'] and metadata_layer.geometry_type != QGIS_LAYER_TYPE_NO_GEOM: self.reproject_feature(geojson_feature, to_layer=True) # case relation data ADD, if father referenced field is pk if is_referenced_field_is_pk: for newid in kwargs['referenced_layer_insert_ids']: if geojson_feature['properties'][ metadata_layer. referencing_field] == newid['clientid']: geojson_feature['properties'][ metadata_layer. referencing_field] = newid['id'] if mode_editing == EDITING_POST_DATA_UPDATED: # control feature locked if not metadata_layer.lock.checkFeatureLocked( geojson_feature['id']): raise Exception( self.no_more_lock_feature_msg.format( geojson_feature['id'], metadata_layer.client_var)) # Send for validation # Note that this may raise a validation error pre_save_maplayer.send(self, layer_metadata=metadata_layer, mode=mode_editing, data=data_extra_fields, user=self.request.user) # Validate and save try: original_feature = None feature = QgsFeature(qgis_layer.fields()) if mode_editing == EDITING_POST_DATA_UPDATED: # add patch for shapefile type, geojson_feature['id'] id int() instead of str() # path to fix into QGIS api geojson_feature[ 'id'] = get_layer_fids_from_server_fids( [str(geojson_feature['id'])], qgis_layer)[0] feature.setId(geojson_feature['id']) # Get feature from data provider before update original_feature = qgis_layer.getFeature( geojson_feature['id']) # We use this feature for geometry parsing only: imported_feature = QgsJsonUtils.stringToFeatureList( json.dumps(geojson_feature), qgis_layer.fields(), None # UTF8 codec )[0] feature.setGeometry(imported_feature.geometry()) # There is something wrong in QGIS 3.10 (fixed in later versions) # so, better loop through the fields and set attributes individually for name, value in geojson_feature['properties'].items( ): feature.setAttribute(name, value) # Loop again for set expressions value: # For update store expression result to use later into update condition field_expresion_values = {} for qgis_field in qgis_layer.fields(): if qgis_field.defaultValueDefinition().expression( ): exp = QgsExpression( qgis_field.defaultValueDefinition( ).expression()) if exp.rootNode().nodeType( ) != QgsExpressionNode.ntLiteral and not exp.hasParserError( ): context = QgsExpressionContextUtils.createFeatureBasedContext( feature, qgis_layer.fields()) context.appendScopes( QgsExpressionContextUtils. globalProjectLayerScopes(qgis_layer)) result = exp.evaluate(context) if not exp.hasEvalError(): feature.setAttribute( qgis_field.name(), result) # Check update if expression default value has to run also on update e not # only on insert newone if qgis_field.defaultValueDefinition( ).applyOnUpdate(): field_expresion_values[ qgis_field.name()] = result elif qgis_field.typeName() in ('date', 'datetime', 'time'): if qgis_field.typeName() == 'date': qtype = QDate elif qgis_field.typeName() == 'datetime': qtype = QDateTime else: qtype = QTime field_idx = qgis_layer.fields().indexFromName( qgis_field.name()) options = qgis_layer.editorWidgetSetup( field_idx).config() if 'field_iso_format' in options and not options[ 'field_iso_format']: if geojson_feature['properties'][ qgis_field.name()]: value = qtype.fromString( geojson_feature['properties'][ qgis_field.name()], options['field_format']) feature.setAttribute( qgis_field.name(), value) # Call validator! errors = feature_validator(feature, metadata_layer.qgis_layer) if errors: raise ValidationError(errors) # Save the feature if mode_editing == EDITING_POST_DATA_ADDED: if has_transactions: if not qgis_layer.addFeature(feature): raise Exception( _('Error adding feature: %s') % ', '.join(qgis_layer.dataProvider(). errors())) else: if not qgis_layer.dataProvider().addFeature( feature): raise Exception( _('Error adding feature: %s') % ', '.join(qgis_layer.dataProvider(). errors())) # Patch for Spatialite provider on pk if qgis_layer.dataProvider().name( ) == 'spatialite': pks = qgis_layer.primaryKeyAttributes() if len(pks) > 1: raise Exception( _(f'Error adding feature on Spatialite provider: ' f'layer {qgis_layer.id()} has more than one pk column' )) # update pk attribute: feature.setAttribute( pks[0], server_fid(feature, qgis_layer.dataProvider())) elif mode_editing == EDITING_POST_DATA_UPDATED: attr_map = {} for name, value in geojson_feature[ 'properties'].items(): if name in qgis_layer.dataProvider( ).fieldNameMap(): if name in field_expresion_values: value = field_expresion_values[name] attr_map[qgis_layer.dataProvider(). fieldNameMap()[name]] = value if has_transactions: if not qgis_layer.changeAttributeValues( geojson_feature['id'], attr_map): raise Exception( _('Error changing attribute values: %s' ) % ', '.join(qgis_layer.dataProvider(). errors())) # Check for errors because of https://github.com/qgis/QGIS/issues/36583 if qgis_layer.dataProvider().errors(): raise Exception(', '.join( qgis_layer.dataProvider().errors())) if not feature.geometry().isNull( ) and not qgis_layer.changeGeometry( geojson_feature['id'], feature.geometry()): raise Exception( _('Error changing geometry: %s') % ', '.join(qgis_layer.dataProvider(). errors())) else: if not qgis_layer.dataProvider( ).changeAttributeValues( {geojson_feature['id']: attr_map}): raise Exception( _('Error changing attribute values: %s' ) % ', '.join(qgis_layer.dataProvider(). errors())) if not feature.geometry().isNull( ) and not qgis_layer.dataProvider( ).changeGeometryValues({ geojson_feature['id']: feature.geometry() }): raise Exception( _('Error changing geometry: %s') % ', '.join(qgis_layer.dataProvider(). errors())) to_res = {} to_res_lock = {} if mode_editing == EDITING_POST_DATA_ADDED: # to exclude QgsFormater used into QgsJsonjExporter is necessary build by hand single json feature ex = QgsJsonExporter(qgis_layer) ex.setIncludeAttributes(False) fnames = [f.name() for f in feature.fields()] jfeature = json.loads( ex.exportFeature( feature, dict(zip(fnames, feature.attributes())))) to_res.update({ 'clientid': geojson_feature['id'], # This might be the internal QGIS feature id (< 0) 'id': server_fid( feature, metadata_layer.qgis_layer.dataProvider()), 'properties': jfeature['properties'] }) # lock news: to_res_lock = metadata_layer.lock.modelLock2dict( metadata_layer.lock.lockFeature(server_fid( feature, metadata_layer.qgis_layer.dataProvider()), save=True)) if bool(to_res): insert_ids.append(to_res) if bool(to_res_lock): lock_ids.append(to_res_lock) # Send post vase signal post_save_maplayer.send( self, layer_metadata=metadata_layer, mode=mode_editing, data=data_extra_fields, user=self.request.user, original_feature=original_feature, to_res=to_res) except ValidationError as ex: raise ValidationError({ metadata_layer.client_var: { mode_editing: { 'id': geojson_feature['id'], 'fields': ex.detail, } } }) except Exception as ex: raise ValidationError({ metadata_layer.client_var: { mode_editing: { 'id': geojson_feature['id'], 'fields': str(ex), } } }) # erasing feature if to do if EDITING_POST_DATA_DELETED in post_layer_data: fids = post_layer_data[EDITING_POST_DATA_DELETED] # get feature fids from server fids from client. fids = get_layer_fids_from_server_fids([str(id) for id in fids], qgis_layer) for feature_id in fids: # control feature locked if not metadata_layer.lock.checkFeatureLocked(str(feature_id)): raise Exception( self.no_more_lock_feature_msg.format( feature_id, metadata_layer.client_var)) # Get feature to delete ex = QgsJsonExporter(qgis_layer) deleted_feature = ex.exportFeature( qgis_layer.getFeature(feature_id)) pre_delete_maplayer.send(self, layer_metatada=metadata_layer, data=deleted_feature, user=self.request.user) qgis_layer.dataProvider().clearErrors() if has_transactions: if not qgis_layer.deleteFeatures( [feature_id]) or qgis_layer.dataProvider().errors(): raise Exception( _('Cannot delete feature: %s') % ', '.join(qgis_layer.dataProvider().errors())) else: if not qgis_layer.dataProvider().deleteFeatures( [feature_id]) or qgis_layer.dataProvider().errors(): raise Exception( _('Cannot delete feature: %s') % ', '.join(qgis_layer.dataProvider().errors())) return insert_ids, lock_ids
def testCreateFeature(self): """ test creating a feature respecting defaults and constraints """ layer = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer&field=flddbl:double", "addfeat", "memory") # add a bunch of features f = QgsFeature() f.setAttributes(["test", 123, 1.0]) f1 = QgsFeature(2) f1.setAttributes(["test_1", 124, 1.1]) f2 = QgsFeature(3) f2.setAttributes(["test_2", 125, 2.4]) f3 = QgsFeature(4) f3.setAttributes(["test_3", 126, 1.7]) f4 = QgsFeature(5) f4.setAttributes(["superpig", 127, 0.8]) self.assertTrue(layer.dataProvider().addFeatures([f, f1, f2, f3, f4])) # no layer self.assertFalse(QgsVectorLayerUtils.createFeature(None).isValid()) # basic tests f = QgsVectorLayerUtils.createFeature(layer) self.assertTrue(f.isValid()) self.assertEqual(f.fields(), layer.fields()) self.assertFalse(f.hasGeometry()) self.assertEqual(f.attributes(), [NULL, NULL, NULL]) # set geometry g = QgsGeometry.fromPointXY(QgsPointXY(100, 200)) f = QgsVectorLayerUtils.createFeature(layer, g) self.assertTrue(f.hasGeometry()) self.assertEqual(f.geometry().asWkt(), g.asWkt()) # using attribute map f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'a', 2: 6.0}) self.assertEqual(f.attributes(), ['a', NULL, 6.0]) # layer with default value expression layer.setDefaultValueDefinition(2, QgsDefaultValue('3*4')) f = QgsVectorLayerUtils.createFeature(layer) self.assertEqual(f.attributes(), [NULL, NULL, 12]) # we do not expect the default value expression to take precedence over the attribute map f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'a', 2: 6.0}) self.assertEqual(f.attributes(), ['a', NULL, 6.0]) # layer with default value expression based on geometry layer.setDefaultValueDefinition(2, QgsDefaultValue('3*$x')) f = QgsVectorLayerUtils.createFeature(layer, g) #adjusted so that input value and output feature are the same self.assertEqual(f.attributes(), [NULL, NULL, 300.0]) layer.setDefaultValueDefinition(2, QgsDefaultValue(None)) # test with violated unique constraints layer.setFieldConstraint(1, QgsFieldConstraints.ConstraintUnique) f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 123}) # since field 1 has Unique Constraint, it ignores value 123 that already has been set and sets to 128 self.assertEqual(f.attributes(), ['test_1', 128, NULL]) layer.setFieldConstraint(0, QgsFieldConstraints.ConstraintUnique) # since field 0 and 1 already have values test_1 and 123, the output must be a new unique value f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 123}) self.assertEqual(f.attributes(), ['test_4', 128, NULL]) # test with violated unique constraints and default value expression providing unique value layer.setDefaultValueDefinition(1, QgsDefaultValue('130')) f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 123}) # since field 1 has Unique Constraint, it ignores value 123 that already has been set and adds the default value self.assertEqual(f.attributes(), ['test_4', 130, NULL]) # fallback: test with violated unique constraints and default value expression providing already existing value # add the feature with the default value: self.assertTrue(layer.dataProvider().addFeatures([f])) f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 123}) # since field 1 has Unique Constraint, it ignores value 123 that already has been set and adds the default value # and since the default value providing an already existing value (130) it generates a unique value (next int: 131) self.assertEqual(f.attributes(), ['test_5', 131, NULL]) layer.setDefaultValueDefinition(1, QgsDefaultValue(None)) # test with manually correct unique constraint f = QgsVectorLayerUtils.createFeature(layer, attributes={0: 'test_1', 1: 132}) self.assertEqual(f.attributes(), ['test_5', 132, NULL])
def replace_expression_text(params: Dict[str, str], response: QgsServerResponse, project: QgsProject) -> None: """ Replace expression texts against layer or features In parameters: LAYER=wms-layer-name STRING=A string with expression between [% and %] or STRINGS=["first string with expression", "second string with expression"] or STRINGS={"key1": "first string with expression", "key2": "second string with expression"} // optionals FEATURE={"type": "Feature", "geometry": {}, "properties": {}} or FEATURES=[{"type": "Feature", "geometry": {}, "properties": {}}, {"type": "Feature", "geometry": {}, "properties": {}}] FORM_SCOPE=boolean to add formScope based on provided features """ layername = params.get('LAYER', '') if not layername: raise ExpressionServiceError( "Bad request error", "Invalid 'ReplaceExpressionText' REQUEST: LAYER parameter is mandatory", 400) # get layer layer = find_vector_layer(layername, project) # layer not found if not layer: raise ExpressionServiceError( "Bad request error", "Invalid LAYER parameter for 'ReplaceExpressionText': {} provided".format(layername), 400) # get strings strings = params.get('STRINGS', '') if not strings: the_string = params.get('STRING', '') if not the_string: raise ExpressionServiceError( "Bad request error", "Invalid 'ReplaceExpressionText' REQUEST: STRING or STRINGS parameter is mandatory", 400) strings = '["{}"]'.format(the_string) # try to load expressions list or dict try: str_json = json.loads(strings) except Exception: QgsMessageLog.logMessage( "JSON loads strings '{}' exception:\n{}".format(strings, traceback.format_exc()), "lizmap", Qgis.Critical) raise ExpressionServiceError( "Bad request error", "Invalid 'ReplaceExpressionText' REQUEST: STRINGS '{}' are not well formed".format(strings), 400) # get features features = params.get('FEATURES', '') if not features: feature = params.get('FEATURE', '') if feature: features = '[' + feature + ']' # create expression context exp_context = QgsExpressionContext() exp_context.appendScope(QgsExpressionContextUtils.globalScope()) exp_context.appendScope(QgsExpressionContextUtils.projectScope(project)) exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer)) # create distance area context da = QgsDistanceArea() da.setSourceCrs(layer.crs(), project.transformContext()) da.setEllipsoid(project.ellipsoid()) # organized strings str_map = {} str_items = [] if isinstance(str_json, list): str_items = enumerate(str_json) elif isinstance(str_json, dict): str_items = str_json.items() for k, s in str_items: str_map[k] = s # create the body body = { 'status': 'success', 'results': [], 'errors': [], 'features': 0 } # without features just replace expression string with layer context if not features: result = {} for k, s in str_map.items(): value = QgsExpression.replaceExpressionText(s, exp_context, da) result[k] = json.loads(QgsJsonUtils.encodeValue(value)) body['results'].append(result) write_json_response(body, response) return # Check features try: geojson = json.loads(features) except Exception: QgsMessageLog.logMessage( "JSON loads features '{}' exception:\n{}".format(features, traceback.format_exc()), "lizmap", Qgis.Critical) raise ExpressionServiceError( "Bad request error", "Invalid 'Evaluate' REQUEST: FEATURES '{}' are not well formed".format(features), 400) if not geojson or not isinstance(geojson, list) or len(geojson) == 0: raise ExpressionServiceError( "Bad request error", "Invalid 'Evaluate' REQUEST: FEATURES '{}' are not well formed".format(features), 400) if ('type' not in geojson[0]) or geojson[0]['type'] != 'Feature': raise ExpressionServiceError( "Bad request error", ("Invalid 'Evaluate' REQUEST: FEATURES '{}' are not well formed: type not defined or not " "Feature.").format(features), 400) # try to load features # read fields feature_fields = QgsJsonUtils.stringToFields( '{ "type": "FeatureCollection","features":' + features + '}', QTextCodec.codecForName("UTF-8")) # read features feature_list = QgsJsonUtils.stringToFeatureList( '{ "type": "FeatureCollection","features":' + features + '}', feature_fields, QTextCodec.codecForName("UTF-8")) # features not well formed if not feature_list: raise ExpressionServiceError( "Bad request error", ("Invalid FEATURES for 'ReplaceExpressionText': not GeoJSON features array " "provided\n{}").format(features), 400) # Extend layer fields with this provided in GeoJSON Features feat_fields = QgsFields(layer.fields()) feat_fields.extend(feature_fields) # form scope add_form_scope = params.get('FORM_SCOPE', '').lower() in ['true', '1', 't'] # loop through provided features to replace expression strings for f in feature_list: # clone the features with all attributes # those defined in layer + fields from GeoJSON Features feat = QgsFeature(feat_fields) feat.setGeometry(f.geometry()) for field in f.fields(): field_name = field.name() if feat_fields.indexOf(field_name) != -1: feat.setAttribute(field_name, f[field_name]) # Add form scope to expression context if add_form_scope: exp_context.appendScope(QgsExpressionContextUtils.formScope(feat)) exp_context.setFeature(feat) exp_context.setFields(feat.fields()) # replace expression strings with the new feature result = {} for k, s in str_map.items(): value = QgsExpression.replaceExpressionText(s, exp_context, da) result[k] = json.loads(QgsJsonUtils.encodeValue(value)) body['results'].append(result) write_json_response(body, response) return
def testCreateFeature(self): """ test creating a feature respecting defaults and constraints """ layer = QgsVectorLayer( "Point?field=fldtxt:string&field=fldint:integer&field=flddbl:double", "addfeat", "memory") # add a bunch of features f = QgsFeature() f.setAttributes(["test", 123, 1.0]) f1 = QgsFeature(2) f1.setAttributes(["test_1", 124, 1.1]) f2 = QgsFeature(3) f2.setAttributes(["test_2", 125, 2.4]) f3 = QgsFeature(4) f3.setAttributes(["test_3", 126, 1.7]) f4 = QgsFeature(5) f4.setAttributes(["superpig", 127, 0.8]) self.assertTrue(layer.dataProvider().addFeatures([f, f1, f2, f3, f4])) # no layer self.assertFalse(QgsVectorLayerUtils.createFeature(None).isValid()) # basic tests f = QgsVectorLayerUtils.createFeature(layer) self.assertTrue(f.isValid()) self.assertEqual(f.fields(), layer.fields()) self.assertFalse(f.hasGeometry()) self.assertEqual(f.attributes(), [NULL, NULL, NULL]) # set geometry g = QgsGeometry.fromPointXY(QgsPointXY(100, 200)) f = QgsVectorLayerUtils.createFeature(layer, g) self.assertTrue(f.hasGeometry()) self.assertEqual(f.geometry().asWkt(), g.asWkt()) # using attribute map f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'a', 2: 6.0 }) self.assertEqual(f.attributes(), ['a', NULL, 6.0]) # layer with default value expression layer.setDefaultValueDefinition(2, QgsDefaultValue('3*4')) f = QgsVectorLayerUtils.createFeature(layer) self.assertEqual(f.attributes(), [NULL, NULL, 12]) # we do not expect the default value expression to take precedence over the attribute map f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'a', 2: 6.0 }) self.assertEqual(f.attributes(), ['a', NULL, 6.0]) # layer with default value expression based on geometry layer.setDefaultValueDefinition(2, QgsDefaultValue('3*$x')) f = QgsVectorLayerUtils.createFeature(layer, g) #adjusted so that input value and output feature are the same self.assertEqual(f.attributes(), [NULL, NULL, 300.0]) layer.setDefaultValueDefinition(2, QgsDefaultValue(None)) # test with violated unique constraints layer.setFieldConstraint(1, QgsFieldConstraints.ConstraintUnique) f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'test_1', 1: 123 }) # since field 1 has Unique Constraint, it ignores value 123 that already has been set and sets to 128 self.assertEqual(f.attributes(), ['test_1', 128, NULL]) layer.setFieldConstraint(0, QgsFieldConstraints.ConstraintUnique) # since field 0 and 1 already have values test_1 and 123, the output must be a new unique value f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'test_1', 1: 123 }) self.assertEqual(f.attributes(), ['test_4', 128, NULL]) # test with violated unique constraints and default value expression providing unique value layer.setDefaultValueDefinition(1, QgsDefaultValue('130')) f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'test_1', 1: 123 }) # since field 1 has Unique Constraint, it ignores value 123 that already has been set and adds the default value self.assertEqual(f.attributes(), ['test_4', 130, NULL]) # fallback: test with violated unique constraints and default value expression providing already existing value # add the feature with the default value: self.assertTrue(layer.dataProvider().addFeatures([f])) f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'test_1', 1: 123 }) # since field 1 has Unique Constraint, it ignores value 123 that already has been set and adds the default value # and since the default value providing an already existing value (130) it generates a unique value (next int: 131) self.assertEqual(f.attributes(), ['test_5', 131, NULL]) layer.setDefaultValueDefinition(1, QgsDefaultValue(None)) # test with manually correct unique constraint f = QgsVectorLayerUtils.createFeature(layer, attributes={ 0: 'test_1', 1: 132 }) self.assertEqual(f.attributes(), ['test_5', 132, NULL])