-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlab3front.py
213 lines (183 loc) · 8.14 KB
/
lab3front.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
'''
CIS 41B Spring 2023
Surajit A Bose
Lab 3 front end
'''
# TODO: Use ttk
import sqlite3
import tkinter as tk
import tkinter.messagebox as tkmb
import webbrowser
from os.path import exists
class DisplayWindow(tk.Toplevel) :
'''
OOP implementation of the tk.Toplevel class.
- Master is MainWindow
- Display restaurant data
- Display clickable URL for restaurant
'''
def __init__(self, master, rest_tup):
'''
Create and display the window of restaurant info
@param master the MainWindow object calling this constructor
@param rest_tup the tuple of restaurant data. Tuple values are in order:
- name
- address
- cuisine
- cost
- url
@return None
'''
super().__init__(master)
self.transient(master)
frame = tk.Frame(self)
for i in range (4):
lab = tk.Label(frame, text = f'{rest_tup[i]}', font =('Helvetica', 14))
# Displaying four labels, labels 0 and 3 in blue, middle two in default color
if i % 3 == 0 :
lab.config(fg = 'blue')
lab.grid()
frame.grid(padx = 20, pady = 20)
tk.Button(self, text = 'Restaurant Website', command = lambda : webbrowser.open(rest_tup[-1])).grid(padx = 5, pady = 10)
class DialogWindow(tk.Toplevel) :
'''
OOP implementation of the tk.Toplevel class.
- Master is MainWindow
- Open a dialog window prompting for user choice
- Return user choice to main window
'''
def __init__(self, master, desired, datab, elems = None) :
'''
Create and display listbox to get user's choice
of city, cuisine, or restaurant
@param master the MainWindow object calling this constructor
@param desired the field from the table(must be city, cuisine, or name)
@param datab the database table (must be Cities, Cuisines, or Restaurants)
@parem elems the list of restaurants meeting criterion of city or cuisine
@return None
'''
super().__init__(master)
self.grab_set()
self.focus_set()
self.transient(master)
self.choice = []
tk.Label(self, text = f'Click on a {desired} to select', font = ('Helvetica', 16), padx = 10, pady = 10).grid()
frame = tk.Frame(self)
sb = tk.Scrollbar(frame, orient = 'vertical')
lb = tk.Listbox(frame, height = 6, yscrollcommand = sb.set)
sb.config(command = lb.yview)
lb.grid(row = 0, column = 0)
sb.grid(row = 0, column = 1, sticky = 'NS')
frame.grid(row = 1, column = 0, padx = 10, pady = 10)
if elems :
self.elems = elems
lb.config(selectmode = 'multiple')
else:
self.elems = []
master.cur.execute(f'SELECT {desired} FROM {datab} ORDER BY {desired}')
for elem in master.cur.fetchall() :
self.elems.append(elem[0])
for elem in self.elems :
lb.insert(tk.END, elem)
tk.Button(self, text = 'Click to select', command = lambda : self.setChoiceAndClose(lb.curselection())).grid(padx = 5, pady = 10)
def setChoiceAndClose(self, indices) :
for ind in indices :
self.choice.append(self.elems[ind])
self.destroy()
@property
def chosen(self):
return self.choice
class MainWindow(tk.Tk) :
'''
OOP implementation of tk.Tk class.
- Create root window for application
- Spawn other windows as needed to display data or get user choice
'''
DEFAULT = 'restaurants.db'
def __init__(self) :
'Instantiate MainWindow object with all necessary widgets'
super().__init__()
# check for existence of database
# TODO: this only checks that file exists, not that it is valid DB
# Note: Could put the whole method in try/except for sqlite3Error
# But that creates a database, opens a window, then fails when user
# clicks on a button. That is bad UX
# Better UX to be non pythonic and use if clause
# TODO: Figure out pythonic way with good UX, e.g., using uri?
if not exists(MainWindow.DEFAULT) :
tkmb.showerror(f'Cannot open {MainWindow.DEFAULT}', parent = self)
raise SystemExit
self.conn = sqlite3.connect(MainWindow.DEFAULT)
self.curr = self.conn.cursor()
self.protocol('WM_DELETE_WINDOW', self.closeout)
self.title('Restaurants')
frame = tk.Frame(self, padx = 20, pady = 20)
frame.grid()
tk.Label(frame, text = 'Local Michelin Guide Restaurants', fg = 'blue', font =('Helvetica', 20, 'bold'), padx = 20, pady = 20 ).grid(row = 0, column = 1, columnspan = 3)
tk.Button(frame, text = 'Search by City', font = ('Helvetica', 20, 'bold'), bg = 'gray', fg = 'black', command = lambda : self.getInitialChoice('city')).grid(row = 3, column = 0, columnspan = 2)
tk.Button(frame, text = 'Search by Cuisine', font = ('Helvetica', 20, 'bold'), bg = 'gray', fg = 'black', command = lambda : self.getInitialChoice('cuisine')).grid(row = 3, column = 2, columnspan = 2)
def getInitialChoice(self, desired) :
'''
Get user's choice of either city or cuisine to list restaurants
Use user's choice to generate list of appropriate restaurants
Pass list on to method to get user's choice of restaurants
'''
datab, field = ('Cities', 'loc') if desired == 'city' else ('Cuisines', 'kind')
dialog = DialogWindow(self, desired, datab)
self.wait_window(dialog)
choice = dialog.chosen
if choice:
rest_list = []
self.curr.execute(f'''SELECT name FROM Restaurants JOIN {datab}
ON Restaurants.{field} = {datab}.id
AND {datab}.{desired} = ?
''', (choice[0],))
for record in self.curr.fetchall() :
rest_list.append(record[0])
self.getRestaurantChoice('name', 'Restaurants', rest_list)
def getRestaurantChoice(self, desired, datab, rest_list) :
'''
Get user's choice of restaurants from those that match criterion of cuisine or city
Pass user's choice on to the method to display card with restaurant deets
@param desired the field from the table(must be name)
@param datab the database table (must be Restaurants)
@parem elems the list of restaurants meeting criterion of city or cuisine
@return None
'''
dialog = DialogWindow(self, desired, datab, rest_list)
self.wait_window(dialog)
chosen_list = dialog.chosen
if chosen_list :
self.displayRestCard(chosen_list)
def displayRestCard(self, rest_list) :
'''
Display all the details of each restaurant chosen by the user
@param rest_list list of restaurants chosen by user
@return None
'''
for rest in rest_list:
self.curr.execute('''SELECT Restaurants.name, Restaurants.addr,
Cuisines.cuisine, Costs.cost,
Restaurants.url
FROM Restaurants JOIN Cuisines JOIN Costs
ON Cuisines.id = Restaurants.kind
AND Costs.id = Restaurants.cost
AND Restaurants.name = ?
''', (rest,))
DisplayWindow(self, self.curr.fetchone())
@property
def cur (self) :
return self.curr
def closeout(self) :
'''
Ask for user confirmation before closing main window.
- If user confirms, close database and quit gracefully
- If user cancels, do nothing
'''
close = tkmb.askokcancel('Confirm close', 'Close all windows and quit?', parent = self)
if close:
self.conn.close()
self.destroy()
self.quit()
if __name__ == '__main__' :
MainWindow().mainloop()