/qcm_alchemy

Share multiple question choices with your student created from simple mardown files

Primary LanguagePythonGNU General Public License v2.0GPL-2.0

Publish a multiple choice questions to your student from a simple markdown file.

Since this project is aimed to French teachers, the rest of the description is in French.

Présentation

Ce projet permet de créer des QCMs depuis un fichier source markdown et de les diffuser facilement aux élèves.

TL:DR;

  1. Vous créez un fichier QCM
  2. Vous créez un compte, le validez via l'email envoyé et vous connectez
  3. Vous envoyez votre QCM
  4. Vous vérifiez qu'il est présenté comme vous l'imaginiez
  5. Vous présentez le numéro du QCM aux élèves
  6. Ils rejoignent, saisissent leur nom, prénom et le numéro en question...
  7. Ils répondent aux questions et valident
  8. Vous pouvez fermer le QCM et empecher les élèves d'y contribuer
  9. Vous récoltez leurs scores et leurs réponses

qcmqkzk

Format de fichier

Le plus simple est de regarder cette vidéo ou de partir de l'exemple.

En gros :

# titre du QCM

## une partie

### question à choix multiple ?

sous texte de la question

- [x] bonne réponse
- [ ] mauvaise réponse a
- [ ] mauvaise réponse b

### question avec une zone de texte ?

- [t]

Deux types de questions :

  • avec une seule bonne réponse. L'élève ne peut en choisir plusieurs. Corrigées automatiquement.
  • avec une zone de texte. L'élève écrit un texte pour répondre. Vous devez lire les réponses.

Intégration de code

Vous pouvez intégrer du code en ligne en l'entourant d'accents graves `f(x)=3`

Pour du code dans le sous-texte :

```python
a = 1
def f(x):
    return x ** 2
```

Attention seuls les "backticks" (accents graves) sont supportés, pas les tildes.

LaTex

LaTex en ligne avec $\int_1^2 x^2 dx$ va produire $\int_1^2 x^2 dx$

LaTex en bloc avec $$

$$
\int_1^2 x^2 dx
$$

va produire

$$ \int_1^2 x^2 dx $$

Images

Vous pouvez présenter une image à condition qu'elle soit hébergée en ligne.

Impossible de charger des images directement dans le fichier, je n'ai pas la place pour les héberger ni aucune envie de l'ajouter.

![sad panda](https://t4.ftcdn.net/jpg/04/77/53/75/360_F_477537591_3WXDC8zpsKBALlg8RBKejezg6SE7YbWh.jpg)

va produire :

sad panda

Tableaux

Les tableaux markdown sont supportés nativement :

| nom   | prénom | note |
| ----- | ------ | ---- |
| Jean  | Dupont | 14   |
| Marie | Frank  | 20   |

va produire :

nom prénom note
Jean Dupont 14
Marie Frank 20

Mode anti-triche

  • Les parties, questions et choix sont mélangés. C'est automatique et différent pour chaque élève.
  • On ne peut pas cliquer sur les images (pour faire une recherche en ligne).
  • Si l'élève quitte la page, ses réponses sont enregistrées et il ne peut plus avancer.
  • Il peut toujours retenter... mais vous verrez à quelle heure il a répondu

Correction

Les points des questions à choix multiple sont calculés automatiquement

Un mode correction est proposé, permettant de consulter chaque devoir pour les réponses textuelles.

On peut exporter l'ensemble des travaux dans un fichier csv (notes et réponses).

Hébergement des données

Elles sont en Europe et je crois respecter le RGPD.

Les élèves n'ont pas de compte, ils indiquent leurs noms, prénom et numéro du devoir. Les réponses des élèves sont effacées après trois jours. Ne tardez pas 😄

Les enseignants doivent créer un compte, je n'en garde que l'email. Chaque QCM et travail d'élève est attribué à un enseignant, via le numéro du QCM.

Les QCM sont eux aussi effacés après trois jours, n'ayant pas la place pour en conserver beaucoup.

Technologies utilisées

  • Python 3
  • Flask (création des pages)
  • Extensions Flask : Flask-WTF, FlaskSQLAlchemy, FlaskLogin etc.
  • Google API & Google Gmail API pour l'envoi d'emails
  • Gunicorn (serveur web WSGI)
  • PostgreSQL (bdd)
  • Mathjax (latex)
  • OVH(service en ligne dans le cloud)

Problèmes, bugs ?

N'hésitez pas à déposer une issue ! Je ferai de mon mieux 😄

Comment ça marche en interne ?

Le code est là pour vous répondre...

Pourquoi ?

  • Je n'aime pas corriger.
  • Les solutions existantes ne répondent à mon besoin. La plus approchante est doctools, consultez DocEval.

Development

Roadmap

Features

  • mode anti triche
  • chronomètre
  • multiple valid answers
  • text answers
  • move something ??? geogebra like object

Models

  • QCM original
  • Student
  • Marks

Parsing

  • fichier .md -> model bdd

Export -> html

  • mélanger
  • regrouper les résultats
  • cookies
  • présenter une question par page
  • lien entre les pages

Marks

  • enregistrer les réponses de l'élève
  • compter les points

Export Marks

  • exporter les notes d'un QCM
  • télécharger un csv
  • nettoyer la bdd après l'envoi et régulièrement (RGPD)

Views

  • upload a .md (basic)
  • validate upload
  • qcm
  • validate

Style

  • base
  • custom for teacher
  • custom for student
  • latex

Mixing answers

  • shuffle les parts, questions, answers
  • lorsqu'on envoie les answers à la vue, faut envoyer l'id_question: id_answer à la vue
  • ensuite on compare id_question: id_answer avec id_question: id(answer where is_correct)

Caching

  • utiliser flask caching
  • stratégie : quand on veut accéder à un QCM, le cacher après l'avoir retrieve, le drop si plus de x qcm ?

Serving

  • gunicorn
  • postgres
  • heroku (i don't understand why I can't use GCP...)
  • VPS + SSL + Domain name : OVH

Migrations

With Flask-Migrate.

  1. Ensure flask app is in environment

  2. flask db init

  3. Everytime you modify a db.Model class, run :

    $ flask db stamp head               # To set the revision in the database to the head, without performing any migrations. You can change head to the required change you want.
    $ flask db migrate -m "comment"     # To detect automatically all the changes.
    $ flask db upgrade                  # To apply all the changes.

I think that's all.

Forms with WTF Forms

Flask-WTF

Almost everything is done.

Last problem is QCM view for student. Check steps below

Tests with Pytest

Doc

Steps

  • présenter un qcm

  • enregistrer les réponses

  • noter les réponses

  • afficher des récaps :

    • ensemble des qcm
    • notes d'un qcm
    • notes d'un étudiant
  • style pour chaque page

  • latex

  • block content etc.

  • exporter en csv

  • nettoyer

    • la bdd
    • les fichiers downloadés
  • mélanger

    • parts
    • questions
    • ajouter "je ne sais pas"
  • serve with gunicorn

  • séparer le modèle de la création des instances

  • déployer qq part : HEROKU : qcmqkzk

    • vues

    • upload

    • download

    • bdd

      • impossible de run heroku et sqlite, il faut switch : hero sqlite

      • postgres

      • Many fixes needed...

        1. setup postgres locally following archwiki

        2. add postgres to heroku with medium some steps are wrong :

          1. Flask-Migrate doesn't work like this anymore.
          2. the uri-database can't start with postgres but with postgresql so we have to ensure the setup is rectified IN THE CODE. Since changing it in setup.sh does nothing.
          3. Once everything is working you can log into the DB with heroku pg:psql postgresql-clear-05212 --app casting-agency-xw where postgresql-clear-05212 is the name of the DB (found here)
          4. You can log into the dyno with heroku run bash, see the logs with heroku log --tail
    • Fix "bad CSRF token" or "CSRF token is missing", raising 400 errors When using a random secret_key in flask with gunicorn, AFAIK the page may be generated by a worker and the next request received by another. When they start, the key is changed. The solution is to setup a key in env, with :

      FLASK_SECRET_KEY: "a random secret key from secrets.urlsafe(16)"
      

      Then read the key in application. See commit issue: #35

  • styling : banner, radio, infos dans base, uniformité

  • test hosting

    • hammering and intense testing. Sort of. Résultat : 13ms pour le contenu. QQ ms pour le rendu. trop jde JS.
  • limit strings, safe inputs and good practices

  • RGPD et tutoriel

  • login pour enseignant ???

    • proposer un numero pour consulter les QCM notes
    • demander le numéro lorsqu'on cherche à consulter les résultats
    • trouver un moyen de consulter les notes pas élève... compliqué
    • migrer la database de heroku
  • refactor app

  • refactor model

  • Text answers

  • light/dark mode

  • refactor views

  • remove setup.sh from git history. Password changed

  • login

    • flask login
    • forcer les enseignants à se logguer
    • empecher les enseignants de consulter des autres QCM que les leurs
    • requirements.txt
    • problème des librairies qui foirent voir l'import initial dans sendmail comment résoudre ça dans heroku ?
    • comment stocker des token dans heroku ?
    • refactor tous les nouveaux trucs, les textes etc.
    • migrer la bdd de heroku
    • supprimer un compte
    • confirm real addresses
    • change password
  • parser : now detects code blocs properly, allowing lines starting with # in code blocks.

  • Flask-WTF

    • simple views (login, reset password, change password, student)
    • new qcms
    • csrf everywhere
    • qcm views for student. SO question doc must be dynamicly composed check for radio element insertion find a way to retrieve correct ids for question -> ansers and text-question -> text-answer
  • Flash instead of message passing to views

  • hosting

    • créer une adresse expres ?
    • utiliser une adresse du nom de domaine ???
  • creates review for students... ????

  • send unique mails per students

    • student mailing list linked to group then to teacher models : teacher <- group <- student-email <-> student <- work
    • option for the teacher
    • associate email to work (?)
    • send lots of unique mails at once... ressource hungry ???
    • what to do when the FAIL
    • how to deal with lags & whatever ???
  • open close a QCM

  • sort marks per column (doesn't work for dates...)

  • QR codes

  • auth students with google accounts...

  • give student list and allow them to pick their name. Won't exclude trolls

    Solution pros cons
    unique links email unique difficult to do
    easy to track ressource hungry
    sign with google unique may be difficult
    pick from list easy trolls may pick another name
    read ip ? doesn't prevent
    open / close easy not much
  • Heroku devient payant et le site est migré chez OVH : qcmqkzk.fr qui est lui même payant mais moins cher.

  • Permettre d'ajouter un texte en début de partie

  • Afficher la date prévue pour la suppression du QCM. Les durées de vie sont spécifiques à chaque modèle implémentant la suppression.

Migration OVH

  1. docker compose pour la bdd et le site
  2. VPS debian OVH, configuré correctement (ssh passwordless etc.)
  3. nom de domaine OVH
  4. SSL proposé par OVH, changer la redirection vers 443

BDD reset

Pour l'instant j'ai 3 soucis

  • la bdd est accessible depuis l'extérieur : bloquer dans ufw. source
  • la bdd est reset à chaque sudo docker-compose down puis sudo docker-compose up -d
    • fix bizarre... ne pas faire down et up mais stop et start
  • les identifiants de bdd ne sont pas sécurisés : pas grave inaccessible depuis l'extérieur
    • utiliser un export local depuis un fichier transféré à la main
    • lire les identifiants dans python et dans docker
    • accéder à la bdd depuis VPS : psql -h 172.17.0.1 -U quentin qcm

setup

  1. debian vps
  • apt-get update && apt-get upgrade
  • apt-get install postgresql postgresql-client
  • setup sshd pour login passwordless depuis PC, disable root login, disable password login, change default port
  • install ufw, disable tout, ajouter port du ssh, ajouter route 443 pour flask
  • installer docker & docker-compose
  • git clone repo, branch docker
  • ajouter à la main le dossier token
  • docker compose build up, docker ps, docker-compose ps, check logs
  1. activer un nom de domain, router vers ip du VPS
  2. ativer ssh OVH, router vers 443

Contrôler

  • accéder au vps depuis qkzk ssh debian@54.38.243.9 -p 44444
  • accéder à la bdd depuis vps psql -h 172.17.0.1 -U quentin qcm
  • relancer le firewall : sudo ufw reload
  • settings du firewall : sudo vim /etc/ufw/after.rules
  • relancer les serveurs : sudo docker-compose stop et sudo docker-compose start
  • logs docker : sudo docker-compose logs -f
  • reconstruire les images sans effacer la bdd : sudo docker-compose down et sudo docker-compose up --build -d

Status

sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
44444                      ALLOW       Anywhere
22/tcp                     ALLOW       Anywhere
44444 (v6)                 ALLOW       Anywhere (v6)
22/tcp (v6)                ALLOW       Anywhere (v6)

443/tcp                    ALLOW FWD   Anywhere
443/tcp (v6)               ALLOW FWD   Anywhere (v6)
sudo docker ps
CONTAINER ID   IMAGE             COMMAND                  CREATED          STATUS         PORTS
                  NAMES
040e81c8632a   qcm_alchemy_app   "gunicorn --bind 0.0…"   39 minutes ago   Up 8 minutes   0.0.0.0:443->443/tcp, :::443->443/tcp       qcm_alchemy_app_1
1fde529fb39b   postgres:latest   "docker-entrypoint.s…"   39 minutes ago   Up 8 minutes   0.0.0.0:5432->5432/tcp, :::5432->5432/tcp   postgres
sudo docker-compose ps
      Name                     Command               State                    Ports
-----------------------------------------------------------------------------------------------------
postgres            docker-entrypoint.sh postgres    Up      0.0.0.0:5432->5432/tcp,:::5432->5432/tcp
qcm_alchemy_app_1   gunicorn --bind 0.0.0.0:44 ...   Up      0.0.0.0:443->443/tcp,:::443->443/tcp
debian@vps-659ff8f4:~/qcm_alchemy$ git diff
diff --git a/server.sh b/server.sh
index 2f2b62f..c56aee4 100755
--- a/server.sh
+++ b/server.sh
@@ -1,4 +1,4 @@
 #!/usr/bin/sh

-gunicorn wsgi:app -w 3 --threads 1 -b 0.0.0.0:8000 --config gunicorn_hooks_config.py
+gunicorn wsgi:app -w 3 --threads 1 -b 0.0.0.0:443
 # gunicorn wsgi:app --config gunicorn_config.py