I. Introduction
Dans une agence de location de véhicules on souhaite connaître leur disponibilité dans le futur en fonction des locations déjà programmées.
636076
L'objectif de ce billet est en fait de planifier les disponibilités des véhicules à partir de données enregistrées dans un fichier Excel ou dans des tables SQLite.
Cette opération sera réalisée en 3 étapes principales :
II. Données externes
Dans notre cas, les données sont enregistrées dans des feuilles Excel ou dans des tables SQLite.
II-A. Fichier contenant les données sur les véhicules
Structure du fichier :
636532
Aperçu du contenu de la feuille Excel :
636063
II-B. Fichier contenant les données sur les locations de véhicules
Structure du fichier source :
636533
Aperçu du contenu de la feuille Excel :
636064
Note importante : pour simplifier, on garde uniquement les colonnes essentielles et on affiche le nom du client et le numéro de véhicule au lieu des identifiants pour savoir directement qui loue le véhicule.
Comme on le verra plus loin, on peut également extraire ces informations d'une base de données SQLite.
III. Module openpyxl
Il permet de lire et d'écrire dans des feuilles de fichiers Excel au format xlsx ou xlsm.
III-A. Installation
Pour installer la librairie exécutez simplement la commande :
pip install openpyxl
C'est plus simple qu'avec pandas.
III-B. Code exemple
On donne ici quelques lignes de code prises dans la documentation pour mieux comprendre comment utiliser openpyxl :
from openpyxl import Workbook
wb = Workbook()
# pointe sur la feuille active
ws = wb.active
# copie la valeur 42 dans la cellule A1
ws['A1'] = 42
# ajout d'une ligne à la feuille
ws.append([1, 2, 3])
# copie la date et l'heure actuelle dans la cellule A2
import datetime
ws['A2'] = datetime.datetime.now()
# sauvegarde du fichier
wb.save("sample.xlsx")
Autre exemple, pour lire le contenu d'une cellule Excel :
var = ws['A1']
Permet de copier le contenu de la cellule A1 de la feuille ws dans la variable var.
Equivalent à :
var = ws.cell(row = 1, column = 1).value
On peut donc lire et écrire dans des cellules précises et sur des lignes ou des colonnes pas forcément adjacentes, ce qui n'est pas vraiment possible avec pandas.
Si vous souhaitez avoir plus d'information sur ce module je vous invite à consulter ce tutoriel.
Note importante : on donnera en complément pour chacune des fonctions utilisant openpyxl le code équivalent avec pandas.
IV. Importation des données dans des listes de dictionnaires
IV-A. Fichier Excel
On récupère les données des deux feuilles Excel dans des listes Python à l'aide du module openpyxl :
from openpyxl import load_workbook # module utilisé pour lire le contenu du fichier xlsx
def importer_feuille(ws):
# lecture et importation des données de la feuille ws dans une liste de dicos
# initialisation de la liste des dictionnaires
liste_dicos=[]
# mémorisation des indices maxi. de ligne et de colonne
ligne_max = ws.max_row
col_max = ws.max_column
# parcours les lignes de la feuille Excel en commençant par la 2e ligne
for i in range(2, ligne_max + 1):
dico = {} # initialisation du dico
# parcours des colonnes de la feuille
for j in range(1, col_max + 1):
# copie de la valeur associée à la clé dans le dictionnaire : vehicule['id_vehicule'] = ws.cell(row = i, column = 1)
cle = ws.cell(row = 1, column = j).value
dico = ws.cell(row = i, column = j).value
# ajout du dico à la liste
liste_dicos.append(dico)
return liste_dicos # retourne la liste des dicos
def importer_donnees(chemin_fichier):
# importe les données sur les véhicules et les locations qui sont contenues dans le fichier Excel
# ouverture du classeur dont l'emplacement est passé en argument
wb = load_workbook(chemin_fichier)
# on pointe sur la feuille vehicules
ws = wb["vehicules"]
# récupération dans une liste des données de la feuille vehicules
liste_vehicules = importer_feuille(ws)
# on pointe maintenant sur la feuille locations_vehicules
ws = wb["locations_vehicules"]
# récupération dans une liste des données de la feuille locations_vehicules
liste_locations = importer_feuille(ws)
return (liste_vehicules,liste_locations) # renvoi des 2 listes de dicos
Fonction équivalente avec pandas :
import pandas
def importer_donnees(chemin_fichier):
# importe les données sur les véhicules et les locations qui sont contenues dans le fichier Excel
# lecture et importation du contenu de la feuille vehicules dans un DataFrame
df = pandas.read_excel(chemin_fichier, sheet_name="vehicules")
# transformation du DataFrame en liste de dictionnaires
liste_vehicules = df.to_dict('records')
# lecture et importation du contenu de la feuille locations_vehicules dans un DataFrame
df = pandas.read_excel(chemin_fichier, sheet_name="locations_vehicules")
# transformation du DataFrame en liste de dictionnaires
liste_locations = df.to_dict('records')
return (liste_vehicules,liste_locations) # renvoi des 2 listes de dicos
IV-B. Base de données SQLite
On peut également extraire les données de tables SQLite à l'aide de la librairie sqlite3 :
import sqlite3
from sqlite3 import Error
def create_connection(db_file):
""" crée une connexion à la base de données SQLite
specifié par le chemin du fichier passé en argument
:param db_file: chemin du fichier db
:return: un objet connexion ou renvoie none
"""
conn = None
try:
conn = sqlite3.connect(db_file) # on crée l'objet connexion
return conn
except Error as e: # gestion de l'erreur
print(e) # affichage du message d'erreur
return conn
def importer_table(connexion, nom_table, liste_cles):
# importation des données de la table dans une liste de dicos
cur = connexion.cursor()
cur.execute("select * from " + nom_table)
# initialise la liste qui contiendra les données de la table
liste_dicos=[]
# récupère le contenu de la table dans une liste
liste_donnees = cur.fetchall()
# parcours de la liste contenant les lignes de la table
for row in liste_donnees:
# initialisation du dico et de l'indice de colonne
dico = {}; j=0
# copie des données de la ligne dans un dictionnaire
for cle in liste_cles:
dico= row
j+=1 # incrémentation de l'indice de colonne
# ajout du dictionnaire à la liste
liste_dicos.append(dico)
return liste_dicos # renvoi la liste de dicos
def importer_donnees():
# importe les données sur les véhicules et les locations qui sont contenues dans les tables SQLite
database = r"C:\sqlite\db\locations.db" # chemin de la base de données
# création de la connexion à la base de données SQLite
conn = create_connection(database)
# si la connexion a été réalisée
if conn is not None:
# création de la liste des clés des dicos pour les véhicules
liste_cles = ['id_vehicule','num_vehicule', 'modele_vehicule','type_vehicule','marque']
# récupération des données de la table vehicules dans une liste
liste_vehicules = importer_table(conn, 'vehicules', liste_cles)
# création de la liste des clés des dicos pour les locations
liste_cles = ['id_location','num_vehicule', 'client','debut_loc','fin_loc']
# récupération des données de la table locations_vehicules dans une liste
liste_locations = importer_table(conn, 'locations_vehicules', liste_cles)
return (liste_vehicules, liste_locations) # renvoi les 2 listes de dicos
else:
print("Erreur! impossible de créer la connexion à la base !")
Résultat obtenu pour les locations :
[
{
"id_location": 1,
"num_vehicule": "AA-016-AA",
"client": "BONNEMAISON",
"debut_loc": datetime.datetime(2023,3,14,15,0),
"fin_loc": datetime.datetime(2023,4,14,15,0)
},
{
"id_location": 2,
"num_vehicule": "AA-007-AA",
"client": "BONNEMAISON",
"debut_loc": datetime.datetime(2023,4,3,12,0),
"fin_loc": datetime.datetime(2023,5,3,12,0)
},
{
"id_location": 3,
"num_vehicule": "AA-008-AA",
"client": "CESARO",
"debut_loc": datetime.datetime(2023,4,24,12,0),
"fin_loc": datetime.datetime(2023,5,24,12,0)
},
...
]
Note importante : l'utilisation de dictionnaires va nous aider par la suite à identifier chaque colonne dans le code Python.
V. Génération de la liste des disponibilités des véhicules
Si on souhaite par exemple connaître les disponibilités du modèle Nissan Qashqai entre le 01/03/2023 12:00 et le 30/06/2023 12:00 :
636065
On voit sur le planning précédent que l'on peut ainsi obtenir les disponibilités des véhicules à partir des locations déjà enregistrées :
636066
Cette opération sera réalisée par la suite à l'aide de deux fonctions Python.
V-A. Génération des disponibilités pour un véhicule
La fonction generer_disponibilites_vehicule va permettre de déterminer les périodes de disponibilité pour chaque véhicule.
Arguments de la fonction :
Déroulé de la fonction :
def generer_disponibilites_vehicule(liste_locations, debut_periode, fin_periode, num_vehicule, modele_vehicule, type_vehicule):
# génère la liste des dispos du véhicule
# filtre de la liste des locations par numéro de véhicule
liste_locations_vehicule = [loc for loc in liste_locations if (loc['num_vehicule']==num_vehicule)]
liste_dispos = []
# tri de la liste des locations du véhicule par date de début de location
liste_locations_vehicule = sorted(liste_locations_vehicule, key=lambda k: (k['debut_loc']))
# on initialise les dates de début et de fin de disponibilité du véhicule avec les dates de début et de fin de la période choisie
debut_dispo = debut_periode
fin_dispo = fin_periode
# parcours de la liste des locations du véhicule
for loc in liste_locations_vehicule:
# si la date de début de location est comprise dans la période [debut_dispo, fin_dispo]
if (debut_dispo
ajouter_dispo(liste_dispos, num_vehicule, modele_vehicule, type_vehicule, debut_dispo, loc['debut_loc'])
# on mémorise la date de fin de location comme date de début de dispo. pour la prochaine location
debut_dispo = loc['fin_loc']
if (debut_dispo < fin_dispo): # si debut_dispo < fin_dispo alors il faut ajouter une période supplémentaire à la liste
# ajout du véhicule à la liste avec ses dates de début et de fin de disponibilité
ajouter_dispo(liste_dispos, num_vehicule, modele_vehicule, type_vehicule, debut_dispo, fin_dispo)
return liste_dispos # renvoi la liste des dispo.
def ajouter_dispo(liste_dispos, num_vehicule, modele_vehicule, type_vehicule, debut_dispo, fin_dispo):
# création du dico contenant les références du véhicule avec ses dates de début et de fin de disponibilité
dispo_vehicule={'num_vehicule': num_vehicule, 'modele_vehicule': modele_vehicule,'type_vehicule': type_vehicule,
'debut_dispo': debut_dispo,'fin_dispo': fin_dispo}
# ajout du dico à la liste des disponibilités
liste_dispos.append(dispo_vehicule)
V-B. Génération des disponibilités pour un modèle ou un type de véhicule
La fonction generer_disponibilites va permettre de générer la liste des disponibilités pour un modèle ou un type de véhicule et pour la période choisie.
Arguments de la fonction :
Déroulé de la fonction :
def generer_disponibilites(liste_vehicules, liste_locations, debut_periode, fin_periode, modele_vehicule='', type_vehicule=''):
# génère la liste des dispos pour la période et le modèle ou le type de véhicule choisis
# génère la liste des véhicules par modèle ou type
# on utilise la fonction lower() dans les conditions pour convertir les deux chaînes de caractères en minuscules et ainsi rendre leur comparaison insensible à la casse.
if modele_vehicule!='': # si un modèle est choisi
liste_vehicules = [vehicule for vehicule in liste_vehicules if (vehicule['modele_vehicule'].lower() == modele_vehicule.lower())]
else:
if type_vehicule!='': # si un type de véhicule est choisie
liste_vehicules = [vehicule for vehicule in liste_vehicules if (vehicule['type_vehicule'].lower() == type_vehicule.lower())]
else:
print("Chosir un modèle ou une type de véhicule !")
# tri de la liste de véhicules par type, modèle et numéro
liste_vehicules = sorted(liste_vehicules, key=lambda k: (k['type_vehicule'], k['modele_vehicule'], k['num_vehicule']))
# initialise la liste des disponibilités
liste_dispos = []
# filtre de la liste des locations en fonction de la période choisie
liste_locations = [loc for loc in liste_locations if (loc['fin_loc']>debut_periode) and (loc['debut_loc']
# parcours de la liste
for vehicule in liste_vehicules:
# création de la liste des disponibilités pour le véhicule
liste_dispos_vehicule = generer_disponibilites_vehicule(liste_locations, debut_periode, fin_periode,
num_vehicule=vehicule['num_vehicule'], modele_vehicule=vehicule['modele_vehicule'], type_vehicule=vehicule['type_vehicule'])
liste_dispos = liste_dispos + liste_dispos_vehicule # ajout à liste_dispos
return liste_dispos # renvoi la liste des disponibilités
Elle renvoie une liste de dictionnaires de la forme :
[
{
"num_vehicule":"AA-007-AA",
"modele_vehicule":"Nissan Qashqai",
"type_vehicule":"SUV",
"debut_dispo":datetime.datetime(2023,3,1,12,0),
"fin_dispo":datetime.datetime(2023,4,3,12,0)
},
{
"num_vehicule":"AA-007-AA",
"modele_vehicule":"Nissan Qashqai",
"type_vehicule":"SUV",
"debut_dispo":datetime.datetime(2023,5,3,12,0),
"fin_dispo":datetime.datetime(2023,6,30,12,0)
},
{
"num_vehicule":"AA-008-AA",
"modele_vehicule":"Nissan Qashqai",
"type_vehicule":"SUV",
"debut_dispo":datetime.datetime(2023,3,1,12,0),
"fin_dispo":datetime.datetime(2023,4,24,12,0)
},
...
]
VI. Exportation de la liste de dictionnaires dans une feuille Excel
On exporte ensuite la liste obtenue dans une feuille Excel à l'aide de cette fonction :
def exporter_dispos(chemin_fichier, nom_feuille, liste_dispos):
# exporte les disponibilités dans une feuille du fichier Excel
# création d'un objet Workbook
wb = Workbook()
# pointe sur la feuille active
ws = wb.active
# renomme la feuille active
ws.title = nom_feuille
# récupération des clés du dictionnaire
liste_cles = list(liste_dispos.keys())
# mise à jour des entêtes de colonne de la feuille : 1re ligne
ws.append(liste_cles)
# parcours de la liste des dispos
for dispo in liste_dispos:
liste_valeurs = list(dispo.values()) # liste de valeurs du dictionnaire
# ajout d'une ligne de valeurs
ws.append(liste_valeurs)
# sauvegarde du classeur
wb.save(chemin_fichier)
On utilise à nouveau le module openpyxl mais cette fois-ci pour copier les données dans une feuille Excel.
Fonction équivalente avec pandas :
import pandas
def exporter_dispos(chemin_fichier, nom_feuille, liste_dispos):
# exporte les disponibilités dans une feuille du fichier Excel
# conversion de la liste de dicos en DataFrame
df = pandas.DataFrame(liste_dispos)
# copie du DataFrame dans une feuille du fichier Excel
df.to_excel(chemin_fichier, sheet_name= nom_feuille)
Résultat obtenu :
636067
VII. Module complet de test
Finalement, nous donnons le module complet contenant les fonctions permettant de générer la liste des disponibilités :
from openpyxl import load_workbook # module openpyxl utilisé pour lire et écrire dans un fichier xlsx
from openpyxl import Workbook # ---
from datetime import datetime
import os
#import pandas
def importer_feuille(ws):
# lecture et importation des données de la feuille ws dans une liste de dicos
# initialisation de la liste des dictionnaires
liste_dicos=[]
# mémorisation des indices maxi. de ligne et de colonne
ligne_max = ws.max_row
col_max = ws.max_column
# parcours les lignes de la feuille Excel en commençant par la 2e ligne
for i in range(2, ligne_max + 1):
dico = {} # initialisation du dico
# parcours des colonnes de la feuille
for j in range(1, col_max + 1):
# copie de la valeur associée à la clé dans le dictionnaire : vehicule['id_vehicule'] = ws.cell(row = i, column = 1)
cle = ws.cell(row = 1, column = j).value
dico = ws.cell(row = i, column = j).value
# ajout du dico à la liste
liste_dicos.append(dico)
return liste_dicos # retourne la liste des dicos
def importer_donnees(chemin_fichier):
# importe les données sur les véhicules et les locations qui sont contenues dans le fichier Excel
# ouverture du classeur dont l'emplacement est passé en argument
wb = load_workbook(chemin_fichier)
# on pointe sur la feuille vehicules
ws = wb["vehicules"]
# récupération dans une liste des données de la feuille vehicules
liste_vehicules = importer_feuille(ws)
# on pointe maintenant sur la feuille locations_vehicules
ws = wb["locations_vehicules"]
# récupération dans une liste des données de la feuille locations_vehicules
liste_locations = importer_feuille(ws)
return (liste_vehicules,liste_locations) # renvoi des 2 listes de dicos
def importer_donnees_v2(chemin_fichier):
# importe les données sur les véhicules et les locations qui sont contenues dans le fichier Excel
# lecture et importation du contenu de la feuille vehicules dans un DataFrame
df = pandas.read_excel(chemin_fichier, sheet_name="vehicules")
# transformation du DataFrame en liste de dictionnaires
liste_vehicules = df.to_dict('records')
# lecture et importation du contenu de la feuille locations_vehicules dans un DataFrame
df = pandas.read_excel(chemin_fichier, sheet_name="locations_vehicules")
# transformation du DataFrame en liste de dictionnaires
liste_locations = df.to_dict('records')
return (liste_vehicules,liste_locations) # renvoi des 2 listes de dicos
def exporter_dispos(chemin_fichier, nom_feuille, liste_dispos):
# exporte les disponibilités dans une feuille du fichier Excel
# création d'un objet Workbook
wb = Workbook()
# pointe sur la feuille active
ws = wb.active
# renomme la feuille active
ws.title = nom_feuille
# récupération des clés du dictionnaire
liste_cles = list(liste_dispos.keys())
# mise à jour des entêtes de colonne de la feuille : 1re ligne
ws.append(liste_cles)
# parcours de la liste des dispos
for dispo in liste_dispos:
liste_valeurs = list(dispo.values()) # liste de valeurs du dictionnaire
# ajout d'une ligne de valeurs
ws.append(liste_valeurs)
# sauvegarde du classeur
wb.save(chemin_fichier)
def exporter_dispos_v2(chemin_fichier, nom_feuille, liste_dispos):
# exporte les disponibilités dans une feuille du fichier Excel
# conversion de la liste de dicos en DataFrame
df = pandas.DataFrame(liste_dispos)
# copie du DataFrame dans une feuille du fichier Excel
df.to_excel(chemin_fichier, sheet_name= nom_feuille)
def generer_disponibilites_vehicule(liste_locations, debut_periode, fin_periode, num_vehicule, modele_vehicule, type_vehicule):
# génère la liste des dispos du véhicule
# filtre de la liste des locations par numéro de véhicule
liste_locations_vehicule = [loc for loc in liste_locations if (loc['num_vehicule']==num_vehicule)]
liste_dispos = []
# tri de la liste des locations du véhicule par date de début de location
liste_locations_vehicule = sorted(liste_locations_vehicule, key=lambda k: (k['debut_loc']))
# on initialise les dates de début et de fin de disponibilité du véhicule avec les dates de début et de fin de la période choisie
debut_dispo = debut_periode
fin_dispo = fin_periode
# parcours de la liste des locations du véhicule
for loc in liste_locations_vehicule:
# si la date de début de location est comprise dans la période [debut_dispo, fin_dispo]
if (debut_dispo
ajouter_dispo(liste_dispos, num_vehicule, modele_vehicule, type_vehicule, debut_dispo, loc['debut_loc'])
# on mémorise la date de fin de location comme date de début de dispo. pour la prochaine location
debut_dispo = loc['fin_loc']
if (debut_dispo < fin_dispo): # si debut_dispo < fin_dispo alors il faut ajouter une période supplémentaire à la liste
# ajout du véhicule à la liste avec ses dates de début et de fin de disponibilité
ajouter_dispo(liste_dispos, num_vehicule, modele_vehicule, type_vehicule, debut_dispo, fin_dispo)
return liste_dispos # renvoi la liste des dispo.
def ajouter_dispo(liste_dispos, num_vehicule, modele_vehicule, type_vehicule, debut_dispo, fin_dispo):
# création du dico contenant les références du véhicule avec ses dates de début et de fin de disponibilité
dispo_vehicule={'num_vehicule': num_vehicule, 'modele_vehicule': modele_vehicule,'type_vehicule': type_vehicule,
'debut_dispo': debut_dispo,'fin_dispo': fin_dispo}
# ajout du dico à la liste des disponibilités
liste_dispos.append(dispo_vehicule)
def generer_disponibilites(liste_vehicules, liste_locations, debut_periode, fin_periode, modele_vehicule='', type_vehicule=''):
# génère la liste des dispos pour la période et le modèle ou le type de véhicule choisis
# génère la liste des véhicules par modèle ou type
# on utilise la fonction lower() dans les conditions pour convertir les deux chaînes de caractères en minuscules et ainsi rendre leur comparaison insensible à la casse.
if modele_vehicule!='': # si un modèle est choisi
liste_vehicules = [vehicule for vehicule in liste_vehicules if (vehicule['modele_vehicule'].lower() == modele_vehicule.lower())]
else:
if type_vehicule!='': # si un type de véhicule est choisie
liste_vehicules = [vehicule for vehicule in liste_vehicules if (vehicule['type_vehicule'].lower() == type_vehicule.lower())]
else:
print("Chosir un modèle ou une type de véhicule !")
# tri de la liste de véhicules par type, modèle et numéro
liste_vehicules = sorted(liste_vehicules, key=lambda k: (k['type_vehicule'], k['modele_vehicule'], k['num_vehicule']))
# initialise la liste des disponibilités
liste_dispos = []
# filtre de la liste des locations en fonction de la période choisie
liste_locations = [loc for loc in liste_locations if (loc['fin_loc']>debut_periode) and (loc['debut_loc']
# parcours de la liste
for vehicule in liste_vehicules:
# création de la liste des disponibilités pour le véhicule
liste_dispos_vehicule = generer_disponibilites_vehicule(liste_locations, debut_periode, fin_periode,
num_vehicule=vehicule['num_vehicule'], modele_vehicule=vehicule['modele_vehicule'], type_vehicule=vehicule['type_vehicule'])
liste_dispos = liste_dispos + liste_dispos_vehicule # ajout à liste_dispos
return liste_dispos # renvoi la liste des disponibilités
# importation du contenu du fichier locations_vehicules.xlsx dans 2 listes de dictionnaires
liste_vehicules, liste_locations = importer_donnees("locations_vehicules.xlsx")
print("Liste des véhicules :\n")
# on affiche le contenu de la liste des véhicules
print(liste_vehicules)
print()
print("Liste des locations :\n")
# on affiche le contenu de la liste des locations
print(liste_locations)
# choix du modèle ou du type de véhicule recherché
modele_vehicule="Nissan Qashqai"
#type_vehicule = "SUV"
# définition de la période de recherche
debut_periode = datetime(2023, 3, 1, 12, 0, 0) # début période = 01/03/2023 12:00
fin_periode = datetime(2023, 6, 30, 12, 0, 0) # fin période = 30/06/2023 12:00
# génération de la liste des disponibilités en fonction des critères retenus
liste_dispos = generer_disponibilites(liste_vehicules, liste_locations, debut_periode, fin_periode, modele_vehicule=modele_vehicule )
if liste_dispos!=[]: # si la liste n'est pas vide
print()
print("Liste des disponibilités :\n")
# affiche les disponibilités par véhicule
print(liste_dispos)
# composition du chemin du fichier et du nom de la feuille de destination
chemin_fichier = 'dispo_vehicules - ' + modele_vehicule + " - " + debut_periode.strftime('%Y-%m-%d %Hh%M') + " - " + fin_periode.strftime('%Y-%m-%d %Hh%M') + ".xlsx"
nom_feuille = 'dispos_' + modele_vehicule
# export du résultat dans une feuille du fichier Excel
exporter_dispos(chemin_fichier, nom_feuille, liste_dispos)
os.startfile(chemin_fichier) # ouverture du fichier Excel
else:
print("Pas de résultat !")
Si vous le souhaitez, vous pouvez récupérer le dossier complet pour tester le code :
636372
VIII. Conclusion
Ce module Python offre donc une solution relativement simple pour obtenir les disponibilités des véhicules à partir de données enregistrées dans des feuilles Excel ou dans des tables SQLite.
Chacun pourra ensuite librement adapter le code pour planifier par exemple les disponibilités de chambres d'hôtel.
Sources :
https://docs.python.org/fr/3/tutorial/datastructures.html
https://openpyxl.readthedocs.io/en/latest/index.html
https://docs.python.org/fr/3/library/sqlite3.html
https://docs.python.org/fr/3.11/library/datetime.html
Vous avez lu gratuitement 1 151 articles depuis plus d'un an.
Soutenez le club developpez.com en souscrivant un abonnement pour que nous puissions continuer à vous proposer des publications.
Soutenez le club developpez.com en souscrivant un abonnement pour que nous puissions continuer à vous proposer des publications.