Django Async Roadmap

Petite parenthèse sur l’évolution de Django. Aujourd’hui, ce qui commence à importer plus que la vitesse, c’est la parallélisation des choses.
Donc avoir un serveur Web asynchrone, c’est censé être mieux qu’un serveur Web synchrone (= bloquant).

  • Django 2.1 : rien d’asynchrone. Aucun travail dessus.
  • Django 2.2 : travail initial pour ajouter la possibilité de faire de l’asynchrone sur l’ORM et les vues, sachant que c’est une option et que tout sera synchrone par défaut. Le support async sera principalement basé sur des pools de threads.
  • Django 3.0 : Ré-écriture de la gestion interne des requêtes pour être 100% asynchrone, ajout de la gestion asynchrone des middleware, forms, cache, sessions, auth. Début du process de dépréciation pour toutes les APIs prévues pour être uniquement asynchrones.
  • Django 3.1 : amélioration du support async, peut-être changement du templating
  • Django 3.2 : Fin du process de dépréciation et Django sera presque entièrement asynchrone.

Django et git (git « pur », pas « github »)

Si jamais vous décidez d’être un vrai guerrier, un de ceux qui veulent tout maîtriser, un de ceux qui, quand on leur donne des ordres, peuvent les refuser (oui c’est rare), et si vous voulez prendre des responsabilités, bref, si vous en avez : faites vous votre propre serveur git !

Je ne décrirai pas comment installer un serveur git sous Linux tellement c’est simple. Et je ne décrirai pas non plus comment faire ça sous Windows : pour des raisons de sécurité, de fiabilité et d’éthique, évitez Windows un point c’est tout.

Donc, sous Linux :

  • Créez un dossier dans lequel vous mettez toutes vos sources (scp est votre ami si vous êtes sur un PC et que vous voulez faire cela sur un autre PC)
  • Supposons que l’on est dans /web/htdocs/interro/htdocs
  • Créez un fichier .gitignore
  • Dans ce fichier, mettez ceci :
    *.pyc
    __pycache__
    myvenv
    db.sqlite3
    /static
    .DS_Store

    Cela signifie « ignore tous ces fichiers (.DS_Store est pour les personnes sous Mac (non je ne vous en veux pas, je suppose que vous ne connaissez pas bien l’histoire de l’informatique pour avoir acheté Apple)) » pour toutes les personnes qui vont cloner ce dossier
  • Ensuite, ajoutez tous les fichiers ainsi : git add --all .
  • Et commitez (oui je sais ce n’est pas français) ou « appliquez » les modifications c’est à dire l’ajout de tous les fichiers ainsi : git commit -m "My application, first commit"
  • Vous devriez avoir un paquet de fichiers ajoutés, vérifiez que tout est bien enregistré en demandant l’état via git status. Vous devez avoir un messsage ressemblant fortement à
    nothing to commit (working directory clean)
  • Vous êtes prêts ! Vous avez crée un repository (j’ai la flemme de traduire en français), et vous y avez appliqué tous vos fichiers. Il ne vous reste plus qu’à le cloner à distance
  • (Update) : par défaut, le serveur travaille sur la branche d’origine (« master »). Seul problème : si vous essayez de faire un « push » à partir de votre PC de développement, git ne sera pas d’accord pour faire un « push » sur la branche « master ». Je pense que c’est pour des raisons de sécurité ou autre, mais c’est plutôt récent, avant cela fonctionnait. La solution : dites sur le serveur de travailler sur une autre branche c’est via l’ordre checkout. Notez bien : c’est sur le serveur. Je ne trouve pas ça très cohérent, j’aurais dit que c’était au client de dire sur quelle branche il veut « push », mais je dois manquer quelque chose… j’ai vraiment beaucoup de lacunes git. Bref. Exemple : git checkout -b development.
  • Ensuite il vous suffira de faire un git clone (dans PyCharm, c’est le menu VCS » git » clone)

Bien sûr, comme tous les gestionnaires de version délocalisés, il vous faudra toujours trois étapes pour valider vos modifications :

  1. Appliquer les modifications en local (git commit -m "mon message expliquant les modifs")
  2. Essayer de récupérer toutes les dernières modifications du repository (git pull) vers votre code « local », au cas où d’autres personnes auraient « pushé » des modifications
  3. Envoyer vos modifications sur le serveur pour qu’elles soient accessibles à tout le monde (git push)

Django : autorisations sur mesure – custom authorizations

Voici comment créer manuellement des groupes + des autorisations personnalisées :

from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User
new_group, created = Group.objects.get_or_create(
    name ='co_branding_trainer')
ct = ContentType.objects.get_for_model(User)
permission_co_branding_trainer = Permission.objects.create(
    codename='co_branding_trainer',
    name='Co-branding trainer', content_type=ct)
new_group.permissions.add(permission_co_branding_trainer)

Une fois cela fait, on a deux possibilités :

  • aller dans le template et se servir de
    {% if perms.app_label.can_do_something %}
    {% endif %}
  • ou modifier le code vue dans le fichier Python. Dans ce cas, on n’a rien dans le template et si la personne connectée n’a pas les droits, la vue fait un forbidden

Comme je préfère la seconde (qui évite d’avoir dans le template des « if » un peu partout), voici comment faire : on crée un mixin qui se chargera de vérifier si, dans la vue, on a les droits : il suffit de chercher PermissionRequiredMixin dans le code de Django, et vous verrez que tout est simple, à partir de là vous pourrez faire descendre vos vues génériques de PermissionRequiredMixin.

Django Channels : astuces

Vous êtes sous Windows ?
Vous avez Python36 ?
Vous voulez faire tourner Django Channels ?
Installez Python 3.5 dans C:\Python35, puis à partir de là il faut tout faire sous Python 3.5.
Vous verrez que lorsque vous aurez tout installé, tout configuré, eh bien… daphne (le serveur Web asynchrone pour Django Channels) ne fonctionnera pas.
En fait, il vous faut modifier le path Windows pour être sûr qu’il pointe d’abord sur Python35 puis sur Python35/Scripts

Sous PyCharm, j’ai (enfin !) trouvé l’astuce : allez dans :
Project » Settings » Tools » Terminal

De là, modifiez Shell path par :
cmd.exe "/k set PATH=C:\Python35;C:\Python35\Scripts;%PATH%"
ou
"cmd.exe" /k ""set PATH=C:\Python35;C:\Python35\Scripts;%PATH%""
(une des deux solutions va fonctionner)

Et vous aurez accès à daphne en ligne de commande !

Django Channels sous Windows : attention !

Bah oui !

Sous Linux, Django Channels est compatible Python 3.5 et plus : c’est vrai.
Pof, comme d’habitude, sudo apt-get install blabla, pip3 install blabla et tout fonctionne.
Sous Windows, ce n’est pas le cas (cela changera sûrement dans les semaines à venir et cet article va rapidement ne plus être vrai), mais pour l’instant, Django Channels ne fonctionne pas sous Windows avec Python 3.6, mais uniquement avec Python 3.5.

De plus, vous devrez télécharger des Gigas et des Gigas de données Microsoft pour avoir le compilateur C++ 14 (si ma mémoire est bonne).

Parce que mon ami Microsoft © a retiré les liens (et si vous les avez je suis preneur) du compilateur en ligne C++, et vous devez télécharger Visual Studio et toutes ses immondices qui vont autour, « téléchargez la version pro », « payez un abonnement », ou le pire : « vous devez être enregistré pour pouvoir lancer Visual Studio » (ce qui, à mon sens, est totalement inadmissible et fait partie des wagons (trains entiers ?) de choses qui font que je prône Linux).

2,8 Go chez moi pour Visual Studio Community ainsi que 5,3 Go pour Microsoft SDKs.

Bref, sous Windows :

  • Installez Python 3.5 (toujour à la racine « C:\Python35« ) pour éviter les problèmes de droits quand il est dans « Program Files« 
  • Installez via la ligne de commande classique « channels » (sans passer par PyCharm) : python.exe -m pip install -U channels

Windows : le shell Linux / Ubuntu enfin acceptable !

Merci à l’un de mes étudiants pour l’astuce :

Sous Windows 10 :

  • Activez le mode développeur ;
  • Cochez l’option « Linux » (il faut la chercher) ;
  • Tentez de lancer un shell via le menu « Démarrer » et tapez « Ubuntu ».

Vous aurez un Shell Linux, mais totalement pourri.

Faites néanmoins le classique « sudo apt-get update && sudo apt-get upgrade« .

Une fois cela terminé, allez récupérer ce qui va vous changer la vie : wsltty.
C’est un outil qui remplace le shell Ubuntu de Windows, mais qui est mieux sur tous les plans.
Et là vous aurez un shell qui ressemble au mintty de Cygwin (mon shell préféré).

JavaScript hacks

/*--------------------------------------*/
var loadjQuery = function(cb){
    var scr = document.createElement('script');
    scr.setAttribute('type', 'text/javascript');
    scr.setAttribute('src', 'https://code.jquery.com/jquery-3.3.1.min.js');
    if(scr.readyState){
       scr.onreadystatechange = function(){
           if(scr.readyState === 'complete' || scr.readyState === 'loaded'){
              scr.onreadystatechange = null;
              if(cb === 'function'){
                 args = [].slice.call(arguments, 1);
                 cb.apply(this, args);
              }
           }
       };
    } else {
        scr.onload = function(){
           if(typeof(cb) === 'function'){
              args = [].slice.call(arguments, 1);
              cb.apply(this, args);
           }
        };
    }

    var head = document.getElementsByTagName('head')[0];
    head.insertBefore(scr, head.firstChild);
}

loadjQuery(function() {
    $('body').empty();
    let getRandomInt = function (max) {
        return Math.floor(Math.random() * Math.floor(max));
    };
    let curr=getRandomInt(5000);
    let addNewAvatar=function() {
        $('body').append(
            $('<img />').attr(
                'src', 'https://avatars.githubusercontent.com/u/'+curr
            ).css({
                'max-width': '50px',
                'display': 'inline-block',
                'float': 'left',
                'margin': '0',
                'padding': '0'
            })
        );
        curr += (1+getRandomInt(3));
        setTimeout(addNewAvatar, 100);
    };
    setTimeout(addNewAvatar, 100);
});

/*--------------------------------------*/
var loadjQuery = function(cb){
    var scr = document.createElement('script');
    scr.setAttribute('type', 'text/javascript');
    scr.setAttribute('src', 'https://code.jquery.com/jquery-3.3.1.min.js');
    if(scr.readyState){
       scr.onreadystatechange = function(){
           if(scr.readyState === 'complete' || scr.readyState === 'loaded'){
              scr.onreadystatechange = null;
              if(cb === 'function'){
                 args = [].slice.call(arguments, 1);
                 cb.apply(this, args);
              }
           }
       };
    } else {
        scr.onload = function(){
           if(typeof(cb) === 'function'){
              args = [].slice.call(arguments, 1);
              cb.apply(this, args);
           }
        };
    }
    var head = document.getElementsByTagName('head')[0];
    head.insertBefore(scr, head.firstChild);
}

loadjQuery(function() {
    $('body').empty();
    $.ajax({
        type: 'get',
        url: 'https://api.magicthegathering.io/v1/cards?page=311'
    }).done(function(data) {
        for (var i=0; i<data.cards.length; i++) {
            $('body').append(
                $('<img />').attr('src', data.cards[i].imageUrl)
            );
        }
    });
});

Exemple : cocktails

Pour avoir la moyenne

Lorsqu’on vient sur le site :

  • Si on n’est pas connecté, afficher le formulaire de connexion
  • Si on est connecté :
    • Appel AJAX #1 : aller demander la liste des cocktails et l’afficher
    • Appel AJAX #2 : lorsqu’on clique sur un cocktail, aller chercher le détail de ce cocktail et l’afficher

Pour dépasser un peu la moyenne

  • Formulaire de connexion
  • Gestion des erreurs / avertissements de base (avertissement si mot de passe trop court, erreur si login existant etc)

Pour dépasser largement moyenne

Possibilité de créer des cocktails


Créer un ingrédient

Appel AJAX simple à faire, nous l’avons vu en cours : envoi d’un formulaire :


Créer une unité

Appel AJAX simple à faire, nous l’avons vu en cours : envoi d’un formulaire :


Créer un cocktail

Le seul appel AJAX complexe à faire, voici l’interface utilisateur, à vous d’écrire le code JavaScript qui va derrière :

Pour avoir la note maximale

  • Ecrire tout correctement, parfaitement indenté (tous les éditeurs modernes vous font l’indentation automatique, servez-vous en !)
  • Tout écrire sous forme de classe (dans le cas de ce projet Cocktails, classe Ingredient, classe Unite, etc)
  • Tout séparer dans des fichiers selon cette organisation : un fichier JavaScript par classe, le nom du fichier = le nom de la classe en minuscule (dans le cas de ce projet Cocktails, classe ingredient.js, classe unite.js, etc)
  • Ecriture complète en ECMA6(plus de var, que des let, fonctions de la forme () = >{} etc)
  • Callbacks avancés avec gestion des closures

Les requêtes « à la » Django : howto / principes

Voici une requête « à la Django ».
« objects » est un objet statique destiné à faire les requêtes
p = un objet du modèle « Person » = un modèle base de données que j’ai fait
g = un objet du modèle « Game » = un modèle base de données que j’ai fait

J’ai crée un modèle intermédiaire « PersonGame » qui lie les deux tables en n/n. Pour comprendre l’idée :
Q(person=p) signifie « dont la personne == p »
~Q(person=p) signifie « dont la personne != p »

Si on met deux « __ » cela signifie « faire une jointure entre les deux modèles », par exemple « person__user » signifie « LEFT JOIN PERSON p ON p.id_user = user.id »

Donc pour tout reprendre :
« Aller chercher dans PersonGame toutes les personnes qui ne sont pas le joueur p »
PersonGame.objects.filter(~Q(person=p), game=g)

…et dont le username vaut « u »
.get(person__user__username=u)

Ce qui donne au final :
PersonGame.objects.filter(
    ~Q(person=p),
    game=g).get(person__user__username=u)

Et voici la requête qu’il aurait fallu écrire « à la main » :

SELECT * FROM PersonGame pg
WHERE pg.id_person != p.id
AND pg.id_game = g.id
LEFT JOIN Person pe on pe.id = p.id
LEFT JOIN User us on us.id = pe.id_user
WHERE us.username = u
LIMIT 1;

En fait ça peut paraître rébarbatif, ou surprenant, mais :
– ça tient en une ligne ;
– quand on a fait 3-4 requêtes comme ça :
  – on arrive à faire n’importe quelle requête un peu complexe très très vite ;
  – on arrive à lire très facilement n’importe quelle requête ;
  – le générateur de requêtes est incroyablement mieux optimisé que celui de Symfony (en fait, techniquement, il est parfait, il génère les LEFT JOIN exactement comme il faut et dans l’ordre le plus efficace en fonction des clés qu’on a précisées dans le modèle, voire il fait des requêtes plus efficaces que ce qu’on aurait éventuellement fait à la main (cela m’est arrivé par deux fois)).

Conclusion : ici aussi, en termes de maintenance = ce qui coûte le plus cher, c’est exceptionnellement rentable.

Linux tar : faire une archive au format « AAAA-MM-JJ_HHMMSS.archive.tar.bz2 »

C’est tout simple sous Linux.
Il faut juste savoir, pour ceux qui commencent à faire quelques lignes de commande sous Linux, que le « backquotes », donc le ` demande à exécuter quelque chose et le remplacer par le résultat de l’exécution.

Exemple : si vous tapez `(date '+%Y.%m.%d-%Hh%Mm%Ss')` alors il exécutera l’ordre date, et le remplacera par la date en cours.

Exemple concret :

tar cjf "`(date '+%Y.%m.%d-%Hh%Mm%Ss')`.monarchive.tar.bz2" htdocs/*

Vous archivera tout le dossier htdocs dans l’archive :
2018.02.06-09h45m05s.monarchive.tar.bz2

En espérant que cela serve à quelqu’un !