diff --git a/lacommunaute/forum/admin.py b/lacommunaute/forum/admin.py index 43b934f33..520b79f3c 100644 --- a/lacommunaute/forum/admin.py +++ b/lacommunaute/forum/admin.py @@ -6,7 +6,7 @@ class ForumAdmin(BaseForumAdmin): fieldsets = BaseForumAdmin.fieldsets - fieldsets[0][1]["fields"] += ("short_description", "certified") + fieldsets[0][1]["fields"] += ("short_description", "certified", "tags") fieldsets[1][1]["fields"] += ( "members_group", "invitation_token", diff --git a/lacommunaute/forum/factories.py b/lacommunaute/forum/factories.py index db7ea75cc..d41a98440 100644 --- a/lacommunaute/forum/factories.py +++ b/lacommunaute/forum/factories.py @@ -38,6 +38,15 @@ def upvoted_by(self, create, extracted, **kwargs): for user in extracted: UpVote.objects.create(voter=user, content_object=self) + @factory.post_generation + def with_tags(self, create, extracted, **kwargs): + if not create or not extracted: + return + + if isinstance(extracted, list): + for tag in extracted: + self.tags.add(tag) + class CategoryForumFactory(ForumFactory): type = Forum.FORUM_CAT diff --git a/lacommunaute/forum/forms.py b/lacommunaute/forum/forms.py index 9edf7ac6c..e4aec9ad9 100644 --- a/lacommunaute/forum/forms.py +++ b/lacommunaute/forum/forms.py @@ -2,6 +2,8 @@ from django import forms from django.conf import settings +from django.forms import CharField, CheckboxSelectMultiple, ModelMultipleChoiceField +from taggit.models import Tag from lacommunaute.forum.models import Forum @@ -36,12 +38,31 @@ class ForumForm(forms.ModelForm): widget=forms.FileInput(attrs={"accept": settings.SUPPORTED_IMAGE_FILE_TYPES.keys()}), ) certified = forms.BooleanField(required=False, label="Certifiée par la communauté de l'inclusion") + tags = ModelMultipleChoiceField( + label="Sélectionner un ou plusieurs tags", + queryset=Tag.objects.all(), + widget=CheckboxSelectMultiple, + required=False, + ) + new_tags = CharField(required=False, label="Ajouter un tag ou plusieurs tags (séparés par des virgules)") + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if self.instance.pk: + self.fields["tags"].initial = self.instance.tags.all() def save(self, commit=True): forum = super().save(commit=False) forum.description = wrap_iframe_in_div_tag(self.cleaned_data.get("description")) + if commit: forum.save() + forum.tags.set(self.cleaned_data["tags"]) + ( + forum.tags.add(*[tag.strip() for tag in self.cleaned_data["new_tags"].split(",")]) + if self.cleaned_data.get("new_tags") + else None + ) return forum class Meta: diff --git a/lacommunaute/forum/migrations/0017_forum_tags.py b/lacommunaute/forum/migrations/0017_forum_tags.py new file mode 100644 index 000000000..51b2179e5 --- /dev/null +++ b/lacommunaute/forum/migrations/0017_forum_tags.py @@ -0,0 +1,24 @@ +# Generated by Django 5.0.7 on 2024-08-07 14:21 + +import taggit.managers +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("forum", "0016_forum_certified"), + ("taggit", "0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx"), + ] + + operations = [ + migrations.AddField( + model_name="forum", + name="tags", + field=taggit.managers.TaggableManager( + help_text="A comma-separated list of tags.", + through="taggit.TaggedItem", + to="taggit.Tag", + verbose_name="Tags", + ), + ), + ] diff --git a/lacommunaute/forum/models.py b/lacommunaute/forum/models.py index bd07c99f1..fdf68e520 100644 --- a/lacommunaute/forum/models.py +++ b/lacommunaute/forum/models.py @@ -9,6 +9,7 @@ from machina.apps.forum.abstract_models import AbstractForum from machina.models import DatedModel from storages.backends.s3boto3 import S3Boto3Storage +from taggit.managers import TaggableManager from lacommunaute.forum.enums import Kind as Forum_Kind from lacommunaute.forum_conversation.models import Topic @@ -40,6 +41,8 @@ class Forum(AbstractForum): upvotes = GenericRelation(UpVote, related_query_name="forum") + tags = TaggableManager() + objects = ForumQuerySet().as_manager() def get_absolute_url(self): diff --git a/lacommunaute/forum/tests/__snapshots__/test_forum_updateview.ambr b/lacommunaute/forum/tests/__snapshots__/test_forum_updateview.ambr new file mode 100644 index 000000000..b3183c63d --- /dev/null +++ b/lacommunaute/forum/tests/__snapshots__/test_forum_updateview.ambr @@ -0,0 +1,51 @@ +# serializer version: 1 +# name: test_selected_tags_are_preloaded[selected_tags_preloaded] + ''' +
+ + + + + + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + + + + + +
+ ''' +# --- diff --git a/lacommunaute/forum/tests/test_forum_updateview.py b/lacommunaute/forum/tests/test_forum_updateview.py index de8edd4c0..3b0b97a00 100644 --- a/lacommunaute/forum/tests/test_forum_updateview.py +++ b/lacommunaute/forum/tests/test_forum_updateview.py @@ -1,14 +1,22 @@ from io import BytesIO import pytest # noqa +from django.conf import settings from django.core.files.uploadedfile import SimpleUploadedFile from django.urls import reverse from faker import Faker from PIL import Image from pytest_django.asserts import assertContains +from taggit.models import Tag from lacommunaute.forum.factories import CategoryForumFactory, ForumFactory from lacommunaute.users.factories import UserFactory +from lacommunaute.utils.testing import parse_response_to_soup, reset_model_sequence_fixture + + +faker = Faker(settings.LANGUAGE_CODE) + +reset_tag_sequence = pytest.fixture(reset_model_sequence_fixture(Tag)) @pytest.fixture @@ -97,3 +105,61 @@ def test_certified_forum(client, db): forum.refresh_from_db() assert forum.certified is True + + +def test_selected_tags_are_preloaded(client, db, reset_tag_sequence, snapshot): + client.force_login(UserFactory(is_superuser=True)) + forum = ForumFactory(with_tags=["iae", "siae", "prescripteur"]) + Tag.objects.create(name="undesired_tag") + url = reverse("forum_extension:edit_forum", kwargs={"pk": forum.pk, "slug": forum.slug}) + + response = client.get(url) + assert response.status_code == 200 + + content_tags = parse_response_to_soup( + response, selector="#div_id_tags", replace_in_href=[tag for tag in Tag.objects.all()] + ) + assert str(content_tags) == snapshot(name="selected_tags_preloaded") + + +def test_added_tags_are_saved(client, db): + client.force_login(UserFactory(is_superuser=True)) + forum = ForumFactory() + + Tag.objects.bulk_create([Tag(name=tag, slug=tag) for tag in [faker.word() for _ in range(3)]]) + tags_list = [faker.word() for i in range(2)] + + url = reverse("forum_extension:edit_forum", kwargs={"pk": forum.pk, "slug": forum.slug}) + response = client.post( + url, + data={ + "name": forum.name, + "short_description": forum.short_description, + "description": forum.description.raw, + "tags": [Tag.objects.first().pk], + "new_tags": ", ".join(tags_list), + }, + ) + + assert response.status_code == 302 + + forum.refresh_from_db() + assert all(tag in [tag.name for tag in forum.tags.all()] for tag in [Tag.objects.first().name] + tags_list) + + +def test_update_forum_without_tag(client, db): + client.force_login(UserFactory(is_superuser=True)) + forum = ForumFactory() + url = reverse("forum_extension:edit_forum", kwargs={"pk": forum.pk, "slug": forum.slug}) + response = client.post( + url, + data={ + "name": forum.name, + "short_description": forum.short_description, + "description": forum.description.raw, + }, + ) + assert response.status_code == 302 + + forum.refresh_from_db() + assert forum.tags.count() == 0 diff --git a/lacommunaute/forum/tests/tests_forms.py b/lacommunaute/forum/tests/tests_forms.py index 6f0897e3c..61c589930 100644 --- a/lacommunaute/forum/tests/tests_forms.py +++ b/lacommunaute/forum/tests/tests_forms.py @@ -48,3 +48,5 @@ def test_form_field(): assert not form.fields["description"].required assert not form.fields["image"].required assert not form.fields["certified"].required + assert not form.fields["tags"].required + assert not form.fields["new_tags"].required diff --git a/lacommunaute/templates/forum/partials/forum_form.html b/lacommunaute/templates/forum/partials/forum_form.html index 572b0bb33..bb4774155 100644 --- a/lacommunaute/templates/forum/partials/forum_form.html +++ b/lacommunaute/templates/forum/partials/forum_form.html @@ -11,6 +11,9 @@ {% include "partials/form_field.html" with field=form.description %} {% include "partials/form_field.html" with field=form.image %} {% include "partials/form_field.html" with field=form.certified %} +
+ {% include "partials/form_field.html" with field=form.tags %} + {% include "partials/form_field.html" with field=form.new_tags %}