Skip to content

Commit 08d08c1

Browse files
authored
Add sorting tab to snippet list. (#602)
1 parent 5314803 commit 08d08c1

File tree

17 files changed

+155
-129
lines changed

17 files changed

+155
-129
lines changed

base/components/components.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from django_components import Component, register
44
from pydantic import BaseModel
55

6+
from base.main import TAB_VAR, ObjectList
67
from base.pagination import PAGE_VAR, Pagination
78
from base.templatetags.base_templatetags import querystring
89

@@ -68,3 +69,38 @@ def get_template_data(self, args, kwargs, slots, context):
6869
"next_page_link": next_page_link,
6970
"page_elements": page_elements,
7071
}
72+
73+
74+
class TabItem(BaseModel):
75+
text: str
76+
is_current: bool
77+
attrs: Optional[dict]
78+
79+
80+
@register("sorting_tabs")
81+
class SortingTabs(Component):
82+
template_file = "sorting_tabs.html"
83+
84+
class Kwargs(BaseModel):
85+
object_list: ObjectList
86+
model_config = {"arbitrary_types_allowed": True}
87+
88+
def create_tab(self, object_list: ObjectList, tab: str) -> TabItem:
89+
verbose_text = tab.replace("_", " ").title()
90+
is_current = tab == object_list.current_tab
91+
link = querystring(None, {**object_list.params, TAB_VAR: tab})
92+
attrs = {"href": link}
93+
if is_current:
94+
attrs["aria-selected"] = "true"
95+
return TabItem(text=verbose_text, is_current=is_current, attrs=attrs)
96+
97+
def create_all_tabs(self, object_list: ObjectList):
98+
tabs = [self.create_tab(object_list, tab) for tab in object_list.sorting_tabs]
99+
return tabs
100+
101+
def get_template_data(self, args, kwargs, slots, context):
102+
object_list = kwargs.object_list
103+
return {
104+
"tabs": self.create_all_tabs(object_list),
105+
"object_list": object_list,
106+
}

base/components/sorting_tabs.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<nav aria-labelledby="sorting-tabs">
2+
<h2 id="sorting-tabs" class="sr-only">Sorting Tabs</h2>
3+
<ul class="m-0 nline-flex gap-2 p-2 border-2 border-base-green-400 rounded-lg font-common">
4+
{% for tab in tabs %}
5+
<li><a {% html_attrs tab.attrs %} class="inline-block px-4 py-2 rounded-md {% if tab.is_current %}bg-base-green-400 text-base-white-400 no-underline{% else %}underline duration-300 transition-colors hover:bg-base-green-400/30 hover:text-base-green-800{% endif %}">{{ tab.text }}</a></li>
6+
{% endfor %}
7+
</ul>
8+
</nav>

base/main.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,42 @@
11
from .pagination import PAGE_VAR, Pagination
22

3+
TAB_VAR = "tab"
4+
35

46
class ObjectList:
57
pagination_class = Pagination
8+
base_ordering = ()
9+
sorting_tabs = {}
610

711
def __init__(self, request, model, queryset, list_per_page):
812
self.model = model
13+
self.opts = model._meta
914
self.queryset = queryset
1015
self.list_per_page = list_per_page
11-
self.params = dict(request.GET.lists())
16+
self.params = dict(request.GET.dict())
17+
self.current_tab = self.params.get(TAB_VAR, None)
18+
if self.opts.ordering:
19+
self.base_ordering = self.opts.ordering
1220
if PAGE_VAR in self.params:
1321
del self.params[PAGE_VAR]
14-
self.result_objects = self.get_objects(request)
22+
self.result_objects = self.get_objects(request, queryset)
1523

1624
def __iter__(self):
1725
return iter(self.result_objects)
1826

27+
def tab_sort(self, queryset):
28+
result_queryset = queryset
29+
if self.current_tab:
30+
sort_value = self.sorting_tabs[self.current_tab]
31+
result_queryset = result_queryset.order_by(*sort_value, *self.base_ordering)
32+
else:
33+
for tab_name, tab_order in self.sorting_tabs.items():
34+
if tab_order == self.base_ordering:
35+
self.current_tab = tab_name
36+
break
37+
38+
return result_queryset
39+
1940
def paginate(self, request, queryset):
2041
pagination = self.pagination_class(
2142
request,
@@ -26,6 +47,7 @@ def paginate(self, request, queryset):
2647
self.pagination = pagination
2748
return pagination.get_objects()
2849

29-
def get_objects(self, request):
30-
paginate_result = self.paginate(request, self.queryset)
50+
def get_objects(self, request, queryset):
51+
tab_result = self.tab_sort(queryset)
52+
paginate_result = self.paginate(request, tab_result)
3153
return paginate_result

base/tests/test_components.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from django.test import RequestFactory, TestCase
2+
3+
from base.components.components import SortingTabs
4+
from base.main import ObjectList
5+
6+
from .models import Fish
7+
8+
9+
class FishList(ObjectList):
10+
sorting_tabs = {
11+
"newest": ("-id",),
12+
"oldest": ("id",),
13+
"high_price": ("-price",),
14+
"low_price": ("price",),
15+
}
16+
17+
18+
class SortingTabsComponentTests(TestCase):
19+
20+
@classmethod
21+
def setUpTestData(cls):
22+
fish_data = [Fish(name=f"fish-{i}", price=i * 100) for i in range(1, 21)]
23+
cls.factory = RequestFactory()
24+
fishs = Fish.objects.bulk_create(fish_data)
25+
cls.queryset = Fish.objects.filter(pk__in=[f.pk for f in fishs])
26+
cls.components = SortingTabs()
27+
28+
def test_create_tab_items(self):
29+
request = self.factory.get("?tab=high_price")
30+
object_list = FishList(request, Fish, self.queryset, 10)
31+
tab = self.components.create_tab(object_list, "newest")
32+
self.assertEqual(tab.text, "Newest")
33+
self.assertFalse(tab.is_current)
34+
self.assertEqual(tab.attrs, {"href": "?tab=newest"})
35+
36+
tab = self.components.create_tab(object_list, "high_price")
37+
self.assertEqual(tab.text, "High Price")
38+
self.assertTrue(tab.is_current)
39+
self.assertEqual(tab.attrs, {"aria-selected": "true", "href": "?tab=high_price"})
40+
41+
def test_create_all_tab_items(self):
42+
request = self.factory.get("")
43+
object_list = FishList(request, Fish, self.queryset, 10)
44+
tabs = self.components.create_all_tabs(object_list)
45+
self.assertEqual(len(tabs), 4)
46+
self.assertEqual(tabs[0].text, "Newest")
47+
self.assertEqual(tabs[1].text, "Oldest")
48+
self.assertEqual(tabs[2].text, "High Price")
49+
self.assertEqual(tabs[3].text, "Low Price")

cab/main.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from base.main import ObjectList
2+
3+
4+
class SnippetList(ObjectList):
5+
sorting_tabs = {
6+
"newest": ("-pub_date",),
7+
"latest_updated": ("-updated_date",),
8+
"highest_rated": ("-rating_score",),
9+
"most_bookmarked": ("-bookmark_count",),
10+
}

cab/tests/tests.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -292,26 +292,6 @@ def test_popular_views(self):
292292
tag_names = [tag.name for tag in resp.context["object_list"]]
293293
self.assertEqual(tag_names, ["world", "goodbye", "haxor", "hello"])
294294

295-
top_bookmarked = reverse("cab_top_bookmarked")
296-
self.assertEqual(top_bookmarked, "/popular/bookmarked/")
297-
298-
resp = self.client.get(top_bookmarked)
299-
self.assertEqual(resp.status_code, 200)
300-
s1, s3, s2 = resp.context["object_list"]
301-
self.assertEqual(s1, self.snippet1)
302-
self.assertEqual(s3, self.snippet3)
303-
self.assertEqual(s2, self.snippet2)
304-
305-
top_rated = reverse("cab_top_rated")
306-
self.assertEqual(top_rated, "/popular/rated/")
307-
308-
resp = self.client.get(top_rated)
309-
self.assertEqual(resp.status_code, 200)
310-
s1, s3, s2 = resp.context["object_list"]
311-
self.assertEqual(s1, self.snippet1)
312-
self.assertEqual(s3, self.snippet3)
313-
self.assertEqual(s2, self.snippet2)
314-
315295
def test_tag_detail(self):
316296
tag_detail = reverse("cab_snippet_matches_tag", args=["world"])
317297
self.assertEqual(tag_detail, "/tags/world/")

cab/urls/popular.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,4 @@
44

55
urlpatterns = [
66
path("languages/", popular.top_languages, name="cab_top_languages"),
7-
path("bookmarked/", popular.top_bookmarked, name="cab_top_bookmarked"),
8-
path("rated/", popular.top_rated, name="cab_top_rated"),
97
]

cab/utils.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
from django.utils.safestring import mark_safe
88
from markdown import markdown as markdown_func
99

10-
from base.main import ObjectList
1110
from base.pagination import Pagination
1211

12+
from .main import SnippetList
13+
1314

1415
def object_list(
1516
request,
@@ -45,7 +46,7 @@ def object_list(
4546
opts = model._meta
4647
if paginate_by:
4748
if queryset.model == Snippet:
48-
object_list = ObjectList(request, queryset.model, queryset, 15)
49+
object_list = SnippetList(request, queryset.model, queryset, 15)
4950
pagination = object_list.pagination
5051
else:
5152
pagination = Pagination(request, model, queryset, paginate_by)

djangosnippets/templates/base.html

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.1/themes/smoothness/jquery-ui.css" />
99
<link rel="preconnect" href="https://fonts.googleapis.com">
1010
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11-
<link href="https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200..1000;1,200..1000&family=Playfair+Display:ital,wght@0,400..900;1,400..900&family=Raleway:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
11+
<link href="https://fonts.googleapis.com/css2?family=Libertinus+Sans:ital,wght@0,400;0,700;1,400&family=Nunito:ital,wght@0,200..1000;1,200..1000&family=Playfair+Display:ital,wght@0,400..900;1,400..900&family=Raleway:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
1212
<link rel="alternate" href="{% url 'cab_feed_latest' %}" type="application/atom+xml" title="Feed of latest snippets" />
1313
<link rel="stylesheet" href="{% static "css/main.css" %}" type="text/css" />
1414
{% block feeds %}{% endblock %}
@@ -45,8 +45,6 @@
4545
<li><a hx-get="{% url 'cab_top_authors' %}" hx-target="#base-container" hx-swap="innerHTML" hx-trigger="click" hx-push-url="true">By author</a></li>
4646
<li><a hx-get="{% url 'cab_language_list' %}" hx-target="#base-container" hx-swap="innerHTML" hx-trigger="click" hx-push-url="true">By language</a></li>
4747
<li><a hx-get="{% url 'cab_top_tags' %}" hx-target="#base-container" hx-swap="innerHTML" hx-trigger="click" hx-push-url="true">By tag</a></li>
48-
<li><a hx-get="{% url 'cab_top_rated' %}" hx-target="#base-container" hx-swap="innerHTML" hx-trigger="click" hx-push-url="true">Highest rated</a></li>
49-
<li><a hx-get="{% url 'cab_top_bookmarked' %}" hx-target="#base-container" hx-swap="innerHTML" hx-trigger="click" hx-push-url="true">Most bookmarked</a></li>
5048
</ul>
5149
</nav>
5250
{% endblock %}
@@ -62,6 +60,7 @@
6260

6361
<div id="base-container">
6462
<h1>{% block content_header %}{% endblock %}</h1>
63+
{% block new_content_header %}{% endblock %}
6564
{% with current_url_name=request.resolver_match.url_name %}
6665
<div id="content" class="{% if current_url_name == 'home' %}w-1/2{% else %}w-full{% endif %}">
6766
{% block content %}

djangosnippets/templates/cab/most_bookmarked.html

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)