M INSTALL.txt => INSTALL.txt +18 -0
@@ 1,1 1,19 @@
+Installing django-pagination
+----------------------------
+To install, first check out the latest version of the application from
+subversion:
+
+ svn co http://django-pagination.googlecode.com/svn/trunk django-pagination
+
+Now, link the inner ``pagination`` project to your Python path:
+
+ sudo ln -s `pwd`/pagination SITE_PACKAGES_DIR/pagination
+
+If you don't know the location of your site packages directory, this hack might
+do the trick for you:
+
+ sudo ln -s `pwd`/pagination `python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()"`/pagination
+
+Now it's installed. Please see README.txt for information on how to use this
+application in your projects.<
\ No newline at end of file
M README.txt => README.txt +59 -0
@@ 1,1 1,60 @@
+How to use django-pagination
+----------------------------
+``django-pagination`` allows for easy Digg-style pagination without modifying
+your views.
+
+There are really 5 steps to setting it up with your projects (not including
+installation, which is covered in INSTALL.txt in this same directory.)
+
+1. List this application in the ``INSTALLED_APPS`` portion of your settings
+ file. Your settings file might look something like:
+
+ INSTALLED_APPS = (
+ # ...
+ 'pagination',
+ )
+
+
+2. Install the pagination middleware. Your settings file might look something
+ like:
+
+ MIDDLEWARE_CLASSES = (
+ # ...
+ 'pagination.middleware.PaginationMiddleware',
+ )
+
+
+3. Add this line at the top of your template to load the pagination tags:
+
+ {% load pagination_tags %}
+
+
+4. Decide on a variable that you would like to paginate, and use the
+ autopaginate tag on that variable before iterating over it. This could
+ take one of two forms (using the canonical ``object_list`` as an example
+ variable):
+
+ {% autopaginate object_list %}
+
+ This assumes that you would like to have the default 20 results per page.
+ If you would like to specify your own amount of results per page, you can
+ specify that like so:
+
+ {% autopaginate object_list 10 %}
+
+ Note that this replaces ``object_list`` with the list for the current page, so
+ you can iterate over the ``object_list`` like you normally would.
+
+
+5. Now you want to display the current page and the available pages, so
+ somewhere after having used autopaginate, use the paginate inclusion tag:
+
+ {% paginate %}
+
+ This does not take any arguments, but does assume that you have already
+ called autopaginate, so make sure to do so first.
+
+
+That's it! You have now paginated ``object_list`` and given users of the site
+a way to navigate between the different pages--all without touching your views.<
\ No newline at end of file
M => +4 -0
@@ 1,4 1,8 @@
class PaginationMiddleware(object):
"""
Inserts a variable representing the current page onto the request object if
it exists in either **GET** or **POST** portions of the request.
"""
def process_request(self, request):
try:
request.page = int(request['page'])
D => +0 -27
@@ 1,27 0,0 @@
from django.conf import settings
default_pagination = getattr(settings, 'DEFAULT_PAGINATION', 20)
class PaginationRegistrar(object):
_registry = {}
def register(self, model, pagination=None):
self._registry[model] = pagination or default_pagination
def unregister(self, model):
try:
del self._registry[model]
except KeyError:
return
def get_for_model(self, model):
if model in self._registry:
return self._registry[model]
return None
def get_registry():
registry = getattr(settings, '_pagination_registry', None)
if registry is None:
registry = PaginationRegistrar()
setattr(settings, '_pagination_registry', registry)
return registry
\ No newline at end of file
M => +115 -63
@@ 3,83 3,116 @@ try:
except NameError:
from sets import Set as set
from django import template
from pagination.registration import get_registry, default_pagination
registry = get_registry()
from django.db.models.query import QuerySet
from django.core.paginator import Paginator, QuerySetPaginator, InvalidPage
#from django.template.loader import render_to_string
register = template.Library()
DEFAULT_PAGINATION = 20
DEFAULT_WINDOW = 4
def do_autopaginate(parser, token):
"""
Splits the arguments to the autopaginate tag and formats them correctly.
"""
split = token.split_contents()
if len(split) == 1:
return AutoPaginateNode()
elif len(split) == 2:
return AutoPaginateNode(queryset_var=split[1])
if len(split) == 2:
return AutoPaginateNode(split[1])
elif len(split) == 3:
try:
paginate_by = int(split[2])
except ValueError:
raise template.TemplateSyntaxError(u'Got %s, but expected integer.' % split[2])
return AutoPaginateNode(split[1], paginate_by=paginate_by)
else:
raise template.TemplateSyntaxError('%r tag takes only one optional argument.' % split[0])
raise template.TemplateSyntaxError('%r tag takes one required argument and one optional argument' % split[0])
class AutoPaginateNode(template.Node):
def __init__(self, queryset_var=None):
if queryset_var:
self.queryset_var = template.Variable(queryset_var)
else:
self.queryset_var = None
"""
Emits the required objects to allow for Digg-style pagination.
First, it looks in the current context for the variable specified. This
should be either a QuerySet or a list.
1. If it is a QuerySet, this ``AutoPaginateNode`` will emit a
``QuerySetPaginator`` and the current page object into the context names
``paginator`` and ``page_obj``, respectively.
2. If it is a list, this ``AutoPaginateNode`` will emit a simple
``Paginator`` and the current page object into the context names
``paginator`` and ``page_obj``, respectively.
It will then replace the variable specified with only the objects for the
current page.
.. note::
It is recommended to use *{% paginate %}* after using the autopaginate
tag. If you choose not to use *{% paginate %}*, make sure to display the
list of availabale pages, or else the application may seem to be buggy.
"""
def __init__(self, queryset_var, paginate_by=DEFAULT_PAGINATION):
self.queryset_var = template.Variable(queryset_var)
self.paginate_by = paginate_by
def render(self, context):
if self.queryset_var is not None:
key = self.queryset_var.var
value = self.queryset_var.resolve(context)
if issubclass(value.__class__, QuerySet):
model = value.model
paginator_class = QuerySetPaginator
else:
value = list(value)
try:
key = self.queryset_var.var
value = self.queryset_var.resolve(context)
if issubclass(value.__class__, QuerySet):
model = value.model
paginator_class = QuerySetPaginator
else:
value = list(value)
try:
model = value[0].__class__
except IndexError:
return u''
paginator_class = Paginator
pagination = registry.get_for_model(model)
if pagination is None:
pagination = default_pagination
paginator = paginator_class(value, pagination)
try:
page_obj = paginator.page(context['request'].page)
except:
return u''
context[key] = page_obj.object_list
context['paginator'] = paginator
context['page_obj'] = page_obj
model = value[0].__class__
except IndexError:
return u''
except template.VariableDoesNotExist:
pass
for d in context:
for key, value in d.iteritems():
if issubclass(value.__class__, QuerySet):
model = value.model
pagination = registry.get_for_model(model)
if pagination is not None:
paginator = QuerySetPaginator(value, pagination)
try:
page_obj = paginator.page(context['request'].page)
except:
return u''
context[key] = page_obj.object_list
context['paginator'] = paginator
context['page_obj'] = page_obj
return u''
paginator_class = Paginator
paginator = paginator_class(value, self.paginate_by)
try:
page_obj = paginator.page(context['request'].page)
except:
return u''
context[key] = page_obj.object_list
context['paginator'] = paginator
context['page_obj'] = page_obj
return u''
def paginate(context, window=4):
def paginate(context, window=DEFAULT_WINDOW):
"""
Renders the ``pagination/pagination.html`` template, resulting in a
Digg-like display of the available pages, given the current page. If there
are too many pages to be displayed before and after the current page, then
elipses will be used to indicate the undisplayed gap between page numbers.
Requires one argument, ``context``, which should be a dictionary-like data
structure and must contain the following keys:
``paginator``
A ``Paginator`` or ``QuerySetPaginator`` object.
``page_obj``
This should be the result of calling the page method on the
aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given
the current page.
This same ``context`` dictionary-like data structure may also include:
``getvars``
A dictionary of all of the **GET** parameters in the current request.
This is useful to maintain certain types of state, even when requesting
a different page.
"""
try:
paginator = context['paginator']
page_obj = context['page_obj']
page_range = paginator.page_range
# First and last are simply the first *n* pages and the last *n* pages,
# where *n* is the current window size.
first = set(page_range[:window])
last = set(page_range[-window:])
# Now we look around our current page, making sure that we don't wrap
# around.
current_start = page_obj.number-1-window
if current_start < 0:
current_start = 0
@@ 88,44 121,63 @@ def paginate(context, window=4):
current_end = 0
current = set(page_range[current_start:current_end])
pages = []
# If there's no overlap between the first set of pages and the current
# set of pages, then there's a possible need for elusion.
if len(first.intersection(current)) == 0:
first_list = sorted(list(first))
second_list = sorted(list(current))
pages.extend(first_list)
diff = second_list[0] - first_list[-1]
diff = second_list[0] - first_list[-1]
# If there is a gap of two, between the last page of the first
# set and the first page of the current set, then we're missing a
# page.
if diff == 2:
pages.append(second_list[0] - 1)
# If the difference is just one, then there's nothing to be done,
# as the pages need no elusion and are correct.
elif diff == 1:
pass
# Otherwise, there's a bigger gap which needs to be signaled for
# elusion, by pushing a None value to the page list.
else:
pages.append(None)
pages.extend(second_list)
else:
pages.extend(sorted(list(first.union(current))))
# If there's no overlap between the current set of pages and the last
# set of pages, then there's a possible need for elusion.
if len(current.intersection(last)) == 0:
second_list = sorted(list(last))
diff = second_list[0] - pages[-1]
# If there is a gap of two, between the last page of the current
# set and the first page of the last set, then we're missing a
# page.
if diff == 2:
pages.append(second_list[0] - 1)
# If the difference is just one, then there's nothing to be done,
# as the pages need no elusion and are correct.
elif diff == 1:
pass
# Otherwise, there's a bigger gap which needs to be signaled for
# elusion, by pushing a None value to the page list.
else:
pages.append(None)
pages.extend(second_list)
else:
pages.extend(sorted(list(last.difference(current))))
getvars = context['request'].GET.copy()
if 'page' in getvars:
del getvars['page']
return {
to_return = {
'pages': pages,
'page_obj': page_obj,
'paginator': paginator,
'is_paginated': paginator.count > paginator.per_page,
'getvars': "&%s" % getvars.urlencode()
}
if 'request' in context:
getvars = context['request'].GET.copy()
if 'page' in getvars:
del getvars['page']
to_return['getvars'] = "&%s" % getvars.urlencode()
return to_return
except KeyError:
return u''
return {}
register.inclusion_tag('pagination/pagination.html', takes_context=True)(paginate)
register.tag('autopaginate', do_autopaginate)
\ No newline at end of file
M => +22 -0
@@ 1,6 1,7 @@
"""
>>> from django.core.paginator import Paginator
>>> from pagination.templatetags.pagination_tags import paginate
>>> from django.template import Template, Context
>>> p = Paginator(range(15), 2)
>>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
@@ 17,4 18,25 @@
>>> p = Paginator(range(21), 2)
>>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
[1, 2, 3, 4, None, 8, 9, 10, 11]
>>> t = Template("{% load pagination_tags %}{% autopaginate var 2 %}{% paginate %}")
# WARNING: Please, please nobody read this portion of the code!
>>> class GetProxy(object):
... def __iter__(self): yield self.__dict__.__iter__
... def copy(self): return self
... def urlencode(self): return u''
>>> class RequestProxy(object):
... page = 1
... GET = GetProxy()
>>>
# ENDWARNING
>>> t.render(Context({'var': range(21), 'request': RequestProxy()}))
u'\\n<div class="pagination">...
>>>
>>> t = Template("{% load pagination_tags %}{% autopaginate var %}{% paginate %}")
>>> t.render(Context({'var': range(21), 'request': RequestProxy()}))
u'\\n<div class="pagination">...
>>>
"""
\ No newline at end of file