IUT alternants : projet JavaScript – jQuery à rendre le 20 juin minuit au plus tard

A rendre le dimanche 20 juin minuit au plus tard


Projet individuel


NB : excepté lorsqu’on se connecte, et se déconnecte, une seule page = aucun rechargement.
C’est totalement différent du projet que vous avez appris/fait en Php cette année.


Comment le rendre

Faites un fichier README.txt et déposez-le ici
Dans le fichier README.txt, précisez :

  • le sujet choisi
  • l’adresse de votre site
  • un nom d’utilisateur
  • un mot de passe
  • (et plusieurs nom/mot de passe, s’il y a plusieurs niveaux de droits (administrateur/visiteur etc.))
  • si vous avez utilisé des librairies spécifiques que je vous ai autorisées, merci de le re-préciser

Sujet

Ce que vous voulez tant que c’est dans le cadre de ce que l’on a vu. Vous avez tout le Web comme inspiration !
N’oubliez pas de me donner le nom et le mot de passe pour se connecter !
Si vous gérez des profils différents (admin / user ou autre), donnez moi les noms et mots de passe de différents profils !


Fonctionnalités obligatoires

  • Connexion + déconnexion (vu en cours)
  • Effets jQuery sur les éléments
  • Appels JSON : au moins deux appels en plus de ceux vus en cours

Sujets possibles

  1. Site de partage de photos
  2. Site de cocktails (cf ci-dessus)
  3. e-rated : site d’appréciations (selon des sujets, à définir)
  4. Ask-a-question : site où l’on pose des questions sur des sujets divers, et des gens répondent
  5. Write-a-book-together : site où l’on se connecte et où on peut écrire un livre à plusieurs
  6. Wedding-couple-site : site où l’on uploade + partage des photos de mariage + livre de commandes
  7. Playing-cards-collection : site où on scanne + échange des cartes (Magic the gathering)
  8. Polls-and-surveys : site de création de sondages (= QCM, exemple très beau ici : quipoquiz)
  9. Poems-generator : faire un cadavre exquis qui génère des poèmes + possibilité pour les utilisateurs de les noter / d’ajouter des mots
  10. The-future-of-post-it : faire un carnet de choses à faire pour les utilisateurs, qui envoie des mails de rappels de ces choses à des dates données
  11. Gift-ideas : un site où l’on va faire des idées de cadeaux / suggérer des idées de cadeaux + les noter (les meilleurs ressortent en premier)
  12. Le-bon-recoin : refaire le bon coin en plus simple
  13. Suggest-crawlers : site de suggestions : on clique sur un mot, il en suggère plein d’autres avec + définitions / liens de sites pour chacuns
  14. Tv-fans : site de présentations + notes d’émissions télé
  15. Faire le jeu SokoBan vu en cours, avec la possibilité de login, enregistrement. Pour les appels JSON supplémentaires, lorsque l’utilisateur choisit un tableau, s’en souvenir (= AJAX) et lorsqu’il se reconnecte, le remettre directement. Puis enregistrer son score lorsqu’il a terminé un niveau + montrer les meilleurs scores.

Pour les sujets qui suivent, ils sont possibles mais plutôt complexes et demandent plus d’investissement. Si vous êtes motivés, demandez-moi plus d’informations, je vous expliquerai les difficultés que vous allez rencontrer.

  1. Turn-by-turn : faire un jeu multijoueurs en tour par tour (jeu de cartes, de poker, ou de plateau etc)
  2. Chat-with-someone : site de chat/discussion
  3. A-maze-ing : site où l’on peut se ballader dans un labyrinthe et essayer d’en trouver la sortie

Sujet imposé si vous n’avez pas d’idée

Cocktails : on se connecte, on a une liste d’éléments (récupérés en JSON) disponibles, on coche ceux qui nous intéressent, on valide, c’est envoyé, et le retour en JSON affiche les cocktails qu’il est possible de faire avec ce que l’on a coché.


Ce que vous devez rendre

Idéalement

Une Url vers un site Web (utilisez Alwaysdata, ou PythonAnywhere, par exemple)

Si vous n’avez pas le choix

Les fichiers source de votre projet


Pour favoriser votre organisation

Utilisez ce que l’on a vu en cours (Google boilerplate)


Librairies autorisées


React autorisé

Note pour ceux qui connaissent / font / du React : la librairie est autorisée, mais il me faut le code d’origine, et non pas le code minifié / de production.


Interdiction d’utiliser une librairie JavaScript qui ne vienne pas des sites autorisés précédemment


Retard

Après le dimanche 11 avril minuit

Passé ce délai ce sera 1 pt par 2 heures de retard (je prendrai en compte la date de réception du mail).
Pour ceux qui essaient vraiment d’aller jusqu’à la dernière minute, toute heure entamée est comptée comme une heure complète. Exemple : un point en moins si je le reçois le 12 avril à 00:01.

N’oubliez pas de me donner le nom et le mot de passe pour se connecter !


Copier-coller

  • Copie sur une autre personne (« je se savais pas comment implémenter telle ou telle fonctionnalité dont j’avais besoin pour aller plus loin, je l’ai copiée sur un autre ») :
    • si la personne est clairement nommée : note pour la fonctionnalité divisée par 2 (uniquement la moitié du travail a été faite) ;
    • 0 aux deux personnes sinon ;
  • Si je m’aperçois que vous avez bêtement copié collé des sources Internet, je vous convoquerai pour vous demander de m’expliquer la fonctionnalité, et :
    • si vous ne savez pas m’expliquer le code alors 0 ;
    • si vous savez m’expliquer tout le code alors votre note totale sera divisée par vous + le nombre de contributeurs à ce projet, ce qui se rapprochera certainement de 0 aussi.

Voici un exemple de ce que vous pouvez faire, si vous choisissez le projet cocktails.


PDFs

JavaScript
jQuery

Python : compiler et faire tourner plusieurs versions sans collisions

Il faut aller chercher le code source qui vous intéresse.

Exemple, faire tourner un « vieux » Python 3.6, aller dans les versions ici et prendre celle qui nous intéresse.

Puis récupérer le code source et le compiler :

mkdir ~/source ; cd ~/source
wget https://www.python.org/ftp/python/3.6.13/Python-3.6.13.tar.xz
tar xvf Python-3.6.13.tar.xz
cd ~/source/Python-3.6.13
./configure && make
sudo make altinstall

Et voilà :

~/source/Python-3.6.13$ python3.6
Python 3.6.13 (default, May 21 2021, 17:12:12) 
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

Serveur asynchrone TCP Python. Et le client C# Unity !

Deux exemples très courts pour vous mettre sur les rails, qui envoient et reçoivent du binaire « pur » = très peu de bande passante, avec une connexion persistante.

Je vous donne deux envois-réception qui devraient vous permettre de faire tous vos envois binaires :

  1. C# : le client envoie un octet, qui correspond à un booléen, pour dire s’il est en big ou little endian ;
  2. C# : le client envoie un message encodé en UTF-8 (oui j’ai trouve la solution qui fonctionne !) ;
  3. Python : le serveur lit ce booléen ;
  4. Python : le serveur lit le message et le dit à voix haute (sous Windows) ;
  5. Python : le serveur envoie un entier non signé, puis deux float ;
  6. C# : le client lit l’entier non signé puis deux floats.

Avec ça, vous avez de quoi comprendre et faire tous les échanges que vous voulez !

Serveur asynchrone TCP Python

import asyncio
import struct
from asyncio import StreamWriter, StreamReader
import pythoncom
import win32com.client as win32_client
HOST = '192.168.1.31'
PORT = 9696
async def handle(reader: StreamReader, writer: StreamWriter):
    is_little_endian = False
    buffer = bytearray(100)
    addr = writer.get_extra_info('peername')
    print(f"Connected with {addr!r}")
    is_little_endian, = struct.unpack_from(
        '?', await reader.read(struct.calcsize('c'))
    )
    print(f'{is_little_endian=}')
    data = await reader.read(4096)
    message = data.decode('utf8')
    pythoncom.CoInitialize()
    speak = win32_client.Dispatch('SAPI.SpVoice')
    speak.Speak(message)
    print(f"Received {message!r} from {addr!r}")
    print(f"Send: {message!r}")
    float1 = 1.1
    float2 = 2.2
    struct.pack_into(
        # =: native order, std. size & alignment
        # H: unsigned short
        # f: float
        "=Hff",
        buffer, 0, 1, float1, float2)
    writer.write(buffer)
    await writer.drain()
    print("Close the connection")
    writer.close()
async def main():
    server = await asyncio.start_server(handle, HOST, PORT)
    print(f'Serving on {server.sockets[0].getsockname()}')
    async with server:
        await server.serve_forever()
asyncio.run(main())

Client C# Unity

using System;
using System.IO;
using System.Net.Sockets;
using UnityEngine;
public class Connexion : MonoBehaviour
{
    public string server;
    public string message;
    public ushort port;
    private void Start()
    {
        // working sample to send text:
        byte[] data = System.Text.Encoding.UTF8.GetBytes(message);
        byte isLittleEndian = BitConverter.IsLittleEndian ? (byte)1 : (byte)0;
        TcpClient client = new TcpClient(server, port);
        NetworkStream stream = client.GetStream();
        // Send the message to the connected TcpServer.
        stream.WriteByte(isLittleEndian);
        stream.Write(data, 0, data.Length);
        Debug.Log($"Sent: {message}");
        // read sample
        BinaryReader reader = new BinaryReader(stream);
        uint len = reader.ReadUInt16();
        var x = reader.ReadSingle();
        var y = reader.ReadSingle();
        Debug.Log("len=" + len);
        Debug.Log($"x={x}, y={y}");
    }
}

Pour la note, ces deux exemples paraissent simples, mais ils m’ont pris un temps fou, et je n’ai eu aucune réponse au bout de 3 semaines sur stackoverflow…

Python : EAFP vs LBYL

Très souvent vous pouvez avoir deux styles de codes différents qui font la même chose en Python :


import os

if os.path.exists("fichier.txt"):
    os.unlink("fichier.txt")

import os
try:
    os.unlink("fichier.txt")
except OSError:  # levé si le fichier n'existe pas
    pass

Alors, lequel choisir ?

Personnellement, j’ai toujours préféré le premier choix, et pourtant… dans la documentation officielle, ils le déconseillent !

Pourquoi cela ? Explication : l’opposé de EAFP, c’est LBYL.

EAFP : Easier to ask for forgiveness than permission

Plus facile de demander pardon que la permission. Ce style de codage très utilisé en Python suppose l’existence de clés ou d’attributs valides et intercepte les exceptions si l’hypothèse s’avère fausse. Ce style propre et rapide se caractérise par la présence de nombreuses déclarations try and except. La technique contraste avec le style LBYL commun à de nombreux autres langages tels que C.

LBYL : Look before you leap

Réfléchir avant d’agir.

Ce style de codage teste explicitement les conditions préalables avant d’effectuer des appels ou des recherches. Ce style contraste avec l’approche EAFP et se caractérise par la présence de nombreuses déclarations if.

Dans un environnement multi-thread, l’approche LBYL peut risquer d’introduire une condition de concurrence entre « la vérification » et « la validation ». Par exemple, le code : if key in mapping: return mapping [key] peut échouer si un autre thread supprime la clé du mappage après le test, mais avant la recherche. Ce problème peut être résolu avec des verrous ou en utilisant l’approche EAFP.

Django et git : bonnes pratiques / idées pour faire du CI

Conseil d’un ami :

  • gitlab n’est pas 100% opensource, ils proposent une édition communautaire limité et la totalité des fonctionnalités est dispo avec la version entreprise, leur modèle économique c’est de brider la version CEE (community) dans pas mal de coin pour te pousser à prendre une licence, perso je trouve que c’est plus un freeware qu’autre chose
  • gitlab est pas mal mais honnêtement pour l’avoir beaucoup (vraiment beaucoup) utilisé dans le passé ce n’est pas la meilleur alternative.

Si tu cherche à mettre en place un truc je te conseille fortement de jeter un œil à :

Cette stack est un peu plus compliquée à mettre qu’un gitlab, mais c’est très puissant, surtout la partie gerrit qui transcende la manière de faire des revues de code.

Si le code Django / Python n’est pas correct, il y a des méthodes pour améliorer les choses notamment :

  • https://nvie.com/posts/a-successful-git-branching-model/
  • https://semver.org/

Il est également important de mettre en place un système de « core developer » même au sein d’un projet privé en entreprise, afin de garantir la cohérence des données et l’intégrité de l’historique. En effet, si tout le monde à les droits d’écriture sur tout, ça finira toujours à un moment donné à partir dans tous les sens… L’idée, c’est de donner les droits d’écriture à seulement 2-3 personnes de confiance qui ont un certain niveau et à tous les autres imposer de faire des forks dans leur namespace gitlab et de proposer des merge request (l’équivalent des pull requests avec github). Ainsi, la revue de code est obligatoire et il n’est plus possible de merger du mauvais code… et les développeurs « standard » (sans droits donc) ont juste 2-3 commandes de base à connaître pour bosser avec les autres. Sans ce genre de système cela ne peut pas réellement fonctionner car git à été developpé pour ce mode de fonctionnement (au même titre que svn et d’autres gestionnaire de version) et donc son workflow est idéal dans ce cadre.

Fonctionner comme ça m’a permis de :

  1. coder un petit robot qui réagissait au merge requests proposé et donc qui checkait tout un tas de choses automatiquement et qui proposait des commandes si les choses n’allaient pas
  2. responsabiliser les gens de l’équipe en les rendant responsables de leur propre forks
  3. faire monter en compétence l’équipe qui était ultra frileuse à faire du versionning puis au final à pris goût au truc…

J’ai fais des petites formations au début pour leur expliquer l’intérêt et expliquer la sémantique des changement et qu’il est important de penser ses commits de manière propre. Cela permet :

  • dans certains cas de retirer (revert) des bugs complet d’un seul coup,
  • de faire du debug de manière automatique sans rien toucher et de trouver les changements responsable d’un bug (git bisect),
  • de faire un gain énorme de qualité au final
  • d’avoir des historiques clairs avec des messages de commit ayant une réelle plus value (exemple)
  • d’automatiser tout un tas d’actions qui vont augmenter la qualité globale du projet et vont réduire les taches inutiles et donc laisser plus de temps pour les trucs fun à faire.

C’est gagnant gagnant, mais au début les gens râlent… il faut juste s’y préparer et avec le temps ils changent d’avis !

Utiliser Chrome pour imprimer / générer un PDF

Google Chrome a plein d’options cachées en ligne de commande, et parmi celles-ci, une option qui permet d’imprimer en PDF.
De plus, si, comme moi, vous ne voulez pas les numéros de pages / nom de fichier en haut et en bas, il vous faut ajouter l’option --print-to-pdf-no-header

google-chrome --headless --disable-gpu --run-all-compositor-stages-before-draw --print-to-pdf-no-header --print-to-pdf=Bureau/test.pdf file://Bureau/git_book.html

Et vous aurez votre fichier PDF qui sera généré ;

[0730/112224.164336:INFO:headless_shell.cc(615)] Written to file Bureau/test.pdf

JavaScript : comment surcharger console.log()

J’avais besoin de décaler mes log pour voir rapidement si j’étais, ou pas, à l’intérieur d’une fonction.

Eh oui, après plusieurs années, même si on peut faire des points d’arrêt, JavaScript est là pour répondre à des événements utilisateurs et autres… donc il ne faut souvent pas arrêter pendant l’exécution ou la visualisation d’une page Web… on n’a toujours pas d’autre choix que de faire le bon console.log() des familles qui fait que le développement est extrêmement ralenti…

Je me suis donc crée les fonctions log_in(), log() et log_out().
Ainsi, quand j’entre dans une fonction, j’appelle log_in(), et tous mes log() dans cette fonction seront décalés, ce qui fait que je vois immédiatement où sont appelés mes logs ! Ensuite, ne pas oublier de faire un log_out().

let logLevel = 0;
let log = function () {
    let args = Array.from(arguments);
    if (logLevel > 0) {
        args = [Array(logLevel).fill(' ').join('')].concat(args);
    }
    console.log.apply(null, args);
};
let log_in = function () {
    log.apply(null, arguments);
    logLevel++;
}, log_out = function (s) {
    if (logLevel>0) {
        logLevel--;
    }
    log.apply(null, arguments);
};

Django : comment vérifier si on est en mode debug dans le template

Vous trouverez l’information dans beaucoup de sites, et cela semble très simple : dans votre template, il suffit de faire :

{% if not debug %}Je suis en debug !{% endif %}

En réalité cela ne suffit pas.

Il faut dans votre fichier settings.py, configurer correctement les adresses IP’s qui précisent qu’on est / ou pas / en mode « développement » :

INTERNAL_IPS = ['127.0.0.1', ]

(Notez que ce code peut être largement optimisé et dépendant de l’environnement, par exemple j’ai fait un settings.py qui prend cela en compte)

Linux : comment monter un disque réseau automatiquement

Sous Mint, Lorsque mon PC démarre, je veux qu’il « monte » automatiquement mes disques et qu’il fasse quelques vérifications au passage (dossier existant etc).
Problème : il arrive parfois la même chose que sous Windows, à savoir que si le mount ne fonctionne pas, il faut le faire à la main.
J’ai donc essayé de suivre plusieurs tutoriels et la plupart disaient d’ajouter dans les tâches planifiées (« cronjob« ) une ligne qui commençait par @reboot.

Je ne sais pas pourquoi, mais sur ma version de Mint, il ne connaissait pas @reboot.

Après pas mal de recherches, voici ce que j’ai trouvé, et qui fonctionne parfaitement :

  • Éditer /etc/rc.local
  • Lui ajouter la ligne de commande que l’on veut faire

Donc mon fichier /etc/rc.local j’ai donc deux lignes de code :
/home/olivier/apply.config.perso.sh -f /home/olivier/boot.log
exit 0

(J’ai enlevé les commentaires très importants qu’il y a dans le fichier, à vous de les lire !)
Le code précédent signifie « lance le script /home/olivier/apply.config.perso.sh« .

Et je me suis fait mon script, soit /home/olivier/apply.config.perso.sh :

(Il prend en compte le paramètre « -f » pour préciser le fichier de sortie, comme cela je logue tout, on n’est jamais assez prudent).
# ----------------------------------------------
# command line options
# first save args to pass them later on to other scripts:
args=("$@")
while [ "$1" != "" ]; do
    case $1 in
        -f | --file ) shift
                                # test if output_filename starts with "-":
                                if [[ "$1" =~ ^-.* ]]; then
                                    usage
                                    exit
                                fi
                                # from that point on:
                                # direct all stdout and stderr to $1:
                                exec &>>$1
                                ;;
        -u ) # ignored here, for mount script (later in there)
                                ;;
        -h | --help ) usage
                                exit
                                ;;
        * ) usage
                                exit 1
    esac
    shift
done
echo "`(date '+%Y.%m.%d-%Hh%Mm%Ss')` $filename > Applying my own configuration..."
sudo -Hu olivier gsettings set org.cinnamon.settings-daemon.peripherals.keyboard delay 120
sudo -Hu olivier gsettings set org.cinnamon.settings-daemon.peripherals.keyboard repeat-interval 18
if mountpoint -q "/mnt/Olivier"; then
    echo "`(date '+%Y.%m.%d-%Hh%Mm%Ss')` $filename > Already mounted, nothing to do."
else
    delay=1
    while ! mountpoint -q "/mnt/Olivier"; do
        echo "`(date '+%Y.%m.%d-%Hh%Mm%Ss')` $filename > Trying to mount..."
        # re-pass the same arguments via "${args[@]}"
        /home/olivier/mount.diskstation.sh "${args[@]}"
        sleep $delay
        if [ "$delay" -gt 60 ]; then
            echo "`(date '+%Y.%m.%d-%Hh%Mm%Ss')` $filename > Couldn't mount! Too long! Exiting."
            exit 1
        fi
        delay=$((delay+5))
    done
    echo "`(date '+%Y.%m.%d-%Hh%Mm%Ss')` $filename > Successful mount."
fi
echo "`(date '+%Y.%m.%d-%Hh%Mm%Ss')` $filename > Done."

Explications :

Les deux ordres gsettings set org.cinnamon.settings-daemon.peripherals.keyboard modifient la vitesse de mon clavier (qui n’est, de base, pas assez rapide pour moi).

L’ordre /home/olivier/mount.diskstation.sh monte mon disque.

Ensuite je fais une boucle qui vérifie si mon « mount » est réussi ou pas : tant que ce n’est pas le cas, il attend un « compteur » qu’il augmente de 5s à chaque boucle, et tout ça au maximum 60 fois. Si vraiment il n’y arrive pas, il logue le problème et quitte.

Tout fonctionne parfaitement depuis maintenant 2 ans !