Modèles plans et vues pour les aéroports

Dans le projet, les données des aéroports sont actuellement stockées sous forme d'une liste de dictionnaires Python codée en dur dans mobility/models/airport.py. Cette approche est simple mais limitée : impossible de modifier les données facilement, ni d'exploiter les relations entre tables.

Important

Objectif de cette section : migrer de JSON vers une base de données.

Vous allez supprimer la liste codée en dur et la remplacer par des requêtes SQL. Concrètement :

  • Avant : get_airports() retourne une liste Python statique définie dans le code.

  • Après : get_airport_list() exécute SELECT * FROM airport et retourne les données de la DB.

Toutes les modifications des données (ajout, suppression) passeront désormais par la base de données.

Pour cela, modifiez le fichier schema.sql en ajoutant le code suivant sans supprimer le code que vous avez déjà mis.

mobility/schema.sql

DROP TABLE IF EXISTS airport;
CREATE TABLE airport(iata_code TEXT PRIMARY KEY,
                     name TEXT,
                     latitude_deg REAL,
                     longitude_deg REAL,
                     iso_country TEXT,
                     FOREIGN KEY (iso_country) REFERENCES country(iso_country)
                    );
INSERT INTO airport(iata_code, name, latitude_deg, longitude_deg, iso_country) VALUES("CRL", "Brussels South Charleroi Airport", 50.459202, 4.45382, "BE");

Ce code ajoute une table airport à notre base de données et insère un aéroport. La table a cinq colonnes : iata_code, name, latitude_deg, longitude_deg et iso_country. La colonne iata_code est une clef primaire, la colonne name est le nom de l'aéroport, les colonnes latitude_deg et longitude_deg sont les coordonnées géographiques de l'aéroport (qui seront utilisées plus tard pour calculer les distances entre aéroports) et la colonne iso_country est une clef étrangère qui fait référence à la colonne iso_country de la table country.

La procédure qui va suivre est très similaire à celle que nous avons suivie pour la table country. Le fichier mobility/models/airport.py existe déjà dans le projet : il contient actuellement une longue liste de dictionnaires codée en dur. Remplacez l'intégralité de son contenu par le code suivant pour passer à une approche basée sur la base de données, puis complétez-le :

mobility/models/airport.py

def get_airport_list():
  # TODO
  # Retourne la liste des aéroports, ainsi que le nom du pays correspondant
  # trier par ordre alphabétique sur base du nom du pays

def search_airport_by_iata_code(iata_code: str):
  # TODO
  # Retourne la ligne correspondant à l'aéroport portant le code iata "iata_code"

class Airport:

   def __init__(self, iata_code, name, latitude_deg, longitude_deg, iso_country):
      # TODO
      # Constructeur de la classe

   def delete(self):
      # TODO
      # Retire l'aéroport de la DB

   def save(self):
      # TODO
      # Sauvegarde l'aéroport dans la DB

   @staticmethod
   def get(iata_code: str):
      # Retourne un objet Aéroport construit à partir des informations de la DB
      # récupérer à partir du code iata "iata_code"
      # Retourne none si aucun aéroport avec le code iata "iata_code"
      # se trouve dans la DB
      # Vous pouvez vous inspirer de la classe Country implémentée précédemment

mobility/models/airport.py

Solution (Essayez d'abord par vous-même)
from mobility.db import get_db

def get_airport_list():
   db = get_db()
   return db.execute(
      'SELECT * FROM airport').fetchall()


def search_airport_by_iata_code(iata_code: str):
   db = get_db()
   return db.execute('SELECT * FROM airport WHERE iata_code=?', (iata_code,)).fetchall()

class Airport:

   def __init__(self, iata_code, name, latitude_deg, longitude_deg, iso_country):
      self.iata_code = iata_code
      self.name = name
      self.latitude_deg = latitude_deg
      self.longitude_deg = longitude_deg
      self.iso_country = iso_country

   def delete(self):
      db = get_db()
      db.execute("DELETE FROM airport WHERE iata_code=?", (self.iata_code,))
      db.commit()

   def save(self):
      db = get_db()
      db.execute("INSERT INTO airport(iata_code, name, latitude_deg, longitude_deg, iso_country) VALUES(?, ?, ?, ?, ?)",
                 (self.iata_code, self.name, self.latitude_deg, self.longitude_deg, self.iso_country))
      db.commit()


   @staticmethod
   def get(iata_code: str):
      db = get_db()
      data = db.execute('SELECT * FROM airport WHERE iata_code=?', (iata_code,)).fetchone()

      if data is None:
         return None
      else:
         return Airport(data["iata_code"], data["name"], data["latitude_deg"], data["longitude_deg"], data["iso_country"])

Le fichier mobility/airport.py existe également déjà dans le projet : il utilise get_airports() sur la liste codée en dur. Remplacez son contenu par le code suivant pour utiliser les nouvelles fonctions SQL :

mobility/airport.py

from flask import (
   Blueprint, redirect, render_template, request, url_for
)

from mobility.models.airport import Airport
from mobility.models.airport import get_airport_list, search_airport_by_iata_code

bp = Blueprint('airport', __name__)

# Define the routes code
@bp.route('/')
def airport_list():
   # TODO
   # Page principale qui affiche la liste des aéroports
   # avec leur nom, code IATA et le nom de leur pays correspondant

@bp.route("/create_airport", methods=["POST"])
def airport_create():
   # TODO
   # Page permettant d'ajouter un aéroport, similaire à "create_country"

@bp.route("/delete_airport/<iata_code>")
def airport_delete(iata_code):
   # TODO
   # Page permettant de retirer un aéroport, similaire à "delete_country"

mobility/airport.py

Solution (Essayez d'abord par vous-même)
from flask import (
   Blueprint, redirect, render_template, request, url_for
)

from mobility.models.airport import Airport
from mobility.models.airport import get_airport_list, search_airport_by_iata_code

bp = Blueprint('airport', __name__)

# Define the routes code
@bp.route('/')
def airport_list():
   airports = get_airport_list()
   return render_template("airports.html", airports=airports)

@bp.route("/create_airport", methods=["POST"])
def airport_create():
   iata_code = request.form["iata_code"]
   if not search_airport_by_iata_code(str(iata_code)):
      name = request.form["name"]
      latitude_deg = request.form["latitude_deg"]
      longitude_deg = request.form["longitude_deg"]
      iso_country = request.form["iso_country"]
      airport = Airport(iata_code, name, float(latitude_deg), float(longitude_deg), iso_country)
      airport.save()
   return redirect(url_for("airport.airport_list"))

@bp.route("/delete_airport/<iata_code>")
def airport_delete(iata_code):
   airport = Airport.get(iata_code)
   if airport:
      airport.delete()
   return redirect(url_for("airport.airport_list"))

Mettez à jour le fichier existant airports.html pour ajouter les colonnes manquantes, un bouton de suppression et un formulaire d'ajout, comme pour les pays.

mobility/templates/airports.html

Solution (Essayez d'abord par vous-même)
{% extends "base.html" %}
{% block content %}
 <h2>New airport</h2>
 <form method="post" action="{{ url_for('airport.airport_create') }}">
    <div>
       <label for="iata_code"> IATA Code: </label>
       <input name="iata_code" id="iata_code" type="text" />
    </div>
    <div>
       <label for="name">Name: </label>
       <input name="name" id="name" type="text" />
    </div>
    <div>
       <label for="latitude_deg">Latitude: </label>
       <input name="latitude_deg" id="latitude_deg" type="text" />
    </div>
    <div>
       <label for="longitude_deg">Longitude: </label>
       <input name="longitude_deg" id="longitude_deg" type="text" />
    </div>
    <div>
       <label for="iso_country">ISO Code: </label>
       <input name="iso_country" id="iso_country" type="text" />
    </div>
 <div>
    <button type="submit">Add an airport</button>
 </div>
 </form>

<h2>Airports list</h2>
<table>
    <tr>
        <th>IATA code</th>
        <th>Name</th>
        <th>Latitude</th>
        <th>Longitude</th>
        <th>Country</th>
        <th>Actions</th>
    </tr>
    {% for airport in airports %}
    <tr>
        <td>{{ airport['iata_code'] }}</td>
        <td>{{ airport['name'] }}</td>
        <td>{{ airport['latitude_deg'] }}</td>
        <td>{{ airport['longitude_deg'] }}</td>
        <td>{{ airport['iso_country'] }}</td>
        <td><a href="{{ url_for('airport.airport_delete', iata_code=airport['iata_code']) }}">Delete</a></td>
    </tr>
    {% endfor %}
 </table>
{% endblock %}

Enfin n'oubliez pas de mettre à jour le fichier __init__.py dans le dossier mobility en ajoutant et modifiant les lignes suivantes.

mobility/__init__.py

from . import country, airport
app.register_blueprint(country.bp)

app.register_blueprint(airport.bp)
app.add_url_rule('/', endpoint='index')

Ajoutez un pays et puis un aéroport à votre base de données par le formulaire en utilisant le même code ISO. Si vous ajoutez un aéroport avec un code ISO sans pays correspondant, l'aéroport ne sera pas affiché.

Voici un exemple de ce que vous devriez voir sur la page airports.html.

../../_images/airport_final.png

Continuez en lisant Utiliser la DB disponible sur moodle!.