List views should have a selectable pagination "page" length:
authorTodd Larsen <tlarsen@google.com>
Fri, 03 Oct 2008 17:21:41 +0000
changeset 265 3c2994f3b85f
parent 264 97b60788cb9a
child 266 3b47bfd4f1b3
List views should have a selectable pagination "page" length: http://code.google.com/p/soc/issues/detail?id=59 Patch by: Chen Lunpeng and Todd Larsen Review by: Augie Fackler Review URL: http://codereviews.googleopensourceprograms.com/1201
app/soc/templates/soc/list/list_pagination.html
app/soc/views/helpers/forms_helpers.py
app/soc/views/helpers/list_helpers.py
app/soc/views/site/docs/list.py
app/soc/views/site/sponsor/list.py
app/soc/views/site/user/list.py
--- a/app/soc/templates/soc/list/list_pagination.html	Fri Oct 03 07:03:11 2008 +0000
+++ b/app/soc/templates/soc/list/list_pagination.html	Fri Oct 03 17:21:41 2008 +0000
@@ -1,4 +1,7 @@
 <div class="pagination">
+
+  {{ pagination_form.as_table }}
+ 
   {% if newest %}
     <a class="novisit" href="{{newest}}">&laquo; First</a>
   {% endif %}
@@ -8,4 +11,4 @@
   <b>{{ first }}{% if last %} - {{ last }}{% endif %}</b>
   {% if next %}<a class="novisit" href="{{next}}">Next &rsaquo;</a>
   {% endif %}
-</div>
\ No newline at end of file
+</div>
--- a/app/soc/views/helpers/forms_helpers.py	Fri Oct 03 07:03:11 2008 +0000
+++ b/app/soc/views/helpers/forms_helpers.py	Fri Oct 03 17:21:41 2008 +0000
@@ -18,12 +18,16 @@
 """
 
 __authors__ = [
+  '"Chen Lunpeng" <forever.clp@gmail.com>',
   '"Todd Larsen" <tlarsen@google.com>',
   ]
 
 
 from google.appengine.ext.db import djangoforms
 
+from django import newforms as forms
+from django.utils import safestring
+
 
 class DbModelForm(djangoforms.ModelForm):
   """Subclass of Django ModelForm that fixes some label and help_text issues.
@@ -68,3 +72,101 @@
         # to the corresponding field help_text.
         if hasattr(model_prop, 'help_text'):
           self.fields[field_name].help_text = model_prop.help_text
+
+
+class SelectQueryArgForm(forms.Form):
+  """URL query argument change control implemented as a Django form.
+  """
+
+  ONCHANGE_JAVASCRIPT_FMT = '''
+<script type="text/javascript"> 
+  function changeArg_%(arg_name)s(item) 
+  {
+    var idx=item.selectedIndex;
+    item.selected=true;
+    var value=item.value 
+    var url = location.href 
+    var reg = /%(arg_name)s=\d+/ 
+    url = url.replace(reg, "%(arg_name)s="+value) 
+    if(url.match(reg))
+      document.location.href = url 
+   else
+      document.location.href = "%(page_path)s?%(arg_name)s="+value; 
+  }
+</script>
+'''
+
+  def __init__(self, page_path, arg_name, choices, field_name,
+               *form_args, **form_kwargs):
+    """
+    Args:
+      page_path: (usually request.path)
+      arg_name: the URL query parameter that determines which choice is
+        selected in the selection control
+      choices: list (or tuple) of value/label string two-tuples, for example:
+        (('10', '10 items per page'), ('25', '25 items per page'))
+      field_name: name of the selection field in the form
+      *form_args: positional arguments passed on to the Form base
+        class __init__()
+      *form_kwargs: keyword arguments passed on to the Form base
+        class __init__()
+    """
+    super(SelectQueryArgForm, self).__init__(*form_args, **form_kwargs)
+    
+    self._script = safestring.mark_safe(self.ONCHANGE_JAVASCRIPT_FMT % {
+        'arg_name': arg_name, 'page_path': page_path,})
+ 
+    onchange_js_call = 'changeArg_%s(this)' % arg_name
+    
+    self.fields[field_name] = forms.ChoiceField(
+        label='', choices=choices,
+        widget=forms.widgets.Select(attrs={'onchange': onchange_js_call}))
+      
+  def as_table(self):
+    """Returns form rendered as HTML <tr> rows -- with no <table></table>.
+    
+    Prepends <script> section with onchange function included.
+    """
+    return self._script + super(SelectQueryArgForm, self).as_table()
+
+  def as_ul(self):
+    """Returns form rendered as HTML <li> list items -- with no <ul></ul>.
+    
+    Prepends <script> section with onchange function included.
+    """
+    return self._script + super(SelectQueryArgForm, self).as_ul()
+
+  def as_p(self):
+    """Returns form rendered as HTML <p> paragraphs.
+    
+    Prepends <script> section with onchange function included.
+    """
+    return self._script + super(SelectQueryArgForm, self).as_p()
+
+
+DEF_SELECT_QUERY_ARG_FIELD_NAME_FMT = 'select_query_arg_%(arg_name)s'
+
+def makeSelectQueryArgForm(
+    request, arg_name, initial_value, choices,
+    field_name_fmt=DEF_SELECT_QUERY_ARG_FIELD_NAME_FMT):
+  """Wrapper that creates a customized SelectQueryArgForm.
+
+  Args:
+    request: the standard Django HTTP request object
+    arg_name: the URL query parameter that determines which choice is
+      selected in the selection control
+    initial_value: the initial value of the selection control
+    choices: list (or tuple) of value/label string two-tuples, for example:
+      (('10', '10 items per page'), ('25', '25 items per page'))
+    field_name_fmt: optional form field name format string; default is
+      DEF_SELECT_QUERY_ARG_FIELD_NAME_FMT; contains these named format
+      specifiers:
+        arg_name: replaced with the arg_name argument
+
+  Returns:
+    a Django form implementing a query argument selection control, for
+    insertion into a template
+  """
+  field_name = field_name_fmt % {'arg_name': arg_name}
+  return SelectQueryArgForm(request.path, arg_name, choices, field_name,
+                            initial={field_name: initial_value})
--- a/app/soc/views/helpers/list_helpers.py	Fri Oct 03 07:03:11 2008 +0000
+++ b/app/soc/views/helpers/list_helpers.py	Fri Oct 03 17:21:41 2008 +0000
@@ -18,11 +18,23 @@
 """
 
 __authors__ = [
+  '"Chen Lunpeng" <forever.clp@gmail.com>',
   '"Pawel Solyga" <pawel.solyga@gmail.com>',
   ]
 
 
-DEF_LIMIT = 10
+from soc.views.helpers import forms_helpers
+
+
+DEF_PAGINATION = 10
+MAX_PAGINATION = 100
+
+DEF_PAGINATION_CHOICES = (
+  ('10', '10 items per page'),
+  ('25', '25 items per page'),
+  ('50', '50 items per page'),
+  ('100', '100 items per page'),
+)
 
 
 def getPreferredListPagination(user=None):
@@ -34,11 +46,11 @@
     """
     # TODO: eventually this limit should be a User profile preference
     #   (stored in the site-wide User Model) preference 
-    return DEF_LIMIT
+    return DEF_PAGINATION
 
 
-def getListParemeters(offset=None, limit=None):
-  """Updates and validates offset and limit values of the list.
+def cleanListParameters(offset=None, limit=None):
+  """Converts and validates offset and limit values of the list.
 
   Args:
     offset: offset in list which defines first item to return
@@ -47,29 +59,22 @@
   Returns:
     updated offset and limit values
   """
-  # update offset value 
-  if offset:
-    try:
-      offset = int(offset)
-    except:
-      offset = 0
-    else:
-      offset = max(0, offset)
-  else:
+  # update offset value
+  try:
+    offset = int(offset)
+  except:
+    # also catches offset=None case where offset not supplied
     offset = 0
-  
+
   # update limit value
-  if limit:
-    try:
-      limit = int(limit)
-    except:
-      limit = DEF_LIMIT
-    else:
-      limit = max(1, min(limit, 100))
-  else:
-    limit = DEF_LIMIT
-  
-  return offset, limit
+  try:
+    limit = int(limit)
+  except:
+    # also catches limit=None case where limit not supplied
+    limit = getPreferredListPagination()
+
+  return max(0, offset), max(1, min(limit, MAX_PAGINATION))
+
 
 DEF_LIST_TEMPLATES = {'list_main': 'soc/list/list_main.html',
                       'list_pagination': 'soc/list/list_pagination.html',
@@ -144,3 +149,65 @@
      'last': len(list_data) > 1 and offset+len(list_data) or None})
   
   return context
+
+
+def makePaginationForm(
+  request, limit, arg_name='limit', choices=DEF_PAGINATION_CHOICES,
+  field_name_fmt=forms_helpers.DEF_SELECT_QUERY_ARG_FIELD_NAME_FMT):
+  """Returns a customized pagination limit selection form.
+  
+  Args:
+    request: the standard Django HTTP request object
+    limit: the initial value of the selection control
+    arg_name: see forms_helpers.makeSelectQueryArgForm(); default is 'limit'
+    choices: see forms_helpers.makeSelectQueryArgForm(); default is
+      DEF_PAGINATION_CHOICES
+    field_name_fmt: see forms_helpers.makeSelectQueryArgForm()
+  """
+  choices = makeNewPaginationChoices(limit=limit, choices=choices)
+  
+  return forms_helpers.makeSelectQueryArgForm(
+      request, arg_name, limit, choices)
+
+
+def makeNewPaginationChoices(limit=DEF_PAGINATION,
+                             choices=DEF_PAGINATION_CHOICES):
+  """Updates the pagination limit selection form.
+
+  Args:
+    limit: the initial value of the selection control;
+      default is DEF_PAGINATION
+    choices: see forms_helpers.makeSelectQueryArgForm();
+      default is DEF_PAGINATION_CHOICES
+
+  Returns:
+    a new pagination choices list if limit is not in
+    DEF_PAGINATION_CHOICES, or DEF_PAGINATION_CHOICES otherwise
+  """
+  # determine where to insert the new limit into choices
+  new_choices = []
+  inserted = False
+  
+  for pagination, label in choices:
+    items = int(pagination)
+
+    if limit == items:
+      # limit is already present, so just return existing choices
+      return choices
+
+    if (not inserted) and (limit < items):
+      # limit needs to be inserted before the current pagination,
+      # so assemble a new choice tuple and append it 
+      choice = (str(limit), '%s items per page' % limit)
+      new_choices.append(choice)
+      inserted = True
+      
+    # append the existing choice
+    new_choices.append((pagination, label))
+
+  if not inserted:
+    # new choice must go last, past all other existing choices
+    choice = (str(limit), '%s items per page' % limit)
+    new_choices.append(choice)
+      
+  return new_choices
--- a/app/soc/views/site/docs/list.py	Fri Oct 03 07:03:11 2008 +0000
+++ b/app/soc/views/site/docs/list.py	Fri Oct 03 17:21:41 2008 +0000
@@ -52,18 +52,14 @@
   if alt_response:
     return alt_response  
   
-  offset = request.GET.get('offset')
-  limit = request.GET.get('limit')
-
-  offset, limit = list_helpers.getListParemeters(offset=offset, limit=limit)
+  offset, limit = list_helpers.cleanListParameters(
+      offset=request.GET.get('offset'), limit=request.GET.get('limit'))
 
   # Fetch one more to see if there should be a 'next' link
   docs = works.getWorksForLimitAndOffset(
       limit + 1, offset=offset, cls=soc.models.document.Document)
 
-  # TODO(tlarsen): uncomment when pagination select control is working.
-  # form = list_helpers.makeSelectNumItemsForm(request, limit)
-  # context['form'] = form
+  context['pagination_form'] = list_helpers.makePaginationForm(request, limit)
 
   list_templates = {'list_main': 'soc/list/list_main.html',
                     'list_pagination': 'soc/list/list_pagination.html',
--- a/app/soc/views/site/sponsor/list.py	Fri Oct 03 07:03:11 2008 +0000
+++ b/app/soc/views/site/sponsor/list.py	Fri Oct 03 17:21:41 2008 +0000
@@ -41,17 +41,13 @@
   if alt_response:
     return alt_response  
   
-  offset = request.GET.get('offset')
-  limit = request.GET.get('limit')
-
-  offset, limit = list_helpers.getListParemeters(offset, limit)
+  offset, limit = list_helpers.cleanListParameters(
+      offset=request.GET.get('offset'), limit=request.GET.get('limit'))
   
   # Fetch one more to see if there should be a 'next' link
   sponsors = sponsor.getSponsorsForLimitAndOffset(limit + 1, offset=offset)
 
-  # TODO(tlarsen): uncomment when pagination select control is working.
-  # form = list_helpers.makeSelectNumItemsForm(request, limit)
-  # context['form'] = form
+  context['pagination_form'] = list_helpers.makePaginationForm(request, limit)
   
   list_templates = {'list_main': 'soc/list/list_main.html',
                     'list_pagination': 'soc/list/list_pagination.html',
--- a/app/soc/views/site/user/list.py	Fri Oct 03 07:03:11 2008 +0000
+++ b/app/soc/views/site/user/list.py	Fri Oct 03 17:21:41 2008 +0000
@@ -52,17 +52,13 @@
   if alt_response:
     return alt_response  
   
-  offset = request.GET.get('offset')
-  limit = request.GET.get('limit')
-
-  offset, limit = list_helpers.getListParemeters(offset=offset, limit=limit)
+  offset, limit = list_helpers.cleanListParameters(
+      offset=request.GET.get('offset'), limit=request.GET.get('limit'))
 
   # Fetch one more to see if there should be a 'next' link
   users = id_user.getUsersForLimitAndOffset(limit + 1, offset=offset)
 
-  # TODO(tlarsen): uncomment when pagination select control is working.
-  # form = list_helpers.makeSelectNumItemsForm(request, limit)
-  # context['form'] = form
+  context['pagination_form'] = list_helpers.makePaginationForm(request, limit)
   
   list_templates = {'list_main': 'soc/list/list_main.html',
                     'list_pagination': 'soc/list/list_pagination.html',