Skip to content

Commit

Permalink
fix: filtrage des questions en utilisant les étiquettes (#794)
Browse files Browse the repository at this point in the history
## Description

### Modifications principales, dans le gabarit
`lacommunaute/templates/forum_conversation/partials/topic_tags.html`

🎸 **suppression du paramètre `page=1` qui forçait le masquage de la zone
de filtre**
🎸 **appel `htmx` de `TopicListView` lors du clic sur les tags, pour
éviter tous les inconvénients d'un rechargement de page complet**

### Petits bonus en passant
🎸 fiabilisation des tests sur les filtres x les étiquettes
🎸 desambiguation des paramètres dans le contexte de la vue
`TopicListView`
🎸 suppression de la gestion d'une liste de `Tag` dans les url (non
terminé), pour n'en gérer plus qu'un seul

## Type de changement

🪲 Correction de bug (changement non cassant qui corrige un problème).
🎨 changement d'UI
🚧 technique

### Points d'attention

🦺 réécriture des tests concernés dans les commits pour améliorer la
lisibilité (voir les passer en style `pytest`)
🦺 renommage des énums pour une meilleure présentation utilisateur
🦺 réécriture des tests de `ForumView` à voir ultérieurement selon les
impacts de l'issue #765


### Captures d'écran (optionnel)

filtrage `ALL`


![image](https://github.com/user-attachments/assets/ae02763b-df65-45f2-b3b3-3093ba3b4fea)

filtrage `ALL` x étiquette


![image](https://github.com/user-attachments/assets/7d444de3-8242-419a-b42e-286b294f4e74)

filtrage `NEW`


![image](https://github.com/user-attachments/assets/614076c0-bbca-49a9-a644-f6295538efed)

filtrage `NEW` x étiquette


![image](https://github.com/user-attachments/assets/1586e1e1-ed3d-47cc-9ac1-14dc28102c6a)

filtrage `CERTIFIED`


![image](https://github.com/user-attachments/assets/7c44186c-551a-4c11-8808-70201b253708)

filtrage `CERTIFIED` x étiquette


![image](https://github.com/user-attachments/assets/1cb8d7d1-1ff9-4edc-916d-b60c4a4c64f4)
  • Loading branch information
vincentporte authored Oct 9, 2024
1 parent 41237f1 commit d03847e
Show file tree
Hide file tree
Showing 10 changed files with 357 additions and 263 deletions.
89 changes: 45 additions & 44 deletions lacommunaute/forum/tests/tests_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,27 +83,24 @@ def test_context(self):
self.assertEqual(response.context_data["loadmoretopic_url"], loadmoretopic_url)
self.assertEqual(response.context_data["forum"], self.forum)
self.assertIsNone(response.context_data["rating"])
self.assertEqual(response.context_data["active_filter_name"], Filters.ALL.label)
self.assertEqual(response.context_data["active_tags"], "")
self.assertEqual(response.context_data["active_filter"], Filters.ALL)
self.assertEqual(list(response.context_data["active_tag"]), [])

for filter, label in Filters.choices:
with self.subTest(filter=filter, label=label):
response = self.client.get(self.url + f"?filter={filter}")
for filter in Filters:
with self.subTest(filter=filter):
response = self.client.get(self.url + f"?filter={filter.value}")
self.assertEqual(
response.context_data["loadmoretopic_url"],
loadmoretopic_url + f"?filter={filter}",
loadmoretopic_url + f"?filter={filter.value}",
)
self.assertEqual(response.context_data["active_filter_name"], label)
self.assertEqual(response.context_data["active_filter"], filter)

response = self.client.get(self.url + "?filter=FAKE")
self.assertEqual(response.context_data["active_filter_name"], Filters.ALL.label)
self.assertEqual(response.context_data["active_filter"], Filters.ALL)

tag = Tag.objects.create(name="tag_1", slug="tag_1")
response = self.client.get(self.url + f"?tags=nonexistant,{tag.name}")
self.assertIn(tag.slug, response.context_data["active_tags"])
self.assertNotIn("nonexistant", response.context_data["active_tags"])
self.assertIn(tag.name, response.context_data["active_tags_label"])
self.assertNotIn("nonexistant", response.context_data["active_tags_label"])
response = self.client.get(self.url + f"?tag={tag.name}")
self.assertEqual(tag, response.context_data["active_tag"])

def test_template_name(self):
response = self.client.get(self.url)
Expand Down Expand Up @@ -358,7 +355,7 @@ def test_filtered_queryset_on_tag(self):

with self.assertNumQueries(20):
response = self.client.get(
reverse("forum_extension:forum", kwargs={"pk": self.forum.pk, "slug": self.forum.slug}), {"tags": tag}
reverse("forum_extension:forum", kwargs={"pk": self.forum.pk, "slug": self.forum.slug}), {"tag": tag}
)
self.assertContains(response, topic.subject)
self.assertNotContains(response, self.topic.subject)
Expand All @@ -368,14 +365,14 @@ def test_queryset_for_unanswered_topics(self):
response = self.client.get(self.url + f"?filter={Filters.NEW.value}")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context_data["paginator"].count, 0)
self.assertEqual(response.context_data["active_filter_name"], Filters.NEW.label)
self.assertEqual(response.context_data["active_filter"], Filters.NEW)

new_topic = TopicFactory(with_post=True, forum=self.forum)

response = self.client.get(self.url + f"?filter={Filters.NEW.value}")
self.assertEqual(response.context_data["paginator"].count, 1)
self.assertContains(response, new_topic.subject, status_code=200)
self.assertEqual(response.context_data["active_filter_name"], Filters.NEW.label)
self.assertEqual(response.context_data["active_filter"], Filters.NEW)

for topic in Topic.objects.exclude(id=new_topic.id):
with self.subTest(topic):
Expand All @@ -385,15 +382,15 @@ def test_queryset_for_certified_topics(self):
response = self.client.get(self.url + f"?filter={Filters.CERTIFIED.value}")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context_data["paginator"].count, 0)
self.assertEqual(response.context_data["active_filter_name"], Filters.CERTIFIED.label)
self.assertEqual(response.context_data["active_filter"], Filters.CERTIFIED)

certified_topic = TopicFactory(with_post=True, with_certified_post=True, forum=self.forum)

response = self.client.get(self.url + f"?filter={Filters.CERTIFIED.value}")
self.assertEqual(response.context_data["paginator"].count, 1)
self.assertContains(response, certified_topic.subject, status_code=200)
self.assertContains(response, certified_topic.certified_post.post.content.raw[:100])
self.assertEqual(response.context_data["active_filter_name"], Filters.CERTIFIED.label)
self.assertEqual(response.context_data["active_filter"], Filters.CERTIFIED)

for topic in Topic.objects.exclude(id=certified_topic.id):
with self.subTest(topic):
Expand Down Expand Up @@ -608,6 +605,22 @@ def test_documentation_forum_with_partner(self, client, db, snapshot, documentat
assert str(content.select("#partner_area")) == snapshot(name="documentation_forum_with_partner")


@pytest.fixture(name="documentation_category_forum_with_descendants")
def documentation_category_forum_with_descendants_fixture():
tags = [faker.word() for _ in range(3)]
category_forum = CategoryForumFactory(with_public_perms=True)
first_child = ForumFactory(parent=category_forum, with_public_perms=True, with_tags=[tags[0]])
second_child = ForumFactory(parent=category_forum, with_public_perms=True, with_tags=[tags[0], tags[1]])
third_child = ForumFactory(parent=category_forum, with_public_perms=True, with_tags=[tags[2]])
# forum without tags
ForumFactory(parent=category_forum, with_public_perms=True)

# edge case: grand_child is filtered out. No actual use case to display them in the subforum list
ForumFactory(parent=third_child, with_public_perms=True, with_tags=[tags[2]])

return category_forum, tags[0], [first_child, second_child]


class TestDocumentationCategoryForumContent:
def test_documentation_category_subforum_list(
self, client, db, snapshot, reset_forum_sequence, documentation_forum
Expand Down Expand Up @@ -636,36 +649,24 @@ def test_documentation_category_foot_content(
assert len(add_documentation_control) == 1
assert str(add_documentation_control[0]) == snapshot(name="documentation_category_add_file_control")

def test_filter_subforums_on_tags(self, client, db):
tags = [faker.word() for _ in range(3)]
category_forum = CategoryForumFactory(with_public_perms=True)
first_child = ForumFactory(parent=category_forum, with_public_perms=True, with_tags=[tags[0]])
second_child = ForumFactory(parent=category_forum, with_public_perms=True, with_tags=[tags[0], tags[1]])
third_child = ForumFactory(parent=category_forum, with_public_perms=True, with_tags=[tags[2]])
# forum without tags
ForumFactory(parent=category_forum, with_public_perms=True)

# edge case: grand_child is filtered out. No actual use case to display them in the subforum list
ForumFactory(parent=third_child, with_public_perms=True, with_tags=[tags[2]])

# no filter
response = client.get(category_forum.get_absolute_url())
assert response.status_code == 200
assert [node.obj for node in response.context_data["sub_forums"].top_nodes] == list(
category_forum.get_children()
)
@pytest.mark.parametrize("filtered, sub_forums_count", [(False, 4), (True, 2)])
def test_filter_subforums_on_tags(
self, client, db, documentation_category_forum_with_descendants, filtered, sub_forums_count
):
category_forum, first_tag, subforums_with_first_tag = documentation_category_forum_with_descendants

# filter on one tag
response = client.get(category_forum.get_absolute_url() + f"?forum_tag={tags[0]}")
assert response.status_code == 200
assert set([node.obj for node in response.context_data["sub_forums"].top_nodes]) == set(
[first_child, second_child]
expected = subforums_with_first_tag if filtered else list(category_forum.get_children())
url = (
category_forum.get_absolute_url() + f"?forum_tag={first_tag}"
if filtered
else category_forum.get_absolute_url()
)

# filtering on multiple tags is not supported yet
response = client.get(category_forum.get_absolute_url() + f"?forum_tag={tags[1]},{tags[2]}")
response = client.get(url)
assert response.status_code == 200
assert set([node.obj for node in response.context_data["sub_forums"].top_nodes]) == set([])
sub_forums = [node.obj for node in response.context_data["sub_forums"].top_nodes]
assert sub_forums == expected
assert len(sub_forums) == sub_forums_count

def test_show_subforum_tag(self, client, db, snapshot, reset_forum_sequence):
category_forum = CategoryForumFactory(with_public_perms=True, for_snapshot=True)
Expand Down
6 changes: 3 additions & 3 deletions lacommunaute/forum_conversation/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@


class Filters(models.TextChoices):
ALL = "ALL", "Les plus récentes"
NEW = "NEW", "En attente de réponse"
CERTIFIED = "CERTIFIED", "Réponse certifiée"
ALL = "ALL", "les plus récentes"
NEW = "NEW", "en attente de réponse"
CERTIFIED = "CERTIFIED", "avec une réponse certifiée"
7 changes: 7 additions & 0 deletions lacommunaute/forum_conversation/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ def with_tags(self, create, extracted, **kwargs):
for tag in extracted:
self.tags.add(tag)

@factory.post_generation
def answered(self, create, extracted, **kwargs):
if not create or not extracted:
return

PostFactory(topic=self)


class AnonymousTopicFactory(TopicFactory):
poster = None
Expand Down
115 changes: 93 additions & 22 deletions lacommunaute/forum_conversation/tests/__snapshots__/tests_views.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -189,11 +189,100 @@
</div>
'''
# ---
# name: TestTopicListView.test_clickable_tags[clickable_tags_page1]
'<a class="tag bg-info-lighter text-info" href="/topics/?tags=tag&amp;page=1">tag</a>'
# name: TestTopicListView.test_clickable_tags[10-query_param1-clickable_tags_page2][clickable_tags_page2]
'<button class="tag bg-info-lighter text-info matomo-event" data-matomo-action="filter" data-matomo-category="engagement" data-matomo-option="topics" hx-get="/topics/?tag=tag&amp;filter=ALL" hx-push-url="true" hx-swap="outerHTML" hx-target="#topicsarea" id="filtertopics-button">tag</button>'
# ---
# name: TestTopicListView.test_clickable_tags[clickable_tags_page2]
'<a class="tag bg-info-lighter text-info" href="/topics/?tags=tag&amp;page=1">tag</a>'
# name: TestTopicListView.test_clickable_tags[None-query_param0-clickable_tags_page1][clickable_tags_page1]
'<button class="tag bg-info-lighter text-info matomo-event" data-matomo-action="filter" data-matomo-category="engagement" data-matomo-option="topics" hx-get="/topics/?tag=tag&amp;filter=ALL" hx-push-url="true" hx-swap="outerHTML" hx-target="#topicsarea" id="filtertopics-button">tag</button>'
# ---
# name: TestTopicListView.test_filter_dropdown_with_tags[filter_dropdown_with_tags]
'''
<div class="dropdown-menu dropdown-menu-end" id="filterTopicsDropdown">
<ul class="list-unstyled">

<li>
<button class="dropdown-item matomo-event" data-matomo-action="filter" data-matomo-category="engagement" data-matomo-option="topics" hx-get="/topics/?filter=ALL&amp;tag=tag" hx-push-url="true" hx-swap="outerHTML" hx-target="#topicsarea" id="filter-ontag-button">les plus récentes</button>
</li>

<li>
<button class="dropdown-item matomo-event" data-matomo-action="filter" data-matomo-category="engagement" data-matomo-option="topics" hx-get="/topics/?filter=NEW&amp;tag=tag" hx-push-url="true" hx-swap="outerHTML" hx-target="#topicsarea" id="filter-ontag-button">en attente de réponse</button>
</li>

<li>
<button class="dropdown-item matomo-event" data-matomo-action="filter" data-matomo-category="engagement" data-matomo-option="topics" hx-get="/topics/?filter=CERTIFIED&amp;tag=tag" hx-push-url="true" hx-swap="outerHTML" hx-target="#topicsarea" id="filter-ontag-button">avec une réponse certifiée</button>
</li>

</ul>
</div>
'''
# ---
# name: TestTopicListView.test_queryset_on_filter[ALL-<lambda>-None][ALL-tagged_topics]
'''
<div class="flex-grow-1" id="topic-list-filter-header">
<span class="h5 m-0">2 questions</span>
les plus récentes

</div>
'''
# ---
# name: TestTopicListView.test_queryset_on_filter[CERTIFIED-<lambda>-<lambda>][CERTIFIED-tagged_topics]
'''
<div class="flex-grow-1" id="topic-list-filter-header">
<span class="h5 m-0">1 question</span>
avec une réponse certifiée

</div>
'''
# ---
# name: TestTopicListView.test_queryset_on_filter[NEW-<lambda>-<lambda>][NEW-tagged_topics]
'''
<div class="flex-grow-1" id="topic-list-filter-header">
<span class="h5 m-0">2 questions</span>
en attente de réponse

</div>
'''
# ---
# name: TestTopicListView.test_queryset_on_tag[-<lambda>-None][-tagged_topics]
'''
<div class="flex-grow-1" id="topic-list-filter-header">
<span class="h5 m-0">2 questions</span>
les plus récentes

</div>
'''
# ---
# name: TestTopicListView.test_queryset_on_tag[buckley-<lambda>-<lambda>][buckley-tagged_topics]
'''
<div class="flex-grow-1" id="topic-list-filter-header">
<span class="h5 m-0">1 question</span>
les plus récentes

<span class="fs-sm">
sous l'étiquette
<button aria-label="Supprimer ce filtre" class="tag bg-info text-white matomo-event" data-bs-placement="top" data-bs-title="Supprimer ce filtre" data-bs-toggle="tooltip" data-matomo-action="unfilter" data-matomo-category="engagement" data-matomo-option="topics" hx-get="/topics/?filter=ALL" hx-push-url="true" hx-swap="outerHTML" hx-target="#topicsarea" id="unfilter-ontag-button">
<i class="ri-close-fill ri-xs"></i>buckley
</button>
</span>

</div>
'''
# ---
# name: TestTopicListView.test_queryset_on_tag[tag-<lambda>-<lambda>][tag-tagged_topics]
'''
<div class="flex-grow-1" id="topic-list-filter-header">
<span class="h5 m-0">2 questions</span>
les plus récentes

<span class="fs-sm">
sous l'étiquette
<button aria-label="Supprimer ce filtre" class="tag bg-info text-white matomo-event" data-bs-placement="top" data-bs-title="Supprimer ce filtre" data-bs-toggle="tooltip" data-matomo-action="unfilter" data-matomo-category="engagement" data-matomo-option="topics" hx-get="/topics/?filter=ALL" hx-push-url="true" hx-swap="outerHTML" hx-target="#topicsarea" id="unfilter-ontag-button">
<i class="ri-close-fill ri-xs"></i>tag
</button>
</span>

</div>
'''
# ---
# name: test_breadcrumbs_on_topic_view[discussion_area_topic]
'''
Expand Down Expand Up @@ -259,21 +348,3 @@
</nav>
'''
# ---
# name: test_queryset_for_tagged_topic[tagged_topic]
'''
<div class="flex-grow-1" id="topic-list-filter-header">
<span class="h5 m-0">1 question
avec l'étiquette buckley
</span>
</div>
'''
# ---
# name: test_queryset_for_tagged_topic[tagged_topics]
'''
<div class="flex-grow-1" id="topic-list-filter-header">
<span class="h5 m-0">2 questions
avec l'étiquette buckley
</span>
</div>
'''
# ---
Loading

0 comments on commit d03847e

Please sign in to comment.