Skip to content

Commit

Permalink
Merge branch 'feature/pagination'
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelkuty committed Nov 14, 2015
2 parents 5b2d8f5 + 84ab989 commit 6ffbe2d
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 44 deletions.
60 changes: 60 additions & 0 deletions docs/source/main/tables.rst
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,66 @@ and then `views.py`
table_class = PaginatedModelTable
PaginatedApiTable
-----------------

Table which implements standard Django Rest Framework pagination style.

You can declare manager if you use ``PaginatedManager`` class from ``horizon_contrib.api`` or just implement ``get_page_data`` method.

.. code-block:: python
from horizon_contrib.tables import PaginatedApiTable
class MyApiTable(PaginatedApiTable):
manager = api.helpdesk.tickets
def get_page_data(self, page=1):
"""returns data for specific page
"""
self._paginator = self.manager.list(
self.request,
search_opts={'page': page})
return self._paginator
and then `views.py`

.. code-block:: python
from horizon_contrib.tables.views import PaginatedView
from .tables import PaginatedApiTable
class IndexView(PaginatedView):
table_class = PaginatedApiTable
If you want loading data in view override ``get_data`` method.

.. code-block:: python
from horizon_contrib.tables.views import PaginatedView
from .tables import PaginatedApiTable
class IndexView(PaginatedView):
table_class = PaginatedApiTable
def get_data(self):
objects = []
table = self.get_table()
page = self.request.GET.get('page', 1)
if table:
try:
objects = helpdesk.tickets.closed(
self.request, search_opts={'page': page})
except Exception as e:
raise e
else:
table._paginator = objects
return objects
LinkedListColumn
----------------

Expand Down
3 changes: 2 additions & 1 deletion horizon_contrib/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from horizon_contrib.api.base import ClientBase
from horizon_contrib.api.managers import Manager
from horizon_contrib.api.pagination import PaginationClient
from horizon_contrib.api.managers import Manager, PaginatedManager
from horizon_contrib.api.models import APIModel
50 changes: 32 additions & 18 deletions horizon_contrib/api/base.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@



import json
import logging

import requests
from requests import exceptions
from django.conf import settings
from horizon import messages
from horizon_contrib.utils import to_dotdict
from .response import ListResponse, DictResponse

LOG = logging.getLogger("client.base")

Expand All @@ -25,6 +23,9 @@ class ClientBase(object):
"""

list_response_class = ListResponse
dict_response_class = DictResponse

def do_request(self, path, method="GET", params={}, headers={}):
'''make raw request'''

Expand Down Expand Up @@ -63,7 +64,7 @@ def process_response(self, response, request):
messages.error(request, msg)
if settings.DEBUG:
LOG.exception(msg)
return to_dotdict(result)
return result
else:
if response.status_code == 401:
raise exceptions.HTTPError('Unautorized 401')
Expand All @@ -76,7 +77,13 @@ def process_response(self, response, request):

def process_data(self, result, request):
'''process result and returns data'''
return result

if isinstance(result, list):
response = self.list_response_class(result)
elif isinstance(result, dict):
response = self.dict_response_class(result)

return response

def process_headers(self, headers, request):
'''process headers for example add auth headers'''
Expand All @@ -94,6 +101,10 @@ def process_url(self, url, request):
'''process url'''
return url

def process_exception(self, exception, request, response):
'''process exception'''
raise exception

def request(self, path, method="GET", params={}, request={}, headers={}):
"""main method which provide
Expand Down Expand Up @@ -123,19 +134,22 @@ def request(self, path, method="GET", params={}, request={}, headers={}):

LOG.debug("%s - %s%s - %s" % (method, self.api, path, params))

# do request
response = self.do_request(
self.process_url('%s%s' % (self.api, path), _request),
self.process_method(method, _request),
self.process_params(params, _request),
self.process_headers(headers, _request))

# process response
result = self.process_response(response, _request)
# process data
data = self.process_data(result, _request)

return data
try:
# do request
response = self.do_request(
self.process_url('%s%s' % (self.api, path), _request),
self.process_method(method, _request),
self.process_params(params, _request),
self.process_headers(headers, _request))

# process response
result = self.process_response(response, _request)
# process data
data = self.process_data(result, _request)
except Exception as e:
self.process_exception(e, _request, response)
else:
return data

def set_api(self):
self.api = '%s://%s:%s%s' % (
Expand Down
39 changes: 34 additions & 5 deletions horizon_contrib/api/managers.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
# -*- coding: UTF-8 -*-

import operator
from horizon_contrib.api.base import ClientBase
from urllib import urlencode
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon_contrib.api.base import ClientBase

from .pagination import PaginationClient


class SearchOptionsMixin(object):
Expand All @@ -18,15 +23,21 @@ def get_query_string(self, search_opts):
'''returns query string from search options'''
return urlencode(search_opts)

def get_url(self, id=None, search_opts={}):
def get_url(self, id=None, search_opts={}, url='/{scope}/'):
query = self.get_query_string(search_opts)
attrs = {
'scope': self.scope
}
if not id:
base_url = '/%s/' % self.scope
base_url = url.format(**attrs)
else:
base_url = '/{0}/{1}/'.format(self.scope, id)
attrs['id'] = id
id_url = url + '{id}/'
base_url = id_url.format(**attrs)
if query:
return '?'.join([base_url,
query])
query])

return base_url


Expand Down Expand Up @@ -109,3 +120,21 @@ def order_by(self, *args, **kwargs):

def filter(self, *args, **kwargs):
raise NotImplementedError

def process_exception(self, exception, request, response):
'''handle exceptions during standard API calls'''
if settings.DEBUG:
# developers info
try:
errors = response.json()
except:
exceptions.handle(request, str(exception))
else:
exceptions.handle(request, str(errors))
# user friendly info
exceptions.handle(request, _('Unable to load %s') % self.scope.title())


class PaginatedManager(PaginationClient, Manager):

pass
2 changes: 0 additions & 2 deletions horizon_contrib/api/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@



import copy

import six
Expand Down
67 changes: 67 additions & 0 deletions horizon_contrib/api/pagination.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@


import logging
from math import ceil

from horizon import exceptions
from django.utils import six
from django.conf import settings
from .base import ClientBase
from .response import ListResponse

LOG = logging.getLogger("client.base")

API_RESULT_PAGE_SIZE = getattr(settings, 'API_RESULT_PAGE_SIZE', 25)


class PaginatedListResponse(ListResponse):

'''Extend response with pagination helpers'''

def __init__(self, data, *args, **kwargs):
self._data = data
super(PaginatedListResponse, self).__init__(
data.get('results'), *args, **kwargs)

@property
def count(self):
return self._data.get('count')

@property
def previous(self):
return self._data.get('previous', None)

@property
def next(self):
return self._data.get('next', None)

@property
def page_range(self):
'''little ugly piece of pagination'''
page_size = float(API_RESULT_PAGE_SIZE)
count = float(self.count)
if ceil(count / page_size) > 0:
return six.moves.range(1, int(ceil(count / page_size)) + 1)
return six.moves.range(1, int(ceil(count / page_size)))


class PaginationClient(ClientBase):
'''Use pagination list reponse'''

list_response_class = PaginatedListResponse

def process_data(self, data, request):
'''optionaly extract results from response'''

# check if data are paginated
if 'results' in data:
response = self.list_response_class(data)
else:
response = data

if 'errors' in data:
if settings.DEBUG:
raise Exception(data['errors'])
exceptions.handle(request, ', '.join(data['errors']))

return response
17 changes: 17 additions & 0 deletions horizon_contrib/api/response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@


import logging

from horizon_contrib.utils.dotdict import dotdict

LOG = logging.getLogger("client.base")


class ListResponse(list):

pass


class DictResponse(dotdict):

pass
2 changes: 1 addition & 1 deletion horizon_contrib/tables/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
FilterAction, UpdateAction,
UpdateColumnAction)
from horizon_contrib.tables.base import (ModelTable, PaginatedModelTable,
PaginatedTable, ReactTable)
PaginatedTable, ReactTable, PaginatedApiTable)
from horizon_contrib.tables.views import IndexView, PaginatedView

from horizon_contrib.tables.columns import LinkedListColumn
Expand Down

0 comments on commit 6ffbe2d

Please sign in to comment.