Login

Pagination Alphabetically compatible with paginator_class

Author:
vascop
Posted:
April 19, 2012
Language:
Python
Version:
1.4
Score:
0 (after 0 ratings)

This is just a modified version of a previous snippet to make it work with unicode and with class-based ListView paginator_class

To use it put this in your urls.py:

from youapp.fileyouchose import NamePaginator urlpatterns = patterns('', url(r'^example/(?P<page>[0-9]+)/$', ListView.as_view(model=myModel,template_name="mytemplate.html",paginator_class=NamePaginator,paginate_by=25), name="url_name"),

And then in your template something like this would work:

{% if page_obj.has_other_pages %}
<div class="row">
    <div class="span12">
        <div class="pagination">
            <ul>
            {% if page_obj.has_previous %}
                <li><a href="{% url page page=page_obj.previous_page_number %}">Prev</a></li>
            {% else %}
                <li class="disabled"><a>Prev</a></li>
            {% endif %}
            {% for p in page_obj.paginator.pages %}
                <li {% if p == page_obj %}class="active"{% endif %}>
                    <a href="{% url category_page page=p.number %}">{{ p }}</a>
                </li>
            {% endfor %}
            {% if page_obj.has_next %}
                <li><a href="{% url page page=page_obj.next_page_number %}">Next</a></li>
            {% else %}
                <li class="disabled"><a>Next</a></li>
            {% endif %}
            </ul>
        </div>
    </div>
</div>
{% endif %}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import string
from django.core.paginator import InvalidPage, EmptyPage

class NamePaginator(object):
    """Pagination for string-based objects"""

    def __init__(self, queryset, paginate_by=25, orphans=0, allow_empty_first_page=True):
        # We ignore allow_empty_first_page and orphans, just here for compliance
        self.pages = []
        self.object_list = queryset
        self.count = len(self.object_list)

        # chunk up the objects so we don't need to iterate over the whole list for each letter
        chunks = {}

        # we sort them by the first model ordering key
        for obj in self.object_list:
            if queryset: obj_str = unicode(getattr(obj, obj._meta.ordering[0]))
            else: obj_str = unicode(obj)

            # some of my models had "first_name" "last_name" sorting, and some first_names
            # were empty so if it fails you can try sorting by the second ordering key
            # which worked for me but do your own thing
            try:
                letter = unicode.upper(obj_str[0])
            except:
                obj_str = unicode(getattr(obj, obj._meta.ordering[1]))
                letter = unicode.upper(obj_str[1])

            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 > paginate_by and \
                    abs(paginate_by - current_page.count) < abs(paginate_by - 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

    # just added the methods I needed to use in the templates
    # feel free to add the ones you need too
    def has_other_pages(self):
        return len(self.object_list) > 0

    def has_previous(self):
        return self.paginator.pages.index(self)

    def has_next(self):
        return self.paginator.pages.index(self) + 2

    def next_page_number(self):
        return self.paginator.pages.index(self) + 2

    def previous_page_number(self):
        return self.paginator.pages.index(self)

    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)

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 6 months ago

Comments

stevenjoseph (on April 19, 2012):

Thanks for this, made a few modifications which might be useful:

16c16,17
<             if on: obj_str = str(getattr(obj, on))
---
>             if on !=None:
>                 obj_str = str(getattr(obj, on)) if isinstance(obj,(str,unicode)) else obj[on]
18,20c19,21       
<             letter = str.upper(obj_str[0])   
--- 
>             letter = obj_str[0].upper()

#

Please login first before commenting.