Skip to content
Snippets Groups Projects
Commit a87309e5 authored by Benjamin Bertrand's avatar Benjamin Bertrand
Browse files

Add pagination to API

Pagination information (next, prev, first, last) is included in the Link
HTTP header.
The total number of entries is provided in the X-Total-Count HTTP
header.
parent 40bf2f05
No related branches found
Tags v0.5.1
No related merge requests found
......@@ -35,8 +35,7 @@ def get_item_by_id_or_ics_id(id_):
@bp.route('/items')
@jwt_required
def get_items():
# TODO: add pagination
return get_generic_model(models.Item, request.args,
return get_generic_model(models.Item,
order_by=models.Item.created_at)
......@@ -133,13 +132,13 @@ def create_item_comment(id_):
@bp.route('/actions')
@jwt_required
def get_actions():
return get_generic_model(models.Action, request.args)
return get_generic_model(models.Action)
@bp.route('/manufacturers')
@jwt_required
def get_manufacturers():
return get_generic_model(models.Manufacturer, request.args)
return get_generic_model(models.Manufacturer)
@bp.route('/manufacturers', methods=['POST'])
......@@ -152,7 +151,7 @@ def create_manufacturer():
@bp.route('/models')
@jwt_required
def get_models():
return get_generic_model(models.Model, request.args)
return get_generic_model(models.Model)
@bp.route('/models', methods=['POST'])
......@@ -165,7 +164,7 @@ def create_model():
@bp.route('/locations')
@jwt_required
def get_locations():
return get_generic_model(models.Location, request.args)
return get_generic_model(models.Location)
@bp.route('/locations', methods=['POST'])
......@@ -178,7 +177,7 @@ def create_locations():
@bp.route('/status')
@jwt_required
def get_status():
return get_generic_model(models.Status, request.args)
return get_generic_model(models.Status)
@bp.route('/status', methods=['POST'])
......
......@@ -9,7 +9,7 @@ This module implements the network API.
:license: BSD 2-Clause, see LICENSE for more details.
"""
from flask import Blueprint, request
from flask import Blueprint
from flask_jwt_extended import jwt_required
from .. import models
from ..decorators import jwt_groups_accepted
......@@ -21,8 +21,7 @@ bp = Blueprint('network_api', __name__)
@bp.route('/scopes')
@jwt_required
def get_scopes():
# TODO: add pagination
return get_generic_model(models.NetworkScope, request.args,
return get_generic_model(models.NetworkScope,
order_by=models.NetworkScope.name)
......@@ -38,8 +37,7 @@ def create_scope():
@bp.route('/networks')
@jwt_required
def get_networks():
# TODO: add pagination
return get_generic_model(models.Network, request.args,
return get_generic_model(models.Network,
order_by=models.Network.address)
......@@ -55,8 +53,7 @@ def create_network():
@bp.route('/interfaces')
@jwt_required
def get_interfaces():
# TODO: add pagination
return get_generic_model(models.Interface, request.args,
return get_generic_model(models.Interface,
order_by=models.Interface.ip)
......@@ -71,7 +68,7 @@ def create_interface():
@bp.route('/macs')
@jwt_required
def get_macs():
return get_generic_model(models.Mac, request.args,
return get_generic_model(models.Mac,
order_by=models.Mac.address)
......
......@@ -23,7 +23,7 @@ bp = Blueprint('user_api', __name__)
@bp.route('/users')
@jwt_required
def get_users():
return get_generic_model(models.User, request.args,
return get_generic_model(models.User,
order_by=models.User.username)
......
......@@ -9,6 +9,7 @@ This module implements useful functions for the API.
:license: BSD 2-Clause, see LICENSE for more details.
"""
import urllib.parse
import sqlalchemy as sa
from flask import current_app, jsonify, request
from ..extensions import db
......@@ -23,20 +24,58 @@ def commit():
raise utils.CSEntryError(str(e), status_code=422)
def get_generic_model(model, args, order_by=None):
def build_pagination_header(pagination, base_url, **kwargs):
"""Return the X-Total-Count and Link header information
:param pagination: flask_sqlalchemy Pagination class instance
:param base_url: request base_url
:param kwargs: extra query string parameters (without page and per_page)
:returns: dict with X-Total-Count and Link keys
"""
header = {'X-Total-Count': pagination.total}
links = []
if pagination.page > 1:
params = urllib.parse.urlencode({'per_page': pagination.per_page,
'page': 1,
**kwargs})
links.append(f'<{base_url}?{params}>; rel="first"')
if pagination.has_prev:
params = urllib.parse.urlencode({'per_page': pagination.per_page,
'page': pagination.prev_num,
**kwargs})
links.append(f'<{base_url}?{params}>; rel="prev"')
if pagination.has_next:
params = urllib.parse.urlencode({'per_page': pagination.per_page,
'page': pagination.next_num,
**kwargs})
links.append(f'<{base_url}?{params}>; rel="next"')
if pagination.pages > pagination.page:
params = urllib.parse.urlencode({'per_page': pagination.per_page,
'page': pagination.pages,
**kwargs})
links.append(f'<{base_url}?{params}>; rel="last"')
if links:
header['Link'] = ', '.join(links)
return header
def get_generic_model(model, order_by=None):
"""Return data from model as json
:param model: model class
:param MultiDict args: args from the request
:param order_by: column to order the result by
:returns: data from model as json
"""
query = utils.get_query(model.query, request.args)
kwargs = request.args.to_dict()
page = int(kwargs.pop('page', 1))
per_page = int(kwargs.pop('per_page', 20))
query = utils.get_query(model.query, **kwargs)
if order_by is None:
order_by = getattr(model, 'name')
instances = query.order_by(order_by)
data = [instance.to_dict() for instance in instances]
return jsonify(data)
pagination = query.order_by(order_by).paginate(page, per_page)
data = [item.to_dict() for item in pagination.items]
header = build_pagination_header(pagination, request.base_url, **kwargs)
return jsonify(data), 200, header
def create_generic_model(model, mandatory_fields=('name',), **kwargs):
......
......@@ -135,15 +135,14 @@ def get_model_choices(model, allow_none=False, attr='name', query=None):
return choices
def get_query(query, args):
def get_query(query, **kwargs):
"""Retrieve the query from the arguments
:param query: sqlalchemy base query
:param MultiDict args: args from a request
:param kwargs: kwargs from a request
:returns: query filtered by the arguments
"""
if args:
kwargs = args.to_dict()
if kwargs:
try:
query = query.filter_by(**kwargs)
except (sa.exc.InvalidRequestError, AttributeError) as e:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment