UE5 : Fondamentaux du développement web frontal, niveau 2

Séance 3 : TD : Bases de données, connexion et modélisation objet

Installations et configurations

Une application Flask commune

Afin que tout le monde réalise ce TD sur une application Flask sans erreurs et correctement configurée:

  1. Installer sur son PC l'application Seance3_app_base_TD
  2. Y installer un environnement virtuel Python3
  3. Installer les dépendances de l'application
  4. Télécharger la base de données factbook.sqlite quelque part sur son PC

Installations système ou applicative

Tout type de base de données peut être branché à une application Flask grâce à des librairies externes de Python. Pour plus de facilités, une base relationnelle SQLite sera utilisée.

  1. Installer sur sa machine SQLite pour Windows ou sqlite3 pour Linux ou MacOS
  2. Installer flask-sqlalchemy dans l'environnement virtuel de l'application

Configuration de la connexion à la base de données

Tout comme notre application est une instance de Flask, nommée app, la connexion à la base de donnée sera une instance de SQLAlchemy, communément nommée db. L'objet db prend comme premier argument l'application Flask. Documentation de l'objet SQLAlchemy

  1. Dans le fichier app.py, créer l'instance db. Attention à bien positionner cette instanciation par rapport à l'instance app et aux imports finaux des routes.
  2. Lancer l'application. L'erreur Either 'SQLALCHEMY_DATABASE_URI' or 'SQLALCHEMY_BINDS' must be set. doit apparaître.
  3. SQLAlchemy a besoin qu'on lui indique le "chemin" vers la base de données SQLite dans une variable SQLALCHEMY_DATABASE_URI
    1. Se demander où doit être valuée cette variable dans l'application: est-ce une donnée sensible? est-ce une donnée qui sera partagée par toutes les machines qui lanceraient l'application?
    2. Deux pistes:
      1. Le nom de la variable SQLALCHEMY_DATABASE_URI et sa valeur nécessitent une seule ligne dans un fichier
      2. La récupération de la valeur par l'application (et donc par db puisque db est configurée avec app) s'écrit en une seule ligne dans un fichier de configuration
    3. La valeur de SQLALCHEMY_DATABASE_URI sera le chemin vers la base de données . Ce chemin peut être absolu ou relatif, et varie selon les OS. Ecrire ce chemin selon la documentation
  4. Lancer l'application. Elle doit cette fois se lancer correctement et afficher * Running on http://127.0.0.1:5000 dans le terminal

Une première requête

Pour le moment, aucun rendu via un template HTML n'est demandé. On va se concentrer sur les retours dans le terminal. Le lancement de l'application sans erreurs est normalement gage de bonne connectivité avec la base de données SQLite. Néanmoins, il faut s'assurer que l'on puisse lire des données, sans utiliser pour le moment les fonctionnalités d'ORM de SQLAlchemy. On va donc simplement exécuter une requête SQL.

Voici le modèle de données de la base: h:300

  1. Il existe une route /pays dans le module routes/generales. Cette route est actuellement fonctionnelle et affiche des données écrites à la main dans le code.
  2. Dans cette route, faire un print des résultats de la requête SQL suivante: SELECT * FROM country LIMIT 10
    1. Cette requête doit s'exécuter sur la base de données, soit sur l'instance db de SQLAlchemy que l'on a configuré plus haut. Faire en sorte d'avoir accès à db dans le module de la route
    2. Comme nous n'utilisons pas encore une méthode orientée objet, nous fournissons une chaîne de caractères correspondant à la requête SQL. AVec les deux documentations suivantes, créer une variable resultats dans la route /pays qui correspondent aux résultats de la requête: https://docs.sqlalchemy.org/en/20/orm/session_api.html#sqlalchemy.orm.Session.execute et Ticket StackOverFlow
    3. Effectuer un print() de resultats
    4. Relancer l'application et appeler localhost:5000/pays
    5. Bien lire le message d'erreur et effectuer la correction. La correction à apporter est explicitement indiquée dans le message d'erreur.
    6. Le résultat correct doit être un retour 200 de localhost:5000/pays et l'affichage dans les logs de
    [("<p>Algeria has known many empires and dynasties starting with the ancient Numidians (3rd century B.C.), Phoenicians, Carthaginians, Romans, Vandals,  ... (
     2790 characters truncated) ... arbon revenues to fund the government and finance the large subsidies for the population has fallen under stress because of dec
     lining oil prices.</p>", 'ag', 'Algeria', 'sovereign'), ("From the late 14th to the mid 19th century a Kingdom of Kongo stretched across central Africa from p
     resent-day northern Angola into the current Congo ... (1141 characters truncated) ...  He pushed through a new constitution in 2010. Joao LOURENCO was elected
     president in August 2017 and became president of the MPLA in September 2018.", 'ao', 'Angola', 'sovereign'), ('Seeking to stop the incorporation of their lan
     d into Rhodesia (Zimbabwe) or the Union of South Africa, in 1885, three tribal chiefs traveled to Great ... (965 characters truncated) ... the world\'s highes
     t rates of HIV/AIDS infection, but also one of Africa\'s most progressive and comprehensive programs for dealing with the disease.', 'bc', 'Botswana', 'sovere
     ign'), ('Present day Benin is comprised of about 42 ethnic groups, including the Yoruba in the southeast, who migrated from what is now Nigeria in the 12th ce
     ... (1657 characters truncated) ... k office in 2016; the space for pluralism, dissent, and free expression has narrowed under his administration. TALON won
     a second term in April 2021.', 'bn', 'Benin', 'sovereign'), ("<p>Established in the 1600s, the Burundi Kingdom has had borders similar to those of modern Buru
     ndi since the 1800s. Burundi’s two major ethnic group ... (2274 characters truncated) ... al court decision allowed him to circumvent a term limit. President
     Evariste NDAYISHIMIYE - from NKURUNZIZA’s ruling party - was elected in 2020.</p>", 'by', 'Burundi', 'sovereign'), ('<p>Chad emerged from a collection of powe
     rful states that controlled the Sahelian belt starting around the 9th century. These states focused on contr ... (3028 characters truncated) ...  Chadian mili
     tary camp in the Lake Chad Basin and killed nearly 100 soldiers; it was the deadliest attack in the history of the Chadian military.</p>', 'cd', 'Chad', 'sove
     reign'), ("Upon independence in 1960, the former French region of Middle Congo became the Republic of the Congo. A quarter century of experimentation with Mar
     xi ... (821 characters truncated) ... rica's largest petroleum producers, but with declining production it will need new offshore oil finds to sustain its oil
     earnings over the long term.", 'cf', 'Congo', 'sovereign'), ("<p>The Kingdom of Kongo ruled the area around the mouth of the Congo River from the 14th to 19t
     h centuries. To the center and east, the Kingdoms of L ... (3506 characters truncated) ... n the DRC (MONUSCO) has operated in the region since 1999 and is th
     e largest and most expensive UN peacekeeping mission in the world.</p> <p>\xa0</p>", 'cg', 'Congo DR', 'sovereign'), ('Much of the area of present-day Cameroo
     n was ruled by powerful chiefdoms before becoming a German colony in 1884 known as Kamerun. After World War I, ... (528 characters truncated) ...  as well as
     a petroleum industry. Despite slow movement toward democratic reform, political power remains firmly in the hands of President Paul BIYA.', 'cm', 'Cameroon',
     'sovereign'), ("The archipelago of the Comoros in the Indian Ocean, composed of the islands of Mayotte, Anjouan, Moheli, and Grande Comore declared independen
     ce from ... (1789 characters truncated) ... between the three main islands. In August 2018, President AZALI formed a new government and subsequently ran and w
     as elected president in March 2019.", 'cn', 'Comoros', 'sovereign')]
    

Les modèles de bases de données, ou comment représenter la base de données en objets SQLAlchemy

Plutôt que d'effectuer des requêtes SQL à la main, l'ORM propose de les générer à notre place. Pour cela, il est nécessaire d'indiquer à l'ORM une représentation de nos données. Les données conservées en base sont alors représentées en une collection d'objets (de classes), ce qui est nommé modèles de base de données. La couche ORM va alors effectuer le mapping modèle>table elle-même.

Chaque table est représentée par un modèle, qui est lui-même une classe Python dérivée de SQLAlchemy(app).Model qui porte le même nom que la table (l'ORM est insensible à la casse pour le nom de table; par convention, il est préférable de nommer chaque classe en camelCase). Les modèles sont stockés dans un au plusieurs fichiers Python dans le package models. Chaque attribut de la classe représente une colonne de la table.

De manière théorique, les classes représentant les tables s'écrivent ainsi (ce qui est entre <<>> est spécifique à la table décrite):

class <<objectName>>(db.Model):
    <<colonne1>> = db.Column(db.<<objectType>>)
    <<colonne2>> = db.Column(db.<<objectType>>)
    ...
  1. Créer le package models, à côté des packages routes ou templates déjà existants
  2. Créer un module factbook dans le package models
  3. Dans ce module, créer une classe représentant la table Country
    1. Les différents types d'objets sont décrits dans la documentation
  4. Importer la classe Country dans routes/generales
  5. Lancer l'application. Une erreur de clé primaire doit apparaître. Cette erreur est normale. La déclaration de l'objet Country doit être identique au modèle physique de la base de données. Or celui-ci contient une clé primaire.
  6. A partir de la documentation et du modèle de données, poser la clé primaire dans la classe Country
  7. Relancer l'application. Elle doit s'exécuter normalement.

Maintenant que la base de données est branchée et qu'un premier objet Country représente une des tables de la base, on va pouvoir brancher la base de données à la route /pays. Le but des étapes suivantes est de ne pas avoir à toucher aux templates, seulement à la fonction pays(): autrement dit, il s'agit simplement de renvoyer au template des données au format demandé

[{
    "nom":"string",
    "capitale":"string",
    "continent":"string"
}]

à partir des résultats d'une requête faite par l'ORM à la base de données.

  1. Traduire le SQL SELECT * FROM Country en SQLAlchemy à partir de la classe Country : https://flask-sqlalchemy.palletsprojects.com/en/2.x/queries/. Plusieurs méthodes sont possibles; toutes doivent renvoyer une liste d'objets qui représentent les pays : [<Country ag>, <Country ao>, <Country bc>, <Country bn>, <Country by>, <Country cd>, <Country cf>, <Country cg>, ..., <Country par>]
  2. Afin de comprendre que le résultat renvoyé est une succession d'objets, on peut tenter d'itérer sur la liste de résultats afin d'afficher:
    1. l'objet courant
    2. son type
    3. Le résultat devrait être pour chaque pays :
    <Country par>
    <class 'app.models.factbook.Country'>
    
  3. Comme Country est une classe, afficher pour chaque pays dans le terminal son nom et son id:
xx
World
  1. L'actuelle variable donnees contient des données écrites en dur. Remplir cette variable avec les données requises de chaque pays pour qu'elles s'affichent dans le template (sans toucher à celui-ci):
    1. le champ capitale prendra "inconnue" comme valeur
    2. le champ continent prendra "inconnu" comme valuer
    3. le champ nom prendra le nom du pays
  2. Lancer l'application et accéder à la page /pays. Les pays doivent s'afficher en tableau.