Repository: zestedesavoir/zds-site

Issue:
Gérer la version minimale de sqllite requise pour le dev
**Description du bug**

La PR #6156  exige implicitement une version de sqlite >= 3.23, car elle utilise les alias TRUE et FALSE qui n'existaient pas auparavant (voir [la documentation associée](https://sqlite.org/lang_expr.html#boolean_expressions)).

Cette dépendance de version n'est spécifiée nul part et n'est un soucis que pour le dev sur des systèmes anciens car Python semble utiliser la version du système.

Aperçu d'un état des lieux rapide par @Situphen :

> - Debian Stretch 9 => par défaut c'est 3.16 donc trop vieux mais la 3.27 est dans stretch-backports
> - Debian Buster 10 => 3.27 donc OK
> - Debian Bulleye 11 => 3.34 donc OK
> - Debian sid => 3.36 donc OK
> - Ubuntu Bionic 18.04 => 3.22 donc trop vieux et idem dans bionic-updates
> - Ubuntu Focal 20.04 => 3.31 donc OK
> - Ubuntu Groovy 20.10 => 3.33 donc OK
> - Ubuntu Hirsute 21.04 => 3.33 donc OK

> Pour ce qui est de Windows et MacOS, il faut Python ≥ 3.7.4 car les versions en dessous embarquent sqlite 3.21.0

**Comment reproduire ?**

Avoir un vieux système avec sqlite<=3.22 et constater que la page d'accueil (entre autre) plante avec une OperationalError à partir de la PR #6156.

**Comportement attendu**

Quoi qu'il en soit, on devrait avoir une vérification à l'installation de l'environnement de dev la version de sqlite utilisée par Python. Il est possible de le faire manuellement (voir ci-dessous), mais l'intégrer dans le processus d'installation serait évidemment un plus pour pouvoir alerter en cas de version trop ancienne.

```python
import sqlite3
sqlite3.sqlite_version
```

Si on impose une version relativement récente de sqlite, il faut garder en tête que ça rajoute une contrainte sur l'âge des systèmes utilisés pour le dév, à moins de proposer des setups à base de conteneurs (qui posent d'autres questions...). 

Pour éviter ces contraintes, il est possible d'utiliser une syntaxe compatible avec sqlite pour le dev et la BDD de production, mais ça peut coûter en lisibilité. À voir quel compromis on veut.

Une dernière solution serait d'éliminer l'usage de SQL brut à travers `extra()`, si possible. Django a une politique assez claire de se [débarasser à terme de cette fonction](https://docs.djangoproject.com/en/2.2/ref/models/querysets/#django.db.models.query.QuerySet.extra) en faisant évoluer les fonctionnalités des autres fonctions de l'API QuerySet pour la rendre inutile. Ils collectent les cas d'usages de `extra()` pour s'assurer que tous les cas d'usages peuvent être couverts autrement.


Discussion:
Personnellement je suis d'avis de remplacer `TRUE` et `FALSE` par `1` et `0` si supporté par MySQL. On perd *très légèrement* en lisibilité de deux bouts de code mais cela permet de garder le support de Python 3.6 sur Windows et MacOS (version encore supportée jusqu'au 23 décembre) ainsi que de Debian Stretch et Ubuntu Bionic 18.04 (distributions encore supportées respectivement jusqu'au 30 juin 2022 et en avril 2023). Il n'y a pas photo à mes yeux sur le fait que la différence entre les avantages et les inconvénients est positive.
Je suis d'accord que l'inconvénient de lisibilité est minime face à l'avantage d'alléger des contraintes de version/système.

Bonnen nouvelle, on peut procéder sereinement au remplacement de TRUE par 1 et FALSE par 0 :

* pas de soucis pour sqlite récent ou les vieilles versions comme déjà discuté ;
* MariaDB est très explicite sur le fait que TRUE et FALSE sont des synonymes respectivement pour 1 et 0 (voir la [documentation](https://mariadb.com/kb/en/sql-language-structure-boolean-literals/)) ;
* la doc de MySQL est moins locace, mais c'est évidemment pareil, TRUE et FALSE sont évalués à 1 et 0 respectivement (voir la [documentation](https://dev.mysql.com/doc/refman/8.0/en/boolean-literals.html)).

On pourra se poser la question de revenir sur du moderne dans un an ou deux. ;-)

Resolving patch:
diff --git a/zds/tutorialv2/managers.py b/zds/tutorialv2/managers.py
--- a/zds/tutorialv2/managers.py
+++ b/zds/tutorialv2/managers.py
@@ -55,7 +55,7 @@ def __get_list(self, subcategories=None, tags=None, content_type=None, with_comm
                 FROM tutorialv2_contentreaction,utils_comment
                 WHERE tutorialv2_contentreaction.related_content_id=`tutorialv2_publishablecontent`.`id`
                 AND utils_comment.id=tutorialv2_contentreaction.comment_ptr_id
-                AND utils_comment.is_visible = TRUE
+                AND utils_comment.is_visible = 1
             """
 
             queryset = queryset.extra(select={"count_note": sub_query})
@@ -237,7 +237,7 @@ def get_last_articles(self, number=0):
         :return: list of last articles expanded with 'count_note' property that prefetches number of comments
         :rtype: list
         """
-        sub_query = "SELECT COUNT(*) FROM {} WHERE {}={} AND {}={} AND utils_comment.is_visible=TRUE".format(
+        sub_query = "SELECT COUNT(*) FROM {} WHERE {}={} AND {}={} AND utils_comment.is_visible=1".format(
             "tutorialv2_contentreaction,utils_comment",
             "tutorialv2_contentreaction.related_content_id",
             "tutorialv2_publishedcontent.content_pk",
diff --git a/zds/tutorialv2/views/lists.py b/zds/tutorialv2/views/lists.py
--- a/zds/tutorialv2/views/lists.py
+++ b/zds/tutorialv2/views/lists.py
@@ -38,7 +38,7 @@ def get_queryset(self):
         :return: list of contents with the right type
         :rtype: list of zds.tutorialv2.models.database.PublishedContent
         """
-        sub_query = "SELECT COUNT(*) FROM {} WHERE {}={} AND {}={} AND utils_comment.is_visible=TRUE".format(
+        sub_query = "SELECT COUNT(*) FROM {} WHERE {}={} AND {}={} AND utils_comment.is_visible=1".format(
             "tutorialv2_contentreaction,utils_comment",
             "tutorialv2_contentreaction.related_content_id",
             "tutorialv2_publishablecontent.id",


Test patch: