import sys from PyQt5 import QtWidgets as qtw from PyQt5 import QtCore as qtc from PyQt5 import QtGui as qtg from PyQt5 import QtSql as qts """ TODO: - Adding new reviews and new coffees """ class DateDelegate(qtw.QStyledItemDelegate): def createEditor(self, parent, option, proxyModelIndex): # make sure to explicitly set the parent # otherwise it pops up in a top-level window! date_inp = qtw.QDateEdit(parent, calendarPopup=True) return date_inp class CoffeeForm(qtw.QWidget): """Form to display/edit all info about a coffee""" def __init__(self, coffees_model, reviews_model): super().__init__() self.setLayout(qtw.QFormLayout()) # Coffee Fields self.coffee_brand = qtw.QLineEdit() self.layout().addRow('Brand: ', self.coffee_brand) self.coffee_name = qtw.QLineEdit() self.layout().addRow('Name: ', self.coffee_name) self.roast = qtw.QComboBox() self.layout().addRow('Roast: ', self.roast) # Map the coffee fields self.coffees_model = coffees_model self.mapper = qtw.QDataWidgetMapper(self) self.mapper.setModel(self.coffees_model) self.mapper.setItemDelegate( qts.QSqlRelationalDelegate(self)) self.mapper.addMapping( self.coffee_brand, self.coffees_model.fieldIndex('coffee_brand') ) self.mapper.addMapping( self.coffee_name, self.coffees_model.fieldIndex('coffee_name') ) self.mapper.addMapping( self.roast, self.coffees_model.fieldIndex('description') ) # retrieve a model for the roasts and setup the combo box roasts_model = coffees_model.relationModel( self.coffees_model.fieldIndex('description')) self.roast.setModel(roasts_model) self.roast.setModelColumn(1) # Cause data to be written when changed # Reviews self.reviews = qtw.QTableView() self.layout().addRow(self.reviews) self.reviews.setModel(reviews_model) self.reviews.hideColumn(0) self.reviews.hideColumn(1) self.reviews.horizontalHeader().setSectionResizeMode( 4, qtw.QHeaderView.Stretch) # Using a custom delegate self.dateDelegate = DateDelegate() self.reviews.setItemDelegateForColumn( reviews_model.fieldIndex('review_date'), self.dateDelegate) # add and delete reviews self.new_review = qtw.QPushButton( 'New Review', clicked=self.add_review) self.delete_review = qtw.QPushButton( 'Delete Review', clicked=self.delete_review) self.layout().addRow(self.new_review, self.delete_review) def show_coffee(self, coffee_index): self.mapper.setCurrentIndex(coffee_index.row()) # show the reviews id_index = coffee_index.siblingAtColumn(0) self.coffee_id = int(self.coffees_model.data(id_index)) self.reviews.model().setFilter(f'coffee_id = {self.coffee_id}') self.reviews.model().setSort(3, qtc.Qt.DescendingOrder) self.reviews.model().select() self.reviews.resizeRowsToContents() self.reviews.resizeColumnsToContents() def delete_review(self): for index in self.reviews.selectedIndexes() or []: self.reviews.model().removeRow(index.row()) self.reviews.model().select() def add_review(self): reviews_model = self.reviews.model() new_row = reviews_model.record() defaults = { 'coffee_id': self.coffee_id, 'review_date': qtc.QDate.currentDate(), 'reviewer': '', 'review': '' } for field, value in defaults.items(): index = reviews_model.fieldIndex(field) new_row.setValue(index, value) inserted = reviews_model.insertRecord(-1, new_row) if not inserted: error = reviews_model.lastError().text() print(f"Insert Failed: {error}") # Select so the new row is editable reviews_model.select() class MainWindow(qtw.QMainWindow): def __init__(self): """MainWindow constructor. Code in this method should define window properties, create backend resources, etc. """ super().__init__() # Code starts here self.stack = qtw.QStackedWidget() self.setCentralWidget(self.stack) # Connect to the database db = qts.QSqlDatabase.addDatabase('QSQLITE') db.setDatabaseName('coffee.db') if not db.open(): qtw.QMessageBox.critical( None, 'DB Connection Error', 'Could not open database file: ' f'{db.lastError().text()}') sys.exit(1) # Check for missing tables required_tables = {'roasts', 'coffees', 'reviews'} missing_tables = required_tables - set(db.tables()) if missing_tables: qtw.QMessageBox.critical( None, 'DB Integrity Error', 'Missing tables, please repair DB: ' f'{missing_tables}') sys.exit(1) # Create the models self.reviews_model = qts.QSqlTableModel() self.reviews_model.setTable('reviews') self.coffees_model = qts.QSqlRelationalTableModel() self.coffees_model.setTable('coffees') self.coffees_model.setRelation( self.coffees_model.fieldIndex('roast_id'), qts.QSqlRelation('roasts', 'id', 'description') ) self.coffees_model.setEditStrategy(0) self.coffees_model.dataChanged.connect(print) self.coffee_list = qtw.QTableView() self.coffee_list.setModel(self.coffees_model) self.stack.addWidget(self.coffee_list) self.coffees_model.select() #self.show() #return self.show_list() # Inserting and deleting rows. toolbar = self.addToolBar('Controls') toolbar.addAction('Delete Coffee(s)', self.delete_coffee) toolbar.addAction('Add Coffee', self.add_coffee) self.coffee_list.setItemDelegate(qts.QSqlRelationalDelegate()) #self.show() #return # The coffee form self.coffee_form = CoffeeForm( self.coffees_model, self.reviews_model ) self.stack.addWidget(self.coffee_form) self.coffee_list.doubleClicked.connect( self.coffee_form.show_coffee) self.coffee_list.doubleClicked.connect( lambda: self.stack.setCurrentWidget(self.coffee_form)) toolbar.addAction("Back to list", self.show_list) # Code ends here self.show() def delete_coffee(self): selected = self.coffee_list.selectedIndexes() for index in selected or []: self.coffees_model.removeRow(index.row()) self.coffees_model.select() def add_coffee(self): self.stack.setCurrentWidget(self.coffee_list) self.coffees_model.insertRows( self.coffees_model.rowCount(), 1) def show_list(self): self.coffee_list.resizeColumnsToContents() self.coffee_list.resizeRowsToContents() self.stack.setCurrentWidget(self.coffee_list) if __name__ == '__main__': app = qtw.QApplication(sys.argv) # it's required to save a reference to MainWindow. # if it goes out of scope, it will be destroyed. mw = MainWindow() sys.exit(app.exec())