From 203e0694257dc0631c7cc53fdcf2f4ac0a5a232a Mon Sep 17 00:00:00 2001 From: Arnaud-D <35631001+Arnaud-D@users.noreply.github.com> Date: Mon, 7 Mar 2022 19:36:06 +0100 Subject: [PATCH] Ajoute des tests et refactorise RemoveSuggestion (#6188) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Ajoute des tests à RemoveSuggestion * Refactorise RemoveSuggestion * Retire un TODO obsolète * Retire un print qui traîne * Retire une variable inutile * Retire un import inutile * Corrige imports --- zds/tutorialv2/forms.py | 12 +- .../tests_views/tests_removesuggestion.py | 121 ++++++++++++++++++ zds/tutorialv2/views/editorialization.py | 58 +++++---- 3 files changed, 162 insertions(+), 29 deletions(-) create mode 100644 zds/tutorialv2/tests/tests_views/tests_removesuggestion.py diff --git a/zds/tutorialv2/forms.py b/zds/tutorialv2/forms.py index 93ff9708a5..0aac241b63 100644 --- a/zds/tutorialv2/forms.py +++ b/zds/tutorialv2/forms.py @@ -11,7 +11,7 @@ from zds.utils.models import SubCategory, Licence from zds.tutorialv2.models import TYPE_CHOICES from zds.utils.models import HelpWriting -from zds.tutorialv2.models.database import PublishableContent, ContentContributionRole +from zds.tutorialv2.models.database import PublishableContent, ContentContributionRole, ContentSuggestion from django.utils.translation import gettext_lazy as _ from zds.member.models import Profile from zds.tutorialv2.utils import slugify_raise_on_invalid, InvalidSlugError @@ -1363,11 +1363,19 @@ def __init__(self, content, *args, **kwargs): class RemoveSuggestionForm(forms.Form): - pk_suggestion = forms.CharField( + pk_suggestion = forms.IntegerField( label=_("Suggestion"), required=True, + error_messages={"does_not_exist": _("La suggestion sélectionnée n'existe pas.")}, ) + def clean_pk_suggestion(self): + pk_suggestion = self.cleaned_data.get("pk_suggestion") + suggestion = ContentSuggestion.objects.filter(id=pk_suggestion).first() + if suggestion is None: + self.add_error("pk_suggestion", self.fields["pk_suggestion"].error_messages["does_not_exist"]) + return pk_suggestion + class ToggleHelpForm(forms.Form): help_wanted = forms.CharField() diff --git a/zds/tutorialv2/tests/tests_views/tests_removesuggestion.py b/zds/tutorialv2/tests/tests_views/tests_removesuggestion.py new file mode 100644 index 0000000000..fdbf1f649f --- /dev/null +++ b/zds/tutorialv2/tests/tests_views/tests_removesuggestion.py @@ -0,0 +1,121 @@ +from django.test import TestCase +from django.urls import reverse +from django.utils.translation import gettext_lazy as _ +from django.utils.html import escape + +from zds.member.tests.factories import ProfileFactory, StaffProfileFactory +from zds.tutorialv2.tests.factories import PublishableContentFactory +from zds.tutorialv2.forms import RemoveSuggestionForm +from zds.tutorialv2.models.database import ContentSuggestion +from zds.tutorialv2.tests import TutorialTestMixin, override_for_contents + + +@override_for_contents() +class RemoveSuggestionPermissionTests(TutorialTestMixin, TestCase): + """Test permissions and associated behaviors, such as redirections and status codes.""" + + def setUp(self): + # Create users + self.author = ProfileFactory().user + self.staff = StaffProfileFactory().user + self.outsider = ProfileFactory().user + + # Create contents and suggestion + self.content = PublishableContentFactory(author_list=[self.author]) + self.suggested_content = PublishableContentFactory() + self.suggestion = ContentSuggestion(publication=self.content, suggestion=self.suggested_content) + self.suggestion.save() + + # Get information to be reused in tests + self.form_url = reverse("content:remove-suggestion", kwargs={"pk": self.content.pk}) + self.login_url = reverse("member-login") + "?next=" + self.form_url + self.content_url = reverse("content:view", kwargs={"pk": self.content.pk, "slug": self.content.slug}) + self.form_data = {"pk_suggestion": self.suggestion.pk} + + def test_not_authenticated(self): + self.client.logout() + self.content.type = "TUTORIAL" + self.content.save() + response = self.client.post(self.form_url, self.form_data) + self.assertRedirects(response, self.login_url) + + def test_authenticated_outsider(self): + self.client.force_login(self.outsider) + self.content.type = "TUTORIAL" + self.content.save() + response = self.client.post(self.form_url, self.form_data) + self.assertEqual(response.status_code, 403) + + def test_authenticated_author(self): + self.client.force_login(self.author) + self.content.type = "TUTORIAL" + self.content.save() + response = self.client.post(self.form_url, self.form_data) + self.assertEqual(response.status_code, 403) + + def test_authenticated_staff_tutorial(self): + self.client.force_login(self.staff) + self.content.type = "TUTORIAL" + self.content.save() + response = self.client.post(self.form_url, self.form_data) + self.assertRedirects(response, self.content_url) + + def test_authenticated_staff_article(self): + self.client.force_login(self.staff) + self.content.type = "ARTICLE" + self.content.save() + response = self.client.post(self.form_url, self.form_data) + self.assertRedirects(response, self.content_url) + + def test_authenticated_staff_opinion(self): + self.client.force_login(self.staff) + self.content.type = "OPINION" + self.content.save() + response = self.client.post(self.form_url, self.form_data) + self.assertEqual(response.status_code, 403) + + +class RemoveSuggestionWorkflowTests(TutorialTestMixin, TestCase): + """Test the workflow of the form, such as validity errors and success messages.""" + + def setUp(self): + # Create users + self.staff = StaffProfileFactory().user + self.author = ProfileFactory().user + + # Create a content + self.content = PublishableContentFactory(author_list=[self.author]) + self.suggested_content_1 = PublishableContentFactory() + self.suggested_content_2 = PublishableContentFactory() + self.suggestion_1 = ContentSuggestion(publication=self.content, suggestion=self.suggested_content_1) + self.suggestion_1.save() + self.suggestion_2 = ContentSuggestion(publication=self.content, suggestion=self.suggested_content_2) + self.suggestion_2.save() + + # Get information to be reused in tests + self.form_url = reverse("content:remove-suggestion", kwargs={"pk": self.content.pk}) + self.success_message_fragment = _("Vous avez enlevé") + self.error_messages = RemoveSuggestionForm.declared_fields["pk_suggestion"].error_messages + # Log in with an authorized user to perform the tests + self.client.force_login(self.staff) + + def test_existing(self): + response = self.client.post(self.form_url, {"pk_suggestion": self.suggestion_1.pk}, follow=True) + # Check that we display correct message + self.assertContains(response, escape(self.success_message_fragment)) + # Check update of database + with self.assertRaises(ContentSuggestion.DoesNotExist): + ContentSuggestion.objects.get(pk=self.suggestion_1.pk) + ContentSuggestion.objects.get(pk=self.suggestion_2.pk) # succeeds + + def test_empty(self): + response = self.client.post(self.form_url, {"pk_suggestion": ""}, follow=True) + self.assertContains(response, escape(self.error_messages["required"])) + + def test_invalid(self): + response = self.client.post(self.form_url, {"pk_suggestion": "420"}, follow=True) # pk must not exist + self.assertContains(response, escape(self.error_messages["does_not_exist"])) + + def test_not_integer(self): + response = self.client.post(self.form_url, {"pk_suggestion": "abcd"}, follow=True) + self.assertContains(response, escape(self.error_messages["invalid"])) diff --git a/zds/tutorialv2/views/editorialization.py b/zds/tutorialv2/views/editorialization.py index 3c94beb743..c19f670eee 100644 --- a/zds/tutorialv2/views/editorialization.py +++ b/zds/tutorialv2/views/editorialization.py @@ -1,51 +1,55 @@ from django.contrib import messages +from django.contrib.auth.decorators import login_required from django.core.exceptions import PermissionDenied from django.shortcuts import get_object_or_404, redirect +from django.utils.decorators import method_decorator from django.utils.translation import gettext_lazy as _ -from zds.member.decorator import LoggedWithReadWriteHability, PermissionRequiredMixin +from zds.member.decorator import LoggedWithReadWriteHability, can_write_and_read_now, PermissionRequiredMixin from zds.tutorialv2.forms import RemoveSuggestionForm, EditContentTagsForm from zds.tutorialv2.mixins import SingleContentFormViewMixin from zds.tutorialv2.models.database import ContentSuggestion, PublishableContent -class RemoveSuggestion(LoggedWithReadWriteHability, SingleContentFormViewMixin): - +class RemoveSuggestion(PermissionRequiredMixin, SingleContentFormViewMixin): form_class = RemoveSuggestionForm + modal_form = True only_draft_version = True - authorized_for_staff = True + permissions = ["tutorialv2.change_publishablecontent"] - def form_valid(self, form): - _type = _("cet article") - if self.object.is_tutorial: - _type = _("ce tutoriel") - elif self.object.is_opinion: + @method_decorator(login_required) + @method_decorator(can_write_and_read_now) + def dispatch(self, *args, **kwargs): + if self.get_object().is_opinion: raise PermissionDenied + return super().dispatch(*args, **kwargs) + + def form_valid(self, form): + suggestion = ContentSuggestion.objects.get(pk=form.cleaned_data["pk_suggestion"]) + suggestion.delete() + messages.success(self.request, self.get_success_message(suggestion)) + return super().form_valid(form) - content_suggestion = get_object_or_404(ContentSuggestion, pk=form.cleaned_data["pk_suggestion"]) - content_suggestion.delete() + def form_invalid(self, form): + form.previous_page_url = self.get_success_url() + return super().form_invalid(form) - messages.success( - self.request, - _('Vous avez enlevé "{}" de la liste des suggestions de {}.').format( - content_suggestion.suggestion.title, _type - ), + def get_success_message(self, content_suggestion): + return _('Vous avez enlevé "{}" de la liste des suggestions de {}.').format( + content_suggestion.suggestion.title, + self.describe_type(), ) + def get_success_url(self): if self.object.public_version: - self.success_url = self.object.get_absolute_url_online() + return self.object.get_absolute_url_online() else: - self.success_url = self.object.get_absolute_url() - - return super().form_valid(form) + return self.object.get_absolute_url() - def form_invalid(self, form): - messages.error(self.request, str(_("Les suggestions sélectionnées n'existent pas."))) - if self.object.public_version: - self.success_url = self.object.get_absolute_url_online() - else: - self.success_url = self.object.get_absolute_url() - return super().form_valid(form) + def describe_type(self): + if self.object.is_tutorial: + return _("ce tutoriel") + return _("cet article") class AddSuggestion(LoggedWithReadWriteHability, PermissionRequiredMixin, SingleContentFormViewMixin):