diff --git a/ratings/tests/migrations/__init__.py b/base/__init__.py
similarity index 100%
rename from ratings/tests/migrations/__init__.py
rename to base/__init__.py
diff --git a/base/exceptions.py b/base/exceptions.py
new file mode 100644
index 00000000..e13df8f9
--- /dev/null
+++ b/base/exceptions.py
@@ -0,0 +1,6 @@
+class IncorectLookupParameter(Exception):
+ """
+ Raised when a query parameter contains an incorrect value.
+ """
+
+ pass
diff --git a/base/migrations/__init__.py b/base/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/base/pagination.py b/base/pagination.py
new file mode 100644
index 00000000..d85e83af
--- /dev/null
+++ b/base/pagination.py
@@ -0,0 +1,58 @@
+from django.core.paginator import InvalidPage, Paginator
+
+from .exceptions import IncorectLookupParameter
+
+PAGE_VAR = "page"
+
+
+class Pagination:
+ def __init__(
+ self,
+ request,
+ model,
+ queryset,
+ list_per_page,
+ ):
+ self.model = model
+ self.opts = model._meta
+ self.queryset = queryset
+ self.list_per_page = list_per_page
+ try:
+ # Get the current page from the query string.
+ self.page_num = int(request.GET.get(PAGE_VAR, 1))
+ except ValueError:
+ self.page_num = 1
+ self.params = dict(request.GET.lists())
+ self.setup()
+
+ @property
+ def page_range(self):
+ """
+ Returns the full range of pages.
+ """
+ return (
+ self.paginator.get_elided_page_range(self.page_num)
+ if self.multi_page
+ else []
+ )
+
+ def setup(self):
+ paginator = Paginator(self.queryset, self.list_per_page)
+ result_count = paginator.count
+ # Determine use pagination.
+ multi_page = result_count > self.list_per_page
+
+ self.result_count = result_count
+ self.multi_page = multi_page
+ self.paginator = paginator
+ self.page = paginator.get_page(self.page_num)
+
+ def get_objects(self):
+ if not self.multi_page:
+ result_list = self.queryset._clone()
+ else:
+ try:
+ result_list = self.paginator.page(self.page_num).object_list
+ except InvalidPage:
+ raise IncorectLookupParameter
+ return result_list
diff --git a/base/templatetags/__init__.py b/base/templatetags/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/base/templatetags/base_templatetags.py b/base/templatetags/base_templatetags.py
new file mode 100644
index 00000000..8cfb0c84
--- /dev/null
+++ b/base/templatetags/base_templatetags.py
@@ -0,0 +1,69 @@
+from collections.abc import Iterable, Mapping
+
+from django import template
+from django.http import QueryDict
+from django.template.exceptions import TemplateSyntaxError
+
+register = template.Library()
+
+
+# This template tag is scheduled to be added in Django 6.0.
+# Imported for use before the release of Django 6.0.
+@register.simple_tag(name="querystring", takes_context=True)
+def querystring(context, *args, **kwargs):
+ """
+ Build a query string using `args` and `kwargs` arguments.
+
+ This tag constructs a new query string by adding, removing, or modifying
+ parameters from the given positional and keyword arguments. Positional
+ arguments must be mappings (such as `QueryDict` or `dict`), and
+ `request.GET` is used as the starting point if `args` is empty.
+
+ Keyword arguments are treated as an extra, final mapping. These mappings
+ are processed sequentially, with later arguments taking precedence.
+
+ A query string prefixed with `?` is returned.
+
+ Raise TemplateSyntaxError if a positional argument is not a mapping or if
+ keys are not strings.
+
+ For example::
+
+ {# Set a parameter on top of `request.GET` #}
+ {% querystring foo=3 %}
+
+ {# Remove a key from `request.GET` #}
+ {% querystring foo=None %}
+
+ {# Use with pagination #}
+ {% querystring page=page_obj.next_page_number %}
+
+ {# Use a custom ``QueryDict`` #}
+ {% querystring my_query_dict foo=3 %}
+
+ {# Use multiple positional and keyword arguments #}
+ {% querystring my_query_dict my_dict foo=3 bar=None %}
+ """
+ if not args:
+ args = [context.request.GET]
+ params = QueryDict(mutable=True)
+ for d in [*args, kwargs]:
+ if not isinstance(d, Mapping):
+ raise TemplateSyntaxError(
+ "querystring requires mappings for positional arguments (got "
+ "%r instead)." % d
+ )
+ for key, value in d.items():
+ if not isinstance(key, str):
+ raise TemplateSyntaxError(
+ "querystring requires strings for mapping keys (got %r "
+ "instead)." % key
+ )
+ if value is None:
+ params.pop(key, None)
+ elif isinstance(value, Iterable) and not isinstance(value, str):
+ params.setlist(key, value)
+ else:
+ params[key] = value
+ query_string = params.urlencode() if params else ""
+ return f"?{query_string}"
diff --git a/base/templatetags/components.py b/base/templatetags/components.py
new file mode 100644
index 00000000..a271b7d0
--- /dev/null
+++ b/base/templatetags/components.py
@@ -0,0 +1,40 @@
+from django import template
+from django.utils.html import format_html
+from django.utils.safestring import mark_safe
+
+from base.pagination import PAGE_VAR
+
+from .base_templatetags import querystring
+
+register = template.Library()
+
+
+@register.simple_tag
+def pagination_number(pagination, i):
+ """
+ Generate an individual page index link in a paginated list.
+ """
+ if i == pagination.paginator.ELLIPSIS:
+ return format_html("{} ", pagination.paginator.ELLIPSIS)
+ elif i == pagination.page_num:
+ return format_html('{} ', i)
+ else:
+ link = querystring(None, pagination.params, {PAGE_VAR: i})
+ return format_html(
+ '{} ',
+ link,
+ i,
+ mark_safe(' class="end"' if i == pagination.paginator.num_pages else ""),
+ i,
+ )
+
+
+@register.inclusion_tag("base/components/pagination.html", name="pagination")
+def pagination_tag(pagination):
+ previous_page_link = f"?{PAGE_VAR}={pagination.page_num - 1}"
+ next_page_link = f"?{PAGE_VAR}={pagination.page_num + 1}"
+ return {
+ "pagination": pagination,
+ "previous_page_link": previous_page_link,
+ "next_page_link": next_page_link,
+ }
diff --git a/base/tests/__init__.py b/base/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/ratings/tests/fixtures/ratings_testdata.json b/base/tests/fixtures/ratings_testdata.json
similarity index 100%
rename from ratings/tests/fixtures/ratings_testdata.json
rename to base/tests/fixtures/ratings_testdata.json
diff --git a/ratings/tests/migrations/0001_initial.py b/base/tests/migrations/0001_initial.py
similarity index 100%
rename from ratings/tests/migrations/0001_initial.py
rename to base/tests/migrations/0001_initial.py
diff --git a/base/tests/migrations/0002_fish.py b/base/tests/migrations/0002_fish.py
new file mode 100644
index 00000000..b312f3d2
--- /dev/null
+++ b/base/tests/migrations/0002_fish.py
@@ -0,0 +1,21 @@
+# Generated by Django 3.2.15 on 2025-06-04 05:19
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tests', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Fish',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=255)),
+ ('price', models.IntegerField()),
+ ],
+ ),
+ ]
diff --git a/base/tests/migrations/__init__.py b/base/tests/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/ratings/tests/models.py b/base/tests/models.py
similarity index 74%
rename from ratings/tests/models.py
rename to base/tests/models.py
index 45b56d22..00d5dc0d 100644
--- a/ratings/tests/models.py
+++ b/base/tests/models.py
@@ -1,6 +1,11 @@
from django.db import models
-from ..models import RatedItemBase, Ratings
+from ratings.models import RatedItemBase, Ratings
+
+
+class Fish(models.Model):
+ name = models.CharField(max_length=255)
+ price = models.IntegerField()
class Food(models.Model):
diff --git a/base/tests/tests.py b/base/tests/tests.py
new file mode 100644
index 00000000..6df8b699
--- /dev/null
+++ b/base/tests/tests.py
@@ -0,0 +1,60 @@
+from django.test import RequestFactory, TestCase
+
+from base.pagination import Pagination
+
+from .models import Fish
+
+
+class PaginationTestCase(TestCase):
+ @classmethod
+ def setUpTestData(cls):
+ fishs = [Fish(name=f"fish-{i}", price=i * 100) for i in range(1, 101)]
+ Fish.objects.bulk_create(fishs)
+ cls.queryset = Fish.objects.all()
+ cls.factory = RequestFactory()
+
+ def test_pagination_attributes(self):
+ request = self.factory.get("/fake-url/")
+ pagination = Pagination(request, Fish, self.queryset, 5)
+ self.assertEqual(pagination.result_count, 100)
+ self.assertTrue(pagination.multi_page)
+ pagination = Pagination(request, Fish, self.queryset, 200)
+ self.assertFalse(pagination.multi_page)
+
+ def test_pagination_page_range(self):
+ request = self.factory.get("/fake-url/")
+ ELLIPSIS = "…"
+ case = [
+ (2, 6, [1, 2, 3, 4, 5, 6, 7, 8, 9, ELLIPSIS, 49, 50]),
+ (3, 10, [1, 2, ELLIPSIS, 7, 8, 9, 10, 11, 12, 13, ELLIPSIS, 33, 34]),
+ (4, 23, [1, 2, ELLIPSIS, 20, 21, 22, 23, 24, 25]),
+ (5, 20, [1, 2, ELLIPSIS, 17, 18, 19, 20]),
+ (10, 8, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
+ (20, 1, [1, 2, 3, 4, 5]),
+ ]
+ for list_per_page, current_page, expected_page_range in case:
+ with self.subTest(list_per_page=list_per_page, current_page=current_page):
+ pagination = Pagination(request, Fish, self.queryset, list_per_page)
+ pagination.page_num = current_page
+ self.assertEqual(list(pagination.page_range), expected_page_range)
+
+ def test_pagination_result_objects(self):
+ request = self.factory.get("/fake-url/")
+ case = [
+ (2, 25, ["49", "50"]),
+ (4, 12, ["45", "46", "47", "48"]),
+ (5, 10, ["46", "47", "48", "49", "50"]),
+ (7, 11, ["71", "72", "73", "74", "75", "76", "77"]),
+ (10, 10, ["91", "92", "93", "94", "95", "96", "97", "98", "99", "100"]),
+ (200, 1, [str(i) for i in range(1, 101)]),
+ ]
+ Fish.objects.all().delete()
+ fishs = [Fish(name=i, price=i * 100) for i in range(1, 101)]
+ Fish.objects.bulk_create(fishs)
+ queryset = Fish.objects.all().order_by("id")
+ for list_per_page, current_page, expect_object_names in case:
+ pagination = Pagination(request, Fish, queryset, list_per_page)
+ pagination.page_num = current_page
+ objects = pagination.get_objects()
+ object_names = list(objects.values_list("name", flat=True))
+ self.assertEqual(object_names, expect_object_names)
diff --git a/cab/utils.py b/cab/utils.py
index f91215ed..1cd8de11 100644
--- a/cab/utils.py
+++ b/cab/utils.py
@@ -2,18 +2,18 @@
import bleach
from django.core.exceptions import ObjectDoesNotExist
-from django.core.paginator import InvalidPage, Paginator
from django.http import Http404, HttpResponse
from django.template import loader
from django.utils.safestring import mark_safe
from markdown import markdown as markdown_func
+from base.pagination import Pagination
+
def object_list(
request,
queryset,
paginate_by=None,
- page=None,
allow_empty=True,
template_name=None,
template_loader=loader,
@@ -28,101 +28,43 @@ def object_list(
Context:
object_list
list of objects
- is_paginated
- are the results paginated?
- results_per_page
- number of objects per page (if paginated)
- has_next
- is there a next page?
- has_previous
- is there a prev page?
- page
- the current page
- next
- the next page
- previous
- the previous page
- pages
- number of pages, total
+ pagination
+ This is a pagination object that holds attributes
+ related to pagination.
+ For more detail, please refer to the `base.pagination.Pagination` class.
hits
number of objects, total
- last_on_page
- the result number of the last of object in the
- object_list (1-indexed)
- first_on_page
- the result number of the first object in the
- object_list (1-indexed)
- page_range:
- A list of the page numbers (1-indexed).
"""
if extra_context is None:
extra_context = {}
queryset = queryset._clone()
+ model = queryset.model
+ opts = model._meta
if paginate_by:
- paginator = Paginator(queryset, paginate_by, allow_empty_first_page=allow_empty)
- if not page:
- page = request.GET.get("page", 1)
+ pagination = Pagination(request, model, queryset, paginate_by)
+ object_list = pagination.get_objects()
- if page == "last":
- page_number = paginator.num_pages
- else:
- try:
- page_number = int(page)
- except ValueError:
- # Page is not 'last', nor can it be converted to an int.
- raise Http404
- try:
- page_obj = paginator.page(page_number)
- except InvalidPage:
- raise Http404
- try:
- next_page = page_obj.next_page_number()
- except InvalidPage:
- next_page = None
- try:
- previous_page = page_obj.previous_page_number()
- except InvalidPage:
- previous_page = None
-
- c = {
- "%s_list" % template_object_name: page_obj.object_list,
- "paginator": paginator,
- "page_obj": page_obj,
- "is_paginated": page_obj.has_other_pages(),
- # Legacy template context stuff. New templates should use page_obj
- # to access this instead.
- "results_per_page": paginator.per_page,
- "has_next": page_obj.has_next(),
- "has_previous": page_obj.has_previous(),
- "page": page_obj.number,
- "next": next_page,
- "previous": previous_page,
- "first_on_page": page_obj.start_index(),
- "last_on_page": page_obj.end_index(),
- "pages": paginator.num_pages,
- "hits": paginator.count,
- "page_range": paginator.page_range,
+ context = {
+ "%s_list" % template_object_name: object_list,
+ "pagination": pagination,
+ "hits": pagination.result_count,
}
else:
- c = {
- "%s_list" % template_object_name: queryset,
- "paginator": None,
- "page_obj": None,
- "is_paginated": False,
+ context = {
+ "%s_list" % template_object_name: object_list,
}
if not allow_empty and len(queryset) == 0:
raise Http404
for key, value in extra_context.items():
if callable(value):
- c[key] = value()
+ context[key] = value()
else:
- c[key] = value
+ context[key] = value
if not template_name:
- model = queryset.model
- template_name = "%s/%s_list.html" % (model._meta.app_label, model._meta.object_name.lower())
- t = template_loader.get_template(template_name)
- return HttpResponse(t.render(c, request=request), content_type=content_type)
+ template_name = "%s/%s_list.html" % (opts.app_label, opts.object_name.lower())
+ template = template_loader.get_template(template_name)
+ return HttpResponse(template.render(context, request=request), content_type=content_type)
def object_detail(
diff --git a/djangosnippets/settings/base.py b/djangosnippets/settings/base.py
index b45e644c..3e2be225 100644
--- a/djangosnippets/settings/base.py
+++ b/djangosnippets/settings/base.py
@@ -56,6 +56,7 @@ def user_url(user):
"allauth.socialaccount.providers.bitbucket",
"allauth.socialaccount.providers.github",
"allauth.socialaccount.providers.twitter",
+ "base",
"cab",
"comments_spamfighter",
"ratings",
diff --git a/djangosnippets/settings/testing.py b/djangosnippets/settings/testing.py
index 2b50b697..7c827369 100644
--- a/djangosnippets/settings/testing.py
+++ b/djangosnippets/settings/testing.py
@@ -27,11 +27,12 @@
"allauth.socialaccount.providers.bitbucket",
"allauth.socialaccount.providers.github",
"allauth.socialaccount.providers.twitter",
+ "base",
+ "base.tests",
"comments_spamfighter",
"cab",
"ratings",
"taggit",
- "ratings.tests",
"rest_framework",
)
diff --git a/djangosnippets/static/scss/main.scss b/djangosnippets/static/scss/main.scss
index 843db1e6..7a50c1ed 100644
--- a/djangosnippets/static/scss/main.scss
+++ b/djangosnippets/static/scss/main.scss
@@ -317,7 +317,7 @@ body.with-sidebar {
@include grid-column(4);
}
}
- .pagination, .count {
+ .count {
text-align: center;
}
}
@@ -358,6 +358,65 @@ body.simple {
}
}
+nav.pagination {
+ display: flex;
+ justify-content: center;
+ text-align: center;
+ ul {
+ margin-left: 1rem;
+ margin-right: 1rem;
+ }
+
+ li {
+ display: inline-block;
+ a, em, span {
+ padding: 5px 10px;
+ line-height: 20px;
+ border: 1px solid transparent;
+ border-radius: 6px;
+ transition: border-color .2s cubic-bezier(0.3, 0, 0.5, 1);
+ cursor: pointer;
+ }
+ a:hover {
+ border-color: $secondary-color;
+ text-decoration: none;
+ }
+ em {
+ font-style: normal;
+ cursor: default;
+ }
+ .current-page {
+ font-weight: bold;
+ color: white;
+ background-color: $secondary-color;
+ }
+ .disabled {
+ color: gray;
+ cursor: default;
+ border-color: transparent;
+ }
+ }
+
+ .previous-page::before, .next-page::after {
+ display: inline-block;
+ width: 1rem;
+ height: 1rem;
+ vertical-align: text-bottom;
+ content: "";
+ background-color: currentColor;
+ }
+
+ .previous-page::before {
+ clip-path: polygon(9.8px 12.8px, 8.7px 12.8px, 4.5px 8.5px, 4.5px 7.5px, 8.7px 3.2px, 9.8px 4.3px, 6.1px 8px, 9.8px 11.7px, 9.8px 12.8px);
+ margin-right: 4px;
+ }
+
+ .next-page::after {
+ clip-path: polygon(6.2px 3.2px, 7.3px 3.2px, 11.5px 7.5px, 11.5px 8.5px, 7.3px 12.8px, 6.2px 11.7px, 9.9px 8px, 6.2px 4.3px, 6.2px 3.2px);
+ margin-left: 4px;
+ }
+}
+
footer {
padding: 30px 0 30px 0;
clear: both;
diff --git a/djangosnippets/templates/base.html b/djangosnippets/templates/base.html
index b02598f7..153a6562 100644
--- a/djangosnippets/templates/base.html
+++ b/djangosnippets/templates/base.html
@@ -39,11 +39,11 @@
{% block secondary_nav %}
{% endblock %}
diff --git a/djangosnippets/templates/base/components/pagination.html b/djangosnippets/templates/base/components/pagination.html
new file mode 100644
index 00000000..aeeadbdf
--- /dev/null
+++ b/djangosnippets/templates/base/components/pagination.html
@@ -0,0 +1,21 @@
+{% load components %}
+
+{% if pagination.multi_page %}
+
+{% endif %}
diff --git a/djangosnippets/templates/cab/language_list.html b/djangosnippets/templates/cab/language_list.html
index f6df2df3..949b5e2b 100644
--- a/djangosnippets/templates/cab/language_list.html
+++ b/djangosnippets/templates/cab/language_list.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% load core_tags %}
+{% load core_tags components %}
{% block head_title %}All languages{% endblock %}
@@ -12,6 +12,6 @@
{% endfor %}
-
+ {% pagination pagination %}
{% endblock %}
diff --git a/djangosnippets/templates/cab/partials/language_list.html b/djangosnippets/templates/cab/partials/language_list.html
index 697a270c..d2e14142 100644
--- a/djangosnippets/templates/cab/partials/language_list.html
+++ b/djangosnippets/templates/cab/partials/language_list.html
@@ -1,4 +1,4 @@
-{% load static %}
+{% load static components %}
@@ -10,6 +10,5 @@ All languages
{% endfor %}
-
-
+ {% pagination pagination %}
diff --git a/djangosnippets/templates/cab/partials/most_bookmarked.html b/djangosnippets/templates/cab/partials/most_bookmarked.html
index fd5031ed..b3ebae58 100644
--- a/djangosnippets/templates/cab/partials/most_bookmarked.html
+++ b/djangosnippets/templates/cab/partials/most_bookmarked.html
@@ -1,5 +1,5 @@
-{% load core_tags %}
+{% load core_tags components %}
{% load static %}
Most bookmarked snippets{% if months %} last {{ months }} months{% endif %}
@@ -30,15 +30,7 @@ Most bookmarked snippets{% if months %} last {{ months }} months{% endif %}<
{% endfor %}
-
+ {% pagination pagination %}
{{ hits }} snippet{{ hits|pluralize }} posted so far.
{% else %}
No snippets posted yet.
diff --git a/djangosnippets/templates/cab/partials/tag_list.html b/djangosnippets/templates/cab/partials/tag_list.html
index 51b725d1..9c5b083e 100644
--- a/djangosnippets/templates/cab/partials/tag_list.html
+++ b/djangosnippets/templates/cab/partials/tag_list.html
@@ -1,5 +1,5 @@
-{% load core_tags %}
+{% load core_tags components %}
All tags
{% if object_list %}
@@ -9,7 +9,7 @@
All tags
{% endfor %}
-
+ {% pagination pagination %}
{% else %}
No tags have been used yet.
{% endif %}
diff --git a/djangosnippets/templates/cab/partials/top_rated.html b/djangosnippets/templates/cab/partials/top_rated.html
index 78286707..0a48a82b 100644
--- a/djangosnippets/templates/cab/partials/top_rated.html
+++ b/djangosnippets/templates/cab/partials/top_rated.html
@@ -1,6 +1,6 @@
{% load static %}
-{% load core_tags %}
+{% load core_tags components %}
Top-rated snippets{% if months %} last {{ months }} months{% endif %}
@@ -28,15 +28,7 @@
Top-rated snippets{% if months %} last {{ months }} months{% endif %}
{% endfor %}
-
+ {% pagination pagination %}
{{ hits }} snippet{{ hits|pluralize }} posted so far.
{% else %}
No snippets posted yet.
diff --git a/djangosnippets/templates/cab/snippet_list.html b/djangosnippets/templates/cab/snippet_list.html
index 2979553c..5fceeb60 100644
--- a/djangosnippets/templates/cab/snippet_list.html
+++ b/djangosnippets/templates/cab/snippet_list.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% load core_tags %}
+{% load core_tags components %}
{% load static %}
{% block bodyclass %}snippet-list{% endblock %}
{% block head_title %}All snippets{% if months %} last {{ months }} months{% endif %}{% endblock %}
@@ -30,15 +30,7 @@
{% endfor %}
-
+ {% pagination pagination %}
{{ hits }} snippet{{ hits|pluralize }} posted so far.
{% else %}
No snippets posted yet.
diff --git a/djangosnippets/templates/cab/tag_list.html b/djangosnippets/templates/cab/tag_list.html
index 4e89f3e9..77114235 100644
--- a/djangosnippets/templates/cab/tag_list.html
+++ b/djangosnippets/templates/cab/tag_list.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% load core_tags %}
+{% load core_tags components %}
{% block head_title %}All tags{% endblock %}
{% block bodyclass %}tags-list{% endblock %}
@@ -14,7 +14,7 @@
{% endfor %}
-
+ {% pagination pagination %}
{% else %}
No tags have been used yet.
{% endif %}
diff --git a/djangosnippets/templates/cab/user_bookmarks.html b/djangosnippets/templates/cab/user_bookmarks.html
index 3acdbb35..69be39c0 100644
--- a/djangosnippets/templates/cab/user_bookmarks.html
+++ b/djangosnippets/templates/cab/user_bookmarks.html
@@ -1,5 +1,5 @@
{% extends "base_user.html" %}
-{% load core_tags %}
+{% load core_tags components %}
{% block bodyclass %}bookmarks{% endblock %}
{% block head_title %}Your bookmarks{% endblock %}
@@ -29,7 +29,7 @@
-
You haven't bookmarked any snippets yet.
{% endif %}
diff --git a/djangosnippets/templates/cab/user_detail.html b/djangosnippets/templates/cab/user_detail.html
index fd89a959..42915ebb 100644
--- a/djangosnippets/templates/cab/user_detail.html
+++ b/djangosnippets/templates/cab/user_detail.html
@@ -1,5 +1,5 @@
{% extends "base_user.html" %}
-{% load cache core_tags %}
+{% load cache core_tags components %}
{% block bodyclass %}user{% endblock %}
{% load static %}
@@ -35,15 +35,7 @@
{% cache 600 author_detail_sidebar author.username %}
{% if request.user.username == author.username %}You've{% else %}{{ author.username }} has{% endif %} posted {{ author.snippet_set.count }} snippet{{ author.snippet_set.count|pluralize }}.
{% endcache %}
-
+ {% pagination pagination %}
{% else %}
No snippets posted yet.
{% endif %}
diff --git a/djangosnippets/templates/search/search.html b/djangosnippets/templates/search/search.html
index fc356ae2..10477594 100644
--- a/djangosnippets/templates/search/search.html
+++ b/djangosnippets/templates/search/search.html
@@ -9,7 +9,7 @@
{% block content %}
{% if form.q.value or form.version.value or form.language.value %}
- {% if page_obj.object_list %}
+ {% if object_list %}
@@ -21,7 +21,7 @@
- {% for result in page_obj.object_list %}
+ {% for result in object_list %}
{% if not result.title|strip %}Untitled{% else %}{{ result.title }}{% endif %} |
{% user_display result.author %} |
diff --git a/ratings/tests/tests.py b/ratings/tests/tests.py
index 9fad7230..aaa7527b 100644
--- a/ratings/tests/tests.py
+++ b/ratings/tests/tests.py
@@ -7,10 +7,11 @@
from django.test.utils import override_settings
from django.urls import reverse
+from base.tests.models import Beverage, BeverageRating, Food
+
from .. import utils as ratings_utils
from .. import views as ratings_views
from ..models import RatedItem
-from ..tests.models import Beverage, BeverageRating, Food
from ..utils import (
calculate_similar_items,
recommendations,