summaryrefslogtreecommitdiff
path: root/tests/pagination
diff options
context:
space:
mode:
authorFlorian Apolloner <florian@apolloner.eu>2013-02-26 09:53:47 +0100
committerFlorian Apolloner <florian@apolloner.eu>2013-02-26 14:36:57 +0100
commit89f40e36246100df6a11316c31a76712ebc6c501 (patch)
tree6e65639683ddaf2027908d1ecb1739e0e2ff853b /tests/pagination
parentb3d2ccb5bfbaf6e7fe1f98843baaa48c35a70950 (diff)
downloaddjango-89f40e36246100df6a11316c31a76712ebc6c501.tar.gz
Merged regressiontests and modeltests into the test root.
Diffstat (limited to 'tests/pagination')
-rw-r--r--tests/pagination/__init__.py0
-rw-r--r--tests/pagination/custom.py20
-rw-r--r--tests/pagination/models.py11
-rw-r--r--tests/pagination/tests.py307
4 files changed, 338 insertions, 0 deletions
diff --git a/tests/pagination/__init__.py b/tests/pagination/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/pagination/__init__.py
diff --git a/tests/pagination/custom.py b/tests/pagination/custom.py
new file mode 100644
index 0000000000..47a932c7e3
--- /dev/null
+++ b/tests/pagination/custom.py
@@ -0,0 +1,20 @@
+from django.core.paginator import Paginator, Page
+
+
+class ValidAdjacentNumsPage(Page):
+
+ def next_page_number(self):
+ if not self.has_next():
+ return None
+ return super(ValidAdjacentNumsPage, self).next_page_number()
+
+ def previous_page_number(self):
+ if not self.has_previous():
+ return None
+ return super(ValidAdjacentNumsPage, self).previous_page_number()
+
+
+class ValidAdjacentNumsPaginator(Paginator):
+
+ def _get_page(self, *args, **kwargs):
+ return ValidAdjacentNumsPage(*args, **kwargs)
diff --git a/tests/pagination/models.py b/tests/pagination/models.py
new file mode 100644
index 0000000000..9dc8d4b776
--- /dev/null
+++ b/tests/pagination/models.py
@@ -0,0 +1,11 @@
+from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+
+
+@python_2_unicode_compatible
+class Article(models.Model):
+ headline = models.CharField(max_length=100, default='Default headline')
+ pub_date = models.DateTimeField()
+
+ def __str__(self):
+ return self.headline
diff --git a/tests/pagination/tests.py b/tests/pagination/tests.py
new file mode 100644
index 0000000000..dea5756672
--- /dev/null
+++ b/tests/pagination/tests.py
@@ -0,0 +1,307 @@
+from __future__ import absolute_import, unicode_literals
+
+from datetime import datetime
+
+from django.core.paginator import (Paginator, EmptyPage, InvalidPage,
+ PageNotAnInteger)
+from django.test import TestCase
+from django.utils import six
+from django.utils import unittest
+
+from .models import Article
+from .custom import ValidAdjacentNumsPaginator
+
+
+class PaginationTests(unittest.TestCase):
+ """
+ Tests for the Paginator and Page classes.
+ """
+
+ def check_paginator(self, params, output):
+ """
+ Helper method that instantiates a Paginator object from the passed
+ params and then checks that its attributes match the passed output.
+ """
+ count, num_pages, page_range = output
+ paginator = Paginator(*params)
+ self.check_attribute('count', paginator, count, params)
+ self.check_attribute('num_pages', paginator, num_pages, params)
+ self.check_attribute('page_range', paginator, page_range, params, coerce=list)
+
+ def check_attribute(self, name, paginator, expected, params, coerce=None):
+ """
+ Helper method that checks a single attribute and gives a nice error
+ message upon test failure.
+ """
+ got = getattr(paginator, name)
+ if coerce is not None:
+ got = coerce(got)
+ self.assertEqual(expected, got,
+ "For '%s', expected %s but got %s. Paginator parameters were: %s"
+ % (name, expected, got, params))
+
+ def test_paginator(self):
+ """
+ Tests the paginator attributes using varying inputs.
+ """
+ nine = [1, 2, 3, 4, 5, 6, 7, 8, 9]
+ ten = nine + [10]
+ eleven = ten + [11]
+ tests = (
+ # Each item is two tuples:
+ # First tuple is Paginator parameters - object_list, per_page,
+ # orphans, and allow_empty_first_page.
+ # Second tuple is resulting Paginator attributes - count,
+ # num_pages, and page_range.
+ # Ten items, varying orphans, no empty first page.
+ ((ten, 4, 0, False), (10, 3, [1, 2, 3])),
+ ((ten, 4, 1, False), (10, 3, [1, 2, 3])),
+ ((ten, 4, 2, False), (10, 2, [1, 2])),
+ ((ten, 4, 5, False), (10, 2, [1, 2])),
+ ((ten, 4, 6, False), (10, 1, [1])),
+ # Ten items, varying orphans, allow empty first page.
+ ((ten, 4, 0, True), (10, 3, [1, 2, 3])),
+ ((ten, 4, 1, True), (10, 3, [1, 2, 3])),
+ ((ten, 4, 2, True), (10, 2, [1, 2])),
+ ((ten, 4, 5, True), (10, 2, [1, 2])),
+ ((ten, 4, 6, True), (10, 1, [1])),
+ # One item, varying orphans, no empty first page.
+ (([1], 4, 0, False), (1, 1, [1])),
+ (([1], 4, 1, False), (1, 1, [1])),
+ (([1], 4, 2, False), (1, 1, [1])),
+ # One item, varying orphans, allow empty first page.
+ (([1], 4, 0, True), (1, 1, [1])),
+ (([1], 4, 1, True), (1, 1, [1])),
+ (([1], 4, 2, True), (1, 1, [1])),
+ # Zero items, varying orphans, no empty first page.
+ (([], 4, 0, False), (0, 0, [])),
+ (([], 4, 1, False), (0, 0, [])),
+ (([], 4, 2, False), (0, 0, [])),
+ # Zero items, varying orphans, allow empty first page.
+ (([], 4, 0, True), (0, 1, [1])),
+ (([], 4, 1, True), (0, 1, [1])),
+ (([], 4, 2, True), (0, 1, [1])),
+ # Number if items one less than per_page.
+ (([], 1, 0, True), (0, 1, [1])),
+ (([], 1, 0, False), (0, 0, [])),
+ (([1], 2, 0, True), (1, 1, [1])),
+ ((nine, 10, 0, True), (9, 1, [1])),
+ # Number if items equal to per_page.
+ (([1], 1, 0, True), (1, 1, [1])),
+ (([1, 2], 2, 0, True), (2, 1, [1])),
+ ((ten, 10, 0, True), (10, 1, [1])),
+ # Number if items one more than per_page.
+ (([1, 2], 1, 0, True), (2, 2, [1, 2])),
+ (([1, 2, 3], 2, 0, True), (3, 2, [1, 2])),
+ ((eleven, 10, 0, True), (11, 2, [1, 2])),
+ # Number if items one more than per_page with one orphan.
+ (([1, 2], 1, 1, True), (2, 1, [1])),
+ (([1, 2, 3], 2, 1, True), (3, 1, [1])),
+ ((eleven, 10, 1, True), (11, 1, [1])),
+ # Non-integer inputs
+ ((ten, '4', 1, False), (10, 3, [1, 2, 3])),
+ ((ten, '4', 1, False), (10, 3, [1, 2, 3])),
+ ((ten, 4, '1', False), (10, 3, [1, 2, 3])),
+ ((ten, 4, '1', False), (10, 3, [1, 2, 3])),
+ )
+ for params, output in tests:
+ self.check_paginator(params, output)
+
+ def test_invalid_page_number(self):
+ """
+ Tests that invalid page numbers result in the correct exception being
+ raised.
+ """
+ paginator = Paginator([1, 2, 3], 2)
+ self.assertRaises(InvalidPage, paginator.page, 3)
+ self.assertRaises(PageNotAnInteger, paginator.validate_number, None)
+ self.assertRaises(PageNotAnInteger, paginator.validate_number, 'x')
+ # With no content and allow_empty_first_page=True, 1 is a valid page number
+ paginator = Paginator([], 2)
+ self.assertEqual(paginator.validate_number(1), 1)
+
+ def test_paginate_misc_classes(self):
+ class CountContainer(object):
+ def count(self):
+ return 42
+ # Paginator can be passed other objects with a count() method.
+ paginator = Paginator(CountContainer(), 10)
+ self.assertEqual(42, paginator.count)
+ self.assertEqual(5, paginator.num_pages)
+ self.assertEqual([1, 2, 3, 4, 5], list(paginator.page_range))
+
+ # Paginator can be passed other objects that implement __len__.
+ class LenContainer(object):
+ def __len__(self):
+ return 42
+ paginator = Paginator(LenContainer(), 10)
+ self.assertEqual(42, paginator.count)
+ self.assertEqual(5, paginator.num_pages)
+ self.assertEqual([1, 2, 3, 4, 5], list(paginator.page_range))
+
+ def check_indexes(self, params, page_num, indexes):
+ """
+ Helper method that instantiates a Paginator object from the passed
+ params and then checks that the start and end indexes of the passed
+ page_num match those given as a 2-tuple in indexes.
+ """
+ paginator = Paginator(*params)
+ if page_num == 'first':
+ page_num = 1
+ elif page_num == 'last':
+ page_num = paginator.num_pages
+ page = paginator.page(page_num)
+ start, end = indexes
+ msg = ("For %s of page %s, expected %s but got %s."
+ " Paginator parameters were: %s")
+ self.assertEqual(start, page.start_index(),
+ msg % ('start index', page_num, start, page.start_index(), params))
+ self.assertEqual(end, page.end_index(),
+ msg % ('end index', page_num, end, page.end_index(), params))
+
+ def test_page_indexes(self):
+ """
+ Tests that paginator pages have the correct start and end indexes.
+ """
+ ten = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ tests = (
+ # Each item is three tuples:
+ # First tuple is Paginator parameters - object_list, per_page,
+ # orphans, and allow_empty_first_page.
+ # Second tuple is the start and end indexes of the first page.
+ # Third tuple is the start and end indexes of the last page.
+ # Ten items, varying per_page, no orphans.
+ ((ten, 1, 0, True), (1, 1), (10, 10)),
+ ((ten, 2, 0, True), (1, 2), (9, 10)),
+ ((ten, 3, 0, True), (1, 3), (10, 10)),
+ ((ten, 5, 0, True), (1, 5), (6, 10)),
+ # Ten items, varying per_page, with orphans.
+ ((ten, 1, 1, True), (1, 1), (9, 10)),
+ ((ten, 1, 2, True), (1, 1), (8, 10)),
+ ((ten, 3, 1, True), (1, 3), (7, 10)),
+ ((ten, 3, 2, True), (1, 3), (7, 10)),
+ ((ten, 3, 4, True), (1, 3), (4, 10)),
+ ((ten, 5, 1, True), (1, 5), (6, 10)),
+ ((ten, 5, 2, True), (1, 5), (6, 10)),
+ ((ten, 5, 5, True), (1, 10), (1, 10)),
+ # One item, varying orphans, no empty first page.
+ (([1], 4, 0, False), (1, 1), (1, 1)),
+ (([1], 4, 1, False), (1, 1), (1, 1)),
+ (([1], 4, 2, False), (1, 1), (1, 1)),
+ # One item, varying orphans, allow empty first page.
+ (([1], 4, 0, True), (1, 1), (1, 1)),
+ (([1], 4, 1, True), (1, 1), (1, 1)),
+ (([1], 4, 2, True), (1, 1), (1, 1)),
+ # Zero items, varying orphans, allow empty first page.
+ (([], 4, 0, True), (0, 0), (0, 0)),
+ (([], 4, 1, True), (0, 0), (0, 0)),
+ (([], 4, 2, True), (0, 0), (0, 0)),
+ )
+ for params, first, last in tests:
+ self.check_indexes(params, 'first', first)
+ self.check_indexes(params, 'last', last)
+
+ # When no items and no empty first page, we should get EmptyPage error.
+ self.assertRaises(EmptyPage, self.check_indexes, ([], 4, 0, False), 1, None)
+ self.assertRaises(EmptyPage, self.check_indexes, ([], 4, 1, False), 1, None)
+ self.assertRaises(EmptyPage, self.check_indexes, ([], 4, 2, False), 1, None)
+
+ def test_page_sequence(self):
+ """
+ Tests that a paginator page acts like a standard sequence.
+ """
+ eleven = 'abcdefghijk'
+ page2 = Paginator(eleven, per_page=5, orphans=1).page(2)
+ self.assertEqual(len(page2), 6)
+ self.assertTrue('k' in page2)
+ self.assertFalse('a' in page2)
+ self.assertEqual(''.join(page2), 'fghijk')
+ self.assertEqual(''.join(reversed(page2)), 'kjihgf')
+
+ def test_get_page_hook(self):
+ """
+ Tests that a Paginator subclass can use the ``_get_page`` hook to
+ return an alternative to the standard Page class.
+ """
+ eleven = 'abcdefghijk'
+ paginator = ValidAdjacentNumsPaginator(eleven, per_page=6)
+ page1 = paginator.page(1)
+ page2 = paginator.page(2)
+ self.assertIsNone(page1.previous_page_number())
+ self.assertEqual(page1.next_page_number(), 2)
+ self.assertEqual(page2.previous_page_number(), 1)
+ self.assertIsNone(page2.next_page_number())
+
+
+class ModelPaginationTests(TestCase):
+ """
+ Test pagination with Django model instances
+ """
+ def setUp(self):
+ # Prepare a list of objects for pagination.
+ for x in range(1, 10):
+ a = Article(headline='Article %s' % x, pub_date=datetime(2005, 7, 29))
+ a.save()
+
+ def test_first_page(self):
+ paginator = Paginator(Article.objects.all(), 5)
+ p = paginator.page(1)
+ self.assertEqual("<Page 1 of 2>", six.text_type(p))
+ self.assertQuerysetEqual(p.object_list, [
+ "<Article: Article 1>",
+ "<Article: Article 2>",
+ "<Article: Article 3>",
+ "<Article: Article 4>",
+ "<Article: Article 5>"
+ ],
+ ordered=False
+ )
+ self.assertTrue(p.has_next())
+ self.assertFalse(p.has_previous())
+ self.assertTrue(p.has_other_pages())
+ self.assertEqual(2, p.next_page_number())
+ self.assertRaises(InvalidPage, p.previous_page_number)
+ self.assertEqual(1, p.start_index())
+ self.assertEqual(5, p.end_index())
+
+ def test_last_page(self):
+ paginator = Paginator(Article.objects.all(), 5)
+ p = paginator.page(2)
+ self.assertEqual("<Page 2 of 2>", six.text_type(p))
+ self.assertQuerysetEqual(p.object_list, [
+ "<Article: Article 6>",
+ "<Article: Article 7>",
+ "<Article: Article 8>",
+ "<Article: Article 9>"
+ ],
+ ordered=False
+ )
+ self.assertFalse(p.has_next())
+ self.assertTrue(p.has_previous())
+ self.assertTrue(p.has_other_pages())
+ self.assertRaises(InvalidPage, p.next_page_number)
+ self.assertEqual(1, p.previous_page_number())
+ self.assertEqual(6, p.start_index())
+ self.assertEqual(9, p.end_index())
+
+ def test_page_getitem(self):
+ """
+ Tests proper behaviour of a paginator page __getitem__ (queryset
+ evaluation, slicing, exception raised).
+ """
+ paginator = Paginator(Article.objects.all(), 5)
+ p = paginator.page(1)
+
+ # Make sure object_list queryset is not evaluated by an invalid __getitem__ call.
+ # (this happens from the template engine when using eg: {% page_obj.has_previous %})
+ self.assertIsNone(p.object_list._result_cache)
+ self.assertRaises(TypeError, lambda: p['has_previous'])
+ self.assertIsNone(p.object_list._result_cache)
+
+ # Make sure slicing the Page object with numbers and slice objects work.
+ self.assertEqual(p[0], Article.objects.get(headline='Article 1'))
+ self.assertQuerysetEqual(p[slice(2)], [
+ "<Article: Article 1>",
+ "<Article: Article 2>",
+ ]
+ )