[main] Added book export to xml
This commit is contained in:
parent
50227b9a2b
commit
142b2d449d
0
src/export/__init__.py
Normal file
0
src/export/__init__.py
Normal file
63
src/export/book_exporter.py
Normal file
63
src/export/book_exporter.py
Normal file
@ -0,0 +1,63 @@
|
||||
from typing import Optional
|
||||
import xml.etree.ElementTree as ET
|
||||
from xml.dom import minidom
|
||||
|
||||
from utils.database import DatabaseManager
|
||||
from utils.errors.export_error import ExportError
|
||||
from utils.errors.no_export_entity_error import NoExportEntityError
|
||||
|
||||
from models.book import Book
|
||||
|
||||
class BookExporter():
|
||||
def save_xml(self, file_path: str):
|
||||
|
||||
xml = self._get_full_xml()
|
||||
|
||||
if xml is None:
|
||||
raise NoExportEntityError("No books found to export")
|
||||
|
||||
with open(file_path, "w", encoding="utf-8") as file:
|
||||
file.write(xml)
|
||||
|
||||
|
||||
def _get_full_xml(self) -> Optional[str]:
|
||||
root = ET.Element("books")
|
||||
|
||||
with DatabaseManager().get_session() as session:
|
||||
self.books = session.query(Book).all()
|
||||
|
||||
if not self.books:
|
||||
return None
|
||||
|
||||
for book in self.books:
|
||||
# Create a <book> element
|
||||
book_element = ET.SubElement(root, "book")
|
||||
|
||||
# Add <title>
|
||||
title_element = ET.SubElement(book_element, "title")
|
||||
title_element.text = book.title
|
||||
|
||||
# Add <description>
|
||||
description_element = ET.SubElement(book_element, "description")
|
||||
description_element.text = book.description
|
||||
|
||||
# Add <year_published>
|
||||
year_published_element = ET.SubElement(book_element, "year_published")
|
||||
year_published_element.text = book.year_published
|
||||
|
||||
# Add <isbn>
|
||||
isbn_element = ET.SubElement(book_element, "isbn")
|
||||
isbn_element.text = book.isbn
|
||||
|
||||
# Add <categories>
|
||||
categories_element = ET.SubElement(book_element, "categories")
|
||||
for category in book.categories:
|
||||
category_element = ET.SubElement(categories_element, "category")
|
||||
category_element.text = category.name
|
||||
|
||||
# Convert the tree to a string
|
||||
tree_str = ET.tostring(root, encoding="unicode")
|
||||
|
||||
# Pretty print the XML
|
||||
pretty_xml = minidom.parseString(tree_str).toprettyxml(indent=(" " * 4))
|
||||
return pretty_xml
|
@ -13,18 +13,18 @@ class BookStatusEnum(enum.Enum):
|
||||
|
||||
|
||||
class Book(Base):
|
||||
__tablename__ = 'book'
|
||||
__tablename__ = 'book'
|
||||
__table_args__ = (UniqueConstraint('isbn'),)
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
author_id = Column(Integer, ForeignKey('author.id'), nullable=False)
|
||||
title = Column(String(100), nullable=False)
|
||||
description = Column(Text, nullable=False)
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
author_id = Column(Integer, ForeignKey('author.id'), nullable=False)
|
||||
title = Column(String(100), nullable=False)
|
||||
description = Column(Text, nullable=False)
|
||||
year_published = Column(String(4), nullable=False)
|
||||
isbn = Column(String(13), nullable=False, unique=True)
|
||||
status = Column(Enum(BookStatusEnum), nullable=False, default=BookStatusEnum.available)
|
||||
created_at = Column(TIMESTAMP, nullable=False, server_default=func.now())
|
||||
last_updated = Column(TIMESTAMP, nullable=False, server_default=func.now())
|
||||
isbn = Column(String(13), nullable=False, unique=True)
|
||||
status = Column(Enum(BookStatusEnum), nullable=False, default=BookStatusEnum.available)
|
||||
created_at = Column(TIMESTAMP, nullable=False, server_default=func.now())
|
||||
last_updated = Column(TIMESTAMP, nullable=False, server_default=func.now())
|
||||
|
||||
# Reference 'Author' as a string to avoid direct import
|
||||
author = relationship('Author', back_populates='books')
|
||||
author = relationship('Author', back_populates='books')
|
||||
categories = relationship('BookCategory',secondary='book_category_link',back_populates='books')
|
||||
|
@ -4,13 +4,19 @@ from sqlalchemy.orm import relationship
|
||||
from .base import Base
|
||||
|
||||
class BookCategory(Base):
|
||||
__tablename__ = 'book_category'
|
||||
__tablename__ = 'book_category'
|
||||
__table_args__ = (UniqueConstraint('name'),)
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
parent_category_id = Column(Integer, ForeignKey('book_category.id'), nullable=True)
|
||||
name = Column(String(100), nullable=False)
|
||||
mature_content = Column(Integer, nullable=False, default=0)
|
||||
last_updated = Column(TIMESTAMP, nullable=False, server_default=func.now())
|
||||
name = Column(String(100), nullable=False)
|
||||
mature_content = Column(Integer, nullable=False, default=0)
|
||||
last_updated = Column(TIMESTAMP, nullable=False, server_default=func.now())
|
||||
|
||||
parent_category = relationship('BookCategory', remote_side=[id])
|
||||
|
||||
books = relationship(
|
||||
'Book',
|
||||
secondary='book_category_link', # Junction table
|
||||
back_populates='categories' # For bidirectional relationship
|
||||
)
|
||||
|
@ -11,5 +11,5 @@ class BookCategoryLink(Base):
|
||||
book_id = Column(Integer, ForeignKey('book.id'), primary_key=True)
|
||||
book_category_id = Column(Integer, ForeignKey('book_category.id'), primary_key=True)
|
||||
|
||||
book = relationship('Book', backref='categories')
|
||||
book_category = relationship('BookCategory', backref='books')
|
||||
book = relationship('Book')
|
||||
book_category = relationship('BookCategory')
|
@ -1,6 +1,8 @@
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QTextEdit, QPushButton, QComboBox, QFormLayout
|
||||
)
|
||||
from PySide6.QtGui import QRegularExpressionValidator
|
||||
from PySide6.QtCore import QRegularExpression
|
||||
from models.book import Book, BookStatusEnum
|
||||
|
||||
class BookEditor(QDialog):
|
||||
@ -21,18 +23,30 @@ class BookEditor(QDialog):
|
||||
self.title_input = QLineEdit(self.book.title)
|
||||
form_layout.addRow("Title:", self.title_input)
|
||||
|
||||
full_author_name = f"{self.book.author.first_name} {self.book.author.last_name}"
|
||||
self.author_input = QLineEdit(full_author_name)
|
||||
form_layout.addRow("Author: ", self.author_input)
|
||||
|
||||
# Description field
|
||||
self.description_input = QTextEdit(self.book.description)
|
||||
form_layout.addRow("Description:", self.description_input)
|
||||
|
||||
# Year published field
|
||||
self.year_input = QLineEdit(self.book.year_published)
|
||||
# self.year_input.setValidator
|
||||
form_layout.addRow("Year Published:", self.year_input)
|
||||
|
||||
# ISBN field
|
||||
self.isbn_input = QLineEdit(self.book.isbn)
|
||||
self.isbn_expression = QRegularExpression("\d{10}|\d{13}")
|
||||
self.isbn_validator = QRegularExpressionValidator(self.isbn_expression)
|
||||
self.isbn_input.setValidator(self.isbn_validator)
|
||||
form_layout.addRow("ISBN:", self.isbn_input)
|
||||
|
||||
all_categories = ", ".join(category.name for category in self.book.categories)
|
||||
self.categories_input = QLineEdit(all_categories)
|
||||
form_layout.addRow("Categories: ", self.categories_input)
|
||||
|
||||
# Status dropdown
|
||||
self.status_input = QComboBox()
|
||||
self.status_input.addItems([status.value for status in BookStatusEnum])
|
||||
|
@ -1,6 +1,7 @@
|
||||
from PySide6.QtGui import QGuiApplication, QAction, QIcon
|
||||
from PySide6.QtQml import QQmlApplicationEngine
|
||||
from PySide6 import QtWidgets, QtCore
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
|
||||
from ui.dashboard.dashboard import LibraryDashboard
|
||||
from ui.book_editor.book_editor import BookEditor
|
||||
@ -8,6 +9,10 @@ from ui.member_editor.member_editor import MemberEditor
|
||||
|
||||
from ui.settings import SettingsDialog
|
||||
|
||||
from export.book_exporter import BookExporter
|
||||
|
||||
from utils.errors.export_error import ExportError
|
||||
|
||||
class LibraryWindow(QtWidgets.QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@ -84,10 +89,25 @@ class LibraryWindow(QtWidgets.QMainWindow):
|
||||
import_members_action.triggered.connect(self.import_data)
|
||||
import_submenu.addAction(import_members_action)
|
||||
|
||||
# Export action
|
||||
export_action = QAction("Export overview", self)
|
||||
export_action.triggered.connect(self.export_data)
|
||||
file_menu.addAction(export_action)
|
||||
# Export submenu
|
||||
export_submenu = QtWidgets.QMenu(self)
|
||||
export_submenu.setTitle("Export")
|
||||
file_menu.addMenu(export_submenu)
|
||||
|
||||
# Export overview
|
||||
export_overview_action = QAction("Export overview", self)
|
||||
export_overview_action.triggered.connect(self.export_data)
|
||||
export_submenu.addAction(export_overview_action)
|
||||
|
||||
# Export books
|
||||
export_books_action = QAction("Export books", self)
|
||||
export_books_action.triggered.connect(self.export_books)
|
||||
export_submenu.addAction(export_books_action)
|
||||
|
||||
# Export members
|
||||
export_members_action = QAction("Export members", self)
|
||||
export_members_action.triggered.connect(self.export_data)
|
||||
export_submenu.addAction(export_members_action)
|
||||
|
||||
file_menu.addSeparator()
|
||||
|
||||
@ -117,6 +137,21 @@ class LibraryWindow(QtWidgets.QMainWindow):
|
||||
if dialog.exec() == QtWidgets.QDialog.Accepted:
|
||||
print("Settings were saved.")
|
||||
|
||||
# region Menu Actions
|
||||
|
||||
def export_books(self):
|
||||
try:
|
||||
file_path = QtWidgets.QFileDialog.getSaveFileName(self, "Save book export", "", ".xml;;")
|
||||
|
||||
if file_path[0]:
|
||||
book_exporter = BookExporter()
|
||||
book_exporter.save_xml(file_path[0] + file_path[1])
|
||||
|
||||
except OSError as e:
|
||||
QMessageBox.critical(self, "Error saving file", f"An error occured when saving the exported data: {e}", QMessageBox.StandardButton.Ok, QMessageBox.StandardButton.NoButton)
|
||||
except ExportError as e:
|
||||
QMessageBox.critical(self, "Error exporting books", f"An error occured when exporting books: {e}", QMessageBox.StandardButton.Ok, QMessageBox.StandardButton.NoButton)
|
||||
|
||||
def new_book(self):
|
||||
pass
|
||||
|
||||
@ -130,4 +165,6 @@ class LibraryWindow(QtWidgets.QMainWindow):
|
||||
pass
|
||||
|
||||
def about(self):
|
||||
QtWidgets.QMessageBox.information(self, "About", "Library app demonstrating the phantom read problem")
|
||||
QtWidgets.QMessageBox.information(self, "About", "Library app demonstrating the phantom read problem")
|
||||
|
||||
# endregion
|
5
src/utils/errors/export_error.py
Normal file
5
src/utils/errors/export_error.py
Normal file
@ -0,0 +1,5 @@
|
||||
class ExportError(Exception):
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
self.message = message
|
6
src/utils/errors/no_export_entity_error.py
Normal file
6
src/utils/errors/no_export_entity_error.py
Normal file
@ -0,0 +1,6 @@
|
||||
from .export_error import ExportError
|
||||
class NoExportEntityError(ExportError):
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
self.message = message
|
Loading…
x
Reference in New Issue
Block a user