Phase 2 评审修复: P0 min_score+&q=500 (ORM→list 顺序) + P1 场景分页(20/page) + P2 文档同步 + 36 tests
This commit is contained in:
+11
-5
@@ -31,12 +31,18 @@ class ScenarioPage(Page):
|
||||
|
||||
def get_context(self, request, *args, **kwargs):
|
||||
context = super().get_context(request, *args, **kwargs)
|
||||
context["projects"] = (
|
||||
projects = (
|
||||
SkeletonProjectPage.objects.live()
|
||||
.filter(scenarios=self)
|
||||
.prefetch_related("languages", "scenarios")
|
||||
.order_by("-first_published_at")
|
||||
)
|
||||
paginator = Paginator(projects, 20)
|
||||
page = request.GET.get("page", "1")
|
||||
try:
|
||||
context["projects"] = paginator.page(int(page))
|
||||
except (ValueError, EmptyPage):
|
||||
context["projects"] = paginator.page(1)
|
||||
return context
|
||||
|
||||
class Meta:
|
||||
@@ -148,6 +154,10 @@ class ProjectIndexPage(Page):
|
||||
projects = projects.filter(frameworks__slug=framework)
|
||||
if database:
|
||||
projects = projects.filter(databases__slug=database)
|
||||
if q:
|
||||
projects = projects.filter(
|
||||
Q(title__icontains=q) | Q(summary__icontains=q)
|
||||
)
|
||||
try:
|
||||
score_val = int(min_score)
|
||||
if 0 <= score_val <= 30:
|
||||
@@ -156,10 +166,6 @@ class ProjectIndexPage(Page):
|
||||
]
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
if q:
|
||||
projects = projects.filter(
|
||||
Q(title__icontains=q) | Q(summary__icontains=q)
|
||||
)
|
||||
|
||||
paginator = Paginator(projects, 20)
|
||||
page = request.GET.get("page", "1")
|
||||
|
||||
@@ -22,6 +22,18 @@
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if projects.paginator.num_pages > 1 %}
|
||||
<nav class="pagination">
|
||||
{% if projects.has_previous %}
|
||||
<a href="?page={{ projects.previous_page_number }}">« Prev</a>
|
||||
{% endif %}
|
||||
<span class="current">Page {{ projects.number }} of {{ projects.paginator.num_pages }}</span>
|
||||
{% if projects.has_next %}
|
||||
<a href="?page={{ projects.next_page_number }}">Next »</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% include "includes/empty_state.html" with message="No projects found in this scenario." %}
|
||||
{% endif %}
|
||||
|
||||
@@ -75,6 +75,46 @@ class ScenarioPageTests(PageTreeMixin, TestCase):
|
||||
self.assertContains(response, "Nothing here yet")
|
||||
|
||||
|
||||
class ScenarioPaginationTests(PageTreeMixin, TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.setUpPageTree()
|
||||
cls.lang = Language.objects.create(name="Python", slug="python")
|
||||
for i in range(21):
|
||||
project = SkeletonProjectPage(
|
||||
title=f"Pagination Project {i+1}",
|
||||
slug=f"pagination-project-{i+1}",
|
||||
summary=f"Project {i+1} for pagination test",
|
||||
github_url="https://github.com/test/pagination",
|
||||
maturity="stable",
|
||||
recommended_for="Testing",
|
||||
structure_score=3, docs_score=3, tests_score=3,
|
||||
example_score=3, dependency_score=3, incremental_score=3,
|
||||
)
|
||||
cls.project_index.add_child(instance=project)
|
||||
project.languages.add(cls.lang)
|
||||
project.scenarios.add(cls.scenario)
|
||||
project.save()
|
||||
|
||||
def test_first_page_has_20_items(self):
|
||||
resp = self.client.get(self.scenario.url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertContains(resp, "Pagination Project 1")
|
||||
self.assertContains(resp, "Pagination Project 20")
|
||||
self.assertNotContains(resp, "Pagination Project 21")
|
||||
|
||||
def test_second_page_has_1_item(self):
|
||||
resp = self.client.get(f"{self.scenario.url}?page=2")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertNotContains(resp, "Pagination Project 1")
|
||||
self.assertContains(resp, "Pagination Project 21")
|
||||
|
||||
def test_invalid_page_falls_back_to_first(self):
|
||||
resp = self.client.get(f"{self.scenario.url}?page=abc")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertContains(resp, "Pagination Project 1")
|
||||
|
||||
|
||||
class ProjectFilterTests(PageTreeMixin, TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
@@ -135,6 +175,21 @@ class ProjectFilterTests(PageTreeMixin, TestCase):
|
||||
resp = self.client.get("/projects/?min_score=abc&page=xyz")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
def test_combined_filters_return_200(self):
|
||||
resp = self.client.get(
|
||||
"/projects/?language=python&framework=django&min_score=18&q=Django"
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertContains(resp, "Django Project")
|
||||
self.assertNotContains(resp, "Go Project")
|
||||
|
||||
def test_combined_filters_no_results_returns_empty_state(self):
|
||||
resp = self.client.get(
|
||||
"/projects/?language=go&framework=django&min_score=18"
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertContains(resp, "No projects match")
|
||||
|
||||
|
||||
class SkeletonProjectDetailTests(PageTreeMixin, TestCase):
|
||||
@classmethod
|
||||
|
||||
Reference in New Issue
Block a user