diff mbox

[Branch,~linaro-validation/lava-server/trunk] Rev 345: Add scaffolding for server side pagination of tables

Message ID 20120214224810.6426.89935.launchpad@ackee.canonical.com
State Accepted
Headers show

Commit Message

Paul Larson Feb. 14, 2012, 10:48 p.m. UTC
Merge authors:
  Michael Hudson-Doyle (mwhudson)
  Zygmunt Krynicki (zkrynicki)
Related merge proposals:
  https://code.launchpad.net/~mwhudson/lava-server/queryset-backend/+merge/92564
  proposed by: Michael Hudson-Doyle (mwhudson)
  review: Approve - Zygmunt Krynicki (zkrynicki)
------------------------------------------------------------
revno: 345 [merge]
committer: Paul Larson <paul.larson@linaro.org>
branch nick: lava-server
timestamp: Tue 2012-02-14 14:46:51 -0800
message:
  Add scaffolding for server side pagination of tables
modified:
  lava/utils/data_tables/backends.py


--
lp:lava-server
https://code.launchpad.net/~linaro-validation/lava-server/trunk

You are subscribed to branch lp:lava-server.
To unsubscribe from this branch go to https://code.launchpad.net/~linaro-validation/lava-server/trunk/+edit-subscription
diff mbox

Patch

=== modified file 'lava/utils/data_tables/backends.py'
--- lava/utils/data_tables/backends.py	2012-01-18 21:15:42 +0000
+++ lava/utils/data_tables/backends.py	2012-02-10 19:47:56 +0000
@@ -17,6 +17,7 @@ 
 # along with LAVA Server.  If not, see <http://www.gnu.org/licenses/>.
 
 from django.core.exceptions import ImproperlyConfigured
+from django.db.models import Q
 
 from lava.utils.data_tables.interface import IBackend
 
@@ -63,10 +64,91 @@ 
         # TODO: Support regex search
         # TODO: Support per-column search
         # 2) Apply sorting
-        for column_index, order in reversed(euery.sorting_columns):
+        for column_index, order in reversed(query.sorting_columns):
             data.sort(key=lambda row: row[column_index], reverse=order == 'desc')
         # 3) Apply offset/limit
         data = data[query.iDisplayStart:query.iDisplayStart + query.iDisplayLength]
         # Remember the subset of the displayed data
         response['aaData'] = data
         return response
+
+
+class Column(object):
+    """
+    Column definition for the QuerySetBackend
+    """
+
+    def __init__(self, name, sort_expr, callback):
+        self.name = name
+        self.sort_expr = sort_expr
+        self.callback = callback
+
+
+class QuerySetBackend(_BackendBase):
+    """
+    Database backend for data tables.
+
+    Stores and processes/computes the data in the database. Needs a queryset
+    object and a mapping between colums and query values.
+    """
+
+    def __init__(self, queryset=None, queryset_cb=None, columns=None,
+                 searching_columns=None):
+        self.queryset = queryset
+        self.queryset_cb = queryset_cb
+        self.columns = columns
+        self.searching_columns = searching_columns
+        if not queryset and not queryset_cb:
+            raise ImproperlyConfigured(
+                "QuerySetBackend requires either queryset or queryset_cb")
+        if not columns:
+            raise ImproperlyConfigured(
+                "QuerySetBackend requires columns")
+
+    def process(self, query):
+        # Get the basic response structure
+        response = super(QuerySetBackend, self).process(query)
+        # Get a queryset object
+        if self.queryset:
+            queryset = self.queryset
+        else:
+            queryset = self.queryset_cb(query.request)
+        # 1) Apply search/filtering
+        if query.sSearch:
+            if query.bRegex:
+                raise NotImplementedError("Searching with regular expresions is not implemented")
+            else:
+                if self.searching_columns is None:
+                    raise NotImplementedError("Searching is not implemented")
+                terms = query.sSearch.split()
+                andQ = None
+                for term in terms:
+                    orQ = None
+                    for col in self.searching_columns:
+                        q = Q(**{col+"__icontains" : term})
+                        orQ = orQ | q if orQ else q
+                    andQ = andQ & orQ if andQ else orQ
+                response['iTotalRecords'] = queryset.count()
+                queryset = queryset.filter(andQ)
+                response['iTotalDisplayRecords'] = queryset.count()
+        else:
+            response['iTotalRecords'] = response['iTotalDisplayRecords'] = queryset.count()
+        # TODO: Support per-column search
+        # 2) Apply sorting
+        order_by = [
+            "{asc_desc}{column}".format(
+                asc_desc="-" if order == 'desc' else '',
+                column=self.columns[column_index].sort_expr)
+            for column_index, order in query.sorting_columns]
+        queryset = queryset.order_by(*order_by)
+        # 3) Apply offset/limit
+        queryset = queryset[query.iDisplayStart:query.iDisplayStart + query.iDisplayLength]
+        #  Compute the response
+        # Note, despite the 'aaData'' identifier we're
+        # returing aoData-typed result (array of objects)
+        # print queryset.values(*[column.filter_expr for column in self.columns])
+        response['aaData'] = [
+            dict([(column.name, column.callback(object))
+                  for column in self.columns])
+            for object in queryset]
+        return response