Example #1
0
def _get_limit_param(request, max_limit):
    """Extract integer limit from request or fail."""
    try:
        limit = int(request.GET.get('limit', 0))
        if limit >= 0:
            return limit
    except ValueError:
        pass
    msg = _("Limit must be an integer 0 or greater and not '%d'")
    raise exceptions.BadRequest(resource='limit', msg=msg)
Example #2
0
    def update(self, request, id, body=None, **kwargs):
        """Updates the specified entity's attributes."""
        parent_id = kwargs.get(self._parent_id_name)
        try:
            payload = body.copy()
        except AttributeError:
            msg = _("Invalid format: %s") % request.body
            raise exceptions.BadRequest(resource='body', msg=msg)
        payload['id'] = id
        self._notifier.info(request.context, self._resource + '.update.start',
                            payload)
        body = Controller.prepare_request_body(request.context,
                                               body,
                                               False,
                                               self._resource,
                                               self._attr_info,
                                               allow_bulk=self._allow_bulk)
        action = self._plugin_handlers[self.UPDATE]
        # Load object to check authz
        # but pass only attributes in the original body and required
        # by the policy engine to the policy 'brain'
        field_list = [
            name for (name, value) in (self._attr_info).items()
            if (value.get('required_by_policy') or value.get('primary_key')
                or 'default' not in value)
        ]
        # Ensure policy engine is initialized
        policy.init()
        orig_obj = self._item(request,
                              id,
                              field_list=field_list,
                              parent_id=parent_id)
        orig_obj.update(body[self._resource])
        attribs = attributes.ATTRIBUTES_TO_UPDATE
        orig_obj[attribs] = body[self._resource].keys()
        try:
            policy.enforce(request.context, action, orig_obj)
        except exceptions.PolicyNotAuthorized:
            # To avoid giving away information, pretend that it
            # doesn't exist
            msg = _('The resource could not be found.')
            raise webob.exc.HTTPNotFound(msg)

        obj_updater = getattr(self._plugin, action)
        kwargs = {self._resource: body}
        if parent_id:
            kwargs[self._parent_id_name] = parent_id
        obj = obj_updater(request.context, id, **kwargs)
        result = {self._resource: self._view(request.context, obj)}
        notifier_method = self._resource + '.update.end'
        self._notifier.info(request.context, notifier_method, result)
        return result
Example #3
0
def paginate_query(query, model, limit, sorts, marker_obj=None):
    """Returns a query with sorting / pagination criteria added.

    Pagination works by requiring a unique sort key, specified by sorts.
    (If sort keys is not unique, then we risk looping through values.)
    We use the last row in the previous page as the 'marker' for pagination.
    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)
    The reason of didn't use OFFSET clause was it don't scale, please refer
    discussion at https://lists.launchpad.net/openstack/msg02547.html

    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
    :param model: the ORM model class
    :param limit: maximum number of items to return
    :param sorts: array of attributes and direction by which results should
                 be sorted
    :param marker: the last item of the previous page; we returns the next
                    results after this value.
    :rtype: sqlalchemy.orm.query.Query
    :return: The query with sorting/pagination added.
    """
    if not sorts:
        return query

    # A primary key must be specified in sort keys
    assert not (limit and len(
        set(dict(sorts).keys())
        & set(model.__table__.primary_key.columns.keys())) == 0)

    # Add sorting
    for sort_key, sort_direction in sorts:
        sort_dir_func = sqlalchemy.asc if sort_direction else sqlalchemy.desc
        try:
            sort_key_attr = getattr(model, sort_key)
        except AttributeError:
            # Extension attribute doesn't support for sorting. Because it
            # existed in attr_info, it will be catched at here
            msg = _("%s is invalid attribute for sort_key") % sort_key
            raise n_exc.BadRequest(resource=model.__tablename__, msg=msg)
        if isinstance(sort_key_attr.property, RelationshipProperty):
            msg = _("The attribute '%(attr)s' is reference to other "
                    "resource, can't used by sort "
                    "'%(resource)s'") % {
                        'attr': sort_key,
                        'resource': model.__tablename__
                    }
            raise n_exc.BadRequest(resource=model.__tablename__, msg=msg)
        query = query.order_by(sort_dir_func(sort_key_attr))

    # Add pagination
    if marker_obj:
        marker_values = [getattr(marker_obj, sort[0]) for sort in sorts]

        # Build up an array of sort criteria as in the docstring
        criteria_list = []
        for i, sort in enumerate(sorts):
            crit_attrs = [(getattr(model, sorts[j][0]) == marker_values[j])
                          for j in moves.xrange(i)]
            model_attr = getattr(model, sort[0])
            if sort[1]:
                crit_attrs.append((model_attr > marker_values[i]))
            else:
                crit_attrs.append((model_attr < marker_values[i]))

            criteria = sqlalchemy.sql.and_(*crit_attrs)
            criteria_list.append(criteria)

        f = sqlalchemy.sql.or_(*criteria_list)
        query = query.filter(f)

    if limit:
        query = query.limit(limit)

    return query