Skip to content
This repository was archived by the owner on Apr 17, 2021. It is now read-only.

Commit 5b80cd5

Browse files
committed
Intial attempt at supporting bootstrap-v4 (alpha)
1 parent 443ea69 commit 5b80cd5

File tree

9 files changed

+232
-6
lines changed

9 files changed

+232
-6
lines changed

bootstrap_pagination/templates/bootstrap_pagination/pager.html renamed to bootstrap_pagination/templates/bootstrap_pagination/pager-bs3.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<a title="{{ previous_title }}" href="{{ previous_page_url|default:"#"|escape }}">{{ previous_label }}</a>
55
</li>
66
{% else %}
7-
<li class="{% if not centered %} next{% endif %} disabled">
7+
<li class="{% if not centered %}previous{% endif %} disabled">
88
<span title="{{ previous_title }}">{{ previous_label }}</span>
99
</li>
1010
{% endif %}
@@ -14,8 +14,8 @@
1414
<a title="{{ next_title }}" href="{{ next_page_url|default:"#"|escape }}">{{ next_label }}</a>
1515
</li>
1616
{% else %}
17-
<li title="{{ next_title }}" class="{% if not centered %} next{% endif %} disabled">
17+
<li title="{{ next_title }}" class="{% if not centered %}next{% endif %} disabled">
1818
<span>{{ next_label }}</span>
1919
</li>
2020
{% endif %}
21-
</ul>
21+
</ul>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<nav>
2+
<ul class="pager">
3+
{% if page.has_previous %}
4+
<li{% if not centered %} class="pager-prev"{% endif %}>
5+
<a title="{{ previous_title }}" href="{{ previous_page_url|default:"#"|escape }}">{{ previous_label }}</a>
6+
</li>
7+
{% else %}
8+
<li class="{% if not centered %} pager-prev{% endif %} disabled">
9+
<span title="{{ previous_title }}">{{ previous_label }}</span>
10+
</li>
11+
{% endif %}
12+
13+
{% if page.has_next %}
14+
<li{% if not centered %} class="pager-next"{% endif %}>
15+
<a title="{{ next_title }}" href="{{ next_page_url|default:"#"|escape }}">{{ next_label }}</a>
16+
</li>
17+
{% else %}
18+
<li title="{{ next_title }}" class="{% if not centered %} pager-next{% endif %} disabled">
19+
<span>{{ next_label }}</span>
20+
</li>
21+
{% endif %}
22+
</ul>
23+
</nav>
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
{% load bootstrap_pagination %}
2+
<nav>
3+
<ul class="pagination{% if size == "small" %} pagination-sm{% endif %}{% if size == "large" %} pagination-lg{% endif %}{% block extra_classes %}{% endblock %}">
4+
{% if show_first_last %}
5+
{% if not page.has_previous %}
6+
<li class="disabled page-item">
7+
<span aria-hidden="true" title="First Page">{{ first_label }}</span>
8+
<span class="sr-only">First Page</span>
9+
</li>
10+
{% else %}
11+
<li class="page-item">
12+
<a title="First Page" href="{{ first_page_url|default:"#"|escape }}">
13+
<span aria-hidden="true">{{ first_label }}</span>
14+
<span class="sr-only">First Page</span>
15+
</a>
16+
</li>
17+
{% endif %}
18+
{% endif %}
19+
{% if show_prev_next %}
20+
{% if not page.has_previous %}
21+
<li class="disabled page-item">
22+
<span aria-hidden="true" title="Previous Page">{{ previous_label }}</span>
23+
<span class="sr-only">Previous Page</span>
24+
</li>
25+
{% else %}
26+
<li class="page-item">
27+
<a title="Previous Page" href="{{ previous_page_url|default:"#"|escape }}">
28+
<span aria-hidden="true">{{ previous_label }}</span>
29+
<span class="sr-only">Previous Page</span>
30+
</a>
31+
</li>
32+
{% endif %}
33+
{% endif %}
34+
{% for pagenum, index_range, url in page_urls %}
35+
{% if page.number == pagenum %}
36+
<li class="active page-item">
37+
<span title="Current Page">{% if show_index_range %} {{ index_range }} {% else %} {{ pagenum }} {%endif %}</span>
38+
</li>
39+
{% else %}
40+
<li class="page-item">
41+
<a title="Page {{ pagenum }} of {{ page.paginator.num_pages }}" href="{{ url|escape }}">{% if show_index_range %} {{ index_range }} {% else %} {{ pagenum }} {%endif %}</a>
42+
</li>
43+
{% endif %}
44+
{% endfor %}
45+
{% if show_prev_next %}
46+
{% if not page.has_next %}
47+
<li class="disabled page-item">
48+
<span aria-hidden="true" title="Next Page">{{ next_label }}</span>
49+
<span class="sr-only">Next Page</span>
50+
</li>
51+
{% else %}
52+
<li class="page-item">
53+
<a title="Next Page" href="{{ next_page_url|default:"#"|escape }}">
54+
<span aria-hidden="true">{{ next_label }}</span>
55+
<span class="sr-only">Next Page</span>
56+
</a>
57+
</li>
58+
{% endif %}
59+
{% endif %}
60+
{% if show_first_last %}
61+
{% if not page.has_next %}
62+
<li class="disabled page-item">
63+
<span aria-hidden="true" title="Last Page">{{ last_label }}</span>
64+
<span class="sr-only">Last Page</span>
65+
</li>
66+
{% else %}
67+
<li class="page-item">
68+
<a title="Last Page" href="{{ last_page_url|default:"#"|escape }}">
69+
<span aria-hidden="true">{{ last_label }}</span>
70+
<span class="sr-only">Last Page</span>
71+
</a>
72+
</li>
73+
{% endif %}
74+
{% endif %}
75+
</ul>
76+
</nav>

bootstrap_pagination/templatetags/bootstrap_pagination.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@ def get_page_url(page_num, current_app, url_view_name, url_extra_args, url_extra
6464
return url
6565

6666

67+
def get_bootstrap_version():
68+
"""Helper version to determine the configured version of bootstrap."""
69+
version = getattr(settings, 'BOOTSTRAP_VERSION', 3)
70+
version = int(version)
71+
72+
# Clap version between supported versions
73+
return max(3, min(version, 4))
74+
75+
6776
class BootstrapPagerNode(Node):
6877
def __init__(self, page, kwargs):
6978
self.page = page
@@ -105,7 +114,9 @@ def render(self, context):
105114
if page.has_next():
106115
next_page_url = get_page_url(page.next_page_number(), context.current_app, url_view_name, url_extra_args, url_extra_kwargs, url_param_name, url_get_params, url_anchor)
107116

108-
return get_template("bootstrap_pagination/pager.html").render(
117+
template = "bootstrap_pagination/pager-bs%i.html" % get_bootstrap_version()
118+
119+
return get_template(template).render(
109120
Context({
110121
'page': page,
111122
'previous_label': previous_label,
@@ -220,7 +231,9 @@ def render(self, context):
220231
if page.has_next():
221232
next_page_url = get_page_url(page.next_page_number(), context.current_app, url_view_name, url_extra_args, url_extra_kwargs, url_param_name, url_get_params, url_anchor)
222233

223-
return get_template("bootstrap_pagination/pagination.html").render(
234+
template = "bootstrap_pagination/pagination-bs%i.html" % get_bootstrap_version()
235+
236+
return get_template(template).render(
224237
Context({
225238
'page': page,
226239
'size': size,

tests/test_pager.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import django.http
1313
from django.core.paginator import Paginator
1414

15+
from tests.utils import override_settings
16+
1517

1618
class PagerTestCase(unittest.TestCase):
1719
def test_example(self):
@@ -29,3 +31,23 @@ def test_example(self):
2931
'request': django.http.HttpRequest()})
3032
html = lxml.html.fragment_fromstring(template.render(c))
3133
self.assertEqual(html.get('class'), 'pager')
34+
35+
@override_settings(BOOTSTRAP_VERSION=4)
36+
def test_bs4(self):
37+
template = get_template_from_string("""
38+
{% load bootstrap_pagination %}
39+
{% bootstrap_pager page_obj %}
40+
""")
41+
42+
objects = ["obj%02x" % idx
43+
for idx in range(30)]
44+
45+
paginator = Paginator(objects, 10)
46+
47+
c = Context({'page_obj': paginator.page(2),
48+
'request': django.http.HttpRequest()})
49+
html = lxml.html.fragment_fromstring(template.render(c))
50+
51+
self.assertEqual(
52+
html.cssselect('nav>ul.pager>li>a')[0].text.strip(),
53+
'Previous Page')

tests/test_paginate.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
import django.http
1313
from django.core.paginator import Paginator
1414

15+
from tests.utils import override_settings
16+
1517

1618
class PaginateTestCase(unittest.TestCase):
19+
1720
def test_example(self):
1821
template = get_template_from_string("""
1922
{% load bootstrap_pagination %}
@@ -32,3 +35,23 @@ def test_example(self):
3235
self.assertEqual(
3336
html.cssselect('[title=\"Current Page\"]')[0].text.strip(),
3437
'2')
38+
39+
@override_settings(BOOTSTRAP_VERSION=4)
40+
def test_bs4(self):
41+
template = get_template_from_string("""
42+
{% load bootstrap_pagination %}
43+
{% bootstrap_paginate page_obj range=10 %}
44+
""")
45+
46+
objects = ["obj%02x" % idx
47+
for idx in range(30)]
48+
49+
paginator = Paginator(objects, 10)
50+
51+
c = Context({'page_obj': paginator.page(2),
52+
'request': django.http.HttpRequest()})
53+
html = lxml.html.fragment_fromstring(template.render(c))
54+
55+
self.assertEqual(
56+
html.cssselect('nav>ul.pagination>li>span[title=\"Current Page\"]')[0].text.strip(),
57+
'2')

tests/test_settings.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@
88
'bootstrap_pagination',
99
)
1010

11-
DATABASES = {}
11+
DATABASES = {
12+
'default': {
13+
'ENGINE': 'django.db.backends.sqlite3',
14+
'NAME': ':memory:',
15+
}
16+
}
1217
MIDDLEWARE_CLASSES = ()
1318

1419
ROOT_URLCONF = 'tests.test_settings.urls'

tests/utils.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from functools import wraps
2+
3+
from django.conf import UserSettingsHolder, settings
4+
try:
5+
from django.test.signals import setting_changed
6+
except ImportError:
7+
setting_changed = None
8+
9+
10+
# This was taken from django 1.9 django.tests.utils, and adapted to be
11+
# compatible with a wider range of django versions.
12+
class override_settings(object):
13+
"""
14+
Acts as either a decorator, or a context manager. If it's a decorator it
15+
takes a function and returns a wrapped function. If it's a contextmanager
16+
it's used with the ``with`` statement. In either event entering/exiting
17+
are called before and after, respectively, the function/block is executed.
18+
"""
19+
def __init__(self, **kwargs):
20+
self.options = kwargs
21+
22+
def __enter__(self):
23+
self.enable()
24+
25+
def __exit__(self, exc_type, exc_value, traceback):
26+
self.disable()
27+
28+
def __call__(self, test_func):
29+
@wraps(test_func)
30+
def inner(*args, **kwargs):
31+
with self:
32+
return test_func(*args, **kwargs)
33+
return inner
34+
35+
def save_options(self, test_func):
36+
if test_func._overridden_settings is None:
37+
test_func._overridden_settings = self.options
38+
else:
39+
# Duplicate dict to prevent subclasses from altering their parent.
40+
test_func._overridden_settings = dict(
41+
test_func._overridden_settings, **self.options)
42+
43+
def enable(self):
44+
# Changing installed apps cannot be done in a backward-compatible way
45+
assert 'INSTALLED_APPS' not in self.options
46+
47+
override = UserSettingsHolder(settings._wrapped)
48+
for key, new_value in self.options.items():
49+
setattr(override, key, new_value)
50+
self.wrapped = settings._wrapped
51+
settings._wrapped = override
52+
for key, new_value in self.options.items():
53+
if setting_changed:
54+
setting_changed.send(sender=settings._wrapped.__class__,
55+
setting=key, value=new_value, enter=True)
56+
57+
def disable(self):
58+
settings._wrapped = self.wrapped
59+
del self.wrapped
60+
for key in self.options:
61+
new_value = getattr(settings, key, None)
62+
if setting_changed:
63+
setting_changed.send(sender=settings._wrapped.__class__,
64+
setting=key, value=new_value, enter=False)

0 commit comments

Comments
 (0)