Coverage for src/lektor_ng/pagination.py: 98%
54 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-03 17:58 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-03 17:58 +0000
1class Pagination:
2 def __init__(self, record, pagination_config):
3 #: the pagination config
4 self.config = pagination_config
5 #: the current page's record
6 self.current = record
7 #: the current page number (1 indexed)
8 self.page = record.page_num
9 #: the number of items to be displayed on a page.
10 self.per_page = pagination_config.per_page
11 #: the total number of items matching the query
12 self.total = pagination_config.count_total_items(record)
14 @property
15 def items(self):
16 """The children for this page."""
17 return self.config.slice_query_for_page(self.current, self.page)
19 @property
20 def pages(self):
21 """The total number of pages."""
22 pages = (self.total + self.per_page - 1) // self.per_page
23 # Even when there are no children, we want at least one page
24 return max(pages, 1)
26 @property
27 def prev_num(self):
28 """The page number of the previous page."""
29 if self.page > 1:
30 return self.page - 1
31 return None
33 @property
34 def has_prev(self):
35 """True if a previous page exists."""
36 return self.page > 1
38 @property
39 def prev(self):
40 """The record for the previous page."""
41 if not self.has_prev:
42 return None
43 return self.config.get_record_for_page(self.current, self.page - 1)
45 @property
46 def has_next(self):
47 """True if a following page exists."""
48 return self.page < self.pages
50 @property
51 def next_num(self):
52 """The page number of the following page."""
53 if self.page < self.pages:
54 return self.page + 1
55 return None
57 @property
58 def next(self):
59 """The record for the following page."""
60 if not self.has_next:
61 return None
62 return self.config.get_record_for_page(self.current, self.page + 1)
64 def for_page(self, page):
65 """Returns the record for a specific page."""
66 if 1 <= page <= self.pages:
67 return self.config.get_record_for_page(self.current, page)
68 return None
70 def iter_pages(self, left_edge=2, left_current=2, right_current=5, right_edge=2):
71 """Iterate over the page numbers in the pagination, with elision.
73 In the general case, this returns the concatenation of three ranges:
75 1. A range (always starting at page one) at the beginning
76 of the page number sequence. The length of the this
77 range is specified by the ``left_edge`` argument (which
78 may be zero).
80 2. A range around the current page. This range will
81 include ``left_current`` pages before, and
82 ``right_current`` pages after the current page. This
83 range always includes the current page.
85 3. Finally, a range (always ending at the last page) at
86 the end of the page sequence. The length of this range
87 is specified by the ``right_edge`` argument.
89 If any of these ranges overlap, they will be merged. A
90 ``None`` will be inserted between non-overlapping ranges to
91 signify that pages have been elided.
93 This is how you could render such a pagination in the templates:
94 .. sourcecode:: html+jinja
95 {% macro render_pagination(pagination, endpoint) %}
96 <div class=pagination>
97 {%- for page in pagination.iter_pages() %}
98 {% if page %}
99 {% if page != pagination.page %}
100 <a href="{{ url_for(endpoint, page=page) }}">{{ page }}</a>
101 {% else %}
102 <strong>{{ page }}</strong>
103 {% endif %}
104 {% else %}
105 <span class=ellipsis>...</span>
106 {% endif %}
107 {%- endfor %}
108 </div>
109 {% endmacro %}
111 """
112 last = 0
113 for num in range(1, self.pages + 1):
114 # pylint: disable=chained-comparison
115 if (
116 num <= left_edge
117 or (num >= self.page - left_current and num <= self.page + right_current)
118 or num > self.pages - right_edge
119 ):
120 if last + 1 != num:
121 yield None
122 yield num
123 last = num
124 if last != self.pages:
125 yield None