from datetime import datetime, timedelta import sys from PyQt6.QtWidgets import ( QApplication, QWidget, QVBoxLayout, QDateEdit, QGridLayout, QHBoxLayout, QPushButton, QLabel, QListWidget, QListWidgetItem, QFrame, QFileDialog, QMessageBox ) from database import init_database, get_database, close_database from models.station import Station from models.rental import Rental from peewee import fn, SQL DATA_ROLE = 1001 LABELS = [ "Avg. rent duration when starting there", "Avg. rent duration when stopping there", "Count of different bikes stopped there", "Most popular destination from there" ] class DatabaseViewer(QWidget): def __init__(self): self.value_labels = {} super().__init__() self.init() self.stations = [] def init(self): self.setWindowTitle("Database stats") self.setGeometry(100, 100, 900, 500) mainLayout = QVBoxLayout() topLayout = QHBoxLayout() self.pathLabel = QLabel("Select database file...") self.openButton = QPushButton("Open") self.openButton.clicked.connect(self.open_file_dialog) topLayout.addWidget(self.pathLabel) topLayout.addWidget(self.openButton) middleLayout = QHBoxLayout() self.stationsListWidget = QListWidget() self.stationsListWidget.itemClicked.connect(self.select_station) middleLayout.addWidget(self.stationsListWidget, 2) self.statsFrame = QFrame() statsLayout = QVBoxLayout(self.statsFrame) self.create_stats_widget(statsLayout) middleLayout.addWidget(self.statsFrame, 2) mainLayout.addLayout(topLayout) mainLayout.addLayout(middleLayout) self.setLayout(mainLayout) def reset_ui(self): self.pathLabel.setText("Select database file...") self.stationsListWidget.clear() for label in LABELS: self.value_labels[label].setText("") def select_station(self, item): station_id = item.data(DATA_ROLE) try: avg_time_start = (Rental .select(fn.AVG(Rental.duration)) .where(Rental.rent_station == station_id) .scalar() ) or 0.0 avg_time_stopped = (Rental .select(fn.AVG(Rental.duration)) .where(Rental.ret_station == station_id) .scalar() ) or 0.0 bikes_count = (Rental .select(fn.COUNT(fn.DISTINCT(Rental.bike_id))) .where(Rental.ret_station == station_id) .scalar() ) or 0 (most_popular, count) = (Rental .select(Rental.ret_station, fn.COUNT(Rental.ret_station).alias("count")) .where(Rental.rent_station == station_id) .group_by(Rental.ret_station) .order_by(SQL("count").desc()) .scalar(as_tuple=True) ) or (0,0) most_popular_station = (Station .select() .where(Station.id == most_popular) .get() ) if count != 0 else None self.value_labels[LABELS[0]].setText(f"{avg_time_start:.2f} minutes") self.value_labels[LABELS[1]].setText(f"{avg_time_stopped:.2f} minutes") self.value_labels[LABELS[2]].setText(str(bikes_count)) self.value_labels[LABELS[3]].setText(f"{most_popular_station.name if most_popular_station is not None else '-'} ({count} trips)") except Exception as ex: self.show_error(f"Failed when executing database queries: {str(ex)}") def create_stats_widget(self, layout): gridLayout = QGridLayout() for index, label in enumerate(LABELS): labelWidget = QLabel(f"{label}:") valueLabel = QLabel() gridLayout.addWidget(labelWidget, index, 0) gridLayout.addWidget(valueLabel, index, 1) self.value_labels[label] = valueLabel layout.addLayout(gridLayout) def open_file_dialog(self): if len(self.stations) > 0: self.openButton.setText("Open") self.stations = [] self.reset_ui() try: close_database() except: self.show_error("Failed to close database!") else: fileName, _ = QFileDialog.getOpenFileName( self, "Open Database File", "", "Sqlite Files (*.sqlite3);;All Files (*)", ) if fileName: self.load_database(fileName) def load_database(self, file_path): self.reset_ui() self.openButton.setText("Close") self.pathLabel.setText(file_path) try: init_database(file_path) self.stations = Station.select().order_by(Station.name) self.update_list() except: self.show_error("Failed to load database!") def update_list(self): self.stationsListWidget.clear() for station in self.stations: item = QListWidgetItem(station.name) item.setData(DATA_ROLE, station.id) self.stationsListWidget.addItem(item) def show_error(self, text): message_box = QMessageBox() message_box.setIcon(QMessageBox.Icon.Critical) message_box.setText("Error") message_box.setWindowTitle("Error") message_box.setInformativeText(text) message_box.exec() def main(): app = QApplication(sys.argv) ex = DatabaseViewer() ex.show() sys.exit(app.exec()) if __name__ == "__main__": main()