Helper dla Pagination w Django

Autor: nme · czwartek, 5 Sierpień, 2010 · Brak komentarzy ·

Wyświetlanie dużej ilości pozycji w aplikacji webowej jest zwykle związane z koniecznością zastosowania paginacji stron. Dobra nowina — Django takowego posiada. Zła nowina — brakuje w nim jednej prostej funkcji.

Ów funkcją jest zawężenie ilości stron do określonej ilości (lub odległości od aktualnie wybranej) i wstawienie wielokropków w określonych miejscach. Śpieszę więc z moim rozwiązaniem.

Aktualizacja: W kodzie funkcji został poprawiony drobny błąd logiczny oraz dodana została obsługa czegoś co nazwałem bounce — czyli "odbijania się"; polega to na tym, że jeśli wybrana jest strona druga, zakres wynosi 3, to z prawej strony widzimy elementy 3, 4, 5 oraz dzięki odbiciu — 6, 7, które powinny być widoczne po lewej stronie, ale wykroczyłoby poza zakres dostępnych stron. Podobnie paginator zachowa się, jeśli odwiedzalibyśmy ostatnie strony. Ta niewielka poprawka sprawia, że paginator zachowuje się znacznie przyjaźniej dla użytkownika. Przykładowe zastosowanie można objerzeć na stronie, dla której go stworzyłem — to strona z przepisami kuchennymi :)

#!/usr/bin/env python
# -*- coding: utf-8 -*-
def ranged_pages (pages, current, dist, border):
 """ Zwraca fragment listy /pages/ zawężoną do /dist/ wokół /current/
 oraz oddalone o /border/ od początku i końca listy """
    if bounce:
        bounce_left = pages.index(current)-dist
        bounce_right = len(pages)-pages.index(current)-1-dist
        if bounce_left < 0:
            dist += abs(bounce_left)
        if bounce_right < 0:
            dist += abs(bounce_right)
    return map(lambda x: [None,x][(x > current-dist-1) &
        (x < current+dist+1) | (pages.index(x) < border) |
        (pages.index(x) > len(pages)-border-1)],
        filter(lambda x: (x > current-dist-1) &
        (x < current+dist+1) | (pages.index(x) < border+1) |
        (pages.index(x) > len(pages)-border-2), pages))
# przykładowe zastosowanie:
pages = map(lambda x:x,range(1,25))
print 'dane wejsciowe:\n%s\n' % pages
print 'aktualny:%d, dystans:%d, od_granic:%d\n%s\n' % \
 ( 12,3,2, ranged_pages(pages, 12, 3, 2))
print 'aktualny:%d, dystans:%d, od_granic:%d\n%s\n' % \
 ( 5,4,3, ranged_pages(pages, 5, 4, 3))

Funkcja ranged_pages zajmuje się całym zadaniem. Jest ona jednocześnie przykładem jak można pisać efektywnego jednolinijkowca, który jest nieczytelny i brzydki jak noc.

Parametry które przyjmuje funkcja to kolejno:

  • lista stron z Pagination(objects).page_range
  • indeks aktualnej strony
  • ilość stron na lewo i prawo od aktualnej, które mają być wyświetlane
  • ilość stron od początku i końca listy

Po uruchomieniu przedstawionego wyżej przykładu dostajemy następujący wynik:

dane wejsciowe: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]

aktualny:12, dystans:3, od_granic:2 [1, 2, None, 9, 10, 11, 12, 13, 14, 15, None, 23, 24]

aktualny:5, dystans:4, od_granic:3 [1, 2, 3, 4, 5, 6, 7, 8, 9, None, 22, 23, 24]

Pozycję None oczywiście podmieniamy wielokropkiem. Robimy to już w konkretnym template.

Dlaczego nazwałem to helperem? Taką konwencję przyjąłem już dawno temu — większość funkcji konkretnej aplikacji które wykorzystywane są przez views.py umieszczam w pliku helpers.py z którego importuje do widoków. Rozwiązanie sprawdza się świetnie, szczególnie jeśli do kodu jakiegoś projektu nie zaglądam zbyt często — nie trzeba się głowić gdzie takie usprawniacze trzymamy.

Jeśli w swoim kodzie nie stosujesz zbyt często funkcji lambda, map, reduce i filter, uważam, że warto zapoznać się z ich dokumentacją. Inne przykłady ich praktycznego zastosowania przedstawiłem we wpisie po angielsku opisującym prosty konwerter CIDR na maskę bitową.

Zostaw komentarz