Exemplo n.º 1
0
    def op(self):
        """Return the operator of the node

        :return str: the operator to use in the filter
        """
        try:
            return self.filter_['op']
        except KeyError:
            raise InvalidFilters("Can't find op of a filter")
Exemplo n.º 2
0
    def value(self):
        """Get the value to filter on

        :return: the value to filter on
        """
        if self.filter_.get('field') is not None:
            try:
                result = getattr(self.model, self.filter_['field'])
            except AttributeError:
                raise InvalidFilters("{} has no attribute {}".format(
                    self.model.__name__, self.filter_['field']))
            else:
                return result
        else:
            if 'val' not in self.filter_:
                raise InvalidFilters("Can't find value or field in a filter")

            return self.filter_['val']
Exemplo n.º 3
0
    def name(self):
        """Return the name of the node or raise a BadRequest exception

        :return str: the name of the field to filter on
        """
        name = self.filter_.get('name')

        if name is None:
            raise InvalidFilters("Can't find name of a filter")

        if SPLIT_REL in name:
            name = name.split(SPLIT_REL)[0]

        if name not in self.schema._declared_fields:
            raise InvalidFilters("{} has no attribute {}".format(
                self.schema.__name__, name))

        return name
Exemplo n.º 4
0
    def column(self):
        """Get the column object
        """
        field = self.name

        model_field = get_model_field(self.schema, field)

        try:
            return getattr(self.model, model_field)
        except AttributeError:
            raise InvalidFilters("{} has no attribute {}".format(
                self.model.__name__, model_field))
Exemplo n.º 5
0
    def related_schema(self):
        """Get the related schema of a relationship field

        :return Schema: the related schema
        """
        relationship_field = self.name

        if relationship_field not in get_relationships(self.schema):
            raise InvalidFilters("{} has no relationship attribute {}".format(
                self.schema.__name__, relationship_field))

        return self.schema._declared_fields[
            relationship_field].schema.__class__
Exemplo n.º 6
0
    def operator(self):
        """Get the function operator from his name

        :return callable: a callable to make operation on a column
        """
        operators = (self.op, self.op + '_', '__' + self.op + '__')

        for op in operators:
            if hasattr(self.column, op):
                return op

        raise InvalidFilters("{} has no operator {}".format(
            self.column.key, self.op))
Exemplo n.º 7
0
    def related_model(self):
        """Get the related model of a relationship field

        :return DeclarativeMeta: the related model
        """
        relationship_field = self.name

        if relationship_field not in get_relationships(self.schema):
            raise InvalidFilters("{} has no relationship attribute {}".format(
                self.schema.__name__, relationship_field))

        return getattr(self.model,
                       get_model_field(
                           self.schema,
                           relationship_field)).property.mapper.class_
Exemplo n.º 8
0
def deserialize_field(marshmallow_field: fields.Field, value: Any) -> Any:
    """
    Deserialize filter/sort value
    :param marshmallow_field: marshmallow field type
    :param value: filter/sort value
    :return:
    """
    try:
        if isinstance(value, list) and type(marshmallow_field) in STANDARD_MARSHMALLOW_FIELDS:
            return [marshmallow_field.deserialize(i_value) for i_value in value]
        if not isinstance(value, list) and isinstance(marshmallow_field, fields.List):
            return marshmallow_field.deserialize([value])
        return marshmallow_field.deserialize(value)
    except ValidationError:
        raise InvalidFilters(f'Bad filter value: {value!r}')
Exemplo n.º 9
0
    def filters(self):
        """Return filters from query string.

        :return list: filter information
        """
        results = []
        filters = self.qs.get('filter')
        if filters is not None:
            try:
                results.extend(json.loads(filters))
            except (ValueError, TypeError):
                raise InvalidFilters("Parse error")
        if self._get_key_values('filter['):
            results.extend(self._simple_filters(self._get_key_values('filter[')))
        return results
Exemplo n.º 10
0
    def column(self):
        """Get the column object

        :param DeclarativeMeta model: the model
        :param str field: the field
        :return InstrumentedAttribute: the column to filter on
        """
        field = self.name

        model_field = get_model_field(self.schema, field)

        try:
            return getattr(self.model, model_field)
        except AttributeError:
            raise InvalidFilters("{} has no attribute {}".format(
                self.model.__name__, model_field))
Exemplo n.º 11
0
 def _isinstance_jsonb(cls, schema: Schema, filter_name: str) -> bool:
     """
     Определяем относится ли фильтр к relationship или к полю JSONB
     :param schema:
     :param filter_name:
     :return:
     """
     fields = filter_name.split(SPLIT_REL)
     for i, i_field in enumerate(fields):
         if isinstance(
                 getattr(schema._declared_fields[i_field], "schema", None),
                 SchemaJSONB):
             if i == (len(fields) - 1):
                 raise InvalidFilters(
                     f"Invalid JSONB filter: {filter_name}")
             return True
         elif isinstance(schema._declared_fields[i_field], Relationship):
             schema = schema._declared_fields[i_field].schema
         else:
             return False
Exemplo n.º 12
0
    def _create_filter(self, self_nested: Any, marshmallow_field, model_column,
                       operator, value):
        """
        Create sqlalchemy filter
        :param Nested self_nested:
        :param marshmallow_field:
        :param model_column: column sqlalchemy
        :param operator:
        :param value:
        :return:
        """
        fields = self_nested.filter_["name"].split(SPLIT_REL)
        field_in_jsonb = fields[-1]
        schema = getattr(marshmallow_field, "schema", None)
        if isinstance(marshmallow_field, Relationship):
            # If filtering by JSONB field of another model is in progress
            mapper = model_column.mapper.class_
            sqlalchemy_relationship_name = get_model_field(schema, fields[1])
            self_nested.filter_["name"] = SPLIT_REL.join(fields[1:])
            marshmallow_field = marshmallow_field.schema._declared_fields[
                fields[1]]
            join_list = [[model_column]]
            model_column = getattr(mapper, sqlalchemy_relationship_name)
            filter, joins = self._create_filter(self_nested, marshmallow_field,
                                                model_column, operator, value)
            join_list += joins
            return filter, join_list
        elif not isinstance(schema, SchemaJSONB):
            raise InvalidFilters(
                f"Invalid JSONB filter: {SPLIT_REL.join(field_in_jsonb)}")
        self_nested.filter_["name"] = SPLIT_REL.join(fields[:-1])
        try:
            for field in fields[1:]:
                marshmallow_field = marshmallow_field.schema._declared_fields[
                    field]
        except KeyError as e:
            raise InvalidFilters(
                f'There is no "{e}" attribute in the "{fields[0]}" field.')
        if hasattr(marshmallow_field, f"_{operator}_sql_filter_"):
            """
            У marshmallow field может быть реализована своя логика создания фильтра для sqlalchemy
            для определённого оператора. Чтобы реализовать свою логику создания фильтра для определённого оператора
            необходимо реализовать в классе поля методы (название метода строится по следующему принципу
            `_<тип оператора>_sql_filter_`). Также такой метод должен принимать ряд параметров
            * marshmallow_field - объект класса поля marshmallow
            * model_column - объект класса поля sqlalchemy
            * value - значения для фильтра
            * operator - сам оператор, например: "eq", "in"...
            """
            for field in fields[1:-1]:
                model_column = model_column.op("->")(field)
            model_column = model_column.op("->>")(field_in_jsonb)
            return (
                getattr(marshmallow_field, f"_{operator}_sql_filter_")(
                    marshmallow_field=marshmallow_field,
                    model_column=model_column,
                    value=value,
                    operator=self_nested.operator,
                ),
                [],
            )

        # Нужно проводить валидацию и делать десериализацию значение указанных в фильтре, так как поля Enum
        # например выгружаются как 'name_value(str)', а в БД хранится как просто число
        value = deserialize_field(marshmallow_field, value)

        property_type = self.get_property_type(
            marshmallow_field=marshmallow_field, schema=self_nested.schema)
        for field in fields[1:-1]:
            model_column = model_column.op("->")(field)
        extra_field = model_column.op("->>")(field_in_jsonb)
        filter_ = ""

        if property_type in {bool, int, str, bytes, Decimal}:
            field = cast(extra_field,
                         self.mapping_type_to_sql_type[property_type])
            if value is None:
                filter_ = field.is_(None)
            else:
                filter_ = getattr(field, self_nested.operator)(value)

        elif property_type == list:
            filter_ = model_column.op("->")(field_in_jsonb).op("?")(
                value[0] if is_seq_collection(value) else value)
            if operator in ["notin", "notin_"]:
                filter_ = not_(filter_)

        return filter_, []
Exemplo n.º 13
0
    def _create_sort(self, self_nested: Any, marshmallow_field, model_column,
                     order):
        """
        Create sqlalchemy sort
        :param Nested self_nested:
        :param marshmallow_field:
        :param model_column: column sqlalchemy
        :param str order: asc | desc
        :return:
        """
        fields = self_nested.sort_["field"].split(SPLIT_REL)
        schema = getattr(marshmallow_field, "schema", None)
        if isinstance(marshmallow_field, Relationship):
            # If sorting by JSONB field of another model is in progress
            mapper = model_column.mapper.class_
            sqlalchemy_relationship_name = get_model_field(schema, fields[1])
            self_nested.sort_["field"] = SPLIT_REL.join(fields[1:])
            marshmallow_field = marshmallow_field.schema._declared_fields[
                fields[1]]
            model_column = getattr(mapper, sqlalchemy_relationship_name)
            return self._create_sort(self_nested, marshmallow_field,
                                     model_column, order)
        elif not isinstance(schema, SchemaJSONB):
            raise InvalidFilters(
                f"Invalid JSONB sort: {SPLIT_REL.join(self_nested.fields)}")
        self_nested.sort_["field"] = SPLIT_REL.join(fields[:-1])
        field_in_jsonb = fields[-1]

        try:
            for field in fields[1:]:
                marshmallow_field = marshmallow_field.schema._declared_fields[
                    field]
        except KeyError as e:
            raise InvalidFilters(
                f'There is no "{e}" attribute in the "{fields[0]}" field.')

        if hasattr(marshmallow_field, f"_{order}_sql_sort_"):
            """
            У marshmallow field может быть реализована своя логика создания сортировки для sqlalchemy
            для определённого типа ('asc', 'desc'). Чтобы реализовать свою логику создания сортировка для
            определённого оператора необходимо реализовать в классе поля методы (название метода строится по
            следующему принципу `_<тип сортировки>_sql_filter_`). Также такой метод должен принимать ряд параметров
            * marshmallow_field - объект класса поля marshmallow
            * model_column - объект класса поля sqlalchemy
            """
            # All values between the first and last field will be the path to the desired value by which to sort,
            # so we write the path through "->"
            for field in fields[1:-1]:
                model_column = model_column.op("->")(field)
            model_column = model_column.op("->>")(field_in_jsonb)
            return getattr(marshmallow_field, f"_{order}_sql_sort_")(
                marshmallow_field=marshmallow_field, model_column=model_column)

        property_type = self.get_property_type(
            marshmallow_field=marshmallow_field, schema=self_nested.schema)

        for field in fields[1:-1]:
            model_column = model_column.op("->")(field)
        extra_field = model_column.op("->>")(field_in_jsonb)
        sort = ""
        order_op = desc_op if order == "desc" else asc_op
        if property_type in self.mapping_type_to_sql_type:
            if sqlalchemy.__version__ >= "1.1":
                sort = order_op(
                    extra_field.astext.cast(
                        self.mapping_type_to_sql_type[property_type]))
            else:
                sort = order_op(
                    extra_field.cast(
                        self.mapping_type_to_sql_type[property_type]))
        return sort