Skip to content

Commit

Permalink
Ajoute une modale d'édition pour les objectifs
Browse files Browse the repository at this point in the history
  • Loading branch information
Arnaud-D committed Jul 15, 2022
1 parent 1432e44 commit 4728965
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 0 deletions.
4 changes: 4 additions & 0 deletions templates/tutorialv2/events/descriptions.part.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@
<a href="{{ performer_href }}">{{ event.performer }}</a> a modifié les tags du contenu.


{% elif event.type == "goals_management" %}
<a href="{{ performer_href }}">{{ event.performer }}</a> a modifié les objectifs du contenu.


{% elif event.type == "suggestions_management" %}
{% if event.action == "add" %}
<a href="{{ performer_href }}">{{ event.performer }}</a> a ajouté une suggestion de contenu.
Expand Down
7 changes: 7 additions & 0 deletions templates/tutorialv2/includes/editorialization.part.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ <h3>Éditorialisation</h3>
{% crispy form_edit_tags %}
</li>

<li>
<a href="#edit-goals" class="open-modal ico-after gear blue">
Modifier les objectifs
</a>
{% crispy form_edit_goals %}
</li>

{% if is_staff and not content.is_opinion %}
<li>
<a href="#add-suggestion" class="open-modal ico-after more blue">
Expand Down
12 changes: 12 additions & 0 deletions zds/tutorialv2/models/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from zds.tutorialv2.views.beta import ManageBetaContent
from zds.tutorialv2.views.contributors import AddContributorToContent, RemoveContributorFromContent
from zds.tutorialv2.views.editorialization import EditContentTags, AddSuggestion, RemoveSuggestion
from zds.tutorialv2.views.goals import EditGoals
from zds.tutorialv2.views.help import ChangeHelp
from zds.tutorialv2.views.validations_contents import (
ReserveValidation,
Expand All @@ -25,6 +26,7 @@
# * Addition
# 1. Add a key in `types`.
# 2. Modify the template "events/description.part.html" so that it is displayed properly.
# 3. Add the appropriate receiver.
#
# * Deletion
# 1. Remove the key in `types` and the corresponding `@receiver`.
Expand All @@ -44,6 +46,7 @@
signals.beta_management: "beta_management",
signals.validation_management: "validation_management",
signals.tags_management: "tags_management",
signals.goals_management: "goals_management",
signals.suggestions_management: "suggestions_management",
signals.help_management: "help_management",
signals.jsfiddle_management: "jsfiddle_management",
Expand Down Expand Up @@ -147,6 +150,15 @@ def record_event_suggestion_management(sender, performer, signal, content, actio
).save()


@receiver(signals.goals_management, sender=EditGoals)
def record_event_goals_management(sender, performer, signal, content, **_):
Event(
performer=performer,
type=types[signal],
content=content,
).save()


@receiver(signals.help_management, sender=ChangeHelp)
def record_event_help_management(sender, performer, signal, content, **_):
Event(
Expand Down
4 changes: 4 additions & 0 deletions zds/tutorialv2/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
# Action is either "add" or "remove".
suggestions_management = Signal()

# Goals management
# For the signal below, the arguments "performer" and "content" shall be provided.
goals_management = Signal()

# Help management
# For the signal below, the arguments "performer" and "content" shall be provided.
help_management = Signal()
Expand Down
67 changes: 67 additions & 0 deletions zds/tutorialv2/tests/tests_views/tests_editgoals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from unittest.mock import patch

from django.test import TestCase
from django.urls import reverse

from zds.member.tests.factories import ProfileFactory, StaffProfileFactory
from zds.tutorialv2.tests.factories import PublishableContentFactory, GoalFactory


class EditGoalsPermissionTests(TestCase):
def setUp(self):
self.user = ProfileFactory().user
self.staff = StaffProfileFactory().user
self.content = PublishableContentFactory()
self.good_url = reverse("content:edit-goals", kwargs={"pk": self.content.pk})
self.bad_url = reverse("content:edit-goals", kwargs={"pk": 42})
self.success_url = reverse("content:view", kwargs={"pk": self.content.pk, "slug": self.content.slug})

def test_unauthenticated_not_existing_pk(self):
"""Invalid pks in URL"""
self.client.logout()
response = self.client.post(self.bad_url)
self.assertEqual(response.status_code, 404)

def test_unauthenticated_redirected(self):
"""As login is required, unauthenticated users shall be redirected to the login page."""
self.client.logout()
response = self.client.post(self.good_url)
self.login_url = f"{reverse('member-login')}?next={self.good_url}"
self.assertRedirects(response, self.login_url)

def test_simple_user_forbidden(self):
"""Simple users shall not be able to access to the view."""
self.client.force_login(self.user)
response = self.client.post(self.good_url)
self.assertEqual(response.status_code, 403)

def test_staff_authorized(self):
"""Staff shall have access to the view."""
self.client.force_login(self.staff)
response = self.client.post(self.good_url)
self.assertRedirects(response, self.success_url)


class EditGoalsFunctionalTests(TestCase):
def setUp(self):
self.staff = StaffProfileFactory().user
self.content = PublishableContentFactory()
self.content = PublishableContentFactory()
self.url = reverse("content:edit-goals", kwargs={"pk": self.content.pk})
self.goals = [GoalFactory() for _ in range(3)]

@patch("zds.tutorialv2.signals.goals_management")
def test_goals_updated(self, goals_management):
self.client.force_login(self.staff)
response = self.client.post(self.url, {"goals": [goal.pk for goal in self.goals]}, follow=True)
self.assertEqual(list(self.content.goals.all()), self.goals)
self.assertContains(response, "alert-box success")
self.assertEqual(goals_management.send.call_count, 1)

@patch("zds.tutorialv2.signals.goals_management")
def test_invalid_parameters(self, goals_management):
self.client.force_login(self.staff)
response = self.client.post(self.url, {"goals": [42]}, follow=True)
self.assertEqual(list(self.content.goals.all()), [])
self.assertContains(response, "alert-box alert")
self.assertFalse(goals_management.send.called)
3 changes: 3 additions & 0 deletions zds/tutorialv2/urls/urls_contents.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from zds.tutorialv2.views.contents import DisplayContent, CreateContent, EditContent, EditContentLicense, DeleteContent
from zds.tutorialv2.views.events import EventsList
from zds.tutorialv2.views.goals import EditGoals
from zds.tutorialv2.views.validations_contents import ActivateJSFiddleInContent
from zds.tutorialv2.views.containers_extracts import (
CreateContainer,
Expand Down Expand Up @@ -184,4 +185,6 @@
path("", RedirectView.as_view(pattern_name="publication:list", permanent=True), name="list"),
# Journal of events
path("evenements/<int:pk>/", EventsList.as_view(), name="events"),
# Goal-based classification
path("modifier-objectifs/<int:pk>/", EditGoals.as_view(), name="edit-goals"),
]
2 changes: 2 additions & 0 deletions zds/tutorialv2/views/contents.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from zds.tutorialv2.models.database import PublishableContent, Validation, ContentContribution, ContentSuggestion
from zds.tutorialv2.utils import init_new_repo
from zds.tutorialv2.views.authors import RemoveAuthorFromContent
from zds.tutorialv2.views.goals import EditGoalsForm
from zds.utils.models import get_hat_from_settings
from zds.mp.utils import send_mp, send_message_mp
from zds.utils.uuslug_wrapper import slugify
Expand Down Expand Up @@ -180,6 +181,7 @@ def get_forms(self, context):
context["formJs"] = form_js
context["form_edit_license"] = EditContentLicenseForm(self.versioned_object)
context["form_edit_tags"] = EditContentTagsForm(self.versioned_object, self.object)
context["form_edit_goals"] = EditGoalsForm(self.object)

if self.versioned_object.requires_validation:
context["formPublication"] = PublicationForm(self.versioned_object, initial={"source": self.object.source})
Expand Down
2 changes: 2 additions & 0 deletions zds/tutorialv2/views/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from zds.tutorialv2.utils import search_container_or_404, last_participation_is_old, mark_read
from zds.tutorialv2.views.containers_extracts import DisplayContainer
from zds.tutorialv2.views.contents import DisplayContent
from zds.tutorialv2.views.goals import EditGoalsForm
from zds.utils.models import CommentVote
from zds.utils.paginator import make_pagination

Expand Down Expand Up @@ -115,6 +116,7 @@ def get_context_data(self, **kwargs):
)

context["form_edit_tags"] = EditContentTagsForm(self.versioned_object, self.object)
context["form_edit_goals"] = EditGoalsForm(self.object)

# pagination of comments
make_pagination(
Expand Down
70 changes: 70 additions & 0 deletions zds/tutorialv2/views/goals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from crispy_forms.bootstrap import StrictButton
from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Layout, Field, ButtonHolder
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.forms import forms, ModelMultipleChoiceField, CheckboxSelectMultiple
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.translation import gettext_lazy as _

from zds.tutorialv2 import signals
from zds.tutorialv2.mixins import SingleContentFormViewMixin
from zds.tutorialv2.models.database import PublishableContent
from zds.tutorialv2.models.goals import Goal
from zds.utils import get_current_user


class EditGoalsForm(forms.Form):
goals = ModelMultipleChoiceField(
label=_("Objectifs de la publication :"),
queryset=Goal.objects.all(),
required=False,
widget=CheckboxSelectMultiple,
)

def __init__(self, content, *args, **kwargs):
kwargs["initial"] = {"goals": content.goals.all()}
super().__init__(*args, **kwargs)

self.helper = FormHelper()
self.helper.form_class = "content-wrapper"
self.helper.form_method = "post"
self.helper.form_id = "edit-goals"
self.helper.form_class = "modal modal-flex"
self.helper.form_action = reverse("content:edit-goals", kwargs={"pk": content.pk})
self.helper.layout = Layout(
Field("goals"),
ButtonHolder(StrictButton("Valider", type="submit")),
)


class EditGoals(LoginRequiredMixin, PermissionRequiredMixin, SingleContentFormViewMixin):
permission_required = "tutorialv2.change_publishablecontent"
model = PublishableContent
form_class = EditGoalsForm
success_message = _("Les objectifs ont bien été modifiés.")
modal_form = True

def dispatch(self, request, *args, **kwargs):
content = get_object_or_404(PublishableContent, pk=self.kwargs["pk"])
success_url_kwargs = {"pk": content.pk, "slug": content.slug}
self.success_url = reverse("content:view", kwargs=success_url_kwargs)
return super().dispatch(request, *args, **kwargs)

def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["content"] = self.object
return kwargs

def form_invalid(self, form):
form.previous_page_url = self.success_url
return super().form_invalid(form)

def form_valid(self, form):
self.object.goals.clear()
new_goals = Goal.objects.filter(id__in=form.cleaned_data["goals"])
self.object.goals.add(*new_goals)
messages.success(self.request, self.success_message)
signals.goals_management.send(sender=self.__class__, performer=get_current_user(), content=self.object)
return super().form_valid(form)

0 comments on commit 4728965

Please sign in to comment.