On Tue, 22 Nov 2005 07:09:06 -0800 [EMAIL PROTECTED] wrote: > What is the Django Way (tm) to paginate complex queries with > user-controlled parameters? That is, if I want to paginate the > results of a foos.get_list({'lots': 'of_complex', ['junk': 'in > here']}) where the query terms are submitted via form, how should I > go about that while maintaining the user-posted values from page to > page?
For propagating values in forms, I have a custom template tag to do it. However, it requires that you put the request object in the context object for your template. In my project I'm using a 'pagevars' object to hold this kind of slightly random thing (i.e. stuff that isn't particularly related to the view), so all my custom template tags can expect to find pagevars.request in the context. The code looks something like this: ----- from django.core import template from django.utils import html class ForwardQueryParamNode(template.Node): def __init__(self, param_name): self.param_name = param_name def render(self, context): request = context['pagevars'].request return '<div><input type="hidden" name="' + self.param_name + \ '" value="' + html.escape(request.GET.get(self.param_name, '')) + \ '" /></div>' # forward_query_param - turn a param in query string into a hidden # input field. Needs to be able to get the request object from the context def do_forward_query_param(parser, token): """ Turns a parameter in a query string into a hidden input field, allowing it to be 'forwarded' as part of the next request in a form submission. It requires one argument (the name of the parameter), and also requires that the request object be in the context as by putting the standard 'pagevars' object in the context. """ try: tag_name, param_name = token.contents.split(None, 1) except ValueError: raise template.TemplateSyntaxError, \ "forward_query_param tag requires an argument" param_name = param_name.strip('"') return ForwardQueryParamNode(param_name) ------ And in the template - inside a <form> put this: {% forward_query_param "order" %} I also have a 'paging_control' and a 'sorting_control' which have similar requirements, and automatically 'forward' or adjust all query string parameters. I've attached them to this e-mail (along with an example template and view) in case you find them useful. They will need modification to work outside my app, but I have re-used them several times already inside my app and found they work quite well. This is not an official Django method at all, but it works for me. There must be something similar to this in the admin, but last time I looked I was unable to extract and re-use it easily. The new admin branch might be different. It does make me think that if we had a standard for putting the request object into the context it would make template tags like this more re-usable. Finally, looking at my code, I think the <input> controls in the template could be refactored much more nicely into their own custom template tags. Luke -- "Outside of a dog, a book is a man's best friend... inside of a dog, it's too dark to read." Luke Plant || L.Plant.98 (at) cantab.net || http://lukeplant.me.uk/
from django.views.generic import list_detail from django.utils.httpwrappers import HttpResponseRedirect from django.core.exceptions import Http404 from django.core.extensions import render_to_response from django.models.members import members from django.models.members.members import MemberDoesNotExist from cciw.apps.cciw.common import * from django.core.extensions import DjangoContext from datetime import datetime, timedelta def index(request): lookup_args = {'dummy_member__exact' : 'False', 'hidden__exact': 'False'} if (request.GET.has_key('online')): lookup_args['last_seen__gte'] = datetime.now() - timedelta(minutes=3) extra_context = standard_extra_context(request, title='Members') order_option_to_lookup_arg( {'adj': ('date_joined',), 'ddj': ('-date_joined',), 'aun': ('user_name',), 'dun': ('-user_name',), 'arn': ('real_name',), 'drn': ('-real_name',), 'als': ('last_seen',), 'dls': ('-last_seen',)}, lookup_args, request, ('user_name',)) extra_context['default_order'] = 'aun' try: search = '%' + request['search'] + '%' lookup_args['where'] = ["(user_name LIKE %s OR real_name LIKE %s)"] lookup_args['params'] = [search, search] except KeyError: pass return list_detail.object_list(request, 'members', 'members', extra_context = extra_context, template_name = 'cciw/members/index', paginate_by=50, extra_lookup_kwargs = lookup_args, allow_empty = True){% extends "cciw/standard" %} {% load view_extras %} {% load forums %} {% block content %}
{% paging_control %}
Icon | Member name {% sorting_control "aun" "dun" "Sort by user name ascending" "Sort by user name descending" %} | 'Real' name {% sorting_control "arn" "drn" "Sort by real name ascending" "Sort by real name descending" %} | Comments | Joined {% sorting_control "adj" "ddj" "Sort by 'date joined' ascending" "Sort by 'date joined' descending" %} | Last seen {% sorting_control "als" "dls" "Sort by 'last seen' ascending" "Sort by 'last seen' descending" %} |
---|---|---|---|---|---|
{% if member.icon %} {{ member.icon_image }} {% endif %} | {{ member.get_link }} | {{ member.real_name|escape }} | {{ member.comments|bb2html }} | {% if member.date_joined %} {{ member.date_joined|date:"d M Y" }} {% endif %} | {% if member.last_seen %} {{ member.last_seen|date:"d M Y h:i" }} {% endif %} |
{% paging_control %}
{% else %}
No members found.
{% endif %} {% endblock %}from django.core import template from django.utils import html from cciw.apps.cciw.settings import * from cciw.apps.cciw.utils import * def page_link(request, page_number, fragment = ''): """Constructs a link to a specific page using the request. Returns HTML escaped value""" return html.escape(modified_query_string(request, {'page': str(page_number)}, fragment)) class PagingControlNode(template.Node): def __init__(self, fragment = ''): self.fragment = fragment def render(self, context): # context has been populated by # generic view paging mechanism cur_page = int(context['page'])-1 total_pages = int(context['pages']) request = context['pagevars'].request output = [] if (total_pages > 1): output.append("— Page %d of %d — " % (cur_page + 1, total_pages)) for i in range(0, total_pages): if (i > 0): output.append(" | ") if i == cur_page: output.append('<span class="pagingLinkCurrent">' + str(i+1) + '</span>') else: output.append( '<a title="%(title)s" class="pagingLink" href="%(href)s">%(pagenumber)d</a>' % \ { 'title': 'Page ' + str(i+1), 'href': page_link(request, i, self.fragment), 'pagenumber': i+1 }) output.append(" | ") if cur_page > 0: output.append( '<a class="pagingLink" title="Previous page" href="%s">«</a>' % \ page_link(request, cur_page - 1, self.fragment)) else: output.append('<span class="pagingLinkCurrent">«</span>') output.append(" ") if cur_page < total_pages - 1: output.append( '<a class="pagingLink" title="Next page" href="%s">»</a>' % \ page_link(request, cur_page + 1, self.fragment)) else: output.append('<span class="pagingLinkCurrent">»</span>') return ''.join(output) def do_paging_control(parser, token): """ Creates a list of links to the pages of a generic view. The paging control requires that the request object be in the context as by putting the standard 'pagevars' object in the context. An optional parameter can be used which contains the fragment to use for paging links, which allows paging to skip any initial common content on the page. Usage:: {% paging_control %} """ parts = token.contents.split(None, 1) if len(parts) > 1: return PagingControlNode(fragment = parts[1]) else: return PagingControlNode() class SortingControlNode(template.Node): def __init__(self, ascending_param, descending_param, ascending_title, descending_title): self.ascending_param = ascending_param self.descending_param = descending_param self.ascending_title = ascending_title self.descending_title = descending_title def render(self, context): request = context['pagevars'].request output = '<span class="sortingControl">' current_order = request.GET.get('order','') if current_order == '': try: current_order = context['default_order'] except KeyError: current_order = '' if current_order == self.ascending_param: output += '<a title="' + self.descending_title +'" href="' + \ html.escape(modified_query_string(request, {'order': self.descending_param})) \ + '"><img class="sortAscActive" src="' + CCIW_MEDIA_ROOT + \ 'images/arrow-up.gif" alt="Sorted ascending" /></a>' elif current_order == self.descending_param: output += '<a title="' + self.ascending_title +'" href="' + \ html.escape(modified_query_string(request, {'order': self.ascending_param})) \ + '"><img class="sortDescActive" src="' + CCIW_MEDIA_ROOT + \ 'images/arrow-down.gif" alt="Sorted descending" /></a>' else: # query string resets page to zero if we use a new type of sorting output += '<a title="' + self.ascending_title +'" href="' + \ html.escape(modified_query_string(request, {'order': self.ascending_param, 'page': 0})) \ + '"><img class="sortAsc" src="' + CCIW_MEDIA_ROOT + \ 'images/arrow-up.gif" alt="Sort ascending" /></a>' output += '</span>' return output def do_sorting_control(parser, token): """ Creates a pair of links that are used for sorting a list on a field. Four parameters are accepted, which must be the query string parameter values that should be used for ascending and descending sorts on a field, and the corresponding title text for those links (in that order). All arguments must be quoted with '"' The query string parameter name is always 'order', and the view function will need to check that parameter and adjust accordingly. The view function should also add 'default_order' to the context, which allows the sorting control to determine what the current sort order is if no 'order' parameter exists in the query string. The sorting control requires that the request object be in the context as by putting the standard 'pagevars' object in the context. It is used for various things, including passing on other query string parameters in the generated URLs. Usage:: {% sorting_control "ad" "dd" "Sort date ascending" "Sort date descending" %} """ bits = token.contents.split('"') if len(bits) != 9: raise template.TemplateSyntaxError, "sorting_control tag requires 4 quoted arguments" return SortingControlNode(bits[1].strip('"'), bits[3].strip('"'), bits[5].strip('"'), bits[7].strip('"'),) class ForwardQueryParamNode(template.Node): def __init__(self, param_name): self.param_name = param_name def render(self, context): # requires the standard extra context request = context['pagevars'].request return '<div><input type="hidden" name="' + self.param_name + \ '" value="' + html.escape(request.GET.get(self.param_name, '')) + \ '" /></div>' # forward_query_param - turn a param in query string into a hidden # input field. Needs to be able to get the request object from the context def do_forward_query_param(parser, token): """ Turns a parameter in a query string into a hidden input field, allowing it to be 'forwarded' as part of the next request in a form submission. It requires one argument (the name of the parameter), and also requires that the request object be in the context as by putting the standard 'pagevars' object in the context. """ try: tag_name, param_name = token.contents.split(None, 1) except ValueError: raise template.TemplateSyntaxError, \ "forward_query_param tag requires an argument" param_name = param_name.strip('"') return ForwardQueryParamNode(param_name) template.register_tag('paging_control', do_paging_control) template.register_tag('sorting_control', do_sorting_control) template.register_tag('forward_query_param', do_forward_query_param)