diff --git a/core/templates/core/fragment/user_visibility.jinja b/core/templates/core/fragments/user_visibility.jinja similarity index 100% rename from core/templates/core/fragment/user_visibility.jinja rename to core/templates/core/fragments/user_visibility.jinja diff --git a/core/views/user.py b/core/views/user.py index 00d5610a..96c95387 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -473,7 +473,7 @@ class UserClubView(UserTabsMixin, CanViewMixin, DetailView): class UserVisibilityFormFragment(FragmentMixin, SuccessMessageMixin, UpdateView): model = User form_class = UserVisibilityForm - template_name = "core/fragment/user_visibility.jinja" + template_name = "core/fragments/user_visibility.jinja" pk_url_kwarg = "user_id" def get_form_kwargs(self): diff --git a/pedagogy/models.py b/pedagogy/models.py index f9731d86..47fdebeb 100644 --- a/pedagogy/models.py +++ b/pedagogy/models.py @@ -126,7 +126,11 @@ class UE(models.Model): Returns: True if the user has already posted a comment on this UE, else False. """ - return self.comments.filter(author=user).exists() + self._has_user_commented = getattr(self, "_has_user_commented", {}) + self._has_user_commented[user] = self._has_user_commented.get( + user, self.comments.filter(author=user).exists() + ) + return self._has_user_commented[user] @cached_property def grade_global_average(self): diff --git a/pedagogy/static/pedagogy/css/pedagogy.scss b/pedagogy/static/pedagogy/css/pedagogy.scss index f0b9863d..d4661e1b 100644 --- a/pedagogy/static/pedagogy/css/pedagogy.scss +++ b/pedagogy/static/pedagogy/css/pedagogy.scss @@ -205,14 +205,14 @@ $pedagogy-white-text: #f0f0f0; grid-area: hours-the; } - #leave_comment_not_allowed { + .leave-comment-not-allowed { p { text-align: center; color: red; } } - #leave_comment { + .leave-comment { .leave-comment-grid-container { display: grid; grid-template-columns: 270px auto; @@ -246,9 +246,11 @@ $pedagogy-white-text: #f0f0f0; margin-top: 20px; } - input[type="submit"] { - float: right; + .right { + display: flex; + justify-content: flex-end; } + } .ue-details-container { @@ -410,8 +412,9 @@ $pedagogy-white-text: #f0f0f0; padding-left: 10px; } - .actions { + .action { float: right; + margin-top: 0; } } diff --git a/pedagogy/templates/pedagogy/fragments/ue_comment.jinja b/pedagogy/templates/pedagogy/fragments/ue_comment.jinja new file mode 100644 index 00000000..6361e8d9 --- /dev/null +++ b/pedagogy/templates/pedagogy/fragments/ue_comment.jinja @@ -0,0 +1,77 @@ +{% from "pedagogy/macros.jinja" import display_star %} +{% from "core/macros.jinja" import user_profile_link %} + +
+ +
+
+

{% trans %}Global grade{% endtrans %}

+

{% trans %}Utility{% endtrans %}

+

{% trans %}Interest{% endtrans %}

+

{% trans %}Teaching{% endtrans %}

+

{% trans %}Work load{% endtrans %}

+
+
+

{{ display_star(comment.grade_global) }}

+

{{ display_star(comment.grade_utility) }}

+

{{ display_star(comment.grade_interest) }}

+

{{ display_star(comment.grade_teaching) }}

+

{{ display_star(comment.grade_work_load) }}

+
+
+
+ +
+
+ +
+ {{ comment.comment|markdown }} +
+ +
+ {% if comment.is_reported %} +

+ {% trans %}This comment has been reported{% endtrans %} +

+ {% endif %} + + {% if comment.author_id == user.id or user.has_perm("pedagogy.change_comment") %} + + {% endif %} + {% if comment.author_id == user.id or user.has_perm("pedagogy.delete_comment") %} +
+ {% csrf_token %} + +
+ {% endif %} +
+ +
+ + +

{{ comment.publish_date.strftime('%d/%m/%Y') }}

+ +

{{ user_profile_link(comment.author) }}

+
+ +
\ No newline at end of file diff --git a/pedagogy/templates/pedagogy/fragments/ue_comment_form.jinja b/pedagogy/templates/pedagogy/fragments/ue_comment_form.jinja new file mode 100644 index 00000000..2adf9228 --- /dev/null +++ b/pedagogy/templates/pedagogy/fragments/ue_comment_form.jinja @@ -0,0 +1,76 @@ +
+ {% if form.is_creation %} +
+ {% trans %}Leave comment{% endtrans %} +
+ {% endif %} +
+ {% csrf_token %} +
+
+ {{ form.non_field_errors() }} + {{ form.author.errors }} + {{ form.ue.errors }} + + {{ form.author }} + {{ form.ue }} + +
+ + {{ form.grade_global.errors }} + {{ form.grade_global }} +
+ +
+ + {{ form.grade_utility.errors }} + {{ form.grade_utility }} +
+ +
+ + {{ form.grade_interest.errors }} + {{ form.grade_interest }} +
+ +
+ + {{ form.grade_teaching.errors }} + {{ form.grade_teaching }} +
+ +
+ + {{ form.grade_work_load.errors }} + {{ form.grade_work_load }} +
+
+
+ + {{ form.comment.errors }} + {{ form.comment }} +
+
+

+ +

+
+ + {% if form.is_creation %} +
+
+ {% endif %} + +
+ +
diff --git a/pedagogy/templates/pedagogy/fragments/ue_detail.jinja b/pedagogy/templates/pedagogy/fragments/ue_detail.jinja new file mode 100644 index 00000000..466e92a3 --- /dev/null +++ b/pedagogy/templates/pedagogy/fragments/ue_detail.jinja @@ -0,0 +1,52 @@ +
+
+
+

{% trans %}Global grade{% endtrans %}

+

{% trans %}Utility{% endtrans %}

+

{% trans %}Interest{% endtrans %}

+

{% trans %}Teaching{% endtrans %}

+

{% trans %}Work load{% endtrans %}

+
+
+

{{ display_star(object.grade_global_average) }}

+

{{ display_star(object.grade_utility_average) }}

+

{{ display_star(object.grade_interest_average) }}

+

{{ display_star(object.grade_teaching_average) }}

+

{{ display_star(object.grade_work_load_average) }}

+
+
+

{% trans %}Objectives{% endtrans %}

+

{{ object.objectives|markdown }}

+

{% trans %}Program{% endtrans %}

+

{{ object.program|markdown }}

+

{% trans %}Earned skills{% endtrans %}

+

{{ object.skills|markdown }}

+

{% trans %}Key concepts{% endtrans %}

+

{{ object.key_concepts|markdown }}

+

{% trans %}UE manager: {% endtrans %}{{ object.manager }}

+
+
+ +
+ {% if object.has_user_already_commented(user) %} +
+

{% trans %}You already posted a comment on this UE. If you want to comment again, please modify or delete your previous comment.{% endtrans %}

+
+
+ {% endif %} + + {% if comments %} +

{% trans %}Comments{% endtrans %}

+
+ {% endif %} + + {% if not object.has_user_already_commented(user) and user.has_perm("pedagogy.add_uecomment") %} + {{ add_comment_form }} + {% endif %} + +
diff --git a/pedagogy/templates/pedagogy/ue_detail.jinja b/pedagogy/templates/pedagogy/ue_detail.jinja index 93423afd..30a944d6 100644 --- a/pedagogy/templates/pedagogy/ue_detail.jinja +++ b/pedagogy/templates/pedagogy/ue_detail.jinja @@ -1,220 +1,74 @@ -{% extends "core/base.jinja" %} -{% from "core/macros.jinja" import user_profile_link %} {% from "pedagogy/macros.jinja" import display_star %} -{% block additional_css %} - -{% endblock %} +{% if is_fragment %} + {% include "pedagogy/fragments/ue_detail.jinja" %} +{% else %} + {% extends "core/base.jinja" %} -{% block title %} - {% trans %}UE Details{% endtrans %} -{% endblock %} + {% block additional_css %} + + {% endblock %} -{% block content %} -
-
- + {% block title %} + {% trans %}UE Details{% endtrans %} + {% endblock %} -

{{ object.code }} - {{ object.title }}

-
-
-
- {% trans %}CM: {% endtrans %}{{ object.hours_CM }} -
-
- {% trans %}TD: {% endtrans %}{{ object.hours_TD }} -
-
- {% trans %}TP: {% endtrans %}{{ object.hours_TP }} -
-
- {% trans %}TE: {% endtrans %}{{ object.hours_TE }} -
-
- {% trans %}THE: {% endtrans %}{{ object.hours_THE }} -
+ {% block content %} -
- {{ object.department }} -
-
- {{ object.credit_type }} -
-
- {{ object.get_semester_display() }} -
-
+
+
+ -
- -
-
-

{% trans %}Global grade{% endtrans %}

-

{% trans %}Utility{% endtrans %}

-

{% trans %}Interest{% endtrans %}

-

{% trans %}Teaching{% endtrans %}

-

{% trans %}Work load{% endtrans %}

-
-
-

{{ display_star(object.grade_global_average) }}

-

{{ display_star(object.grade_utility_average) }}

-

{{ display_star(object.grade_interest_average) }}

-

{{ display_star(object.grade_teaching_average) }}

-

{{ display_star(object.grade_work_load_average) }}

-
-
-

{% trans %}Objectives{% endtrans %}

-

{{ object.objectives|markdown }}

-

{% trans %}Program{% endtrans %}

-

{{ object.program|markdown }}

-

{% trans %}Earned skills{% endtrans %}

-

{{ object.skills|markdown }}

-

{% trans %}Key concepts{% endtrans %}

-

{{ object.key_concepts|markdown }}

-

{% trans %}UE manager: {% endtrans %}{{ object.manager }}

-
-
- -
- {% if object.has_user_already_commented(user) %} -
-

{% trans %}You already posted a comment on this UE. If you want to comment again, please modify or delete your previous comment.{% endtrans %}

-
- {% elif user.has_perm("pedagogy.add_uecomment") %} -
- {% trans %}Leave comment{% endtrans %} -
-
- {% csrf_token %} -
-
- {{ form.non_field_errors() }} - {{ form.author.errors }} - {{ form.ue.errors }} - - {{ form.author }} - {{ form.ue }} - -
- - {{ form.grade_global.errors }} - {{ form.grade_global }} -
- -
- - {{ form.grade_utility.errors }} - {{ form.grade_utility }} -
- -
- - {{ form.grade_interest.errors }} - {{ form.grade_interest }} -
- -
- - {{ form.grade_teaching.errors }} - {{ form.grade_teaching }} -
- -
- - {{ form.grade_work_load.errors }} - {{ form.grade_work_load }} -
-
-
- - {{ form.comment.errors }} - {{ form.comment }} -
-
-

-
+

{{ object.code }} - {{ object.title }}

+
+
+
+ {% trans %}CM: {% endtrans %}{{ object.hours_CM }} +
+
+ {% trans %}TD: {% endtrans %}{{ object.hours_TD }} +
+
+ {% trans %}TP: {% endtrans %}{{ object.hours_TP }} +
+
+ {% trans %}TE: {% endtrans %}{{ object.hours_TE }} +
+
+ {% trans %}THE: {% endtrans %}{{ object.hours_THE }}
-
- {% endif %} -
- {% if comments %} -

{% trans %}Comments{% endtrans %}

+
+ {{ object.department }} +
+
+ {{ object.credit_type }} +
+
+ {{ object.get_semester_display() }} +
+
+ +
+ + {% include "pedagogy/fragments/ue_detail.jinja" %} + {% for comment in comments %} -
- -
-
-

{% trans %}Global grade{% endtrans %}

-

{% trans %}Utility{% endtrans %}

-

{% trans %}Interest{% endtrans %}

-

{% trans %}Teaching{% endtrans %}

-

{% trans %}Work load{% endtrans %}

-
-
-

{{ display_star(comment.grade_global) }}

-

{{ display_star(comment.grade_utility) }}

-

{{ display_star(comment.grade_interest) }}

-

{{ display_star(comment.grade_teaching) }}

-

{{ display_star(comment.grade_work_load) }}

-
-
-
- -
-
- -
- {{ comment.comment|markdown }} -
- -
- {% if comment.is_reported %} -

- {% trans %}This comment has been reported{% endtrans %} -

- {% endif %} - - {% if comment.author_id == user.id or user.has_perm("pedagogy.change_comment") %} -

- - {% trans %}Edit{% endtrans %} - - {% endif %} - {% if comment.author_id == user.id or user.has_perm("pedagogy.delete_comment") %} - - {% trans %}Delete{% endtrans %} - -

- {% endif %} -
- -
- - -

{{ comment.publish_date.strftime('%d/%m/%Y') }}

- -

{{ user_profile_link(comment.author) }}

-
- -
+
+ {% include "pedagogy/fragments/ue_comment.jinja" %} +
{% endfor %} - {% endif %} +
-
-{% endblock %} + + {% endblock %} +{% endif %} diff --git a/pedagogy/urls.py b/pedagogy/urls.py index 9edcf861..cc296f73 100644 --- a/pedagogy/urls.py +++ b/pedagogy/urls.py @@ -24,12 +24,14 @@ from django.urls import path from pedagogy.views import ( + UECommentCreateView, UECommentDeleteView, + UECommentDetailView, UECommentReportCreateView, UECommentUpdateView, UECreateView, UEDeleteView, - UEDetailFormView, + UEDetailView, UEGuideView, UEModerationFormView, UEUpdateView, @@ -38,7 +40,17 @@ from pedagogy.views import ( urlpatterns = [ # Urls displaying the actual application for visitors path("", UEGuideView.as_view(), name="guide"), - path("ue//", UEDetailFormView.as_view(), name="ue_detail"), + path("ue//", UEDetailView.as_view(), name="ue_detail"), + path( + "ue//comment", + UECommentCreateView.as_view(), + name="comment_create", + ), + path( + "comment//", + UECommentDetailView.as_view(), + name="comment_detail", + ), path( "comment//edit/", UECommentUpdateView.as_view(), diff --git a/pedagogy/views.py b/pedagogy/views.py index 4f21248e..4614e40e 100644 --- a/pedagogy/views.py +++ b/pedagogy/views.py @@ -24,11 +24,13 @@ from django.conf import settings from django.contrib.auth.mixins import PermissionRequiredMixin from django.db.models import Exists, OuterRef +from django.http import HttpResponse from django.shortcuts import get_object_or_404 from django.urls import reverse, reverse_lazy from django.views.generic import ( CreateView, DeleteView, + DetailView, FormView, TemplateView, UpdateView, @@ -36,7 +38,7 @@ from django.views.generic import ( from core.auth.mixins import PermissionOrAuthorRequiredMixin from core.models import Notification, User -from core.views import DetailFormView +from core.views.mixins import AllowFragment, FragmentMixin, UseFragmentsMixin from pedagogy.forms import ( UECommentForm, UECommentModerationForm, @@ -46,37 +48,56 @@ from pedagogy.forms import ( from pedagogy.models import UE, UEComment, UECommentReport -class UEDetailFormView(PermissionRequiredMixin, DetailFormView): - """Display every comment of an UE and detailed infos about it. - - Allow to comment the UE. - """ - - model = UE - pk_url_kwarg = "ue_id" - template_name = "pedagogy/ue_detail.jinja" +class UECommentCreateView(PermissionRequiredMixin, FragmentMixin, CreateView): + model = UEComment + template_name = "pedagogy/fragments/ue_comment_form.jinja" form_class = UECommentForm - permission_required = "pedagogy.view_ue" + permission_required = "pedagogy.add_uecomment" + object = None # Avoid initialisation bug with FragmentMixin + + @property + def ue(self): + if hasattr(self, "_ue"): + return self._ue + self._ue = get_object_or_404(UE, id=self.kwargs.get("ue_id")) + return self._ue def has_permission(self): - if self.request.method == "POST" and not self.request.user.has_perm( - "pedagogy.add_uecomment" - ): - # if it's a POST request, the user is trying to add a new UEComment - # thus he also needs the "add_uecomment" permission + if self.ue.has_user_already_commented(self.request.user): return False return super().has_permission() def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs["author_id"] = self.request.user.id - kwargs["ue_id"] = self.object.id + kwargs["ue_id"] = self.ue.id kwargs["is_creation"] = True return kwargs - def form_valid(self, form): - form.save() - return super().form_valid(form) + def get_context_data(self, **kwargs): + return super().get_context_data(**kwargs) | { + "action": reverse("pedagogy:comment_create", kwargs={"ue_id": self.ue.id}) + } + + def get_success_url(self): + return reverse("pedagogy:comment_detail", kwargs={"comment_id": self.object.id}) + + +class UEDetailView( + PermissionRequiredMixin, UseFragmentsMixin, AllowFragment, DetailView +): + """Display every comment of an UE and detailed infos about it.""" + + model = UE + pk_url_kwarg = "ue_id" + template_name = "pedagogy/ue_detail.jinja" + permission_required = "pedagogy.view_ue" + fragments = { + "add_comment_form": UECommentCreateView, + } + + def get_fragment_data(self): + return {"add_comment_form": {"ue_id": self.object.id}} def get_context_data(self, **kwargs): return super().get_context_data(**kwargs) | { @@ -85,22 +106,33 @@ class UEDetailFormView(PermissionRequiredMixin, DetailFormView): .annotate_is_reported() .select_related("author") .order_by("-publish_date") - ) + ), } - def get_success_url(self): - # once the new ue comment has been saved - # redirect to the same page we are currently - return self.request.path + +class UECommentDetailView(PermissionRequiredMixin, DetailView): + model = UEComment + pk_url_kwarg = "comment_id" + template_name = "pedagogy/fragments/ue_comment.jinja" + permission_required = "pedagogy.view_ue" + context_object_name = "comment" + + def dispatch(self, *args, **kwargs): + res: HttpResponse = super().dispatch(*args, **kwargs) + res.headers["HX-Trigger"] = "CommentUpdate" + return res + + def get_context_data(self, **kwargs): + return super().get_context_data(**kwargs) | {"ue": self.object.ue} -class UECommentUpdateView(PermissionOrAuthorRequiredMixin, UpdateView): +class UECommentUpdateView(PermissionOrAuthorRequiredMixin, AllowFragment, UpdateView): """Allow edit of a given comment.""" model = UEComment form_class = UECommentForm pk_url_kwarg = "comment_id" - template_name = "core/edit.jinja" + template_name = "pedagogy/fragments/ue_comment_form.jinja" permission_required = "pedagogy.change_uecomment" author_field = "author" @@ -111,11 +143,18 @@ class UECommentUpdateView(PermissionOrAuthorRequiredMixin, UpdateView): kwargs["is_creation"] = False return kwargs + def get_context_data(self, **kwargs): + return super().get_context_data(**kwargs) | { + "action": reverse( + "pedagogy:comment_update", kwargs={"comment_id": self.object.id} + ) + } + def get_success_url(self): - return reverse("pedagogy:ue_detail", kwargs={"ue_id": self.object.ue_id}) + return reverse("pedagogy:comment_detail", kwargs={"comment_id": self.object.id}) -class UECommentDeleteView(PermissionOrAuthorRequiredMixin, DeleteView): +class UECommentDeleteView(PermissionOrAuthorRequiredMixin, AllowFragment, DeleteView): """Allow to delete a given comment.""" model = UEComment @@ -124,8 +163,11 @@ class UECommentDeleteView(PermissionOrAuthorRequiredMixin, DeleteView): permission_required = "pedagogy.delete_uecomment" author_field = "author" - def get_success_url(self): - return reverse("pedagogy:ue_detail", kwargs={"ue_id": self.object.ue_id}) + def form_valid(self, form): + self.object.delete() + response = HttpResponse(status=200) + response.headers["HX-Trigger"] = "CommentUpdate" + return response class UEGuideView(PermissionRequiredMixin, TemplateView):