Updated documentation and reduced overall complexity by stripping away the registrati...
authorfloguy <floguy@7f1efe38-554e-0410-b69d-834cb44da2d5>
Tue, 1 Jul 2008 06:40:52 +0000 (06:40 +0000)
committerfloguy <floguy@7f1efe38-554e-0410-b69d-834cb44da2d5>
Tue, 1 Jul 2008 06:40:52 +0000 (06:40 +0000)
git-svn-id: https://django-pagination.googlecode.com/svn/trunk@15 7f1efe38-554e-0410-b69d-834cb44da2d5

INSTALL.txt
README.txt
pagination/middleware.py
pagination/registration.py [deleted file]
pagination/templatetags/pagination_tags.py
pagination/tests.py

index 8b13789..b370b5d 100644 (file)
@@ -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
index 8b13789..0834d6c 100644 (file)
@@ -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
index 8a541fb..dd69377 100644 (file)
@@ -1,4 +1,8 @@
 class PaginationMiddleware(object):
 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'])
     def process_request(self, request):
         try:
             request.page = int(request['page'])
diff --git a/pagination/registration.py b/pagination/registration.py
deleted file mode 100644 (file)
index 29edbbe..0000000
+++ /dev/null
@@ -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
index 6db06ba..8a88815 100644 (file)
@@ -3,83 +3,116 @@ try:
 except NameError:
     from sets import Set as set
 from django import template
 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.db.models.query import QuerySet
 from django.core.paginator import Paginator, QuerySetPaginator, InvalidPage
-#from django.template.loader import render_to_string
 
 register = template.Library()
 
 
 register = template.Library()
 
+DEFAULT_PAGINATION = 20
+DEFAULT_WINDOW = 4
+
 def do_autopaginate(parser, token):
 def do_autopaginate(parser, token):
+    """
+    Splits the arguments to the autopaginate tag and formats them correctly.
+    """
     split = token.split_contents()
     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:
     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):
 
 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):
 
     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:
             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''
                 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''
 
         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
     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:])
         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
         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 = []
             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)
         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 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
             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))))
             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 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 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
             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))))
             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,
             '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:
     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
 register.inclusion_tag('pagination/pagination.html', takes_context=True)(paginate)
 register.tag('autopaginate', do_autopaginate)
\ No newline at end of file
index 21aa350..a1d23f0 100644 (file)
@@ -1,6 +1,7 @@
 """
 >>> from django.core.paginator import Paginator
 >>> from pagination.templatetags.pagination_tags import paginate
 """
 >>> 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']
 
 >>> p = Paginator(range(15), 2)
 >>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
 >>> p = Paginator(range(21), 2)
 >>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
 [1, 2, 3, 4, None, 8, 9, 10, 11]
 >>> 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
 """
\ No newline at end of file