Ejemplo n.º 1
0
    def _parse_sort_keys(self, params):
        sort_keys_dirs = []
        sort = params.get('sort')
        sort_keys = params.get('sort_key')
        if sort:
            for sort_dir_key in sort.split(","):
                comps = sort_dir_key.split(":")
                if len(comps) == 1:  # Use default sort order
                    sort_keys_dirs.append((comps[0], self.sort_dir))
                elif len(comps) == 2:
                    sort_keys_dirs.append(
                        (comps[0], self._validate_sort_dir(comps[1])))
                else:
                    raise exceptions.InvalidSortKey(key=comps)
        elif sort_keys:
            sort_keys = sort_keys.split(',')
            sort_dirs = params.get('sort_dir')
            if not sort_dirs:
                sort_dirs = [self.sort_dir] * len(sort_keys)
            else:
                sort_dirs = sort_dirs.split(',')

            if len(sort_dirs) < len(sort_keys):
                sort_dirs += [self.sort_dir
                              ] * (len(sort_keys) - len(sort_dirs))
            for sk, sd in zip(sort_keys, sort_dirs):
                sort_keys_dirs.append((sk, self._validate_sort_dir(sd)))

        return sort_keys_dirs
Ejemplo n.º 2
0
    def apply(self, query, model, enforce_valid_params=True):
        """Returns a query with sorting / pagination criteria added.

        Pagination works by requiring a unique sort_key specified by sort_keys.
        (If sort_keys is not unique, then we risk looping through values.)
        We use the last row in the previous page as the pagination 'marker'.
        So we must return values that follow the passed marker in the order.
        With a single-valued sort_key, this would be easy: sort_key > X.
        With a compound-values sort_key, (k1, k2, k3) we must do this to repeat
        the lexicographical ordering:
        (k1 > X1) or (k1 == X1 && k2 > X2) or (k1 == X1 && k2 == X2 && k3 > X3)
        We also have to cope with different sort_directions.
        Typically, the id of the last row is used as the client-facing
        pagination marker, then the actual marker object must be fetched from
        the db and passed in to us as marker.
        :param query: the query object to which we should add
        paging/sorting/filtering
        :param model: the ORM model class
        :param enforce_valid_params: check for invalid enteries in self.params

        :rtype: sqlalchemy.orm.query.Query
        :returns: The query with sorting/pagination/filtering added.
        """

        # Add filtering
        if CONF.api_settings.allow_filtering:
            # Exclude (valid) arguments that are not used for data filtering
            filter_params = {
                k: v
                for k, v in self.params.items()
                if k not in self._auxiliary_arguments
            }

            secondary_query_filter = filter_params.pop(
                "project_id", None) if (model == models.Amphora) else None

            # Tranlate arguments from API standard to data model's field name
            filter_params = (model.__v2_wsme__.
                             translate_dict_keys_to_data_model(filter_params))
            if 'loadbalancer_id' in filter_params:
                filter_params['load_balancer_id'] = filter_params.pop(
                    'loadbalancer_id')

            # Apply tags filtering for the models which support tags.
            query = self._apply_tags_filtering(filter_params, model, query)

            # Drop invalid arguments
            self.filters = {
                k: v
                for (k, v) in filter_params.items()
                if k in vars(model.__data_model__())
            }

            if enforce_valid_params and (len(self.filters) <
                                         len(filter_params)):
                raise exceptions.InvalidFilterArgument()

            query = model.apply_filter(query, model, self.filters)
            if secondary_query_filter is not None:
                query = query.filter(
                    model.load_balancer.has(project_id=secondary_query_filter))

        # Add sorting
        if CONF.api_settings.allow_sorting:
            # Add default sort keys (if they are OK for the model)
            keys_only = [k[0] for k in self.sort_keys]
            for key in constants.DEFAULT_SORT_KEYS:
                if key not in keys_only and hasattr(model, key):
                    self.sort_keys.append((key, self.sort_dir))

            for current_sort_key, current_sort_dir in self.sort_keys:
                sort_dir_func = {
                    constants.ASC: sqlalchemy.asc,
                    constants.DESC: sqlalchemy.desc,
                }[current_sort_dir]

                try:
                    sort_key_attr = getattr(model, current_sort_key)
                except AttributeError:
                    raise exceptions.InvalidSortKey(key=current_sort_key)
                query = query.order_by(sort_dir_func(sort_key_attr))

        # Add pagination
        if CONF.api_settings.allow_pagination:
            default = ''  # Default to an empty string if NULL
            if self.marker is not None:
                marker_object = self._parse_marker(query.session, model)
                if not marker_object:
                    raise exceptions.InvalidMarker(key=self.marker)
                marker_values = []
                for sort_key, _ in self.sort_keys:
                    v = getattr(marker_object, sort_key)
                    if v is None:
                        v = default
                    marker_values.append(v)

                # Build up an array of sort criteria as in the docstring
                criteria_list = []
                for i in range(len(self.sort_keys)):
                    crit_attrs = []
                    for j in range(i):
                        model_attr = getattr(model, self.sort_keys[j][0])
                        default = PaginationHelper._get_default_column_value(
                            model_attr.property.columns[0].type)
                        attr = sa_sql.expression.case(
                            [
                                (
                                    model_attr != None,  # noqa: E711 # pylint: disable=singleton-comparison
                                    model_attr),
                            ],
                            else_=default)
                        crit_attrs.append((attr == marker_values[j]))

                    model_attr = getattr(model, self.sort_keys[i][0])
                    default = PaginationHelper._get_default_column_value(
                        model_attr.property.columns[0].type)
                    attr = sa_sql.expression.case(
                        [
                            (
                                model_attr != None,  # noqa: E711 # pylint: disable=singleton-comparison
                                model_attr),
                        ],
                        else_=default)
                    this_sort_dir = self.sort_keys[i][1]
                    if this_sort_dir == constants.DESC:
                        if self.page_reverse == "True":
                            crit_attrs.append((attr > marker_values[i]))
                        else:
                            crit_attrs.append((attr < marker_values[i]))
                    elif this_sort_dir == constants.ASC:
                        if self.page_reverse == "True":
                            crit_attrs.append((attr < marker_values[i]))
                        else:
                            crit_attrs.append((attr > marker_values[i]))
                    else:
                        raise exceptions.InvalidSortDirection(
                            key=this_sort_dir)

                    criteria = sa_sql.and_(*crit_attrs)
                    criteria_list.append(criteria)

                f = sa_sql.or_(*criteria_list)
                query = query.filter(f)

            if self.limit is not None:
                query = query.limit(self.limit)

        model_list = query.all()

        links = None
        if CONF.api_settings.allow_pagination:
            links = self._make_links(model_list)

        return model_list, links
Ejemplo n.º 3
0
    def apply(self, query, model):
        """Returns a query with sorting / pagination criteria added.

        Pagination works by requiring a unique sort_key specified by sort_keys.
        (If sort_keys is not unique, then we risk looping through values.)
        We use the last row in the previous page as the pagination 'marker'.
        So we must return values that follow the passed marker in the order.
        With a single-valued sort_key, this would be easy: sort_key > X.
        With a compound-values sort_key, (k1, k2, k3) we must do this to repeat
        the lexicographical ordering:
        (k1 > X1) or (k1 == X1 && k2 > X2) or (k1 == X1 && k2 == X2 && k3 > X3)
        We also have to cope with different sort_directions.
        Typically, the id of the last row is used as the client-facing
        pagination marker, then the actual marker object must be fetched from
        the db and passed in to us as marker.
        :param query: the query object to which we should add
        paging/sorting/filtering
        :param model: the ORM model class

        :rtype: sqlalchemy.orm.query.Query
        :returns: The query with sorting/pagination/filtering added.
        """

        # Add filtering
        if CONF.api_settings.allow_filtering:
            filter_attrs = [
                attr for attr in dir(model.__v2_wsme__)
                if not callable(getattr(model.__v2_wsme__, attr))
                and not attr.startswith("_")
            ]
            self.filters = {
                k: v
                for (k, v) in self.params.items() if k in filter_attrs
            }

            query = model.apply_filter(query, model, self.filters)

        # Add sorting
        if CONF.api_settings.allow_sorting:
            # Add default sort keys (if they are OK for the model)
            keys_only = [k[0] for k in self.sort_keys]
            for key in constants.DEFAULT_SORT_KEYS:
                if key not in keys_only and hasattr(model, key):
                    self.sort_keys.append((key, self.sort_dir))

            for current_sort_key, current_sort_dir in self.sort_keys:
                sort_dir_func = {
                    constants.ASC: sqlalchemy.asc,
                    constants.DESC: sqlalchemy.desc,
                }[current_sort_dir]

                try:
                    sort_key_attr = getattr(model, current_sort_key)
                except AttributeError:
                    raise exceptions.InvalidSortKey(key=current_sort_key)
                query = query.order_by(sort_dir_func(sort_key_attr))

        # Add pagination
        if CONF.api_settings.allow_pagination:
            default = ''  # Default to an empty string if NULL
            if self.marker is not None:
                marker_object = self._parse_marker(query.session, model)
                if not marker_object:
                    raise exceptions.InvalidMarker(key=self.marker)
                marker_values = []
                for sort_key, _ in self.sort_keys:
                    v = getattr(marker_object, sort_key)
                    if v is None:
                        v = default
                    marker_values.append(v)

                # Build up an array of sort criteria as in the docstring
                criteria_list = []
                for i in range(len(self.sort_keys)):
                    crit_attrs = []
                    for j in range(i):
                        model_attr = getattr(model, self.sort_keys[j][0])
                        default = PaginationHelper._get_default_column_value(
                            model_attr.property.columns[0].type)
                        attr = sa_sql.expression.case(
                            [
                                (
                                    model_attr != None,  # noqa: E711
                                    model_attr),
                            ],
                            else_=default)
                        crit_attrs.append((attr == marker_values[j]))

                    model_attr = getattr(model, self.sort_keys[i][0])
                    default = PaginationHelper._get_default_column_value(
                        model_attr.property.columns[0].type)
                    attr = sa_sql.expression.case(
                        [
                            (
                                model_attr != None,  # noqa: E711
                                model_attr),
                        ],
                        else_=default)
                    this_sort_dir = self.sort_keys[i][1]
                    if this_sort_dir == constants.DESC:
                        crit_attrs.append((attr < marker_values[i]))
                    elif this_sort_dir == constants.ASC:
                        crit_attrs.append((attr > marker_values[i]))
                    else:
                        raise exceptions.InvalidSortDirection(
                            key=this_sort_dir)

                    criteria = sa_sql.and_(*crit_attrs)
                    criteria_list.append(criteria)

                f = sa_sql.or_(*criteria_list)
                query = query.filter(f)

            if self.limit is not None:
                query = query.limit(self.limit)

        model_list = query.all()

        links = None
        if CONF.api_settings.allow_pagination:
            links = self._make_links(model_list)

        return model_list, links