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) """ 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 _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 = parameters.copy() opts = parameters_options for field in opts.pattern_fields: pattern = parameters.pop(field, None) if pattern: dict_query[field] = RegexWrapper(pattern) for field in tuple(opts.list_fields or ()): data = parameters.pop(field, None) if data: if not isinstance(data, (list, tuple)): raise MakeGetAllQueryError("expected list", field) exclude = [t for t in data if t.startswith("-")] include = list(set(data).difference(exclude)) mongoengine_field = field.replace(".", "__") if include: dict_query[f"{mongoengine_field}__in"] = include if exclude: dict_query[f"{mongoengine_field}__nin"] = [ t[1:] for t in exclude ] 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 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)