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
--- 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}}">« First</a>
{% endif %}
@@ -8,4 +11,4 @@
<b>{{ first }}{% if last %} - {{ last }}{% endif %}</b>
{% if next %}<a class="novisit" href="{{next}}">Next ›</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',