From 06dcb0763324a07663ba651fbdcaf6ff861baa76 Mon Sep 17 00:00:00 2001 From: Klaynight <63601267+Klaynight-dev@users.noreply.github.com> Date: Thu, 16 May 2024 09:36:55 +0200 Subject: [PATCH 1/3] Logs update - Update logs in one row instead of three rows - Added a cache removal function in the "option" menu (removes all log files) --- README.md | 72 ++--- content/css/style.css | 68 ---- library_logic.py | 11 +- logs.py | 49 ++- main.py | 720 ++++++++++-------------------------------- requirements.txt | 3 +- 6 files changed, 234 insertions(+), 689 deletions(-) diff --git a/README.md b/README.md index d139903..ce73397 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,36 @@ -# Gestion de Bibliothèque - -Ce projet est une application de gestion de bibliothèque réalisée en Python, exploitant la bibliothèque Tkinter pour son interface graphique. - -## Fonctionnalités - -- **Ajout de Livres** : Permet d'ajouter de nouveaux livres à la bibliothèque en spécifiant le titre, l'auteur, l'ISBN et le nombre de copies. -- **Recherche de Livres** : Permet de rechercher des livres par titre, auteur, ISBN ou nombre d'exemplaires disponibles. -- **Emprunt et Retour de Livres** : Permet aux utilisateurs d'emprunter et de retourner des livres. Les exemplaires disponibles sont automatiquement mis à jour. -- **Suppression de Livres** : Possibilité de supprimer des livres de la bibliothèque. -- **Importation/Exportation depuis/vers CSV** : Permet d'importer et d'exporter les données de la bibliothèque au format CSV. - -## Comment Utiliser - -1. Assurez-vous d'avoir Python installé sur votre système. -2. Installez les dépendances en exécutant `pip install -r requirements.txt`. -3. Exécutez le fichier `main.py` pour lancer l'application. -4. Utilisez l'interface graphique pour ajouter, rechercher, emprunter, retourner ou supprimer des livres. - -## Structure du Projet - -- **`gui.py`** : Fichier principal de l'interface graphique de l'application. -- **`library_logic.py`** : Contient les classes `Library` et `Book` pour la logique de gestion de la bibliothèque. -- **`logs.py`** : Contient des fonctions utilitaires de log. - -## Contributeurs - -- [Klaynight-dev](https://github.com/klaynight-dev) - Responsable du développement - -## Contributions - -Les contributions sont les bienvenues ! Pour des suggestions, des problèmes ou des améliorations, veuillez ouvrir une issue ou une pull request. - -## Licence - -Ce projet est sous licence GPL 3.0. Veuillez consulter le fichier [LICENSE](Licence) pour plus de détails. +# Gestion de Bibliothèque + +Ce projet est une application de gestion de bibliothèque réalisée en Python, exploitant la bibliothèque Tkinter pour son interface graphique. + +## Fonctionnalités + +- **Ajout de Livres** : Permet d'ajouter de nouveaux livres à la bibliothèque en spécifiant le titre, l'auteur, l'ISBN et le nombre de copies. +- **Recherche de Livres** : Permet de rechercher des livres par titre, auteur, ISBN ou nombre d'exemplaires disponibles. +- **Emprunt et Retour de Livres** : Permet aux utilisateurs d'emprunter et de retourner des livres. Les exemplaires disponibles sont automatiquement mis à jour. +- **Suppression de Livres** : Possibilité de supprimer des livres de la bibliothèque. +- **Importation/Exportation depuis/vers CSV** : Permet d'importer et d'exporter les données de la bibliothèque au format CSV. + +## Comment Utiliser + +1. Assurez-vous d'avoir Python installé sur votre système. +2. Installez les dépendances en exécutant `pip install -r requirements.txt`. +3. Exécutez le fichier `main.py` pour lancer l'application. +4. Utilisez l'interface graphique pour ajouter, rechercher, emprunter, retourner ou supprimer des livres. + +## Structure du Projet + +- **`gui.py`** : Fichier principal de l'interface graphique de l'application. +- **`library_logic.py`** : Contient les classes `Library` et `Book` pour la logique de gestion de la bibliothèque. +- **`logs.py`** : Contient des fonctions utilitaires de log. + +## Contributeurs + +- [Klaynight-dev](https://github.com/klaynight-dev) - Responsable du développement + +## Contributions + +Les contributions sont les bienvenues ! Pour des suggestions, des problèmes ou des améliorations, veuillez ouvrir une issue ou une pull request. + +## Licence + +Ce projet est sous licence MIT. Veuillez consulter le fichier [LICENSE](Licence) pour plus de détails. diff --git a/content/css/style.css b/content/css/style.css index 4de8cab..683c0ac 100644 --- a/content/css/style.css +++ b/content/css/style.css @@ -76,71 +76,3 @@ QTreeWidget QAbstractItemView QLineEdit { background-color: #ffffff; } - -QTabWidget::pane { - border: 1px solid lightgray; - top: -1px; - background: rgb(245, 245, 245); -} - -QTabBar::tab { - background: rgb(210, 210, 210); - border: 1px solid lightgray; - padding-left: 10px; - padding-right: 10px; - padding-top: 5px; - padding-bottom: 5px; -} - -QTabBar::tab:selected { - background: rgb(210, 250, 255); - margin-bottom: -1px; - border-top-left-radius: 10px; - border-top-right-radius: 10px; -} - -QTabBar::tab:hover { - background: rgb(225, 225, 225) -} - -QScrollArea { - border: none; -} - -QScrollBar { - background: rgb(245, 245, 245); - border-radius: 8px; -} - -QScrollBar:horizontal { - height: 8px; -} - -QScrollBar:vertical { - width: 8px; -} - -QScrollBar::handle { - background: rgb(138, 138, 138); - border-radius: 5px; -} - -QScrollBar::handle:horizontal { - height: 25px; - min-width: 10px; -} - -QScrollBar::handle:vertical { - width: 25px; - min-height: 10px; -} - -QScrollBar::add-line { - border: none; - background: none; -} - -QScrollBar::sub-line { - border: none; - background: none; -} diff --git a/library_logic.py b/library_logic.py index 07fb8ed..32dcdda 100644 --- a/library_logic.py +++ b/library_logic.py @@ -67,23 +67,20 @@ def update_book_details(self, book): break # Sortir de la boucle une fois le livre mis à jour # Méthode pour afficher les livres en fonction de différents critères de recherche - def display_books(self, query=None, by_isbn=False, by_author=False, by_title=False, by_total_copies=False, by_available_copies=False, by_publisher=False, by_ID=False): + def display_books(self, query=None, by_isbn=False, by_author=False, by_title=False, by_copies=False, by_publisher=False): if query: filtered_books = [] for book in self.books: if (by_isbn and query.lower() in book.isbn.lower()) or \ (by_author and query.lower() in book.author.lower()) or \ (by_title and query.lower() in book.title.lower()) or \ - (by_available_copies and query.lower() in str(book.available_copies).lower()) or \ - (by_publisher and query.lower() in book.publisher.lower()) or \ - (by_total_copies and query.lower() in str(book.total_copies).lower()) or \ - (by_ID and query.lower() in str(book.book_id).lower()): + (by_copies and query.lower() in str(book.available_copies).lower()) or \ + (by_publisher and query.lower() in book.publisher.lower()): # Recherche par Publisher filtered_books.append(book) return filtered_books else: return self.books - # Méthode pour importer des livres à partir d'un fichier CSV def import_from_csv(self, file_path): try: @@ -118,7 +115,7 @@ def import_from_csv(self, file_path): # Méthode pour obtenir un livre par son ID def get_book_by_id(self, book_id): for book in self.books: - if book.book_id == int(book_id): + if book.book_id == book_id: return book log_action(f"Aucun livre trouvé avec l'ID={book_id}", success=False) return None # Si aucun livre correspondant à l'ID n'est trouvé diff --git a/logs.py b/logs.py index f3584f6..cf2b93c 100644 --- a/logs.py +++ b/logs.py @@ -1,25 +1,24 @@ -# logs.py - -import os -from datetime import datetime - -def create_log_directory(): - log_directory = 'data/logs/' - if not os.path.exists(log_directory): - os.makedirs(log_directory) - -def get_log_filename(): - now = datetime.now() - log_directory = 'data/logs/' - return f"{log_directory}{now.strftime('%Y-%m-%d_%H')}.log" - -def log_action(action, success=True, error_message=None): - log_filename = get_log_filename() - with open(log_filename, 'a') as log_file: - log_file.write(f"Timestamp: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") - log_file.write(f"Action: {action}\n") - log_file.write(f"Success: {'Yes' if success else 'No'}\n") - if not success and error_message: - log_file.write(f"Error: {error_message}\n") - log_file.write("\n") - +# logs.py + +import os +from datetime import datetime + +def create_log_directory(): + log_directory = 'data/logs/' + if not os.path.exists(log_directory): + os.makedirs(log_directory) + +def get_log_filename(): + now = datetime.now() + log_directory = 'data/logs/' + return f"{log_directory}{now.strftime('%Y-%m-%d_%H')}_log.txt" + +def log_action(action, success=True, error_message=None): + log_filename = get_log_filename() + with open(log_filename, 'a') as log_file: + log_file.write(f"Timestamp: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") + log_file.write(f"Action: {action}\n") + log_file.write(f"Success: {'Yes' if success else 'No'}\n") + if not success and error_message: + log_file.write(f"Error: {error_message}\n") + log_file.write("\n") diff --git a/main.py b/main.py index dc25df2..b14cccd 100644 --- a/main.py +++ b/main.py @@ -13,22 +13,14 @@ QApplication, QMainWindow, QLabel, QLineEdit, QPushButton, QVBoxLayout, QHBoxLayout, QWidget, QComboBox, QMessageBox, QFileDialog, QTreeWidget, QTreeWidgetItem, QTableWidgetItem, QHeaderView, QDialog, QAbstractItemView, - QMenu, QCheckBox, QInputDialog, QTabWidget, QSizePolicy, QAction, QTextBrowser) -log_action(f"Importation de la biblioteque 'PyQt5.QtWidgets' avec comme fonction 'QApplication, QMainWindow, QLabel, QLineEdit, QPushButton, QVBoxLayout,QHBoxLayout, QWidget, QComboBox, QMessageBox, QFileDialog, QTreeWidget,QTreeWidgetItem, QTableWidgetItem, QHeaderView, QDialog, QAbstractItemView, QMenu, QCheckBox, QInputDialog, QTabWidget, QSizePolicy, QAction, QTextBrowser'", success=True) + QMenu, QCheckBox, QInputDialog, QTabWidget, QSizePolicy) +log_action(f"Importation de la biblioteque 'PyQt5.QtWidgets' avec comme fonction 'QApplication, QMainWindow, QLabel, QLineEdit, QPushButton, QVBoxLayout,QHBoxLayout, QWidget, QComboBox, QMessageBox, QFileDialog, QTreeWidget,QTreeWidgetItem, QTableWidgetItem, QHeaderView, QDialog, QAbstractItemView, QMenu, QCheckBox, QInputDialog, QTabWidget, QSizePolicy'", success=True) from PyQt5.QtGui import (QPixmap, QIcon) log_action(f"Importation de la biblioteque 'PyQt5.QtGui' avec comme fonction 'QPixmap, QIcon'", success=True) from PyQt5.QtCore import Qt, QSettings log_action(f"Importation de la biblioteque 'PyQt5.QtCore' avec comme fonction 'Qt, QSettings'", success=True) -from PyQt5.QtPrintSupport import QPrinter, QPrintDialog -log_action(f"Importation de la biblioteque 'PyQt5.QtPrintSupport' avec comme fonction 'QPrinter, QPrintDialog'", success=True) -from reportlab.pdfgen import canvas -log_action(f"Importation de la biblioteque 'reportlab.pdfgen' avec comme fonction 'canvas'", success=True) -from reportlab.lib.pagesizes import letter -log_action(f"Importation de la biblioteque 'reportlab.lib.pagesizes' avec comme fonction 'letter'", success=True) from library_logic import * log_action(f"Importation du fichier 'library_logic.py'", success=True) -from user_logic import * -log_action(f"Importation du fichier 'user_logic.py'", success=True) from dialog_logic import * log_action(f"Importation du fichier 'dialog_logic.py'", success=True) @@ -43,14 +35,13 @@ def __init__(self): # Set window properties self.setWindowTitle("Jobi - Gestionnaire de bibliothèque") self.setGeometry(100, 100, 800, 500) - self.setWindowIcon(QIcon('icon.png')) # Create a central widget to hold the layout self.central_widget = QWidget() self.setCentralWidget(self.central_widget) # Apply CSS styles to certain components - self.setStyleSheet(load_stylesheet('content\\css\\style.css')) + self.setStyleSheet(load_stylesheet('content\css\style.css')) # Create a main layout for the central widget self.layout = QVBoxLayout(self.central_widget) @@ -59,9 +50,9 @@ def __init__(self): current_dir = os.path.dirname(os.path.realpath(__file__)) self.file_path = None - self.file_path_book = None - self.file_path_user = None - self.file_path_take = None + + # Set the application icon in the taskbar + self.setWindowIcon(QIcon('icon.png')) # Display the logo in the banner self.logo_label = QLabel(self) @@ -71,40 +62,45 @@ def __init__(self): self.settings = QSettings("Jobi", "gestion_library_app") self.library = Library() - self.user_gestion = User_gestion() # Create a tab widget without specifying geometry self.tab_widget = QTabWidget(self.central_widget) + + # Set the size policy to Expanding self.tab_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) # Books Tab self.books_tab = QWidget() + + # Set up the book management components in the books tab books_layout = QVBoxLayout() + self.setup_search_section() - self.setup_table("book_table",7,["ID", "Titre", "Auteur", "Maison d'édition", "ISBN", "Exemplaires", "Exemplaires Disponibles"]) + self.setup_book_table() + self.book_table.itemChanged.connect(self.update_book_info) + books_layout.addWidget(self.book_table) + + # Add more book management-related components if needed + self.books_tab.setLayout(books_layout) self.tab_widget.addTab(self.books_tab, "Gestion des Livres") # Users Tab self.users_tab = QWidget() user_layout = QVBoxLayout() - self.setup_user_tab() - self.user_table.itemChanged.connect(self.update_user_info) + + # Add user-specific widgets and functionality here self.users_tab.setLayout(user_layout) self.tab_widget.addTab(self.users_tab, "Gestion des Utilisateurs") - # Take Tab - self.take_tab = QWidget() - take_layout = QVBoxLayout() - self.setup_take_tab() - self.take_tab.setLayout(take_layout) - self.tab_widget.addTab(self.take_tab, "Gestion des Empreints") - # Set up stretch factors to prioritize the content in the layout self.layout.addWidget(self.tab_widget, stretch=1) + # Add the borrow/return/remove sections directly to the main layout + self.layout.addStretch(1) + # Load saved CSV data self.load_saved_csv() @@ -114,171 +110,6 @@ def __init__(self): # Initialize bottom_layout as an instance attribute self.bottom_layout = None - self.tab_widget.currentChanged.connect(self.onTabChanged) - - self.create_menu() - - #Zone de text pour n'import quelle fonctionnalisté -# self.text_browser = QTextBrowser(self) -# books_layout.addWidget(self.text_browser) - - def toggle_fullscreen(self): - if self.isFullScreen(): - self.showNormal() - else: - self.showFullScreen() - - def create_menu(self): - # Menu Fichier - file_menu = self.menuBar().addMenu("Fichier") - - open_action = QAction(QIcon(), "Importer", self) - open_action.setShortcut("Ctrl+O") - open_action.triggered.connect(self.import_from_csv) - log_action("Importation d'un fichier depuis le menu fichier") - file_menu.addAction(open_action) - -# save_action = QAction(QIcon(), "Enregistrer", self) -# save_action.setShortcut("Ctrl+S") -# save_action.triggered.connect(self.) -# file_menu.addAction(save_action) - - save_as_action = QAction(QIcon(), "Enregistrer sous", self) - save_as_action.setShortcut("Ctrl+Shift+S") - save_as_action.triggered.connect(self.export_to_csv) - log_action("Exportation du fichier depuis le menu fichier") - file_menu.addAction(save_as_action) - - file_menu.addSeparator() - - print_action = QAction(QIcon(), "Imprimer toute la librairie", self) - print_action.setShortcut("Ctrl+P") - print_action.triggered.connect(self.print_all_files) - log_action("Impression de la librairie depuis le menu fichier") - file_menu.addAction(print_action) - - print_action = QAction(QIcon(), "Imprimer un livre par ID", self) - print_action.setShortcut("Ctrl+Shift+p") - print_action.triggered.connect(self.print_selected_file) - log_action("Impression d'un livre depuis le menu fichier") - file_menu.addAction(print_action) - - file_menu.addSeparator() - - quit_action = QAction(QIcon(), "Quitter", self) - quit_action.setShortcut("Ctrl+Q") - quit_action.triggered.connect(self.close) - log_action("Fermeture de l'application depuis le menu fichier") - file_menu.addAction(quit_action) - - - # Menu Option - file_menu = self.menuBar().addMenu("Option") - - fullscreen = QAction(QIcon(), "Pleine écran", self) - fullscreen.setShortcut("F11") - fullscreen.triggered.connect(self.toggle_fullscreen) - log_action("Mise en pleine écrant/Mise en fenétré") - file_menu.addAction(fullscreen) - - # Menu Édition -# edit_menu = self.menuBar().addMenu("Édition") -# -# undo_action = QAction(QIcon(), "Annuler", self) -# undo_action.setShortcut("Ctrl+Z") -# undo_action.triggered.connect(self.text_edit.undo) -# edit_menu.addAction(undo_action) -# -# redo_action = QAction(QIcon(), "Rétablir", self) -# redo_action.setShortcut("Ctrl+Y") -# redo_action.triggered.connect(self.text_edit.redo) -# edit_menu.addAction(redo_action) -# -# copy_action = QAction(QIcon(), "Copier", self) -# copy_action.setShortcut("Ctrl+C") -# copy_action.triggered.connect(self.text_edit.copy) -# edit_menu.addAction(copy_action) -# -# paste_action = QAction(QIcon(), "Coller", self) -# paste_action.setShortcut("Ctrl+V") -# paste_action.triggered.connect(self.text_edit.paste) -# edit_menu.addAction(paste_action) - - def print_all_files(self): - QMessageBox.information(self, "En développement", "Cette fonctionnalité est en cours de développement") -# if self.tab_widget.currentIndex()==0: -# # Imprimer tous les livres -# self.print_files(self.library.books) - - def print_selected_file(self): - if self.tab_widget.currentIndex()==0: - # Demander à l'utilisateur d'entrer l'ID du livre - book_id, ok = QInputDialog.getText(self, 'Entrer l\'ID du livre', 'ID du livre:') - if ok: - if book_id: - book = self.library.get_book_by_id(book_id) - if book: - self.print_files([book]) - else: - self.text_browser.setText(f"Aucun livre trouvé avec l'ID {book_id}") - else: - self.text_browser.setText("Veuillez spécifier un ID de livre") - - def print_files(self, books): - if self.tab_widget.currentIndex() == 0: - # Demander à l'utilisateur où enregistrer le fichier PDF - file_path, _ = QFileDialog.getSaveFileName(self, 'Enregistrer le PDF', '', 'PDF Files (*.pdf)') - if not file_path: - return # L'utilisateur a annulé la sauvegarde - - try: - # Créer un fichier PDF avec la bibliothèque reportlab - pdf = canvas.Canvas(file_path, pagesize=letter) - pdf.setFont("Helvetica", 12) - - # Imprimer les détails des livres dans le PDF - for book in books: - pdf.drawString(100, 750, f"Book ID: {book.book_id}") - pdf.drawString(100, 730, f"Title: {book.title}") - pdf.drawString(100, 710, f"Author: {book.author}") - pdf.drawString(100, 690, f"ISBN: {book.isbn}") - pdf.drawString(100, 670, f"Total Copies: {book.total_copies}") - pdf.drawString(100, 650, f"Available Copies: {book.available_copies}") - pdf.drawString(100, 630, f"Publisher: {book.publisher}") - pdf.drawString(100, 610, "\n") - - # Sauvegarder et fermer le fichier PDF - pdf.save() - - # Afficher une boîte de dialogue pour indiquer que le PDF a été créé avec succès - QMessageBox.information(self, "PDF Créé", "Le PDF a été créé avec succès.", QMessageBox.Ok) - - # Journaliser l'action - log_action(f"Création du fichier PDF réussie : {file_path}", success=True) - except Exception as e: - # Journaliser l'erreur en cas d'échec - log_action(f"Erreur lors de la création du fichier PDF : {str(e)}", success=False) - - - def onTabChanged(self, index): - if index == 0: - new_text = 'Ajouter un livre' - self.btn_open_add_book_dialog.setText(new_text) - log_action('Changement d\'onglet vers \'Ajouter un livre\'', success=True) - self.setWindowTitle(f"Jobi - {self.file_path_book if self.file_path_book!=None else 'Gestionnaire de bibliothèque'}") - log_action(f"Changement du Windows Title par 'Jobi - {self.file_path_book if self.file_path_book!=None else 'Gestionnaire de bibliothèque'}'") - elif index == 1: - new_text = 'Ajouter un utilisateur' - self.btn_open_add_book_dialog.setText(new_text) - log_action('Changement d\'onglet vers \'Ajouter un utilisateur\'', success=True) - self.setWindowTitle(f"Jobi - {self.file_path_user if self.file_path_user!=None else 'Gestionnaire de bibliothèque'}") - log_action(f"Changement du Windows Title par 'Jobi - {self.file_path_user if self.file_path_user!=None else 'Gestionnaire de bibliothèque'}'") - elif index == 2: - new_text = 'Ajouter un emprunt' - self.btn_open_add_book_dialog.setText(new_text) - log_action('Changement d\'onglet vers \'Ajouter un emprunt\'', success=True) - self.setWindowTitle(f"Jobi - {self.file_path_take if self.file_path_take!=None else 'Gestionnaire de bibliothèque'}") - log_action(f"Changement du Windows Title par 'Jobi - {self.file_path_take if self.file_path_take!=None else 'Gestionnaire de bibliothèque'}'") def closeEvent(self, event): # Sauvegarde du fichier CSV avant de fermer l'application @@ -324,171 +155,59 @@ def setup_search_section(self): self.search_combobox = QComboBox() self.search_combobox.setObjectName("search_combobox") - search_options = ["Titre", "Auteur", "Maison d'édition", "ISBN", "Exemplaires", "Exemplaires Diponibles", "ID"] + search_options = ["Titre", "Auteur", "Maison d'édition", "ISBN", "Exemplaires", "Exemplaires Diponibles"]#, "ID"] self.search_combobox.addItems(search_options) self.search_combobox.currentIndexChanged.connect(self.search_books) search_layout.addWidget(self.search_type_label) search_layout.addWidget(self.search_combobox) self.layout.addLayout(search_layout) - - def setup_table(self, val_name, colomn_nbr, colomn_names): - # Utilisez setattr pour créer dynamiquement un attribut d'instance avec le nom spécifié - setattr(self, val_name, QTreeWidget()) - - # Utilisez l'attribut d'instance pour accéder à votre QTreeWidget - getattr(self, val_name).setColumnCount(colomn_nbr) - getattr(self, val_name).setHeaderLabels(colomn_names) -# getattr(self, val_name).setSortingEnabled(True) # A resoudre pour eviter le mauvais rangement - header = getattr(self, val_name).header() + def setup_book_table(self): + self.book_table = QTreeWidget() + self.book_table.setColumnCount(7) # Il y a 7 colonnes dans votre cas + self.book_table.setHeaderLabels(["ID", "Titre", "Auteur", "Maison d'édition", "ISBN", "Exemplaires", "Exemplaires Disponibles"]) + header = self.book_table.header() header.setSectionResizeMode(QHeaderView.ResizeToContents) - - # Ajoutez le QTreeWidget au layout de votre classe - self.layout.addWidget(getattr(self, val_name)) + self.layout.addWidget(self.book_table) # Rendre tous les éléments éditables dans le tableau - getattr(self, val_name).setEditTriggers(QAbstractItemView.DoubleClicked | QAbstractItemView.SelectedClicked | QAbstractItemView.EditKeyPressed) - + self.book_table.setEditTriggers(QAbstractItemView.DoubleClicked | QAbstractItemView.SelectedClicked | QAbstractItemView.EditKeyPressed) + # Activer la détection du clic droit pour le menu contextuel - getattr(self, val_name).setContextMenuPolicy(Qt.CustomContextMenu) - getattr(self, val_name).customContextMenuRequested.connect(self.show_user_context_menu) + self.book_table.setContextMenuPolicy(Qt.CustomContextMenu) + self.book_table.customContextMenuRequested.connect(self.show_context_menu) + # Détection du double-clic sur l'en-tête de colonne pour le renommage + header = self.book_table.header() + header.sectionDoubleClicked.connect(self.rename_column) + + def setup_books_tab(self): + # Set up the book management components in the books tab + books_layout = QVBoxLayout() - def setup_user_tab(self): - # Set up the user management components in the users tab - user_layout = QVBoxLayout() - - # Set up the user management table and related components in the users tab - self.setup_table("user_table", 6,["ID", "Nom", "Prénom", "Email", "Addresse", "Nombre d'Empreint"]) - self.user_table.itemChanged.connect(self.update_user_info) - - user_layout.addWidget(self.user_table) - - self.users_tab.setLayout(user_layout) - - def show_user_context_menu(self, pos): - menu = QMenu(self) - copy_action = menu.addAction("Copier") - delete_action = menu.addAction("Supprimer") - modify_action = menu.addAction("Modifier") - - action = menu.exec_(self.user_table.mapToGlobal(pos)) - if action == copy_action: - selected_item = self.user_table.currentItem() - if selected_item is not None: - clipboard = QApplication.clipboard() - clipboard.setText(selected_item.text(self.user_table.currentColumn())) - elif action == modify_action: - selected_item = self.user_table.currentItem() - if selected_item is not None: - self.edit_cell(selected_item) - elif action == delete_action: - selected_item = self.user_table.currentItem() - if selected_item is not None: - log_action(f"Tentative de suppression de l'utilisateur avec l'ID={selected_item.text(0)}", success=True) - # Handle deletion confirmation and process similarly to books if needed - - def update_user_info(self): - try: - selected_items = self.user_table.selectedItems() - for item in selected_items: - user_id = int(item.text(0)) - user = self.user_gestion.get_user_by_id(user_id) - - column = self.user_table.currentColumn() - column_name = self.user_table.headerItem().text(column) # Récupération du nom de la colonne - - # Mettre à jour les détails de l'utilisateur selon la colonne - # Example: if column == 1: user.name = new_value - # ... - - # Appel à la méthode de la classe Library pour mettre à jour les détails de l'utilisateur - # self.library.update_user_details(user) - # ... - - # Log action pour indiquer la modification de la colonne - log_action(f"Modification sur la colonne '{column_name}': 'old_value' -> 'new_value'", success=True) - - except Exception as e: - # Enregistrement de l'erreur dans les logs - log_action(f"Erreur lors de la mise à jour des détails de l'utilisateur : {str(e)}", success=False) - print("Une erreur s'est produite :", e) - - def setup_take_table(self): - self.take_table = QTreeWidget() - self.take_table.setColumnCount(3) # Adjust the number of columns as needed - self.take_table.setHeaderLabels(["ID", "ID du livre", "Titre", "Auteur", "ISBN", "ID de l'empreinteur", "Nom de l'empreinteur", "Prénom de l'empreinteur"]) - header = self.take_table.header() - header.setSectionResizeMode(QHeaderView.ResizeToContents) - self.layout.addWidget(self.take_table) + # Set up the book management table and related components in the books tab + self.setup_search_section() + self.setup_book_table() + self.setup_borrow_return_remove_sections() + self.setup_import_export_buttons() + self.book_table.itemChanged.connect(self.update_book_info) - # Rendre tous les éléments éditables dans le tableau - self.take_table.setEditTriggers(QAbstractItemView.DoubleClicked | QAbstractItemView.SelectedClicked | QAbstractItemView.EditKeyPressed) + books_layout.addWidget(self.book_table) - # Activer la détection du clic droit pour le menu contextuel - self.take_table.setContextMenuPolicy(Qt.CustomContextMenu) - self.take_table.customContextMenuRequested.connect(self.show_take_context_menu) + self.books_tab.setLayout(books_layout) - def setup_take_tab(self): + def setup_user_tab(self): # Set up the user management components in the users tab - take_layout = QVBoxLayout() - - # Set up the user management table and related components in the users tab - self.setup_take_table() - self.take_table.itemChanged.connect(self.update_take_info) - - take_layout.addWidget(self.take_table) - - self.take_tab.setLayout(take_layout) - - def show_take_context_menu(self, pos): - menu = QMenu(self) - copy_action = menu.addAction("Copier") - delete_action = menu.addAction("Supprimer") - modify_action = menu.addAction("Modifier") - - action = menu.exec_(self.take_table.mapToGlobal(pos)) - if action == copy_action: - selected_item = self.take_table.currentItem() - if selected_item is not None: - clipboard = QApplication.clipboard() - clipboard.setText(selected_item.text(self.take_table.currentColumn())) - elif action == modify_action: - selected_item = self.take_table.currentItem() - if selected_item is not None: - self.edit_take_cell(selected_item) - elif action == delete_action: - selected_item = self.take_table.currentItem() - if selected_item is not None: - log_action(f"Tentative de suppression de l'empreint avec l'ID={selected_item.text(0)}", success=True) - # Handle deletion confirmation and process similarly to books if needed - - def update_take_info(self): - try: - selected_items = self.take_table.selectedItems() - for item in selected_items: - take_id = int(item.text(0)) - take = self.library.get_take_by_id(take_id) - - column = self.user_table.currentColumn() - column_name = self.user_table.headerItem().text(column) # Récupération du nom de la colonne - - # Mettre à jour les détails de l'utilisateur selon la colonne - # Example: if column == 1: take.name = new_value - # ... - - # Appel à la méthode de la classe Library pour mettre à jour les détails de l'empreint - # self.library.update_take_details(take) - # ... + user_layout = QVBoxLayout() - # Log action pour indiquer la modification de la colonne - log_action(f"Modification sur la colonne '{column_name}': 'old_value' -> 'new_value'", success=True) + # Add user-specific widgets and functionality here + # Example: user_label = QLabel("User ID:") + # user_entry = QLineEdit() + # user_layout.addWidget(user_label) + # user_layout.addWidget(user_entry) - except Exception as e: - # Enregistrement de l'erreur dans les logs - log_action(f"Erreur lors de la mise à jour des détails de l'empreint : {str(e)}", success=False) - print("Une erreur s'est produite :", e) + self.users_tab.setLayout(user_layout) def setup_borrow_return_remove_sections(self): # Add the borrow/return/remove sections directly to the main layout @@ -553,10 +272,6 @@ def setup_import_export_buttons(self): self.btn_import = QPushButton("Importer depuis CSV") self.btn_import.clicked.connect(self.import_from_csv) self.import_export_layout.addWidget(self.btn_import) - -# self.current = QPushButton("onglet") -# self.current.clicked.connect(self.currentonglet) -# self.import_export_layout.addWidget(self.current) self.btn_export = QPushButton("Exporter vers CSV") self.btn_export.clicked.connect(self.export_to_csv) @@ -572,9 +287,7 @@ def setup_import_export_buttons(self): # Ajoute cette méthode dans AddBookDialog def set_library_app_reference(self, library_app): self.library_app = library_app - -# def currentonglet(self): -# print(self.tab_widget.currentIndex()) + # Fonction pour afficher le menu contextuel lors du clic droit def show_context_menu(self, pos): @@ -681,209 +394,125 @@ def update_book_table(self, books=None): # Rendre tous les éléments de cet item éditables for column in range(self.book_table.columnCount()): item.setFlags(item.flags() | Qt.ItemIsEditable) - - def update_user_table(self, users=None): - self.user_table.clear() - if not users: - users = self.user_gestion.display_users() - - for user in users: - item = QTreeWidgetItem(self.user_table) - item.setText(0, str(user.user_id)) - item.setText(1, user.fname) - item.setText(2, user.name) - item.setText(3, str(user.mail)) - item.setText(4, user.take) - - # Rendre tous les éléments de cet item éditables - for column in range(self.user_table.columnCount()): - item.setFlags(item.flags() | Qt.ItemIsEditable) def take_book(self): - current_tab_index = self.tab_widget.currentIndex() - if current_tab_index == 0: - book_id = self.entry_take.text() - if book_id=='': - log_action("Aucune ID de livre n'a été défini !", success=False, error_message="ID non défini") - QMessageBox.warning(self, "ID non défini", "Aucune ID de livre n'a été défini !") - - else: - success, message = self.library.take_book_by_id(book_id) - if success: - # Enregistrement de l'action dans les logs - log_action(f"Emprunt d'un livre avec l'ID={book_id}", success=True) - QMessageBox.information(self, "Emprunt", message) - self.update_book_table() - else: - # Enregistrement de l'erreur dans les logs - log_action(f"Erreur lors de l'emprunt d'un livre avec l'ID={book_id}: {str(message)}", success=False) - QMessageBox.warning(self, "Emprunt impossible", message) - + book_id = self.entry_take.text() + success, message = self.library.take_book_by_id(book_id) + if success: + # Enregistrement de l'action dans les logs + log_action(f"Emprunt d'un livre avec l'ID={book_id}", success=True) + QMessageBox.information(self, "Emprunt", message) + self.update_book_table() + else: + # Enregistrement de l'erreur dans les logs + log_action(f"Erreur lors de l'emprunt d'un livre avec l'ID={book_id}: {str(e)}", success=False) + QMessageBox.warning(self, "Emprunt impossible", message) + def return_book(self): - current_tab_index = self.tab_widget.currentIndex() - if current_tab_index == 0: - book_id = self.entry_return.text() - if book_id=='': - log_action("Aucune ID de livre n'a été défini !", success=False, error_message="ID non défini") - QMessageBox.warning(self, "ID non défini", "Aucune ID de livre n'a été défini !") - - else: - success, message = self.library.return_book_by_id(book_id) - if success: - # Enregistrement de l'action dans les logs - log_action(f"Retour d'un livre avec l'ID={book_id}", success=True) - QMessageBox.information(self, "Retour", message) - self.update_book_table() - else: - # Enregistrement de l'erreur dans les logs - log_action(f"Erreur lors du retour d'un livre avec l'ID={book_id}: {str(message)}", success=False) - QMessageBox.warning(self, "Retour impossible", message) + book_id = self.entry_return.text() + success, message = self.library.return_book_by_id(book_id) + if success: + # Enregistrement de l'action dans les logs + log_action(f"Retour d'un livre avec l'ID={book_id}", success=True) + QMessageBox.information(self, "Retour", message) + self.update_book_table() + else: + # Enregistrement de l'erreur dans les logs + log_action(f"Erreur lors du retour d'un livre avec l'ID={book_id}: {str(e)}", success=False) + QMessageBox.warning(self, "Retour impossible", message) def remove_book(self): - current_tab_index = self.tab_widget.currentIndex() - if current_tab_index == 0: - book_id = self.entry_remove.text() - if book_id=='': - log_action("Aucune ID de livre n'a été défini !", success=False, error_message="ID non défini") - QMessageBox.warning(self, "ID non défini", "Aucune ID de livre n'a été défini !") - + book_id = self.entry_remove.text() + success, message = self.library.remove_book_by_id(book_id) + if success: + # Enregistrement de l'action dans les logs + log_action(f"Suppression d'un livre avec l'ID={book_id}", success=True) + QMessageBox.information(self, "Suppression", message) + self.update_book_table() + else: + # Enregistrement de l'erreur dans les logs + log_action(f"Erreur lors de la suppression d'un livre avec l'ID={book_id}: {str(e)}", success=False) + QMessageBox.warning(self, "Suppression impossible", message) + + def delete_selected_item(self): + selected_item = self.book_table.currentItem() + if selected_item is not None: + book_id = selected_item.text(0) + # Utilisez la fonction remove_book_by_id de la bibliothèque pour supprimer l'élément + success, message = self.library.remove_book_by_id(book_id) + if success: + # Enregistrement de l'action dans les logs + log_action(f"Suppression d'un livre avec l'ID={book_id}", success=True) + QMessageBox.information(self, "Suppression", message) + self.update_book_table() # Mettre à jour l'interface utilisateur après la suppression else: - success, message = self.library.remove_book_by_id(book_id) + # Enregistrement de l'erreur dans les logs + log_action(f"Erreur lors de la suppression d'un livre avec l'ID={book_id}: {message}", success=False) + QMessageBox.warning(self, "Suppression impossible", message) + + def import_from_csv(self): + new_book_id = None # Initialisation de new_book_id en dehors du bloc try + + try: + file_path, _ = QFileDialog.getOpenFileName(self, "Sélectionner un fichier CSV", "", "CSV Files (*.csv)") + self.setWindowTitle(f"Jobi - {file_path}") + log_action(f"Modification de la banniere superieur par : {file_path}", success=False) + if file_path: + success = self.library.import_from_csv(file_path) if success: # Enregistrement de l'action dans les logs - log_action(f"Suppression d'un livre avec l'ID={book_id}", success=True) - QMessageBox.information(self, "Suppression", message) + log_action("Importation réussie depuis un fichier CSV", success=True) + QMessageBox.information(self, "Importation réussie", "Les livres ont été importés avec succès depuis le fichier CSV.") self.update_book_table() else: # Enregistrement de l'erreur dans les logs - log_action(f"Erreur lors de la suppression d'un livre avec l'ID={book_id}: {str(message)}", success=False) - QMessageBox.warning(self, "Suppression impossible", message) - - def delete_selected_item(self): - current_tab_index = self.tab_widget.currentIndex() - if current_tab_index == 0: - selected_item = self.book_table.currentItem() - if selected_item is not None: - book_id = selected_item.text(0) - # Utilisez la fonction remove_book_by_id de la bibliothèque pour supprimer l'élément - success, message = self.library.remove_book_by_id(book_id) - if success: - # Enregistrement de l'action dans les logs - log_action(f"Suppression d'un livre avec l'ID={book_id}", success=True) - QMessageBox.information(self, "Suppression", message) - self.update_book_table() # Mettre à jour l'interface utilisateur après la suppression - else: - # Enregistrement de l'erreur dans les logs - log_action(f"Erreur lors de la suppression d'un livre avec l'ID={book_id}: {message}", success=False) - QMessageBox.warning(self, "Suppression impossible", message) - - def import_from_csv(self): - current_tab_index = self.tab_widget.currentIndex() - if current_tab_index == 0: - new_book_id = None # Initialisation de new_book_id en dehors du bloc try - - try: - file_path, _ = QFileDialog.getOpenFileName(self, "Sélectionner un fichier CSV", "", "CSV Files (*.csv)") - self.setWindowTitle(f"Jobi - {file_path}") - log_action(f"Changement du Windows Title par 'Jobi - {file_path}'") - self.file_path_book = file_path - log_action(f"Modification de la banniere superieur par : {file_path}", success=False) - if file_path: - success = self.library.import_from_csv(file_path) - if success: - # Enregistrement de l'action dans les logs - log_action("Importation réussie depuis un fichier CSV", success=True) - QMessageBox.information(self, "Importation réussie", "Les livres ont été importés avec succès depuis le fichier CSV.") - self.update_book_table() - else: - # Enregistrement de l'erreur dans les logs - log_action("Erreur lors de l'importation depuis un fichier CSV", success=False) - QMessageBox.critical(self, "Erreur d'importation", "Une erreur est survenue lors de l'importation depuis le fichier CSV.") - except Exception as e: - # Enregistrement de l'erreur dans les logs - log_action(f"Erreur lors de l'importation depuis un fichier CSV : {str(e)}", success=False) - # Gérer l'erreur (affichage d'un message à l'utilisateur ou autre) - QMessageBox.critical(self, "Erreur d'importation", "Une erreur est survenue lors de l'importation depuis le fichier CSV.") - - if current_tab_index == 1: - new_user_id = None # Initialisation de new_user_id en dehors du bloc try - - try: - file_path, _ = QFileDialog.getOpenFileName(self, "Sélectionner un fichier CSV", "", "CSV Files (*.csv)") - self.setWindowTitle(f"Jobi - {file_path}") - self.file_path_user = file_path - log_action(f"Modification de la banniere superieur par : {file_path}", success=False) - if file_path: - success = self.user_gestion.import_from_csv(file_path) - if success: - # Enregistrement de l'action dans les logs - log_action("Importation réussie depuis un fichier CSV", success=True) - QMessageBox.information(self, "Importation réussie", "Les utilisateurs ont été importés avec succès depuis le fichier CSV.") - self.update_user_table() - else: - # Enregistrement de l'erreur dans les logs - log_action("Erreur lors de l'importation depuis un fichier CSV", success=False) - QMessageBox.critical(self, "Erreur d'importation", "Une erreur est survenue lors de l'importation depuis le fichier CSV.") - except Exception as e: - # Enregistrement de l'erreur dans les logs - log_action(f"Erreur lors de l'importation depuis un fichier CSV : {str(e)}", success=False) - # Gérer l'erreur (affichage d'un message à l'utilisateur ou autre) - QMessageBox.critical(self, "Erreur d'importation", "Une erreur est survenue lors de l'importation depuis le fichier CSV.") - + log_action("Erreur lors de l'importation depuis un fichier CSV", success=False) + QMessageBox.critical(self, "Erreur d'importation", "Une erreur est survenue lors de l'importation depuis le fichier CSV.") + except Exception as e: + # Enregistrement de l'erreur dans les logs + log_action(f"Erreur lors de l'importation depuis un fichier CSV : {str(e)}", success=False) + # Gérer l'erreur (affichage d'un message à l'utilisateur ou autre) + QMessageBox.critical(self, "Erreur d'importation", "Une erreur est survenue lors de l'importation depuis le fichier CSV.") + def export_to_csv(self): - current_tab_index = self.tab_widget.currentIndex() - try: - if current_tab_index == 0: # Export books - file_path, _ = QFileDialog.getSaveFileName(self, "Enregistrer le fichier CSV", "", "CSV Files (*.csv)") - if file_path: - with open(file_path, 'w', newline='', encoding='utf-8') as file: - writer = csv.writer(file) - writer.writerow(['Title', 'Author', 'Publisher', 'ISBN', 'Total Copies']) - for book in self.library.display_books(): - writer.writerow([book.title, book.author, book.publisher, book.isbn, book.available_copies]) - log_action("Exportation réussie des livres vers un fichier CSV", success=True) - QMessageBox.information(self, "Exportation réussie", "Les données des livres ont été exportées avec succès vers un fichier CSV.") - - elif current_tab_index == 1: # Export users - file_path, _ = QFileDialog.getSaveFileName(self, "Enregistrer le fichier CSV", "", "CSV Files (*.csv)") - if file_path: - with open(file_path, 'w', newline='', encoding='utf-8') as file: - writer = csv.writer(file) - writer.writerow(['Fname', 'Name', 'Email', 'Address', 'Empreint']) - for user in self.user_gestion.display_users(): - writer.writerow([user.fname, user.name, user.mail, user.address, user.take]) - log_action("Exportation réussie des utilisateurs vers un fichier CSV", success=True) - QMessageBox.information(self, "Exportation réussie", "Les données des utilisateurs ont été exportées avec succès vers un fichier CSV.") - + file_path, _ = QFileDialog.getSaveFileName(self, "Enregistrer le fichier CSV", "", "CSV Files (*.csv)") + if file_path: + with open(file_path, 'w', newline='', encoding='utf-8') as file: + writer = csv.writer(file) + writer.writerow(['Title', 'Author', 'Publisher', 'ISBN', 'Total Copies']) + for book in self.library.display_books(): + writer.writerow([book.title, book.author, book.isbn, book.available_copies]) + # Enregistrement de l'action dans les logs + log_action("Exportation réussie vers un fichier CSV", success=True) + QMessageBox.information(self, "Exportation réussie", "Les données ont été exportées avec succès vers un fichier CSV.") except Exception as e: + # Enregistrement de l'erreur dans les logs log_action(f"Erreur lors de l'exportation vers un fichier CSV : {str(e)}", success=False) + # Gérer l'erreur (affichage d'un message à l'utilisateur ou autre) QMessageBox.critical(self, "Erreur d'exportation", f"Une erreur est survenue lors de l'exportation : {str(e)}") - - + def export_save_to_csv(self, file_path, books): - current_tab_index = self.tab_widget.currentIndex() - if current_tab_index == 0: - # Exporter les données des livres dans un fichier CSV - try: - with open(file_path, 'w', newline='', encoding='utf-8') as file: - writer = csv.writer(file) - writer.writerow(['ID', 'Title', 'Author', 'Publisher', 'ISBN', 'Total Copies', 'Available Copies']) - - for book in books: - writer.writerow([ - book.book_id, - book.title, - book.author, - book.publisher, - book.isbn, - book.total_copies, - book.available_copies - ]) - - log_action(f"Sauvegarde réussie vers {file_path}", success=True) - except Exception as e: - log_action(f"Erreur lors de la sauvegarde du fichier : {str(e)}", False, str(e)) + # Exporter les données des livres dans un fichier CSV + try: + with open(file_path, 'w', newline='', encoding='utf-8') as file: + writer = csv.writer(file) + writer.writerow(['ID', 'Title', 'Author', 'Publisher', 'ISBN', 'Total Copies', 'Available Copies']) + + for book in books: + writer.writerow([ + book.book_id, + book.title, + book.author, + book.publisher, + book.isbn, + book.total_copies, + book.available_copies + ]) + + log_action(f"Sauvegarde réussie vers {file_path}", success=True) + except Exception as e: + log_action(f"Erreur lors de la sauvegarde du fichier : {str(e)}", False, str(e)) # Fonction d'importation depuis un fichier CSV save @@ -907,18 +536,9 @@ def import_save_from_csv(self, file_path): # Fonction pour éditer une cellule lors du double-clic def edit_cell(self, item): - current_tab_index = self.tab_widget.currentIndex() - if current_tab_index==0: - current_column = self.book_table.currentColumn() - self.book_table.editItem(item, current_column) - - if current_tab_index==1: - current_column = self.user_table.currentColumn() - self.user_table.editItem(item, current_column) - - if current_tab_index==2: - current_column = self.take_table.currentColumn() - self.take_table.editItem(item, current_column) + current_column = self.book_table.currentColumn() + self.book_table.editItem(item, current_column) + def rename_column(self, column): new_name, ok = QInputDialog.getText(self, "Renommer la colonne", f"Entrez un nouveau nom pour la colonne {column + 1}") @@ -936,24 +556,22 @@ def search_books(self): books = self.library.display_books(query=query, by_title=True) elif search_type == "Auteur": books = self.library.display_books(query=query, by_author=True) - elif search_type == "Exemplaires Disponible": - books = self.library.display_books(query=query, by_available_copies=True) elif search_type == "Exemplaires": - books = self.library.display_books(query=query, by_total_copies=True) - elif search_type == "Maison d'édition": - books = self.library.display_books(query=query, by_publisher=True) - elif search_type == "ID": - books = self.library.display_books(query=query, by_ID=True) - + books = self.library.display_books(query=query, by_copies=True) else: books = self.library.display_books(query=query) self.update_book_table(books) - #"Titre", "Auteur", "Maison d'édition", "ISBN", "Exemplaires", "Exemplaires Diponibles", "ID" + def main(): app = QApplication(sys.argv) + # Définir les variables d'environnement pour gérer l'échelle des périphériques + os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" # Activer le facteur d'échelle par écran contrôlé par le plugin de la plateforme + os.environ["QT_SCREEN_SCALE_FACTORS"] = "1" # Définir les DPI spécifiques pour chaque écran + os.environ["QT_SCALE_FACTOR"] = "1" # Définir le facteur d'échelle global pour l'application + window = LibraryApp() window.load_saved_csv() # Chargement du fichier CSV sauvegardé window.show() diff --git a/requirements.txt b/requirements.txt index 241c21d..c61dbde 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ # requirements.txt -PyQt5==5.15.6 -reportlab==3.5.68 +PyQt5==5.15.4 From 4bae1a09fc3117923e20d1137b88aaea09ceaf15 Mon Sep 17 00:00:00 2001 From: Klaynight <63601267+Klaynight-dev@users.noreply.github.com> Date: Thu, 16 May 2024 09:38:51 +0200 Subject: [PATCH 2/3] Delete README.md --- README.md | 36 ------------------------------------ 1 file changed, 36 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index ce73397..0000000 --- a/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# Gestion de Bibliothèque - -Ce projet est une application de gestion de bibliothèque réalisée en Python, exploitant la bibliothèque Tkinter pour son interface graphique. - -## Fonctionnalités - -- **Ajout de Livres** : Permet d'ajouter de nouveaux livres à la bibliothèque en spécifiant le titre, l'auteur, l'ISBN et le nombre de copies. -- **Recherche de Livres** : Permet de rechercher des livres par titre, auteur, ISBN ou nombre d'exemplaires disponibles. -- **Emprunt et Retour de Livres** : Permet aux utilisateurs d'emprunter et de retourner des livres. Les exemplaires disponibles sont automatiquement mis à jour. -- **Suppression de Livres** : Possibilité de supprimer des livres de la bibliothèque. -- **Importation/Exportation depuis/vers CSV** : Permet d'importer et d'exporter les données de la bibliothèque au format CSV. - -## Comment Utiliser - -1. Assurez-vous d'avoir Python installé sur votre système. -2. Installez les dépendances en exécutant `pip install -r requirements.txt`. -3. Exécutez le fichier `main.py` pour lancer l'application. -4. Utilisez l'interface graphique pour ajouter, rechercher, emprunter, retourner ou supprimer des livres. - -## Structure du Projet - -- **`gui.py`** : Fichier principal de l'interface graphique de l'application. -- **`library_logic.py`** : Contient les classes `Library` et `Book` pour la logique de gestion de la bibliothèque. -- **`logs.py`** : Contient des fonctions utilitaires de log. - -## Contributeurs - -- [Klaynight-dev](https://github.com/klaynight-dev) - Responsable du développement - -## Contributions - -Les contributions sont les bienvenues ! Pour des suggestions, des problèmes ou des améliorations, veuillez ouvrir une issue ou une pull request. - -## Licence - -Ce projet est sous licence MIT. Veuillez consulter le fichier [LICENSE](Licence) pour plus de détails. From 6bc02e02ff74caec3e76ebb1bc53f015076e1190 Mon Sep 17 00:00:00 2001 From: Klaynight <63601267+Klaynight-dev@users.noreply.github.com> Date: Thu, 16 May 2024 09:40:21 +0200 Subject: [PATCH 3/3] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c61dbde..241c21d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ # requirements.txt -PyQt5==5.15.4 +PyQt5==5.15.6 +reportlab==3.5.68