def get_range_field_query(cls, field: str, data: Sequence[Optional[str]]) -> RegexQ: """ Return a range query for the provided field. The data should contain min and max values Both intervals are included. For open range queries either min or max can be None In case the min value is None the records with missing or None value from db are included """ if not isinstance(data, (list, tuple)) or len(data) != 2: raise errors.bad_request.ValidationError( f"Min and max values should be specified for range field {field}" ) min_val, max_val = data if min_val is None and max_val is None: raise errors.bad_request.ValidationError( f"At least one of min or max values should be provided for field {field}" ) mongoengine_field = field.replace(".", "__") query = {} if min_val is not None: query[f"{mongoengine_field}__gte"] = min_val if max_val is not None: query[f"{mongoengine_field}__lte"] = max_val q = RegexQ(**query) if min_val is None: q |= RegexQ(**{mongoengine_field: None}) return q
def get_list_field_query(cls, field: str, data: Sequence[Optional[str]]) -> Q: """ Get a proper mongoengine Q object that represents an "or" query for the provided values with respect to the given list field, with support for "none of empty" in case a None value is included. - Exclusion can be specified by a leading "-" for each value (API versions <2.8) or by a preceding "__$not" value (operator) - AND can be achieved using a preceding "__$all" or "__$and" value (operator) """ if not isinstance(data, (list, tuple)): raise MakeGetAllQueryError("expected list", field) # TODO: backwards compatibility only for older API versions helper = cls.ListFieldBucketHelper(legacy=True) actions = bucketize( data, key=helper.key, value_transform=helper.value_transform ) allow_empty = None in actions.get("in", {}) mongoengine_field = field.replace(".", "__") q = RegexQ() for action in filter(None, actions): q &= RegexQ( **{ f"{mongoengine_field}__{action}": list( set(filter(None, actions[action])) ) } ) if not allow_empty: return q return ( q | Q(**{f"{mongoengine_field}__exists": False}) | Q(**{mongoengine_field: []}) )
def get_list_field_query(cls, field: str, data: Sequence[Optional[str]]) -> RegexQ: """ Get a proper mongoengine Q object that represents an "or" query for the provided values with respect to the given list field, with support for "none of empty" in case a None value is included. - Exclusion can be specified by a leading "-" for each value (API versions <2.8) or by a preceding "__$not" value (operator) - AND can be achieved using a preceding "__$all" or "__$and" value (operator) """ if not isinstance(data, (list, tuple)): data = [data] helper = cls.ListFieldBucketHelper(legacy=True) global_op = helper.get_global_op(data) actions = helper.get_actions(data) mongoengine_field = field.replace(".", "__") queries = [ RegexQ( ** {f"{mongoengine_field}__{action}": list(set(actions[action]))}) for action in filter(None, actions) ] if not queries: q = RegexQ() else: q = RegexQCombination(operation=global_op, children=queries) if not helper.allow_empty: return q return (q | RegexQ(**{f"{mongoengine_field}__exists": False}) | RegexQ(**{mongoengine_field: []}) | RegexQ(**{mongoengine_field: None}))
def _prepare_query_no_company(cls, parameters=None, parameters_options=QueryParameterOptions()): """ Prepare a query object based on the provided query dictionary and various fields. NOTE: BE VERY CAREFUL WITH THIS CALL, as it allows creating queries that span across companies. :param parameters_options: Specifies options for parsing the parameters (see ParametersOptions) :param parameters: Query dictionary (relevant keys are these specified by the various field names parameters). Supported parameters: - <field_name>: <value> Will query for items with this value in the field (see QueryParameterOptions for specific rules on handling values). Only items matching ALL of these conditions will be retrieved. - <any|all>: {fields: [<field1>, <field2>, ...], pattern: <pattern>} Will query for items where any or all provided fields match the provided pattern. :return: mongoengine.Q query object """ parameters_options = parameters_options or cls.get_all_query_options dict_query = {} query = RegexQ() if parameters: parameters = { k: cls._get_fixed_field_value(k, v) for k, v in parameters.items() } opts = parameters_options for field in opts.pattern_fields: pattern = parameters.pop(field, None) if pattern: dict_query[field] = RegexWrapper(pattern) for field, data in cls._pop_matching_params( patterns=opts.list_fields, parameters=parameters).items(): query &= cls.get_list_field_query(field, data) for field, data in cls._pop_matching_params( patterns=opts.range_fields, parameters=parameters).items(): query &= cls.get_range_field_query(field, data) for field in opts.fields or []: data = parameters.pop(field, None) if data is not None: dict_query[field] = data for field in opts.datetime_fields or []: data = parameters.pop(field, None) if data is not None: if not isinstance(data, list): data = [data] for d in data: # type: str m = ACCESS_REGEX.match(d) if not m: continue try: value = parse_datetime(m.group("value")) prefix = m.group("prefix") modifier = ACCESS_MODIFIER.get(prefix) f = field if not modifier else "__".join( (field, modifier)) dict_query[f] = value except (ValueError, OverflowError): pass for field, value in parameters.items(): for keys, func in cls._multi_field_param_prefix.items(): if field not in keys: continue try: data = cls.MultiFieldParameters(**value) except Exception: raise MakeGetAllQueryError("incorrect field format", field) if not data.fields: break if any("._" in f for f in data.fields): q = reduce( lambda a, x: func( a, Q(__raw__={ x: { "$regex": data.pattern, "$options": "i" } })), data.fields, Q()) else: regex = RegexWrapper(data.pattern, flags=re.IGNORECASE) sep_fields = [ f.replace(".", "__") for f in data.fields ] q = reduce(lambda a, x: func(a, RegexQ(**{x: regex})), sep_fields, RegexQ()) query = query & q return query & RegexQ(**dict_query)