From patchwork Tue May 29 02:27:10 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Michael-Doyle Hudson X-Patchwork-Id: 9015 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 801E823E1B for ; Tue, 29 May 2012 02:27:13 +0000 (UTC) Received: from mail-gh0-f180.google.com (mail-gh0-f180.google.com [209.85.160.180]) by fiordland.canonical.com (Postfix) with ESMTP id 3458EA186F6 for ; Tue, 29 May 2012 02:27:13 +0000 (UTC) Received: by ghbz12 with SMTP id z12so1821261ghb.11 for ; Mon, 28 May 2012 19:27:12 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=x-forwarded-to:x-forwarded-for:delivered-to:received-spf :content-type:mime-version:x-launchpad-project:x-launchpad-branch :x-launchpad-message-rationale:x-launchpad-branch-revision-number :x-launchpad-notification-type:to:from:subject:message-id:date :reply-to:sender:errors-to:precedence:x-generated-by :x-launchpad-hash:x-gm-message-state; bh=PwXuekFR5YLQBT/zwaVOdmyx4UQLfVWyFjK6rdnPho0=; b=kNZGdPshh0zZ2fd2mkkegRO7gdOCN6sL1zU5xNs2zcgZDYKXa+du6LiBDWpzQZM2uG eL4lh1Ki4mk1bWaMbTtQqDak6vRrphheGDKIm+95X7AE8RRqsIkO+YKLT4jHGg7G7kCL eHPLOLKD/TbLb+e76QdseY9yuXmTZPJxq/+u/TEOzw306k+kieFIO89LA0xIuNCYN8qL +BDX/0uFpDaGBWnEXjk88jTf6zwkB9HG9Mo/NIjNl3Omsuh+Ac4ag05qT1eW8MzY8G1a 9UIl6WgaO7w/AywYcP8jVSrc6sXYsS9LmFAP9PF6xsPht1bgZhzLILIg2l6p3AydsKH1 Ukrg== Received: by 10.50.95.135 with SMTP id dk7mr5647199igb.53.1338258432555; Mon, 28 May 2012 19:27:12 -0700 (PDT) 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.24.148 with SMTP id v20csp99200ibb; Mon, 28 May 2012 19:27:11 -0700 (PDT) Received: by 10.216.228.202 with SMTP id f52mr6217322weq.223.1338258431109; Mon, 28 May 2012 19:27:11 -0700 (PDT) Received: from indium.canonical.com (indium.canonical.com. [91.189.90.7]) by mx.google.com with ESMTPS id 71si16222595wej.31.2012.05.28.19.27.10 (version=TLSv1/SSLv3 cipher=OTHER); Mon, 28 May 2012 19:27:11 -0700 (PDT) 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 1SZC9O-0001Yj-I0 for ; Tue, 29 May 2012 02:27:10 +0000 Received: from ackee.canonical.com (localhost [127.0.0.1]) by ackee.canonical.com (Postfix) with ESMTP id 7F01BE002E for ; Tue, 29 May 2012 02:27: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: 374 X-Launchpad-Notification-Type: branch-revision To: Linaro Patch Tracker From: noreply@launchpad.net Subject: [Branch ~linaro-validation/lava-server/trunk] Rev 374: allow the creation of DataTablesTables that are backed by data rather than a queryset Message-Id: <20120529022710.24766.78978.launchpad@ackee.canonical.com> Date: Tue, 29 May 2012 02:27: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="15316"; Instance="launchpad-lazr.conf" X-Launchpad-Hash: bd99de7a319e85dc5c86bd70bed85c0fb428bdca X-Gm-Message-State: ALoCoQl71EiGdpdzTKbS4qjdgCsePleZ1pS56ThI8pafyTsFU2yY7C7vuwra+WfzyPAi9+njHqci Merge authors: Michael Hudson-Doyle (mwhudson) Related merge proposals: https://code.launchpad.net/~mwhudson/lava-server/data-backed-tables/+merge/106735 proposed by: Michael Hudson-Doyle (mwhudson) review: Approve - Spring Zhang (qzhang) ------------------------------------------------------------ revno: 374 [merge] committer: Michael Hudson-Doyle branch nick: trunk timestamp: Tue 2012-05-29 14:25:01 +1200 message: allow the creation of DataTablesTables that are backed by data rather than a queryset modified: lava/utils/data_tables/tables.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/tables.py' --- lava/utils/data_tables/tables.py 2012-04-20 19:58:36 +0000 +++ lava/utils/data_tables/tables.py 2012-05-29 01:33:39 +0000 @@ -18,9 +18,13 @@ """Tables designed to be used with the DataTables jQuery plug in. -There are just three steps to using this: - -1) Define the table:: +It is expected that most tables will be backed onto database queries, but it +is possible to create tables backed onto regular Python instances. + +There are just three steps to creating a database-backed table: + +1) Define the table, which means the columns and the queryset that supplies + them with data:: class BookTable(DataTablesTable): author = Column() @@ -33,6 +37,10 @@ def book_table_json(request): return BookTable.json(request) + Don't forget urls.py: + + url(r'^book_table_json$', 'book_table_json'), + 3) Include the table in the view for the page you are building:: def book_list(request): @@ -85,9 +93,15 @@ in the json view need to be consistent, many of the options that you can pass to Table's __init__ are not available for DataTablesTable. In practice this means that you need a DataTablesTable subclass for each different table. + +If you want to create a DataTablesTable based table, just pass a sequence to +the data= argument of the constructor (and do not pass anything for the source +argument) and supply the table instance to {% render_table %} (no need for a +get_queryset method or a json view in this case). The 'params' argument in +this case, if passed, is stored on the instance where it can be accessed if +needed when rendering a column. """ -from abc import ABCMeta, abstractmethod import simplejson from django.template import RequestContext @@ -99,48 +113,54 @@ from lava.utils.data_tables.backends import TableBackend -class MetaTable(ABCMeta, Table.__metaclass__): - pass - - class DataTablesTable(Table): """A table designed to be used with the DataTables jQuery plug in. """ - __metaclass__ = MetaTable - def __init__(self, id, source=None, params=(), sortable=None, - empty_text=None, attrs=None, template=None): + empty_text=None, attrs=None, template=None, data=None): """Initialize the table. Options that Table supports that affect the data presented are not supported in this subclass. Extra paramters are: :param id: The id of the table in the resulting HTML. You just need - to provide somethign that will be unique in the generated page. + to provide something that will be unique in the generated page. :param source: The URL to get json data from. :param params: A tuple of arguments to pass to the get_queryset() method. + :param data: The data to base the table on, if any. """ + if data is not None: + if source is not None or self.source is not None: + raise AssertionError( + "Do not specify both data and source when building a " + "DataTablesTable") + self.params = params + data_backed_table = True + else: + data_backed_table = False + data = [] + if source is not None: + self.source = source if template is None: template = 'ajax_table.html' - # The reason we pass data=[] here and patch the queryset in below is - # because of a bootstrapping issue. We want to sort the initial - # queryset, and this is much cleaner if the table has has its .columns - # set up which is only done in Table.__init__... + # Even if this is an ajax backed table, we pass data here and patch + # the queryset in below because of a bootstrapping issue: we want to + # sort the initial queryset, and this is much cleaner if the table has + # has its .columns set up which is only done in Table.__init__... super(DataTablesTable, self).__init__( - data=[], sortable=sortable, empty_text=empty_text, attrs=attrs, + data=data, sortable=sortable, empty_text=empty_text, attrs=attrs, template=template) - self.full_queryset = self.get_queryset(*params) - self.full_length = self.full_queryset.count() - ordering = self.datatable_opts.get('aaSorting', [[0, 'asc']]) - sorted_queryset = TableBackend(self).apply_sorting_columns( - self.full_queryset, ordering) - display_length = self.datatable_opts.get('iDisplayLength', 10) - del self.data.list - self.data.queryset = sorted_queryset[:display_length] - if source is not None: - self.source = source + if not data_backed_table: + self.full_queryset = self.get_queryset(*params) + self.full_length = self.full_queryset.count() + ordering = self.datatable_opts.get('aaSorting', [[0, 'asc']]) + sorted_queryset = TableBackend(self).apply_sorting_columns( + self.full_queryset, ordering) + display_length = self.datatable_opts.get('iDisplayLength', 10) + del self.data.list + self.data.queryset = sorted_queryset[:display_length] # We are careful about modifying the attrs here -- if it comes from # class Meta:-type options, we don't want to modify the original # value! @@ -182,13 +202,17 @@ """The DataTable options for this table, serialized as JSON.""" opts = { 'bJQueryUI': True, - 'bServerSide': True, 'bProcessing': True, - 'sAjaxSource': self.source, - 'bFilter': bool(self.searchable_columns), - 'iDeferLoading': self.full_length, + 'bFilter': True, } opts.update(self.datatable_opts) + if self.source is not None: + opts.update({ + 'bServerSide': True, + 'sAjaxSource': self.source, + 'bFilter': bool(self.searchable_columns), + 'iDeferLoading': self.full_length, + }) aoColumnDefs = opts['aoColumnDefs'] = [] for col in self.columns: aoColumnDefs.append({ @@ -198,28 +222,30 @@ }) return simplejson.dumps(opts) - # Subclasses must override get_queryset() and may want to provide values - # for source, datatable_opts and searchable_columns. - - @abstractmethod + # Any subclass might want to provide values for datatable_opts. + + # Extra DataTable options. Values you might want to override here include + # 'iDisplayLength' (how big to make the table's pages by default) and + # 'aaSorting' (the initial sort of the table). See + # http://datatables.net/usage/options for more. + datatable_opts = {} + + # Subclasses that use dynamic data *must* override get_queryset() and may + # want to provide values for source, and searchable_columns. + def get_queryset(self, *args): """The data the table displays. The return data will be sorted, filtered and sliced depending on how the table is manipulated by the user. """ + raise NotImplementedError(self.get_queryset) # The URL to get data from (i.e. the sAjaxSource of the table). Often # it's more convenient to pass this to the table __init__ to allow the # code to be laid out in a more logical order. source = None - # Extra DataTable options. Values you might want to override here include - # 'iDisplayLength' (how big to make the table's pages by default) and - # 'aaSorting' (the initial sort of the table). See - # http://datatables.net/usage/options for more. - datatable_opts = {} - # Perform searches by looking in these columns. Searching will not be # enabled unless you set this. Searching is only supported in textual # columns for now (supporting an IntegerField with Choices seems possible