import string
from django.core.paginator import InvalidPage, EmptyPage
class NamePaginator(object):
"""Pagination for string-based objects"""
def __init__(self, object_list, on=None, per_page=25):
self.object_list = object_list
self.count = len(object_list)
self.pages = []
# chunk up the objects so we don't need to iterate over the whole list for each letter
chunks = {}
for obj in self.object_list:
if on: obj_str = str(getattr(obj, on))
else: obj_str = str(obj)
letter = str.upper(obj_str[0])
if letter not in chunks: chunks[letter] = []
chunks[letter].append(obj)
# the process for assigning objects to each page
current_page = NamePage(self)
for letter in string.ascii_uppercase:
if letter not in chunks:
current_page.add([], letter)
continue
sub_list = chunks[letter] # the items in object_list starting with this letter
new_page_count = len(sub_list) + current_page.count
# first, check to see if sub_list will fit or it needs to go onto a new page.
# if assigning this list will cause the page to overflow...
# and an underflow is closer to per_page than an overflow...
# and the page isn't empty (which means len(sub_list) > per_page)...
if new_page_count > per_page and \
abs(per_page - current_page.count) < abs(per_page - new_page_count) and \
current_page.count > 0:
# make a new page
self.pages.append(current_page)
current_page = NamePage(self)
current_page.add(sub_list, letter)
# if we finished the for loop with a page that isn't empty, add it
if current_page.count > 0: self.pages.append(current_page)
def page(self, num):
"""Returns a Page object for the given 1-based page number."""
if len(self.pages) == 0:
return None
elif num > 0 and num <= len(self.pages):
return self.pages[num-1]
else:
raise InvalidPage
@property
def num_pages(self):
"""Returns the total number of pages"""
return len(self.pages)
class NamePage(object):
def __init__(self, paginator):
self.paginator = paginator
self.object_list = []
self.letters = []
@property
def count(self):
return len(self.object_list)
@property
def start_letter(self):
if len(self.letters) > 0:
self.letters.sort(key=str.upper)
return self.letters[0]
else: return None
@property
def end_letter(self):
if len(self.letters) > 0:
self.letters.sort(key=str.upper)
return self.letters[-1]
else: return None
@property
def number(self):
return self.paginator.pages.index(self) + 1
def add(self, new_list, letter=None):
if len(new_list) > 0: self.object_list = self.object_list + new_list
if letter: self.letters.append(letter)
def __repr__(self):
if self.start_letter == self.end_letter:
return self.start_letter
else:
return '%c-%c' % (self.start_letter, self.end_letter)
Comments
Very useful. I've made it unicode-safe (replace all
str()withunicode()and the__repr__()with__unicode__(), iterate on chunks sorted by key instead of iterating onstring.ascii_uppercase, don't usestr().upper()but<obj>.upper()instead) and more complete. Working on putting objects starting with numbers or symbols (in the unicode sense) in their own chunk too.#
Thanks for the snippet and thanks to HM for the unicode-ification. Said that, there are a couple of typos in the template: need ifequal instead if and p instead of page. Should be:
{% ifequal p page %}<span class="selected">{{ page }}</span>{% else %}<a href="?page={{ p.number }}">{{ p }}</a>{% endifequal %}#
How hard would it be to change the pagination a little to offer 26 pages, 1 per letter, instead of x pages, 25 (or y) entries per page (across letters)? Is this a good place to start, or should I be using another alg?
#
@datakid23
what You talking about is not a pagination but a filtration
#
You can look at #2025 ;]
#
HM, just curious if you ever finished working on your version of the code? If so, would you ever consider posting it for others to learn from?
#
Hi
It would be good to have a configurable parameter which if specified would accordingly paginate alphabetically or in groups
For example I want the whole letters to be displayed (Even return blank page if there are no objects for the alphabet searched )
#
@HM it is possible to you to update the unicode stuff?? It would be nice ;)
#