Skip to content
This repository was archived by the owner on Aug 11, 2021. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 12 additions & 25 deletions dj_pagination/settings.py → dj_pagination/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,28 +29,15 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


from django.conf import settings


DEFAULT_PAGINATION = getattr(settings, "PAGINATION_DEFAULT_PAGINATION", 20)
DEFAULT_WINDOW = getattr(settings, "PAGINATION_DEFAULT_WINDOW", 4)
DEFAULT_MARGIN = getattr(settings, "PAGINATION_DEFAULT_MARGIN", DEFAULT_WINDOW)
DEFAULT_ORPHANS = getattr(settings, "PAGINATION_DEFAULT_ORPHANS", 0)
INVALID_PAGE_RAISES_404 = getattr(settings, "PAGINATION_INVALID_PAGE_RAISES_404", False)
DISPLAY_PAGE_LINKS = getattr(settings, "PAGINATION_DISPLAY_PAGE_LINKS", True)
PREVIOUS_LINK_DECORATOR = getattr(
settings, "PAGINATION_PREVIOUS_LINK_DECORATOR", "‹‹ "
)
NEXT_LINK_DECORATOR = getattr(
settings, "PAGINATION_NEXT_LINK_DECORATOR", " ››"
)
DISPLAY_DISABLED_PREVIOUS_LINK = getattr(
settings, "PAGINATION_DISPLAY_DISABLED_PREVIOUS_LINK", False
)
DISPLAY_DISABLED_NEXT_LINK = getattr(
settings, "PAGINATION_DISPLAY_DISABLED_NEXT_LINK", False
)
DISABLE_LINK_FOR_FIRST_PAGE = getattr(
settings, "PAGINATION_DISABLE_LINK_FOR_FIRST_PAGE", True
)
DEFAULT_PAGINATION = 20
DEFAULT_WINDOW = 4
DEFAULT_MARGIN = DEFAULT_WINDOW
DEFAULT_ORPHANS = 0
INVALID_PAGE_RAISES_404 = False
INVALID_PAGE_TRIGGERS_301 = False
DISPLAY_PAGE_LINKS = True
PREVIOUS_LINK_DECORATOR = "‹‹ "
NEXT_LINK_DECORATOR = " ››"
DISPLAY_DISABLED_PREVIOUS_LINK = False
DISPLAY_DISABLED_NEXT_LINK = False
DISABLE_LINK_FOR_FIRST_PAGE = True
7 changes: 7 additions & 0 deletions dj_pagination/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,15 @@
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from django.shortcuts import redirect

try:
from django.utils.deprecation import MiddlewareMixin
except ImportError:
MiddlewareMixin = object

from .exceptions import PaginationRedirect


def get_page(self, suffix):
"""
Expand All @@ -59,3 +62,7 @@ class PaginationMiddleware(MiddlewareMixin):

def process_request(self, request):
request.__class__.page = get_page

def process_exception(self, request, exception):
if isinstance(exception, PaginationRedirect):
return redirect(exception.url, permanent=True)
47 changes: 31 additions & 16 deletions dj_pagination/templatetags/pagination_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,12 @@
from django.template.loader import select_template
from django.utils.text import unescape_string_literal

# TODO, import this normally later on
from dj_pagination.settings import *
from .. import defaults, exceptions


def get_setting(name):
"""Look for a setting by its name prepended by "PAGINATION_" or use its default value."""
return getattr(settings, "PAGINATION_%s" % name, getattr(defaults, name))


def do_autopaginate(parser, token):
Expand Down Expand Up @@ -140,9 +144,9 @@ def __init__(
context_var=None,
):
if paginate_by is None:
paginate_by = DEFAULT_PAGINATION
paginate_by = get_setting("DEFAULT_PAGINATION")
if orphans is None:
orphans = DEFAULT_ORPHANS
orphans = get_setting("DEFAULT_ORPHANS")
self.queryset_var = Variable(queryset_var)
if isinstance(paginate_by, int):
self.paginate_by = paginate_by
Expand Down Expand Up @@ -186,14 +190,22 @@ def render(self, context):
try:
page_obj = paginator.page(request.page(page_suffix))
except InvalidPage:
if INVALID_PAGE_RAISES_404:
if get_setting("INVALID_PAGE_RAISES_404"):
raise Http404(
"Invalid page requested. If DEBUG were set to "
+ "False, an HTTP 404 page would have been shown instead."
)
context[key] = []
context["invalid_page"] = True
return ""
if get_setting("INVALID_PAGE_TRIGGERS_301"):
getvars = request.GET.copy()
if "page%s" % page_suffix in getvars:
del getvars["page%s" % page_suffix]
getvars = "?%s" % getvars.urlencode() if len(getvars.keys()) > 0 else ""
url = "%s%s" % (request.path, getvars)
raise exceptions.PaginationRedirect(url)
else:
context[key] = []
context["invalid_page"] = True
return ""
if self.context_var is not None:
context[self.context_var] = page_obj.object_list
else:
Expand Down Expand Up @@ -241,7 +253,7 @@ def do_paginate(parser, token):
return PaginateNode(template)


def paginate(context, window=DEFAULT_WINDOW, margin=DEFAULT_MARGIN):
def paginate(context, window=None, margin=None):
"""
Renders the ``pagination/pagination.html`` template, resulting in a
Digg-like display of the available pages, given the current page. If there
Expand Down Expand Up @@ -276,11 +288,14 @@ def paginate(context, window=DEFAULT_WINDOW, margin=DEFAULT_MARGIN):
window=2, margin=0, current=5 ... 3 4 [5] 6 7 ...
window=2, margin=0, current=11 ... 7 8 9 10 [11]
"""

window = get_setting("DEFAULT_WINDOW") if window is None else window
if window < 0:
raise ValueError('Parameter "window" cannot be less than zero')

margin = get_setting("DEFAULT_MARGIN") if margin is None else margin
if margin < 0:
raise ValueError('Parameter "margin" cannot be less than zero')

try:
paginator = context["paginator"]
page_obj = context["page_obj"]
Expand Down Expand Up @@ -332,17 +347,17 @@ def paginate(context, window=DEFAULT_WINDOW, margin=DEFAULT_MARGIN):
new_context = {
"MEDIA_URL": settings.MEDIA_URL,
"STATIC_URL": getattr(settings, "STATIC_URL", None),
"disable_link_for_first_page": DISABLE_LINK_FOR_FIRST_PAGE,
"display_disabled_next_link": DISPLAY_DISABLED_NEXT_LINK,
"display_disabled_previous_link": DISPLAY_DISABLED_PREVIOUS_LINK,
"display_page_links": DISPLAY_PAGE_LINKS,
"disable_link_for_first_page": get_setting("DISABLE_LINK_FOR_FIRST_PAGE"),
"display_disabled_next_link": get_setting("DISPLAY_DISABLED_NEXT_LINK"),
"display_disabled_previous_link": get_setting("DISPLAY_DISABLED_PREVIOUS_LINK"),
"display_page_links": get_setting("DISPLAY_PAGE_LINKS"),
"is_paginated": paginator.count > paginator.per_page,
"next_link_decorator": NEXT_LINK_DECORATOR,
"next_link_decorator": get_setting("NEXT_LINK_DECORATOR"),
"page_obj": page_obj,
"page_suffix": page_suffix,
"pages": pages,
"paginator": paginator,
"previous_link_decorator": PREVIOUS_LINK_DECORATOR,
"previous_link_decorator": get_setting("PREVIOUS_LINK_DECORATOR"),
"records": records,
}
if "request" in context:
Expand Down
66 changes: 59 additions & 7 deletions dj_pagination/tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,19 @@
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from django.core.paginator import Paginator
from django.http import HttpRequest as DjangoHttpRequest
from django.http import Http404, HttpRequest as DjangoHttpRequest, QueryDict
from django.template import Template, Context

from django.test import override_settings
try:
from django.test import SimpleTestCase
except ImportError: # Django 1.2 compatible
from django.test import TestCase as SimpleTestCase

from dj_pagination.paginator import InfinitePaginator, FinitePaginator
from dj_pagination.templatetags.pagination_tags import paginate
from dj_pagination.middleware import PaginationMiddleware

from dj_pagination import middleware
from dj_pagination.exceptions import PaginationRedirect

class HttpRequest(DjangoHttpRequest):
page = lambda self, suffix: 1
Expand Down Expand Up @@ -322,6 +321,42 @@ def test_multiple_pagination(self):
self.assertIn('<a href="?page_var2=2"', content)
self.assertIn('<a href="?page_var=2"', content)

def test_invalid_page_fail_silently(self):
t = Template(
"{% load pagination_tags %}"
"{% autopaginate var %}{% paginate %}invalid:{{ invalid_page }}"
)
request = DjangoHttpRequest()
request.GET = QueryDict("a=2&page=3")
content = t.render(Context({"var": range(21), "request": request}))
self.assertNotIn('<div class="pagination">', content)
self.assertIn('invalid:True', content)

@override_settings(PAGINATION_INVALID_PAGE_TRIGGERS_301=True)
def test_invalid_page_redirect_only_page(self):
t = Template("{% load pagination_tags %}{% autopaginate var %}{% paginate %}")
request = DjangoHttpRequest()
request.GET = QueryDict("page=3")
with self.assertRaises(PaginationRedirect) as e:
t.render(Context({"var": range(21), "request": request}))
self.assertEqual(e.exception.url, '')

@override_settings(PAGINATION_INVALID_PAGE_TRIGGERS_301=True)
def test_invalid_page_redirect_other_parameters(self):
t = Template("{% load pagination_tags %}{% autopaginate var %}{% paginate %}")
request = DjangoHttpRequest()
request.GET = QueryDict("a=2&page=3")
with self.assertRaises(PaginationRedirect) as e:
t.render(Context({"var": range(21), "request": request}))
self.assertEqual(e.exception.url, '?a=2')

@override_settings(PAGINATION_INVALID_PAGE_RAISES_404=True)
def test_invalid_page_raise_404(self):
t = Template("{% load pagination_tags %}{% autopaginate var %}{% paginate %}")
request = DjangoHttpRequest()
request.GET = QueryDict("a=2&page=3")
with self.assertRaises(Http404):
t.render(Context({"var": range(21), "request": request}))

class InfinitePaginatorTestCase(SimpleTestCase):
def setUp(self):
Expand Down Expand Up @@ -424,7 +459,24 @@ class MiddlewareTestCase(SimpleTestCase):
"""

def test_get_page_in_request(self):
middleware = PaginationMiddleware()
middleware_instance = middleware.PaginationMiddleware()
request = DjangoHttpRequest()
middleware.process_request(request)
middleware_instance.process_request(request)
self.assertEqual(request.page(""), 1)

def test_get_page_in_request_exception(self):
# We are mocking the redirect method without unittest mock as it is
# difficult to add it to the project for Python 2 and 3
def mock_redirect(url, permanent):
return url, permanent

middleware_instance = middleware.PaginationMiddleware()
request = DjangoHttpRequest()
exception = PaginationRedirect("example.com")
origin_redirect = middleware.redirect
try:
middleware.redirect = mock_redirect
response = middleware_instance.process_exception(request, exception)
self.assertEqual(response, ('example.com', True))
finally:
middleware.redirect = origin_redirect
6 changes: 6 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ behavior of the pagination tags. Here's an overview:
``invalid_page`` context variable. ``True`` does the former and ``False``
does the latter. Defaults to False

``PAGINATION_INVALID_PAGE_TRIGGERS_301``
Determines whether an invalid page triggers a 301 redirect to page 1 or just
sets the ``invalid_page`` context variable. ``True`` does the former and
``False`` does the latter. Defaults to False. Only active if
``PAGINATION_INVALID_PAGE_RAISES_404`` is set to False.

``PAGINATION_DISPLAY_PAGE_LINKS``
If set to ``False``, links for single pages will not be displayed. Defaults to True.

Expand Down