mirror of
https://github.com/ae-utbm/sith.git
synced 2026-08-26 15:19:15 +00:00
Merge pull request #1460 from ae-utbm/pedagogy
Fix star width and rewrite star widget with nice hovering
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-06-10 20:18+0200\n"
|
"POT-Creation-Date: 2026-08-21 14:10+0200\n"
|
||||||
"PO-Revision-Date: 2016-07-18\n"
|
"PO-Revision-Date: 2016-07-18\n"
|
||||||
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
||||||
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
||||||
@@ -4969,6 +4969,10 @@ msgstr "Ne pas voter"
|
|||||||
msgid "This user has already commented on this UE"
|
msgid "This user has already commented on this UE"
|
||||||
msgstr "Cet utilisateur a déjà commenté cette UE"
|
msgstr "Cet utilisateur a déjà commenté cette UE"
|
||||||
|
|
||||||
|
#: pedagogy/forms.py
|
||||||
|
msgid "UE comment can't be empty."
|
||||||
|
msgstr "Un commentaire d'UE ne peut pas être vide."
|
||||||
|
|
||||||
#: pedagogy/forms.py
|
#: pedagogy/forms.py
|
||||||
msgid "Accepted reports"
|
msgid "Accepted reports"
|
||||||
msgstr "Signalements acceptés"
|
msgstr "Signalements acceptés"
|
||||||
|
|||||||
+29
-10
@@ -22,6 +22,8 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||||
|
from django.forms.fields import ValidationError
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from core.models import User
|
from core.models import User
|
||||||
@@ -30,7 +32,7 @@ from pedagogy.models import UE, UEComment, UECommentReport
|
|||||||
|
|
||||||
|
|
||||||
class UEForm(forms.ModelForm):
|
class UEForm(forms.ModelForm):
|
||||||
"""Form handeling creation and edit of an UE."""
|
"""Form handling creation and edit of an UE."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UE
|
model = UE
|
||||||
@@ -68,22 +70,28 @@ class UEForm(forms.ModelForm):
|
|||||||
self.fields["author"].initial = author_id
|
self.fields["author"].initial = author_id
|
||||||
|
|
||||||
|
|
||||||
class StarList(forms.NumberInput):
|
class StarList(forms.RadioSelect):
|
||||||
template_name = "pedagogy/starlist.jinja"
|
template_name = "pedagogy/starlist.jinja"
|
||||||
|
|
||||||
def __init__(self, nubmer_of_stars=0):
|
def __init__(self, number_of_stars=0, attrs=None):
|
||||||
super().__init__(None)
|
super().__init__(
|
||||||
self.number_of_stars = nubmer_of_stars
|
attrs=attrs,
|
||||||
|
choices=(
|
||||||
|
(choice, _("Do not vote") if choice == -1 else choice)
|
||||||
|
for choice in range(-1, number_of_stars)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def get_context(self, name, value, attrs):
|
def get_context(self, name, value, attrs):
|
||||||
context = super().get_context(name, value, attrs)
|
context = super().get_context(name, value, attrs)
|
||||||
context["number_of_stars"] = range(0, self.number_of_stars)
|
context["statics"] = {
|
||||||
context["translations"] = {"do_not_vote": _("Do not vote")}
|
"css": staticfiles_storage.url("pedagogy/css/starlist.scss"),
|
||||||
|
}
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class UECommentForm(forms.ModelForm):
|
class UECommentForm(forms.ModelForm):
|
||||||
"""Form handeling creation and edit of an UEComment."""
|
"""Form handling creation and edit of an UEComment."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UEComment
|
model = UEComment
|
||||||
@@ -129,11 +137,22 @@ class UECommentForm(forms.ModelForm):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Ensure that at least one value is exists
|
||||||
|
if (
|
||||||
|
all(
|
||||||
|
grade == -1
|
||||||
|
for key, grade in self.cleaned_data.items()
|
||||||
|
if key.startswith("grade_")
|
||||||
|
)
|
||||||
|
and not self.cleaned_data["comment"]
|
||||||
|
):
|
||||||
|
raise ValidationError(message=_("UE comment can't be empty."))
|
||||||
|
|
||||||
return self.cleaned_data
|
return self.cleaned_data
|
||||||
|
|
||||||
|
|
||||||
class UECommentReportForm(forms.ModelForm):
|
class UECommentReportForm(forms.ModelForm):
|
||||||
"""Form handeling creation and edit of an UEReport."""
|
"""Form handling creation and edit of an UEReport."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UECommentReport
|
model = UECommentReport
|
||||||
@@ -153,7 +172,7 @@ class UECommentReportForm(forms.ModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class UECommentModerationForm(forms.Form):
|
class UECommentModerationForm(forms.Form):
|
||||||
"""Form handeling bulk comment deletion."""
|
"""Form handling bulk comment deletion."""
|
||||||
|
|
||||||
accepted_reports = forms.ModelMultipleChoiceField(
|
accepted_reports = forms.ModelMultipleChoiceField(
|
||||||
UECommentReport.objects.all(),
|
UECommentReport.objects.all(),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
@import "core/static/core/devices";
|
||||||
@import "core/static/core/colors";
|
@import "core/static/core/colors";
|
||||||
|
|
||||||
|
|
||||||
@@ -7,10 +8,6 @@ $pedagogy-hover-blue: #0e97ce;
|
|||||||
$pedagogy-light-blue: #caf0ff;
|
$pedagogy-light-blue: #caf0ff;
|
||||||
$pedagogy-white-text: #f0f0f0;
|
$pedagogy-white-text: #f0f0f0;
|
||||||
|
|
||||||
$small-devices: 576px;
|
|
||||||
$medium-devices: 768px;
|
|
||||||
$large-devices: 992px;
|
|
||||||
|
|
||||||
.pedagogy {
|
.pedagogy {
|
||||||
&.star-not-checked {
|
&.star-not-checked {
|
||||||
color: #f7f7f7;
|
color: #f7f7f7;
|
||||||
@@ -256,7 +253,7 @@ $large-devices: 992px;
|
|||||||
|
|
||||||
.ue-details-container {
|
.ue-details-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 150px 100px auto;
|
grid-template-columns: 150px 130px auto;
|
||||||
grid-template-rows: 156px 1fr;
|
grid-template-rows: 156px 1fr;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"grade grade-stars ue-infos"
|
"grade grade-stars ue-infos"
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
fieldset.star {
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: inline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checked {
|
||||||
|
color: orange;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unchecked {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unchecked.hovered {
|
||||||
|
color: #FFD700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.removed {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
label:first-child {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:has(input:required:invalid) {
|
||||||
|
border: 1px solid;
|
||||||
|
border-color: #c00000;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,58 +1,52 @@
|
|||||||
<div>
|
<link-once rel="stylesheet" type="text/css" href="{{ statics.css }}" defer></link-once>
|
||||||
<style>
|
<fieldset
|
||||||
.checked {
|
class="star"
|
||||||
color : orange;
|
x-data="{ 'value': {{ widget.value.0 }}, 'hover': -1 }"
|
||||||
}
|
@mouseover.outside="hover = -1"
|
||||||
.unchecked {
|
{% include "django/forms/widgets/attrs.html" %}
|
||||||
color : gray;
|
>
|
||||||
}
|
|
||||||
.star input[type="radio"] {
|
|
||||||
display : none;
|
|
||||||
}
|
|
||||||
.star {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
{# Do not vote button #}
|
{# Do not vote button #}
|
||||||
<label class="star">
|
<label
|
||||||
<input type="radio" name="{{ widget.name }}" value="-1" onclick='
|
@mouseover="hover = -1"
|
||||||
var stars = document.getElementsByClassName("{{ widget.name }}");
|
for="{{ widget.optgroups.0.1.0.attrs.id }}"
|
||||||
for (var i = 0; i < stars.length; i++){
|
>
|
||||||
var attrs = stars[i].getAttribute("class");
|
<input
|
||||||
attrs = attrs.replace("unchecked", "");
|
type="radio"
|
||||||
attrs = attrs.replace("checked", "");
|
name="{{ widget.name }}"
|
||||||
stars[i].setAttribute("class", attrs + " unchecked");
|
value="{{ widget.optgroups.0.1.0.value }}"
|
||||||
}
|
@click="value = -1"
|
||||||
' checked>
|
{% with widget=widget.optgroups.0.1.0 %}
|
||||||
<span class="fa fa-times-circle"> {{ translations.do_not_vote }}</span>
|
{% include "django/forms/widgets/attrs.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
>
|
||||||
|
<i class="fa fa-times-circle"></i>
|
||||||
|
{{ widget.optgroups.0.1.0.label }}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{# Star widget #}
|
{# Star widget #}
|
||||||
{% for i in number_of_stars %}
|
{% for opt in widget.optgroups|slice:"1:" %}
|
||||||
<label class="star">
|
<label
|
||||||
<input type="radio" name="{{ widget.name }}" value="{{ forloop.counter0 }}" onclick='
|
@mouseover="hover = {{ opt.1.0.value }}"
|
||||||
var stars = document.getElementsByClassName("{{ widget.name }}");
|
for="{{ opt.1.0.attrs.id }}"
|
||||||
|
>
|
||||||
for (var i = 0; i < stars.length; i++){
|
<input
|
||||||
var attrs = stars[i].getAttribute("class");
|
type="radio"
|
||||||
attrs = attrs.replace("unchecked", "");
|
name="{{ widget.name }}"
|
||||||
attrs = attrs.replace("checked", "");
|
value="{{ opt.1.0.value }}"
|
||||||
if (i > {{ forloop.counter0 }}){
|
@click="value = {{ opt.1.0.value }}"
|
||||||
stars[i].setAttribute("class", attrs + " unchecked");
|
{% with widget=opt.1.0 %}
|
||||||
} else {
|
{% include "django/forms/widgets/attrs.html" %}
|
||||||
stars[i].setAttribute("class", attrs + " checked");
|
{% endwith %}
|
||||||
}
|
>
|
||||||
}
|
<i
|
||||||
'>
|
class="{{ widget.name }} fa fa-star"
|
||||||
<i class="{{ widget.name }} fa fa-star unchecked"></i>
|
:class="{
|
||||||
|
'checked': value >= {{ opt.1.0.value }},
|
||||||
|
'unchecked': value < {{ opt.1.0.value }},
|
||||||
|
'hovered': hover >= {{ opt.1.0.value }},
|
||||||
|
'removed': hover >= 0 && value >= {{ opt.1.0.value }} && hover < {{ opt.1.0.value }},
|
||||||
|
}"
|
||||||
|
></i>
|
||||||
</label>
|
</label>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</fieldset>
|
||||||
{# Restaure previous (-1 is default) #}
|
|
||||||
<script type="text/javascript">
|
|
||||||
document.querySelector("input[name='{{ widget.name }}'][value='{{ widget.value }}']").click()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
@@ -89,13 +89,14 @@
|
|||||||
<p>{% trans %}You already posted a comment on this UE. If you want to comment again, please modify or delete your previous comment.{% endtrans %}</p>
|
<p>{% trans %}You already posted a comment on this UE. If you want to comment again, please modify or delete your previous comment.{% endtrans %}</p>
|
||||||
</div>
|
</div>
|
||||||
{% elif user.has_perm("pedagogy.add_uecomment") %}
|
{% elif user.has_perm("pedagogy.add_uecomment") %}
|
||||||
<details class="accordion" id="leave_comment">
|
<details class="accordion" id="leave_comment" {% if form.errors %}open{%endif%}>
|
||||||
<summary>{% trans %}Leave comment{% endtrans %}</summary>
|
<summary>{% trans %}Leave comment{% endtrans %}</summary>
|
||||||
<div class="accordion-content">
|
<div class="accordion-content">
|
||||||
<form action="{{ url('pedagogy:ue_detail', ue_id=object.id) }}" method="post" enctype="multipart/form-data">
|
<form action="{{ url('pedagogy:ue_detail', ue_id=object.id) }}" method="post" enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="leave-comment-grid-container">
|
<div class="leave-comment-grid-container">
|
||||||
<div class="form-stars">
|
<div class="form-stars">
|
||||||
|
{{ form.non_field_errors() }}
|
||||||
{{ form.author.errors }}
|
{{ form.author.errors }}
|
||||||
{{ form.ue.errors }}
|
{{ form.ue.errors }}
|
||||||
|
|
||||||
|
|||||||
@@ -339,6 +339,24 @@ class TestUVCommentCreationAndDisplay(TestCase):
|
|||||||
response = self.client.get(self.ue_url)
|
response = self.client.get(self.ue_url)
|
||||||
self.assertContains(response, text="Superbe UE")
|
self.assertContains(response, text="Superbe UE")
|
||||||
|
|
||||||
|
def test_create_ue_empty_comment_fail(self):
|
||||||
|
self.client.force_login(self.tutu)
|
||||||
|
response = self.client.post(
|
||||||
|
self.ue_url,
|
||||||
|
{
|
||||||
|
"author": self.tutu.id,
|
||||||
|
"ue": UE.objects.get(code="PA00").id,
|
||||||
|
"grade_global": -1,
|
||||||
|
"grade_utility": -1,
|
||||||
|
"grade_interest": -1,
|
||||||
|
"grade_teaching": -1,
|
||||||
|
"grade_work_load": -1,
|
||||||
|
"comment": "",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
self.assertInHTML("Un commentaire d'UE ne peut pas être vide.", response.text)
|
||||||
|
|
||||||
def test_create_ue_comment_unauthorized_fail(self):
|
def test_create_ue_comment_unauthorized_fail(self):
|
||||||
nb_comments = self.ue.comments.count()
|
nb_comments = self.ue.comments.count()
|
||||||
# Test with anonymous user
|
# Test with anonymous user
|
||||||
|
|||||||
Reference in New Issue
Block a user