Example #1
0
    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
Example #2
0
    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: []})
        )
Example #3
0
    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}))
Example #4
0
    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)