From patchwork Tue Feb 14 22:48:10 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Larson X-Patchwork-Id: 6777 Return-Path: X-Original-To: patchwork@peony.canonical.com Delivered-To: patchwork@peony.canonical.com Received: from fiordland.canonical.com (fiordland.canonical.com [91.189.94.145]) by peony.canonical.com (Postfix) with ESMTP id 7DD4D23EB0 for ; Tue, 14 Feb 2012 22:48:14 +0000 (UTC) Received: from mail-iy0-f180.google.com (mail-iy0-f180.google.com [209.85.210.180]) by fiordland.canonical.com (Postfix) with ESMTP id 244ECA1891D for ; Tue, 14 Feb 2012 22:48:14 +0000 (UTC) Received: by iabz7 with SMTP id z7so786325iab.11 for ; Tue, 14 Feb 2012 14:48:13 -0800 (PST) Received: by 10.43.48.65 with SMTP id uv1mr17560346icb.57.1329259693586; Tue, 14 Feb 2012 14:48:13 -0800 (PST) X-Forwarded-To: linaro-patchwork@canonical.com X-Forwarded-For: patch@linaro.org linaro-patchwork@canonical.com Delivered-To: patches@linaro.org Received: by 10.231.66.135 with SMTP id n7cs102441ibi; Tue, 14 Feb 2012 14:48:13 -0800 (PST) Received: by 10.180.14.73 with SMTP id n9mr20892323wic.16.1329259691910; Tue, 14 Feb 2012 14:48:11 -0800 (PST) Received: from indium.canonical.com (indium.canonical.com. [91.189.90.7]) by mx.google.com with ESMTPS id u6si1162145wec.4.2012.02.14.14.48.10 (version=TLSv1/SSLv3 cipher=OTHER); Tue, 14 Feb 2012 14:48:11 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of bounces@canonical.com designates 91.189.90.7 as permitted sender) client-ip=91.189.90.7; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of bounces@canonical.com designates 91.189.90.7 as permitted sender) smtp.mail=bounces@canonical.com Received: from ackee.canonical.com ([91.189.89.26]) by indium.canonical.com with esmtp (Exim 4.71 #1 (Debian)) id 1RxRAQ-00080u-GS for ; Tue, 14 Feb 2012 22:48:10 +0000 Received: from ackee.canonical.com (localhost [127.0.0.1]) by ackee.canonical.com (Postfix) with ESMTP id 72CCFE1438 for ; Tue, 14 Feb 2012 22:48:10 +0000 (UTC) MIME-Version: 1.0 X-Launchpad-Project: lava-server X-Launchpad-Branch: ~linaro-validation/lava-server/trunk X-Launchpad-Message-Rationale: Subscriber X-Launchpad-Branch-Revision-Number: 345 X-Launchpad-Notification-Type: branch-revision To: Linaro Patch Tracker From: noreply@launchpad.net Subject: [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> Date: Tue, 14 Feb 2012 22:48:10 -0000 Reply-To: noreply@launchpad.net Sender: bounces@canonical.com Errors-To: bounces@canonical.com Precedence: bulk X-Generated-By: Launchpad (canonical.com); Revision="14781"; Instance="launchpad-lazr.conf" X-Launchpad-Hash: 086ff6fd4a8175d35656a95a32b9354427afcbe8 X-Gm-Message-State: ALoCoQmzCndxh1CGlp9t7wgcmpmGXWnyQER2V6NNZl1Tr17ImeYscmNzzcEY76TRJ21gzUxKVFTh 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 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 === 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 . 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