Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quiz sondage #6486

Open
wants to merge 88 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
0e4a448
quizz: Intégration IHM
artragis Sep 20, 2020
fd130d5
divers fix
artragis Sep 20, 2020
37c472f
POC
artragis Sep 20, 2020
d68cce6
Remontée des stats et blocs pour correction
artragis Sep 21, 2020
3b570b2
Permet de bien corriger les quizz où une réponse n'a pas été donnée
artragis Feb 20, 2021
9fd81bb
modèles de stats
artragis Sep 27, 2020
2064551
Utilise la fonction get_or_create de django qui est plus robuste
artragis Oct 9, 2020
cd7fc95
ne pas fusionner la correction
artragis Nov 14, 2020
2d5471b
reformat
artragis Dec 19, 2020
109edc7
merge migrations
artragis Dec 19, 2020
15772e7
Revert bad modif
artragis Dec 19, 2020
b2a0561
Update templates/tutorialv2/view/content.html
artragis Dec 19, 2020
15c9c8e
Update templates/tutorialv2/view/container.html
artragis Dec 19, 2020
db80c8d
Fix deux/trois soucis sur les quizz
artragis Oct 30, 2022
cb5eeca
doc lint
artragis Oct 30, 2022
a2588ac
doc lint
artragis Oct 30, 2022
496cc30
clean up models
artragis Oct 30, 2022
5bf27a0
Apply suggestions from code review
artragis Oct 31, 2022
458f1c5
Rend le JS plus lisible
artragis Oct 31, 2022
f76544b
Indente le span
artragis Oct 31, 2022
7961f52
linter hell
artragis Oct 31, 2022
f7306b5
Billets précédents et suivants : uniquement ceux choisis par le Staff
atman0eirb Dec 30, 2022
83a14cd
modify test to accept the Fix #5793
atman0eirb Jan 13, 2023
e721ba2
import de PickListOperation pour le test
atman0eirb Jan 13, 2023
b858112
Merge branch 'dev' into dev
philippemilink Jan 14, 2023
26d76be
add explanation, validaion of quizz if there is answer
atman0eirb Mar 18, 2023
6aa48a0
quik fix
atman0eirb Mar 19, 2023
62b07c6
conflict
atman0eirb Mar 19, 2023
0052db6
reglage de bug d'affichage apres le submit de la reponse
Mar 19, 2023
9093ffa
fixed explanation and result display
atman0eirb Mar 23, 2023
0a638da
Merge branch 'quiz_sondage' of github.com:atman0eirb/zds-site into qu…
atman0eirb Mar 23, 2023
3ce3f5d
display corrects answers with green
atman0eirb Mar 27, 2023
4d0e68d
no result display any more, result is clear by colors
atman0eirb Mar 27, 2023
0da5ae2
Merge remote branch 'origin/quiz_sondage'
atman0eirb Mar 27, 2023
fbab71a
Merge remote branch 'origin/quiz_sondage'
atman0eirb Mar 27, 2023
266f492
problem des statistiques doublées résolu
atman0eirb Apr 3, 2023
c9793a5
fix le titre des quizzs dans la section statistique
Apr 3, 2023
6816722
Merge branch 'quiz_sondage' of github.com:atman0eirb/zds-site into qu…
Apr 3, 2023
e6415af
problem des statistiques doublées résolu
atman0eirb Apr 3, 2023
f4f2353
problème des statistiques doubles résolu à l'origine
atman0eirb Apr 5, 2023
baeae57
résolve conflicts
atman0eirb Apr 5, 2023
4bc5404
disabled submit after answer&& non connected user can't answer a qui…
atman0eirb Apr 5, 2023
0cb1154
survey start Js file
atman0eirb Apr 6, 2023
bce2090
added a quizz markdown code generator
atman0eirb Apr 6, 2023
77fc952
made quizz statistics in other page than contenent statistics
atman0eirb Apr 8, 2023
a32d1f1
non connected user can responses only one time per session, addes qu…
atman0eirb Apr 12, 2023
034dc5e
deleted that quizz generator
atman0eirb Apr 12, 2023
02648d8
fixed issue of text between quizz and submit button
atman0eirb Apr 16, 2023
44f8626
added a button to create quizz squelette on editor; still have an is…
atman0eirb Apr 16, 2023
9d9c10c
fix explanation dislay
atman0eirb Apr 17, 2023
78f8174
added icons for a best result display for daltoniens
atman0eirb Apr 17, 2023
c50d72b
factorisaton of icon code
atman0eirb Apr 18, 2023
4e369a1
used mathjax for a good display of formula
atman0eirb Apr 20, 2023
50bd56c
fixed issue of bad display in quizz statistics
atman0eirb Apr 22, 2023
60edb12
rajout chemin pour titre dans statistique des quizzs
Apr 23, 2023
d265bac
changed display of explication
atman0eirb Apr 23, 2023
eee0046
fix explanation dislay
atman0eirb Apr 17, 2023
a07fb12
added icons for a best result display for daltoniens
atman0eirb Apr 17, 2023
e205615
factorisaton of icon code
atman0eirb Apr 18, 2023
32a0fef
used mathjax for a good display of formula
atman0eirb Apr 20, 2023
51d951e
fixed content of statistics sent from client
atman0eirb Apr 23, 2023
19a2875
fixed conflict
atman0eirb Apr 23, 2023
6234832
quizzs works only on turorials pages
atman0eirb Apr 25, 2023
eed909a
in one session a non connected user can answers only one time
atman0eirb Apr 27, 2023
0ac7e4d
good display of statistics, possibility to delete quizzs and questio…
atman0eirb Apr 28, 2023
3512eb3
quick fix
atman0eirb Apr 28, 2023
33243c0
correction faute de francais
Apr 29, 2023
6ed7cff
fixed issues on authors statistics page
atman0eirb Apr 30, 2023
bf95026
quick conflict
atman0eirb Apr 30, 2023
729bdb6
ajout des premiers tests
Apr 30, 2023
314caa1
fixed display of explanation in tutorial draft version
atman0eirb Apr 30, 2023
4bbec24
quick fix of last commit
atman0eirb Apr 30, 2023
91e89b6
quizz squelette on editor
atman0eirb Apr 30, 2023
e9c39bf
maked answers element as labels , still need some style :)
atman0eirb May 1, 2023
b2c6328
ajout de tests supplémentaires
May 1, 2023
76ea273
quizz squelette dans l'editeur n'est possible que dans contenus et s…
atman0eirb May 3, 2023
e1003f2
modification de style pour quizz et stat
May 3, 2023
53509b4
complément dans la documentation
May 3, 2023
e71fb82
fix -> boutton submit confondu avec la ligne de fin de section
May 3, 2023
8348e85
Merge branch 'quiz_sondage' of github.com:atman0eirb/zds-site into qu…
May 3, 2023
079d5e7
remise de la doc
May 3, 2023
59dcda2
quick fix
atman0eirb May 4, 2023
23ca81b
added Http404 for invalid date format
atman0eirb May 7, 2023
275dc4c
added tests of statistics deletting view
atman0eirb May 7, 2023
40d2967
quick fix of quizz squelette
atman0eirb May 8, 2023
317b47f
sondage in other branch Forum_sondage
atman0eirb May 8, 2023
d9d29b8
quick fix
atman0eirb May 9, 2023
3a0fe77
deletting users responses when quizz question answers were updated /…
atman0eirb May 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions assets/js/content-quizz.js
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ function getQuestionText(question){
let title
if (question.querySelector('.math')) {
const annotation = question.querySelector('.math annotation');
title = annotation.textContent.trim();
title = '$'+annotation.textContent.trim()+'$';
}
else {
title = question.textContent;
Expand All @@ -427,7 +427,7 @@ function getAnswerText(liWrapper){
let answer;
if (mathElement) {
const annotationElement = mathElement.querySelector('annotation');
answer = annotationElement.textContent.trim();
answer = '$'+annotationElement.textContent.trim()+'$';
} else {
answer = liWrapper.textContent;
}
Expand Down Expand Up @@ -482,24 +482,24 @@ document.querySelectorAll('form.quizz').forEach(form => {
result: {}
}

const CurrentFormQuestions = [...form.querySelectorAll('.custom-block-heading')].map(question => getQuestionText(question));

Object.keys(answers).forEach(name => {

const element = document.querySelector(`.custom-block[data-name="${name}"]`)

let title = getQuestionText(element.querySelector('.custom-block-heading'))

statistics.result[title] = {
evaluation: 'bad',
labels: []
}
statistics.expected[title] = {}

//make statistics of concerned form only

const CurrentFormQuestions = [...form.querySelectorAll('.custom-block-heading')].map(question => getQuestionText(question));

if (CurrentFormQuestions.includes(title)) {

statistics.result[title] = {
evaluation: 'bad',
labels: []
}
statistics.expected[title] = {}

const availableResponses = element.querySelectorAll('input')
for (let i = 0; i < availableResponses.length; i++) {
// wee need to get the question label for statistics
Expand All @@ -512,7 +512,7 @@ document.querySelectorAll('form.quizz').forEach(form => {
element.querySelectorAll('input:checked').forEach(node => {

// remove eventual glued corretion
let label = node.parentElement.textContent
let label = getAnswerText(node.parentElement)

statistics.result[title].labels.push(label.trim())
})
Expand Down
172 changes: 138 additions & 34 deletions templates/misc/quizz.graph.part.html
Original file line number Diff line number Diff line change
@@ -1,47 +1,151 @@
{% load i18n %}
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/katex.min.css">
<script src="https://unpkg.com/[email protected]/dist/katex.min.js"></script>
{% load emarkdown%}


<div id="{{ tab_name }}" class="tabcontent">


{% for quizz_name, questions in quizz.items %}
<header>
<h3>{{ quizz_name }}</h3>
<button class="init-stats-button" data-quizz="{{ quizz_name }}">Initialize Stats</button>
</header>
{% for question, stats in questions.items %}
<article class="stat-graph">
<h4>{% trans graph_title %} <span id="formule">{{ question }}</span></h4>
<ul class="quizz-answers">
{% for answer_name, answer_stats in stats.responses.items %}
<li class="{% if answer_stats.good %}quizz-forget{% endif %} quizz-stats">
<span id="formule">{{ answer_name }}</span>
<span>
<progress aria-label="{% trans 'Résultat pour' %} {{ answer_name }}"
max="{{ stats.total }}" value="{{ answer_stats.nb }}"/>
</span>
<span class="over-progress">{{answer_stats.nb}} / {{ stats.total }}</span>
</li>
{% endfor %}
</ul>
</article>
{% endfor %}
{% endfor %}
<header>
<h3 class="quizz_name">{{ quizz_name }}</h3>
<button class="delete-quizz-button" data-quizz="{{ quizz_name }}"><i class="fas fa-trash-alt"></i></button>
</header>
{% for question, stats in questions.items %}
<article class="stat-graph">
<h4>{% trans graph_title %} <span>{{ question|emarkdown }}</span></h4>
<button class="delete-question-button" data-quizz="{{ quizz_name }}" data-question="{{question}}"><i
class="fas fa-trash-alt"></i></button>
<ul class="quizz-answers">
{% for answer_name, answer_stats in stats.responses.items %}
<li class="{% if answer_stats.good %} quizz-good{% endif %} quizz-stats">
<span>{{ answer_name|emarkdown }}</span>
<span>
<progress aria-label="{% trans 'Résultat pour' %} {{ answer_name }}" max="{{ stats.total }}"
value="{{ answer_stats.nb }}" />
</span>
<span class="over-progress">{{answer_stats.nb}} / {{ stats.total }}</span>
</li>
{% endfor %}
</ul>
</article>
{% endfor %}
{% endfor %}
</div>
<script>

var elements = document.querySelectorAll("#formule");
var regex = /[^\w\s]/g; // Expression régulière pour les caractères spéciaux

for (var i = 0; i < elements.length; i++) {
if (regex.test(elements[i].textContent)) { // Si l'élément contient des caractères spéciaux
katex.render(elements[i].textContent, elements[i]);
}
else {
elements[i].innerText = elements[i].textContent; // Remplacer la balise span par un texte simple
function deleteQuizz(quizzName, question) {
const csrfmiddlewaretoken = document.querySelector('input[name=\'csrfmiddlewaretoken\']').value
const xhttp = new XMLHttpRequest()
const url = 'http://0.0.0.0:8000/contenus/delete_quizz/'
const data = question ? JSON.stringify({ 'quizzName': quizzName, 'question': question }) : JSON.stringify({ 'quizzName': quizzName })
xhttp.open('POST', url)
xhttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
xhttp.setRequestHeader('Content-Type', 'application/json')
xhttp.setRequestHeader('X-CSRFToken', csrfmiddlewaretoken)
xhttp.onload = function () {
location.reload()
}
xhttp.send(data)
Copy link
Member

@philippemilink philippemilink Apr 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non, on ne fait pas comme ça, notamment l'URL codée en dur, ça ne va pas (on fait comment quand on passera en prod ?). Inspire-toi ce qui est fait pour la liste des contenus à mettre en avant : http://0.0.0.0:8080/mise-en-avant/unes/requetes/ (connecté en tant qu'admin). Ça se passe et ou encore ici.

}

// Add an event listener to the quizz delete button
var deleteButtons = document.querySelectorAll(".delete-quizz-button");
for (var i = 0; i < deleteButtons.length; i++) {
deleteButtons[i].addEventListener('click', function () {
var quizzName = this.getAttribute('data-quizz');
deleteQuizz(quizzName, '');
});
}


// Add an event listener to the question delete button
var deleteButtons = document.querySelectorAll(".delete-question-button");
for (var i = 0; i < deleteButtons.length; i++) {
deleteButtons[i].addEventListener('click', function () {
var quizzName = this.getAttribute('data-quizz');
var question = this.getAttribute('data-question');
deleteQuizz(quizzName, question);
});
}


// Replace the quizz name by the stats part of the quizz name
var quizzNames = document.querySelectorAll(".quizz_name");
for (var i = 0; i < quizzNames.length; i++) {
var quizzName = quizzNames[i].innerText;
quizzName = quizzName.replace("#", "");
var parts = quizzName.split("/");
var statsPart = parts[parts.length - 2] + "/" + parts[parts.length - 1];
quizzNames[i].innerText = statsPart;
}



</script>

<style>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tout ce qui suit devrait aller quelque part dans assets/scss/.

.quizz-chart {
margin-top: 10px;
}


header {
border: 1px solid #ddd;
}


/* Style for the header of each quiz */
.tabcontent header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}

.tabcontent h3 {
font-size: 24px;
margin: 0;
}

/* Style for the initialize stats button */
.delete-quizz-button,
.delete-question-button {
background-color: #e44833;
border: none;
color: white;
padding: 10px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin-left: 10px;
cursor: pointer;
}

.delete-stats-button:hover {
background-color: #3e8e41;
}

/* Style for each question and its stats */
.stat-graph {
margin-bottom: 20px;
border: 1px solid #ddd;
padding: 10px;
}

h4 span {
display: inline-block;
}

li.quizz-stats {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}

li.quizz-good {
background-color: #a6f566;
border-color: #155724;
color: #155724;
}
</style>
8 changes: 7 additions & 1 deletion zds/tutorialv2/urls/urls_contents.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@
from zds.tutorialv2.views.lists import TagsListView, ContentOfAuthor, ListContentReactions
from zds.tutorialv2.views.alerts import SendContentAlert, SolveContentAlert
from zds.tutorialv2.views.misc import RequestFeaturedContent, FollowNewContent, WarnTypo
from zds.tutorialv2.views.statistics import ContentStatisticsView, ContentQuizzStatistics, QuizzContentStatistics
from zds.tutorialv2.views.statistics import (
ContentStatisticsView,
ContentQuizzStatistics,
DeleteQuizz,
QuizzContentStatistics,
)
from zds.tutorialv2.views.comments import (
SendNoteFormView,
UpdateNoteView,
Expand Down Expand Up @@ -209,4 +214,5 @@
# quizz
path("reponses/<int:pk>/<slug:slug>/", ContentQuizzStatistics.as_view(), name="answer-quizz"),
path("stats_quizz/<int:pk>/<slug:slug>/", QuizzContentStatistics.as_view(), name="stats-quizz"),
path("delete_quizz/", DeleteQuizz.as_view(), name="delete_quizz"),
]
83 changes: 74 additions & 9 deletions zds/tutorialv2/views/statistics.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
import itertools
import json
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On importe loads juste en-dessous, donc cette ligne est inutile, et tu peux directement appeler loads au lieu de json.loads.

import uuid
from collections import OrderedDict, Counter
import logging
import urllib.parse
from datetime import timedelta, datetime, date
from json import loads, dumps

from django.shortcuts import get_object_or_404, redirect
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Essai de garder les imports liés à Django ensemble, donc déplace cette ligne plus bas.

from django.views import View
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tu te sers de View, pour créer une vue de suppression, donc cette vue devrait plutôt hériter de DeleteView, et cet import devient inutile.

from django.db.models import Subquery
import requests
from django.conf import settings
from django.contrib import messages
from django.core.exceptions import PermissionDenied
from django.db.models import Count
from django.forms.utils import ErrorDict
from django.http import StreamingHttpResponse
from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse, StreamingHttpResponse
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tu importes HttpResponse et JsonResponse, mais tu ne t'en sers pas...

from django.utils.translation import gettext_lazy as _
from django.views.generic import FormView
from django.views.generic import FormView, DeleteView
from zds.member.decorator import LoggedWithReadWriteHability
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idem, cet import n'est pas utilisé.


from zds.tutorialv2.forms import ContentCompareStatsURLForm, QuizzStatsForm
from zds.tutorialv2.mixins import (
SingleContentDetailViewMixin,
SingleContentViewMixin,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idem, pas utilisé.

SingleOnlineContentDetailViewMixin,
SingleOnlineContentFormViewMixin,
)
Expand Down Expand Up @@ -356,11 +361,7 @@ def build_quizz_stats(self, end_date, start_date):
.all()
):
full_answers_total[available_answer.label] = {"good": available_answer.is_good, "nb": 0}
name = (
available_answer.related_question.url.split("/")[-2]
+ "/"
+ available_answer.related_question.url.split("/")[-1].split("#")[-1]
)
name = available_answer.related_question.url
question = available_answer.related_question.question
for r in total_per_label:
if (
Expand All @@ -371,8 +372,72 @@ def build_quizz_stats(self, end_date, start_date):
if name not in quizz_stats:
quizz_stats[name] = OrderedDict()
quizz_stats[name][question] = {"total": total_per_question[base_question], "responses": full_answers_total}
return quizz_stats
sorted_quizz_stats = {}
for name in sorted(quizz_stats.keys()):
sorted_quizz_stats[name] = quizz_stats[name]
return sorted_quizz_stats


class QuizzContentStatistics(ContentStatisticsView):
template_name = "tutorialv2/stats/quizz_stats.html"


class DeleteQuizz(View):
def get_start_and_end_dates(self):
end_date = self.request.GET.get("end_date", None)
try:
end_date = datetime.strptime(end_date, "%Y-%m-%d").date()
except TypeError:
end_date = date.today()
except ValueError:
end_date = date.today()
messages.error(self.request, _("La date de fin fournie est invalide."))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Euh, on veut afficher le message pour les deux exceptions TypeError et ValueError, non ? Et je ne sais plus si Python permet d'écrire des choses du genre except TypeError, ValueError:, ça permettrait de factoriser du code.

Et idem pour la date de début juste en-dessous.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

en python le double catch c'est except (ValueError, TypeError) as e si tu as besoin de la variable, sinon c'est simplement except TypeError, ValueError en effet.


start_date = self.request.GET.get("start_date", None)
try:
start_date = datetime.strptime(start_date, "%Y-%m-%d").date()
except TypeError:
start_date = end_date - timedelta(days=7)
except ValueError:
start_date = end_date - timedelta(days=7)
messages.error(self.request, _("La date de début fournie est invalide."))

if start_date > end_date:
end_date, start_date = start_date, end_date

return start_date, end_date

def post(self, request):

start_date, end_date = self.get_start_and_end_dates()

data = json.loads(request.body)

# Extract the quizzName from the data
quizz_name = data.get("quizzName")
question = data.get("question")

if question:
related_question_ids = QuizzQuestion.objects.filter(url=quizz_name, question=question).values_list(
"id", flat=True
)
else:
related_question_ids = QuizzQuestion.objects.filter(url=quizz_name).values_list("id", flat=True)

try:
QuizzUserAnswer.objects.filter(
related_question_id__in=Subquery(related_question_ids), date_answer__range=(start_date, end_date)
).delete()
except Exception as e:
return HttpResponseBadRequest(f"An error occurred while deleting the quiz: {str(e)}")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Il ne devrait pas y avoir d'erreur à ce niveau-là. S'il y a des erreurs, c'est qu'il faut les éviter en amont.


# Delete all QuizzUserAnswer objects
QuizzUserAnswer.objects.all().delete()

# Delete all QuizzAvailableAnswer objects
QuizzAvailableAnswer.objects.all().delete()

# Delete all QuizzQuestion objects
QuizzQuestion.objects.all().delete()

return StreamingHttpResponse(dumps({"status": "ok"}))