Warning: Can't synchronize with repository "(default)" (Unsupported version control system "svn": No module named svn). Look in the Trac log for more information.

Ticket #1115: paginate.py.diff.1

File paginate.py.diff.1, 10.6 KB (added by randall@…, 6 years ago)

patch for paginate.py

Line 
1Index: paginate.py
2===================================================================
3--- paginate.py (revision 1888)
4+++ paginate.py (working copy)
5@@ -1,3 +1,5 @@
6+import re
7+import types
8 from math import ceil
9 import logging
10 
11@@ -2,4 +4,12 @@
12 import cherrypy
13+import sqlobject
14 from sqlobject.main import SelectResults
15 
16+try:
17+    # Can't depend on sqlalchemy being available.
18+    import sqlalchemy
19+    from sqlalchemy.ext.selectresults import SelectResults as SASelectResults
20+except ImportError:
21+    SASelectResults = None
22+
23 import turbogears
24@@ -16,15 +26,19 @@
25         def decorated(func, *args, **kw):
26             page = int(kw.pop('tg_paginate_no', 1))
27             limit_ = int(kw.pop('tg_paginate_limit', limit))
28-            order = kw.pop('tg_paginate_order', default_order)
29-            reversed = kw.pop('tg_paginate_reversed', None)
30-           
31+            order = kw.pop('tg_paginate_order', None)
32+            ordering = kw.pop('tg_paginate_ordering', None)
33+
34+            # Convert ordering str to a dict.
35+            if ordering:
36+                ordering = convert_ordering(ordering)
37+
38             if not allow_limit_override:
39                 limit_ = limit
40+
41+            log.debug("Pagination params: page=%s, limit=%s, order=%s "
42+                      "", page, limit_, order)
43             
44-            log.debug("Pagination params: page=%s, limit=%s, order=%s, "
45-                      "reversed=%s", page, limit_, order, reversed)
46-           
47             # get the output from the decorated function   
48             output = func(*args, **kw)
49             if not isinstance(output, dict):
50@@ -37,18 +51,32 @@
51             if order and not default_order:
52                 raise "If you want to enable ordering you need " \
53                       "to provide a default_order"
54-           
55+            elif default_order and not ordering:
56+                ordering = {default_order:[0, True]}
57+            elif ordering and order:
58+                sort_ordering(ordering, order)
59+            log.info('ordering %s' % ordering)
60+
61             row_count = 0
62-            if isinstance(var_data, SelectResults):
63+            if isinstance(var_data, SelectResults) or \
64+                    (SASelectResults and isinstance(var_data, SASelectResults)):
65                 row_count = var_data.count()
66-                col = getattr(var_data.sourceClass.q, order, None)
67-                if default_order:
68-                    if col:
69-                        var_data = var_data.orderBy(col)
70+                if ordering:
71+                    # Build order_by list.
72+                    order_cols = range(len(ordering))
73+                    for (colname, order_opts) in ordering.items():
74+                        col = sql_get_column(colname, var_data)
75+                        if not col:
76+                            raise StandardError, "The order column (%s) doesn't exist" % colname
77+                        order_by_expr = sql_order_col(col, order_opts[1])
78+                        order_cols[order_opts[0]] = order_by_expr
79+                    # May need to address potential of ordering already
80+                    # existing in var_data.
81+                    # SO and SA differ on this method name.
82+                    if hasattr(var_data, 'orderBy'):
83+                        var_data = var_data.orderBy(order_cols)
84                     else:
85-                        raise "The order column (%s) doesn't exist" % order
86-                    if reversed:
87-                        var_data = var_data.reversed()
88+                        var_data = var_data.order_by(order_cols)
89             elif isinstance(var_data, list):
90                 row_count = len(var_data)
91             else:
92@@ -69,15 +97,15 @@
93             #input_values = cherrypy.request.input_values.copy()
94             input_values = kw.copy()
95             input_values.pop('self', None)
96-           
97+
98             cherrypy.request.paginate = Paginate(current_page=page,
99-                                                 limit=limit_,
100-                                                 pages=pages_to_show,
101-                                                 page_count=page_count,
102-                                                 input_values=input_values,
103-                                                 order=order,
104-                                                 reversed=reversed)
105-                                                 
106+                                             limit=limit_,
107+                                             pages=pages_to_show,
108+                                             page_count=page_count,
109+                                             input_values=input_values,
110+                                             order=order,
111+                                             ordering=ordering)
112+                                             
113             # we replace the var with the sliced one
114             endpoint = offset + limit_
115             log.debug("slicing data between %d and %d", offset, endpoint)
116@@ -98,18 +126,30 @@
117 class Paginate:
118     """class for variable provider"""
119     def __init__(self, current_page, pages, page_count, input_values,
120-                 limit, order, reversed):
121+                 limit, order, ordering):
122+        # If we have a nested dict from a form, it must be denormalized before
123+        # it can be put into a GET URL.
124+        #     {'form_field':{'text':'hello', 'hidden':''}
125+        # Goes to:
126+        #     {'form_field.text':'hello', 'form_field.hidden':''}
127+        # I'm going to be specific so as to minimize breakage.
128+        for (ikey, ivalue) in input_values.items():
129+            if isinstance(ivalue, dict) and len(ivalue) > 1:
130+                del input_values[ikey]
131+                for (sub_ikey, sub_ivalue) in ivalue.items():
132+                    new_key = '%s.%s' % (ikey, sub_ikey)
133+                    input_values[new_key] = sub_ivalue
134+                 
135         self.pages = pages
136         self.limit = limit
137         self.page_count = page_count
138         self.current_page = current_page
139         self.input_values = input_values
140         self.order = order
141-        self.reversed = reversed
142-       
143+        self.ordering = ordering
144+             
145         self.input_values.update(dict(tg_paginate_limit=limit,
146-                                      tg_paginate_order=order,
147-                                      tg_paginate_reversed=reversed))
148+                                      tg_paginate_ordering=ordering))
149         if current_page < page_count:
150             self.input_values.update(dict(
151                                 tg_paginate_no=current_page+1,
152@@ -136,31 +176,17 @@
153             self.href_prev = None
154             self.href_first = None
155             
156-    def get_href(self, page, order=None, reverse_order=None):
157+    def get_href(self, page, order=None, reversed=None):
158+        # Note that reversed is not used.  It should be cleaned up here and in
159+        # the template.  I'm not removing it now because I don't want to break
160+        # the API.
161+        order = order or None
162+        self.input_values['tg_paginate_no'] = page
163         if order:
164-            if order == self.order:
165-                if self.reversed:
166-                    reversed = None
167-                else:
168-                    reversed = True
169-            else:
170-                reversed = None
171-                if reverse_order:
172-                    if reversed:
173-                        reversed = None
174-                    else:
175-                        reversed = True
176-        else:
177-            order = self.order
178-            reversed = self.reversed
179-       
180-        self.input_values.update(dict(tg_paginate_no=page,
181-                                      tg_paginate_order=order,
182-                                      tg_paginate_reversed=reversed))
183-       
184+            self.input_values['tg_paginate_order'] = order
185+
186         return turbogears.url('', self.input_values)
187 
188-
189 def _select_pages_to_show(current_page, page_count, max_pages):
190     pages_to_show = []
191     
192@@ -186,3 +212,70 @@
193         end = page_count
194         
195     return range(start, end+1)
196+
197+def sort_ordering(ordering, sort_name):
198+    """Rearrange ordering based on sort_name."""
199+    log.info('sort called with %s and %s' % (ordering, sort_name))
200+    if sort_name not in ordering:
201+        ordering[sort_name] = [-1, True]
202+    if ordering[sort_name][0] == 0:
203+        # Flip
204+        ordering[sort_name][1] = not ordering[sort_name][1]
205+    else:
206+        ordering[sort_name][0] = 0
207+        for key in ordering.keys():
208+            if key != sort_name and ordering[key][0] < len(ordering) - 1:
209+                ordering[key][0] += 1
210+
211+def sql_get_column(colname, var_data):
212+    """Return a column from var_data based on colname."""
213+    if isinstance(var_data, SelectResults):
214+        col = getattr(var_data.sourceClass.q, colname, None)
215+    elif isinstance(var_data, SASelectResults):
216+        col = getattr(var_data._query.table.c, colname, None)
217+    else:
218+        raise StandardError, 'expected SelectResults'
219+    return col
220+
221+def sql_order_col(col, ascending=True):
222+    """Return an ordered col for col."""
223+    if isinstance(col, sqlalchemy.schema.Column):
224+        if ascending:
225+            order_col = sqlalchemy.sql.asc(col)
226+        else:
227+            order_col = sqlalchemy.sql.desc(col)
228+    elif isinstance(col, types.InstanceType):
229+        # I don't like using InstanceType, but that's what sqlobject col type
230+        # is.
231+        if ascending:
232+            order_col = col
233+        else:
234+            order_col = sqlobject.DESC(col)
235+    else:
236+        raise StandardError, 'expected Column, but got %s' % str(type(col))
237+    return order_col
238+   
239+# Ordering re:
240+ordering_expr = re.compile(r"('\w+'): ?\[(\d+), ?(True|False)\]")
241+
242+def convert_ordering(ordering):
243+    """Covert ordering unicode string to dict."""
244+
245+    # eval would be simple, but insecure.
246+    if not isinstance(ordering, (str, unicode)):
247+        raise ValueError, "ordering should be string or unicode."
248+    new_ordering = {}
249+    try:
250+        ordering_info_find = ordering_expr.findall(ordering)
251+        emsg = "Didn't match ordering for %s." % str(ordering)
252+        assert len(ordering_info_find) > 0, emsg
253+        for ordering_info in ordering_info_find:
254+            ordering_key = str(ordering_info[0]).strip("'")
255+            ordering_order = int(ordering_info[1])
256+            ordering_reverse = bool(ordering_info[2])
257+            new_ordering[ordering_key] = [ordering_order,
258+                                          ordering_reverse]
259+    except StandardError, e:
260+        log.debug('FAILED to convert ordering.')
261+        new_ordering = None
262+    return new_ordering