- Insertion de la date du jour dans le visa lors de sa création - Mode d'emploi simplifié
493 lines
19 KiB
Python
493 lines
19 KiB
Python
# ~~ Script DORY Digitanie ~~
|
|
|
|
# Voir algorithme.txt pour plus de détails et commentaires
|
|
|
|
|
|
|
|
# ----- INSTALLATION DE PIP ET CONTROLE MODULES -----
|
|
# Repris depuis script Banbou
|
|
import subprocess
|
|
|
|
def check_and_update_pip():
|
|
# Vérifier s'il existe une version plus récente de pip
|
|
try:
|
|
result = subprocess.run(['pip', 'install', '--upgrade', 'pip', '--disable-pip-version-check'], capture_output=True, text=True)
|
|
if result.returncode == 0:
|
|
print("pip a été mis à jour avec succès.") #TODO bizarre, c'est affiché alors qu'il n'y avait pas d'internet
|
|
except subprocess.CalledProcessError:
|
|
print("La mise à jour de pip a échoué.")
|
|
|
|
# Lancer le code deux fois
|
|
for _ in range(2):
|
|
check_and_update_pip()
|
|
|
|
|
|
# Liste des bibliothèques à vérifier et installer
|
|
bibliotheques = ['csv', 'os', 're', 'shutil', 'sys', 'openpyxl']
|
|
|
|
for bibliotheque in bibliotheques:
|
|
try:
|
|
__import__(bibliotheque)
|
|
except ImportError:
|
|
# Installer la bibliothèque
|
|
print(f"{bibliotheque} n'est pas installée. Installation en cours...")
|
|
subprocess.check_call(['pip', 'install', bibliotheque])
|
|
print(f"{bibliotheque} a été installée avec succès.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import os # getcwd(), walk()
|
|
import openpyxl # gestion et manipulation de fichier Open XML
|
|
import re # RegEx
|
|
from shutil import copyfile # copie de fichiers
|
|
from datetime import datetime # obj date du jour
|
|
|
|
|
|
|
|
class _Fichier:
|
|
def __init__(self,
|
|
nom_original="Pas de nom original",
|
|
chemin="Pas de chemin",
|
|
extension="Pas d'extension",
|
|
nom= "Pas de nom",
|
|
implication="Non-conforme"):
|
|
self.nom_original = nom_original # - son nom original - string
|
|
self.chemin = chemin # - son chemin - string
|
|
self.extension = extension # - son extension - string
|
|
self.nom = nom # - son nom formaté - string
|
|
self.implication = implication # - son implication - enum
|
|
# {"Nécessaire",
|
|
# "Auxiliaire",
|
|
# "Non-conforme",
|
|
# "A-ignorer"}
|
|
def afficher(self):
|
|
"""Affiche dans la sortie standard les éléments du fichier
|
|
"""
|
|
print("nom : ".center(16), self.nom)
|
|
print("nom orig : ".center(16), self.nom_original)
|
|
print("ext : ".center(16), self.extension)
|
|
print("chemin : ".center(16), self.chemin)
|
|
print("implication :".center(16), self.implication)
|
|
|
|
def impliquer(self):
|
|
"""Définir l'implication d'un fichier
|
|
|
|
Les fichiers nécessaires sont indispensables sinon l'importation
|
|
de la couche échoue.
|
|
Les fichiers optionels (ou auxiliaires) contiennent des données
|
|
supplémentaires lors de l'importation.
|
|
Les fichiers non conformes pourraient être importer dans QGIS. Mais,
|
|
ne sont pas conformes au cahier des charges.
|
|
Les fichiers ignorés ne fonctionnent pas dans QGIS.
|
|
L'implication est définie en fonction de l'extension du fichier.
|
|
"""
|
|
match self.extension:
|
|
case "dbf" | "cpg" | "prj" | "shx" | "shp":
|
|
self.implication = "Necessaire"
|
|
case "qix" | "qmd" | "qml":
|
|
self.implication = "Auxiliaire"
|
|
case "gpkg" | "sqlite":
|
|
self.implication = "Non-conforme"
|
|
case _:
|
|
self.implication = "A-ignorer"
|
|
# TODO : gerer la casse des extensions pour que le controle se fasse
|
|
|
|
|
|
class _Projet:
|
|
def __init__(self,
|
|
nom="Pas de nom",
|
|
necessaires = {},
|
|
auxiliaires = {},
|
|
non_conformes = {},
|
|
nb_fichiers=0,
|
|
conformite="Non conforme",
|
|
projection="Inconnu",
|
|
encodage="Inconnu"):
|
|
self.nom = nom # - son nom - string
|
|
self.necessaires = necessaires # - son dictionnaire de fichiers nécessaires avec pour clé l'extention
|
|
# de chaque fichier - dico de paire string : _Fichier
|
|
self.auxiliaires = auxiliaires # - son dictionnaire de fichiers nécessaires avec pour clé l'extention
|
|
# de chaque fichier - dico de paire string : _Fichier
|
|
self.non_conformes = non_conformes # - son dictionnaire de fichiers nécessaires avec pour clé l'extention
|
|
# de chaque fichier - dico de paire string : _Fichier
|
|
self.nb_fichiers = nb_fichiers # - son nombre de fichiers - entier
|
|
self.conformite = conformite # - sa conformité - string
|
|
self.projection = projection # - sa projection géodésique - string // ou énumération --> voir Constantes
|
|
self.encodage = encodage # - son type d'encodage prévu dans QGIS string
|
|
# /* pour ce script, ce devrait être forcement "UTF8" */
|
|
|
|
def afficher(self):
|
|
"""Affiche dans la sortie standard le contenu d'un projet
|
|
"""
|
|
print("nom : ".center(16), self.nom)
|
|
print("nb fichiers :".center(16), self.nb_fichiers)
|
|
print("conformité :".center(16), self.conformite)
|
|
print("projection :".center(16), self.projection)
|
|
print("encodage :".center(16), self.encodage)
|
|
print("necessaires :".center(16), self.necessaires)
|
|
print("auxiliaires :".center(16), self.auxiliaires)
|
|
print("non_conformes :".center(16), self.non_conformes)
|
|
|
|
def cataloguer(self, fichier):
|
|
"""Catalogue le fichier dans le projet
|
|
|
|
La méthode ajoute le fichier suivant son extension
|
|
dans un des dictionnaires du projet
|
|
"""
|
|
match fichier.implication:
|
|
case "Necessaire":
|
|
self.necessaires[fichier.extension] = fichier
|
|
case "Auxiliaire":
|
|
self.auxiliaires[fichier.extension] = fichier
|
|
case "Non-conforme":
|
|
self.non_conformes[fichier.extension] = fichier
|
|
|
|
def lire_projection(self):
|
|
"""Récupère la projection des données géomatiques définie.
|
|
|
|
Lis la première ligne du fichier *.PRJ du projet
|
|
et controle la présence de la chaine "Lambert_93"
|
|
"""
|
|
if "prj" in self.necessaires:
|
|
df = open(self.necessaires["prj"].chemin)
|
|
chaine = df.readline()
|
|
df.close()
|
|
#cherche le motif : "Ligne qui commence par PROJCS["
|
|
# puis suivi de 0 ou plusieurs caracteres
|
|
#puis suivi de Lambert_93"
|
|
motif = re.findall('^PROJCS[[]".*Lambert_93"', chaine)
|
|
print("motif = ", motif)
|
|
if motif:
|
|
self.projection = "LAMBERT93"
|
|
else:
|
|
print(projet.nom, " : pas de PRJ trouvé --> pas de projection définie")
|
|
|
|
def lire_encodage(self):
|
|
"""Récupère l'encodage prévu pour les fichiers dans QGIS
|
|
|
|
Lis la première ligne du fichier *.CPG du projet
|
|
et controle la présence de la chaine "UTF-8"
|
|
"""
|
|
if "cpg" in self.necessaires:
|
|
df = open(self.necessaires["cpg"].chemin)
|
|
chaine = df.readline()
|
|
df.close()
|
|
if "UTF-8" in chaine:
|
|
self.encodage = "UTF8"
|
|
else:
|
|
print(projet.nom, " : pas de CPG trouvé --> pas d'encodage définie")
|
|
|
|
def valider(self):
|
|
"""Valide la conformité du projet
|
|
|
|
Controle si les 5 fichiers differents nécessaires sont présents.
|
|
Controle si la projection est correcte (pour le moment Lambert93).
|
|
Controle si l'encodage défini pour l'import dans QGIS est bien UTF8.
|
|
Si ces 3 controles sont OK, alors le projet est défini comme valide.
|
|
"""
|
|
nb_controles_OK = 0
|
|
# Controle présences fichiers
|
|
valide = [False, False, False, False, False]
|
|
for extension in self.necessaires.keys():
|
|
match extension:
|
|
case "dbf":
|
|
valide[0] = True
|
|
case "cpg":
|
|
valide[1] = True
|
|
case "prj" :
|
|
valide[2] = True
|
|
case "shx" :
|
|
valide[3] = True
|
|
case "shp" :
|
|
valide[4] = True
|
|
if valide[0] and valide[1] and valide[2] and valide[3] and valide[4]:
|
|
nb_controles_OK += 1
|
|
|
|
# Controle projection
|
|
if self.projection == "LAMBERT93":
|
|
nb_controles_OK += 1
|
|
|
|
# Controle encodage
|
|
if self.encodage == "UTF8":
|
|
nb_controles_OK += 1
|
|
|
|
# Si les 3 controles sont OK ALors le projet est conforme
|
|
if nb_controles_OK == 3:
|
|
self.conformite = "Conforme"
|
|
|
|
def copier(self):
|
|
"""Copie les fichiers du projets
|
|
"""
|
|
pass
|
|
# TODO :
|
|
|
|
|
|
|
|
def formatter(chaine):
|
|
"""Formate selon nomenclature.
|
|
|
|
Formate la chaine de caractères passée en paramètre :
|
|
Enlève tous les accents français. Enlève la cédille du C.
|
|
Remplace les espaces ' ' et les traits d'union '-' par
|
|
des tirets bas '_'.
|
|
Ne traite pas pour le moment le AE et OE ligaturé.
|
|
"""
|
|
#TODO : gérer les accents sur majuscules
|
|
#TODO : amélioration ou exercice, utiliser la méthode str.translate() et maketrans
|
|
resultat = ""
|
|
for c in chaine:
|
|
match c:
|
|
case "à" | "â" | "ä":
|
|
resultat+= "a"
|
|
case "é" | "è" | "ê" | "ë":
|
|
resultat+= "e"
|
|
case "î" | "ï":
|
|
resultat+= "i"
|
|
case "ô" | "ö":
|
|
resultat+= "o"
|
|
case "ù" | "û" | "ü":
|
|
resultat+= "u"
|
|
case "ÿ":
|
|
resultat+= "y"
|
|
case "ç":
|
|
resultat+= "c"
|
|
case " " | "-":
|
|
resultat+= "_"
|
|
case _:
|
|
resultat+= c
|
|
return resultat
|
|
|
|
|
|
|
|
|
|
# DEBUT DU PROGRAMME
|
|
|
|
liste = [] # - une liste des fichiers list _Fichier
|
|
dico = {} # - un dictionnaire des projets avec pour clé le nom de chaque projet
|
|
# dico de paire string : Entité Projet
|
|
racine = os.getcwd() # - un chemin absolue vers le repertoire racine à traiter string
|
|
print("Racine : ".center(18), racine)
|
|
|
|
date = datetime.today().strftime("%d/%m/%Y") # objet contenant la date du jour
|
|
|
|
|
|
|
|
# --recupérer le chemin du dossier à traiter
|
|
# Si il y a PAS un dossier de traitement dans le working directory Alors
|
|
pas_de_dossier = True
|
|
|
|
for a in os.scandir():
|
|
#si 'a' est un dossier et n'est pas un dossier de la liste ci dessous
|
|
if a.is_dir() and a.name not in ["Travail", "Livraison", ".git"]:
|
|
#il y a un dossier
|
|
pas_de_dossier = False
|
|
print(f"Dossier à Traiter trouvé : {a.name}")
|
|
racine = a
|
|
if pas_de_dossier:
|
|
print("Pas de dossier trouvé...\nFin de programme\n")
|
|
# fin du programme
|
|
exit()
|
|
|
|
|
|
# --analyser le dossier
|
|
# initialiser une liste de _Fichiers
|
|
liste = []
|
|
# Pour Chaque fichier des dossiers et sous dossiers à explorer Faire
|
|
for dossier_courant, list_sousdossiers, list_fichiers in os.walk(racine): ##TODO voir la fonction fwalk qui renvoie un 4-tuple(dirpath, dirnames, filenames, dirfd), and it supports dir_fd
|
|
for fichier_courant in list_fichiers:
|
|
# --lire, analyser chaque fichier et peupler la liste
|
|
# initialiser un _Fichier
|
|
ce_Fichier = _Fichier()
|
|
# lire son chemin
|
|
ce_Fichier.chemin = dossier_courant + "\\" + fichier_courant
|
|
# déterminer son nom original et son extension
|
|
# lire le nom original
|
|
# lire son extension
|
|
ce_Fichier.nom_original, ce_Fichier.extension = fichier_courant.split(".")
|
|
# formatter et écrire le nom
|
|
ce_Fichier.nom = formatter(ce_Fichier.nom_original)
|
|
# déterminer son implication
|
|
ce_Fichier.impliquer()
|
|
# ajouter le _Fichier à la liste
|
|
liste.append(ce_Fichier)
|
|
# Fin Pour
|
|
|
|
# /* Ici la liste des _Fichiers doit être correctement remplie */
|
|
|
|
# initialiser un dictionnaire de _Projets avec comme clé leurs noms respectifs
|
|
print("Init Dictionnaire des Projets\n")
|
|
dico = {}
|
|
# Pour Chaque fichier de la liste Faire
|
|
for fichier_courant in liste:
|
|
if fichier_courant.implication not in "A-ignorer":
|
|
# --associer le fichier courant à un projet du dictionnaire
|
|
# /* Vérifier que le projet existe déjà dans le dico */
|
|
# Si le nom du fichier courant n'est pas dans le dico Alors
|
|
if fichier_courant.nom not in dico:
|
|
# initialiser un _Projet
|
|
projet = _Projet()
|
|
projet.necessaires = {}
|
|
projet.auxiliaires = {}
|
|
projet.non_conformes = {}
|
|
projet.nom = fichier_courant.nom
|
|
print("création clé : ", projet.nom)
|
|
# ajouter le nom comme clé dans le dictionnaire
|
|
# associer le projet à cette clé
|
|
dico[projet.nom] = projet
|
|
# Fin Si
|
|
# cataloguer le fichier dans le projet concerné
|
|
projet.cataloguer(fichier_courant)
|
|
# il y a un fichier de plus au projet
|
|
projet.nb_fichiers += 1
|
|
else:
|
|
# sinon le fichier est a ignorer
|
|
print("Ignoré : ".center(16), fichier_courant.nom_original)
|
|
# Fin Pour
|
|
|
|
# Pour Chaque projet du dictionnaire Faire
|
|
for projet in dico.values():
|
|
# --analyser et completer les infos du projet courant
|
|
# /* conformité, projection, encodage etc */
|
|
# lire la projection du projet courant
|
|
projet.lire_projection()
|
|
# lire l'encodage du projet courant
|
|
projet.lire_encodage()
|
|
# Si tous les fichiers nécessaires sont présents ET
|
|
# la projection est correcte ET
|
|
# l'encodage est correct Alors
|
|
# passer le projet à conforme
|
|
projet.valider()
|
|
# Fin Si
|
|
# Fin Pour
|
|
|
|
# /* Ici le dictionnaire des _Projets doit être correctement remplie */
|
|
|
|
# --produire le rapport
|
|
# créer un classeur Excel
|
|
# TODO pour le moment je vais partir sur un modele deja ecrit que j'ouvre
|
|
# Contrainte : l'opérateur doit placer le modèle dans le même dossier que le script
|
|
classeur = openpyxl.load_workbook("modele_VISA_DORY.xlsx")
|
|
feuille = classeur.active
|
|
|
|
cell = feuille["A2"]
|
|
cell.value = "Dossier : "+ racine.name
|
|
|
|
# Pour chaque projet du dictionnaire Faire
|
|
ligne_courante = 4
|
|
for nom_projet, projet in dico.items():
|
|
# --completer la feuille avec les infos du projet courant
|
|
# /* Note : Chaque ligne de la feuille correspond à un projet */
|
|
# écrire le nom du projet
|
|
cell = feuille["A"+str(ligne_courante)]
|
|
cell.value = nom_projet
|
|
#print(cell.value)
|
|
|
|
# écrire le type d'extention
|
|
cell = feuille["B"+str(ligne_courante)]
|
|
if "shp" in projet.necessaires:
|
|
# /* Note : ce sera "SHP" pour les projets conformes et le type
|
|
# natif pour les non-conformes */
|
|
cell.value = "SHP"
|
|
else:
|
|
for extension in projet.non_conformes:
|
|
cell.value = extension.upper()
|
|
print(cell.value)
|
|
|
|
# écrire le nombre de fichiers du projet
|
|
cell = feuille["C"+str(ligne_courante)]
|
|
cell.value = projet.nb_fichiers
|
|
|
|
# écrire l'encodage
|
|
cell = feuille["D"+str(ligne_courante)]
|
|
cell.value = projet.encodage
|
|
|
|
# écrire la projection
|
|
cell = feuille["E"+str(ligne_courante)]
|
|
cell.value = projet.projection
|
|
|
|
# écrire la validité (conformité)
|
|
cell = feuille["F"+str(ligne_courante)]
|
|
if projet.conformite in "Conforme":
|
|
cell.value = "OK"
|
|
else:
|
|
cell.value = "FAIL"
|
|
|
|
ligne_courante+= 1
|
|
|
|
# encadrer le "tout" pour délimiter le tableau de la feuille
|
|
# Fin Pour
|
|
cell = feuille["B2"]
|
|
cell.value = "Créé : " + date
|
|
|
|
# /* Ici le rapport doit être correctement produit et remplie */
|
|
|
|
|
|
# --produire le dossier de mise à disposition
|
|
# créer un dossier "Travail" et un dossier "Livraison"
|
|
try:
|
|
os.mkdir("Travail")
|
|
except FileExistsError as erreur:
|
|
print(erreur)
|
|
#os.mkdir("Livraison")
|
|
# --créer l'arborescence du dossier "Travail"
|
|
# le dossier Travail devient le working directory
|
|
# créer un dossier "00-Cadastre"
|
|
# On va plutot sauvegarder le classeur dans "Travail" si le dossier existe
|
|
|
|
classeur.save("Travail\\VISA_DORY_PRE.xlsx")
|
|
#print("ligne_courante = ", ligne_courante )
|
|
|
|
|
|
try:
|
|
os.mkdir("Travail\\00-Cadastre")
|
|
except FileExistsError as erreur:
|
|
print(erreur)
|
|
# créer un dossier "01-Donnees_Valides"
|
|
try:
|
|
os.mkdir("Travail\\01-Donnees_Valides")
|
|
except FileExistsError as erreur:
|
|
print(erreur)
|
|
# créer un dossier "02-Donnees_Invalides
|
|
try:
|
|
os.mkdir("Travail\\02-Donnees_Invalides")
|
|
except FileExistsError as erreur:
|
|
print(erreur)
|
|
# créer un dossier "03-Production"
|
|
try:
|
|
os.mkdir("Travail\\03-Production")
|
|
except FileExistsError as erreur:
|
|
print(erreur)
|
|
# créer un dossier "04-Verif_Geometrie"
|
|
try:
|
|
os.mkdir("Travail\\04-Verif_Geometrie")
|
|
except FileExistsError as erreur:
|
|
print(erreur)
|
|
# --peupler le dossier "Travail"
|
|
# Pour Chaque projet du dictionnaire Faire
|
|
dossier_cible = "Travail"
|
|
for projet in dico.values():
|
|
# copier les fichiers des projets conforme dans le dossier "01"
|
|
if projet.conformite in "Conforme":
|
|
for fichier in projet.necessaires.values():
|
|
#print("chemin : ".center(30), fichier.chemin)
|
|
#print("nom : ".center(30), fichier.nom + "." + fichier.extension)
|
|
copyfile(fichier.chemin, "Travail\\01-Donnees_Valides\\"+fichier.nom+"."+fichier.extension)
|
|
for fichier in projet.auxiliaires.values():
|
|
copyfile(fichier.chemin, "Travail\\01-Donnees_Valides\\"+fichier.nom+"."+fichier.extension)
|
|
else:
|
|
for fichier in projet.necessaires.values():
|
|
copyfile(fichier.chemin, "Travail\\02-Donnees_Invalides\\"+fichier.nom+"."+fichier.extension)
|
|
for fichier in projet.auxiliaires.values():
|
|
copyfile(fichier.chemin, "Travail\\02-Donnees_Invalides\\"+fichier.nom+"."+fichier.extension)
|
|
for fichier in projet.non_conformes.values():
|
|
copyfile(fichier.chemin, "Travail\\02-Donnees_Invalides\\"+fichier.nom+"."+fichier.extension)
|
|
|
|
# Fin Pour
|
|
|
|
# /* Ici les dossiers doivent être correctement produit et remplie */
|
|
|
|
# ARRET DU PROGRAMME
|