def expression_eval(expression_text, project_id=None, qgs_layer_id=None, form_data=None, formatter=0): """Evaluates a QgsExpression and returns the result :param expression_text: The QgsExpression text :type expression_text: str :param project_id: ID of the qdjango project, defaults to None :type project_id: int, optional :param qgs_layer_id: ID of the QGIS Layer, defaults to None :type qgslayer_id: str, optional :param form_data: A dictionary that maps to a GeoJSON representation of the feature currently edited in the form :type form_data: dict, optional :param formatter: Indicate if form_data values contains formatter values or original features value. :type formatter: int, optional """ expression = QgsExpression(expression_text) expression_context = QgsExpressionContext() layer = None for func_name in expression.referencedFunctions(): if func_name in FORBIDDEN_FUNCTIONS: raise ExpressionForbiddenError( _('Function "{}" is not allowed for security reasons!').format( func_name)) for var_name in expression.referencedVariables(): if var_name in FORBIDDEN_VARIABLES: raise ExpressionForbiddenError( _('Variable "{}" is not allowed for security reasons!').format( var_name)) if project_id is not None: try: project = Project.objects.get(pk=project_id) if qgs_layer_id is not None: try: layer = project.layer_set.get(qgs_layer_id=qgs_layer_id) except Layer.DoesNotExist: raise ExpressionLayerError( _('QGIS layer with id "{}" could not be found!'). format(qgs_layer_id)) expression_contex = QgsExpressionContextUtils.globalProjectLayerScopes( layer.qgis_layer) else: expression_contex = QgsExpressionContextUtils.globalScope() expression_context.appendScope( QgsExpressionContextUtils.projectScope( project.qgis_project)) except Project.DoesNotExist: raise ExpressionProjectError( _('QDjango project with id "{}" could not be found!').format( project_id)) else: expression_contex = QgsExpressionContextUtils.globalScope() if form_data is not None: if layer is None: raise ExpressionLayerError( _('A valid QGIS layer is required to process form data!')) try: # Case by formatter # formatter == 1 : get featureid from layer, usually must be used with formatter form_data # formatter == 0 : default behavior if formatter == 0: fields = layer.qgis_layer.fields() form_feature = QgsJsonUtils.stringToFeatureList( json.dumps(form_data), fields, None)[0] # Set attributes manually because QgsJsonUtils does not respect order for k, v in form_data['properties'].items(): form_feature.setAttribute(k, v) else: qgis_feature_request = QgsFeatureRequest() exp = expression_from_server_fids( [form_data['id']], layer.qgis_layer.dataProvider()) qgis_feature_request.combineFilterExpression(exp) form_feature = get_qgis_features(layer.qgis_layer, qgis_feature_request)[0] expression_context.appendScope( QgsExpressionContextUtils.formScope(form_feature)) expression_context.setFeature(form_feature) except: raise ExpressionFormDataError() valid, errors = expression.checkExpression(expression_text, expression_context) if not valid: raise ExpressionParseError(errors) result = expression.evaluate(expression_context) if expression.hasEvalError(): raise ExpressionEvalError(expression.evalErrorString()) return result
def calculate_node( node, node_attr_name, node_attr_id, layer, discarded_feats): operator = node.get('operator', DEFAULT_OPERATOR) # for backwards compatibility, we treat the old # 'Use a custom field (no recalculation) as the new one with no parentheses if operator in (OPERATORS_DICT['CUSTOM'], 'Use a custom field (no recalculation)'): customFormula = node.get('customFormula', '') expression = QgsExpression(customFormula) valid, err_msg = QgsExpression.checkExpression(customFormula, None) if not valid: raise InvalidFormula( 'Invalid formula "%s": %s' % (customFormula, err_msg)) if customFormula == '': # use the custom field values instead of recalculating them request = QgsFeatureRequest().setFlags( QgsFeatureRequest.NoGeometry).setSubsetOfAttributes( [node['field']], layer.fields()) for feat in layer.getFeatures(request): if feat[node['field']] == NULL: discard_feat = True discarded_feat = DiscardedFeature( feat.id(), 'Missing value') discarded_feats.add(discarded_feat) return discarded_feats else: # attempt to retrieve a formula from the description and to # calculate the field values based on that formula context = QgsExpressionContext() context.appendScope(QgsExpressionContextUtils.layerScope(layer)) expression.prepare(context) request = QgsFeatureRequest().setFlags( QgsFeatureRequest.NoGeometry) with edit(layer): for feat in layer.getFeatures(request): context.setFeature(feat) value = expression.evaluate(context) if expression.hasEvalError(): raise ValueError(expression.evalErrorString()) if value == NULL: discard_feat = True discarded_feat = DiscardedFeature( feat.id(), 'Missing value') discarded_feats.add(discarded_feat) layer.changeAttributeValue(feat.id(), node_attr_id, value) return discarded_feats # the existance of children should already be checked children = node['children'] with edit(layer): request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry) for feat in layer.getFeatures(request): # If a feature contains any NULL value, discard_feat will # be set to True and the corresponding node value will be # set to NULL discard_feat = False feat_id = feat.id() # init node_value to the correct value depending on the # node's operator if operator in SUM_BASED_OPERATORS: node_value = 0 elif operator in MUL_BASED_OPERATORS: node_value = 1 else: raise InvalidOperator('Invalid operator: %s' % operator) for child in children: if 'field' not in child: raise InvalidChild() # for instance, if the RI can't be calculated, then # also the IRI can't be calculated # But it shouldn't happen, because all the children # should be previously linked to corresponding fields if feat[child['field']] == NULL: discard_feat = True discarded_feat = DiscardedFeature(feat_id, 'Missing value') discarded_feats.add(discarded_feat) break # proceed to the next feature # multiply a variable by -1 if it isInverted try: inversion_factor = -1 if child['isInverted'] else 1 except KeyError: # child is not inverted inversion_factor = 1 if operator in IGNORING_WEIGHT_OPERATORS: # although these operators ignore weights, they # take into account the inversion child_weighted = \ feat[child['field']] * inversion_factor else: # also multiply by the weight child_weighted = ( child['weight'] * feat[child['field']] * inversion_factor) if operator in SUM_BASED_OPERATORS: node_value += child_weighted elif operator in MUL_BASED_OPERATORS: node_value *= child_weighted else: error_message = 'Invalid operator: %s' % operator raise RuntimeError(error_message) if discard_feat: node_value = NULL elif operator == OPERATORS_DICT['AVG']: # it is equivalent to do a weighted sum with equal weights, or # to do the simple sum (ignoring weights) and dividing by the # number of children (we use the latter solution) node_value /= len(children) # for sure, len(children)!=0 elif operator == OPERATORS_DICT['GEOM_MEAN']: # the geometric mean # (see http://en.wikipedia.org/wiki/Geometric_mean) # is the product of the N combined items, elevated by 1/N try: # NOTE: in python2 this check was the default. In python3 # it would produce a complex number without raising any # error if (node_value < 0 and not (1. / len(children)).is_integer()): raise ValueError('negative number cannot be raised' ' to a fractional power') node_value **= 1. / len(children) except ValueError: node_value = NULL discarded_feat = DiscardedFeature(feat_id, 'Invalid value') discarded_feats.add(discarded_feat) layer.changeAttributeValue( feat_id, node_attr_id, node_value) return discarded_feats