Декоратор для удобного постраничного разбиения
Стандартное решение предлагаемое джанго для постраничного разбиения списка объектов - это класс django.core.paginator.ObjectPaginator
Здесь можно посмотреть примеры использования ObjectPaginator [djangoproject.com]
Однако, использование ObjectPaginator в голом виде не совсем удобно. В самом деле, нужно:
- извлечь номер страницы из GET-данных или URL запроса
- создать QuerySet объектов для пагинации
- создать ObjectPaginator, передав ему этот QuerySet
- получить объекты текущей страницы, путём вызова метода get_page у пагинатора
Вся эта рутина так и просится в декоратор. Без лишних слов, собственно, код декоратора )
Поясню, как я его использую. Дело в том, что все мои view возвращают не объект класса HttpResponse, но обыкновенный словарь, который потом обрабатывается декоратором render_to. Подробнее об этом вы можете прочитать в этой статье [web-brains.com]
Ну а в декораторе paged я просто дополняю этот словарь, который вернула моя view. Я кладу в словарь элементы с ключами page, pages и список объектов. Имя элемента со списком задаётся в первом позиционном аргументе декоратора. Чтобы декоратор работал, нужно из view вернуть QuerySet объектов для пагинации в элементе с ключом paged_qs.
Код ещё сырой, я его написал час назад. До этого я использова не такой навороченный декоратор. Если у вас есть замечания и предложения, с радостью выслушаю.
Ну, и, конечно, было бы неплохо, чтобы список со страничками сам строился ;-)
В этом нам поможет следующий inclusion tag.
А это pagination.html
Здесь можно посмотреть примеры использования ObjectPaginator [djangoproject.com]
Однако, использование ObjectPaginator в голом виде не совсем удобно. В самом деле, нужно:
- извлечь номер страницы из GET-данных или URL запроса
- создать QuerySet объектов для пагинации
- создать ObjectPaginator, передав ему этот QuerySet
- получить объекты текущей страницы, путём вызова метода get_page у пагинатора
Вся эта рутина так и просится в декоратор. Без лишних слов, собственно, код декоратора )
def paged(paged_list_name, per_page=10):
def decorator(func):
def wrapper(request, *args, **kwargs):
result = func(request, *args, **kwargs)
if not isinstance(result, dict):
return result
try:
page = int(request.GET.get('page', 1))
except ValueError:
page = 1
from django.core.paginator import ObjectPaginator
paginator = ObjectPaginator(result['paged_qs'], per_page)
result[paged_list_name] = paginator.get_page(page - 1)
result['page'] = page
result['pages'] = paginator.pages
return result
return wrapper
return decorator
Поясню, как я его использую. Дело в том, что все мои view возвращают не объект класса HttpResponse, но обыкновенный словарь, который потом обрабатывается декоратором render_to. Подробнее об этом вы можете прочитать в этой статье [web-brains.com]
Ну а в декораторе paged я просто дополняю этот словарь, который вернула моя view. Я кладу в словарь элементы с ключами page, pages и список объектов. Имя элемента со списком задаётся в первом позиционном аргументе декоратора. Чтобы декоратор работал, нужно из view вернуть QuerySet объектов для пагинации в элементе с ключом paged_qs.
Код ещё сырой, я его написал час назад. До этого я использова не такой навороченный декоратор. Если у вас есть замечания и предложения, с радостью выслушаю.
Ну, и, конечно, было бы неплохо, чтобы список со страничками сам строился ;-)
В этом нам поможет следующий inclusion tag.
@register.inclusion_tag('site/pagination.html',takes_context=True)
def pagination(context,adjacent_pages=5):
page_list = range(
max(1,context['page'] - adjacent_pages),
min(context['pages'],context['page'] + adjacent_pages) + 1)
lower_page = None
higher_page = None
if not 1 == context['page']:
lower_page = context['page'] - 1
if not 1 in page_list:
page_list.insert(0,1)
if not 2 in page_list:
page_list.insert(1,'.')
if not context['pages'] == context['page']:
higher_page = context['page'] + 1
if not context['pages'] in page_list:
if not context['pages'] - 1 in page_list:
page_list.append('.')
page_list.append(context['pages'])
get_params = '&'.join(['%s=%s' % (x[0],','.join(x[1])) for x in
context['request'].GET.iteritems() if not x[0] == 'page'])
if get_params:
get_params = get_params + '&'
return {
'get_params': get_params,
'lower_page': lower_page,
'higher_page': higher_page,
'page': context['page'],
'pages': context['pages'],
'page_list': page_list}
А это pagination.html
<div class="pagination">
{% ifnotequal pages 1 %}
Страницы:
{% if lower_page %}
<a href="?page={{ lower_page }}">«</a>
{% endif %}
{% for number in page_list %}
{% ifequal number "." %}...
{% else %}
{% ifequal number page %}{{ number }}{% endifequal %}
{% ifnotequal number page %}
<a href="?page={{ number }}">{{ number }}</a>
{% endifnotequal %}
{% endifequal %}
{% endfor %}
{% if higher_page %}
<a href="?page={{ higher_page }}">»</a>
{% endif %}
{% endifnotequal %}
</div>






Comments
Отлично, заюзал оба декоратора :)
Чтобы не заморачиваься с paged_qs, предлагаю заменить:
На:
Гм, действительно. Только вот пока сомневаюсь, юзать ли это улучшение в моих проектах. Я уже наделал кучу на старом декораторе и, видимо, начну путаться, если сейчас заюзаю новую версию в некоторых проектах )
Столкнулся с ситуацией, когда страниц много и при больших цифрах пейджер не вмещается в строку. А при уменьшении adjacent_pages не хорошо на границах. Захотелось, чтобы на границах adjacent_pages увеличивался.
Заменил:
На:
В резуальтате получается что-то типа:
<<< 1 … 15208 15209 15210 15211 15212 15213 15214 … 15220 >>>
Ох, попозже помедирую. Ты, кстати, а аську не писал? Вроде приходил запрос авторизации, но аську у меня вышибло, так что тока жабер работает.
Не. Хотел было, но подумал, что тебе сейчас не до меня :)
Где-то тут бага: get_params = ‘&’.join([‘%s=%s’ % (x[0],’,’.join(x[1])) for x in context[‘request’].GET.iteritems() if not x[0] == ‘page’])
Пипец, ну и месиво же я полтора года назад писал ))
Как-нить соберусь на недельке и перепишу этот древнючий код, тем более, он в pybb используется.
О, респект, нашёл. В этом кусочке ’,’.join нужно заменить на ’’.join
В {{ get_params }}, наверное, нужно |safe добавить.
И ещё херь непонятная. Вот, глянь, что с параметром t сталося: http://pydev.ru/forum/topic/318/?t=test
Кстати, в плане сео пажинация не идеальна.
В общем случае нужно равномерное распределение пиара по страницам. Тогда, как минимум, ссылки на первую и последнюю страницы имеет смысл выводить через js. Иначе на них будет скапливаться большой пиар, они же на каждой паге отображаются.