· 6 years ago · Jan 25, 2020, 02:58 PM
1import tkinter as tk
2import tkinter.messagebox
3import csv
4import datetime
5from fpdf import FPDF
6import os
7import webbrowser
8
9# Setting constants used
10TITLE_FONT = ("Segoe UI", 26)
11SUB_TEXT_FONT = ("Segoe UI", 12)
12ADMIN_USERNAME = "admin"
13ADMIN_PASSWORD = "admin"
14POSITIONS = []
15with open("config.txt", "r") as config_file_txt:
16 csv_reader = csv.reader(config_file_txt)
17 for config in csv_reader:
18 if config[0] == "positions":
19 for index, position_name in enumerate(config):
20 if index != 0:
21 POSITIONS.append(position_name)
22
23
24# Function to make PDF file with results
25def make_printout(data, controller):
26 # Check if 'results.pdf' already exists or is open
27 if os.path.isfile("results.pdf"):
28 try:
29 os.remove("results.pdf")
30 except WindowsError:
31 tk.messagebox.showerror("Error", "The file 'results.pdf' is already open, please close it.")
32 return None
33 # Construct PDF
34 pdf = FPDF()
35 pdf.add_page()
36 pdf.set_font("Arial", size=14, style="B")
37 pdf.cell(200, 10, txt="Voting Results", ln=1, align="C")
38 pdf.image("gsu_logo.png", x=10, y=8, w=35)
39 pdf.ln(10)
40 for position in data.items():
41 candidates = position[1]
42 pdf.set_font("Arial", size=14, style="B")
43 pdf.cell(100, 10, txt=position[0], ln=1)
44 pdf.set_font("Arial", size=11)
45 pdf.cell(35, 6, txt="Candidate", align="C")
46 pdf.cell(35, 6, txt="1st preference", align="C")
47 pdf.cell(35, 6, txt="2nd preference", align="C")
48 pdf.cell(35, 6, txt="3rd preference", align="C")
49 pdf.cell(35, 6, txt="4th preference", align="C", ln=1)
50 candidates_sorted = []
51 for candidate in candidates.items():
52 row = [candidate[0], candidate[1][0], candidate[1][1], candidate[1][2], candidate[1][3]]
53 candidates_sorted.append(row)
54 candidates_sorted.sort()
55 for candidate in candidates_sorted:
56 pdf.cell(35, 6, txt=candidate[0], align="C")
57 pdf.cell(35, 6, txt=str(candidate[1]), align="C")
58 pdf.cell(35, 6, txt=str(candidate[2]), align="C")
59 pdf.cell(35, 6, txt=str(candidate[3]), align="C")
60 pdf.cell(35, 6, txt=str(candidate[4]), align="C", ln=1)
61 pdf.ln(5)
62 candidates = controller.votes.get_winner(position[0])
63 pdf.cell(100, 6, txt="Winner: " + candidates[0][4], ln=1)
64 total_votes = 0
65 for vote in candidates:
66 total_votes += vote[0]
67 text = "Votes received: " + str(candidates[0][0]) + " ("\
68 + str(int(round((candidates[0][0] / total_votes) * 100))) + "%)"
69 pdf.cell(100, 6, txt=text, ln=1)
70 pdf.cell(100, 6, txt="Total votes: " + str(total_votes), ln=1)
71 pdf.ln(7)
72
73 pdf.output("results.pdf")
74 webbrowser.open_new("results.pdf")
75
76
77# Function to make a title label
78def title_label(root, text, grid):
79 if grid:
80 return tk.Label(root, text=text, font=TITLE_FONT) \
81 .grid(column=0, row=0, columnspan=2, sticky=tk.W, padx=40, pady=10)
82 return tk.Label(root, text=text, font=TITLE_FONT)
83
84
85# Function to make normal text
86def sub_text(root, text):
87 return tk.Label(root, text=text, font=SUB_TEXT_FONT)
88
89
90class User:
91 def __init__(self, login_id, first_name, last_name):
92 # Basic details about user
93 self.login_id = login_id
94 self.first_name = first_name
95 self.last_name = last_name
96 self.preferences_voted = {}
97 # self.preferences_voted = {
98 # "President": self.is_pref_full("President"),
99 # "GSU Officer": self.is_pref_full("GSU Officer"),
100 # "Faculty Officer": self.is_pref_full("Faculty Officer")
101 # }
102 for position in POSITIONS:
103 self.preferences_voted[position] = self.is_pref_full(position)
104 self.can_vote = self.votes_available()
105 self.position_chosen = ""
106
107 def votes_available(self):
108 # Check if all positions have been fully voted for
109 if not self.preferences_voted["President"][0] and not self.preferences_voted["GSU Officer"][0] \
110 and not self.preferences_voted["Faculty Officer"][0]:
111 return False
112 return True
113
114 def is_pref_full(self, position):
115 preferences_chosen = []
116 with open("Votes.txt", "r") as votes_file:
117 reader = csv.reader(votes_file)
118 for line in reader:
119 # Look for votes for user and position
120 if line[2] == self.login_id and line[5] == position:
121 preferences_chosen.append([line[6], line[3], line[4]])
122 # If 4 votes exists, change can_vote
123 if len(preferences_chosen) == 4:
124 can_vote = False
125 btn_state = "disabled"
126 else:
127 can_vote = True
128 btn_state = "normal"
129
130 return [can_vote, preferences_chosen, btn_state]
131
132
133class Application(tk.Tk):
134 def __init__(self, *args, **kwargs):
135 self.votes = Votes()
136 self.current_user = User("", "", "")
137 tk.Tk.__init__(self, *args, **kwargs)
138 self.title("Voting System")
139 self.position_voting = ""
140 self.position_admining = ""
141 self.position_viewing = ""
142 container = tk.Frame(self)
143 container.pack(expand=True, side="top", fill="both")
144 container.grid_rowconfigure(0, weight=1)
145 container.grid_columnconfigure(0, weight=1)
146 self.frames = {}
147 # Place all frames inside the main tkinter root
148 for x in (LoginFrame, ChoosePositionFrame, CheckDate, VoteFrame, VoteFrame1, VoteFrame2,
149 ManagementInterfaceFrame, ChooseDateFrame, EditPositionsFrame, ViewResultsFrame, ChooseDetailsFrame,
150 ViewDetailsFrame):
151 frame = x(container, self)
152 self.frames[x] = frame
153 frame.grid(column=0, row=0, sticky=tk.NSEW)
154 # Show first frame
155 self.show_frame(LoginFrame)
156
157 def show_frame(self, name):
158 frame = self.frames[name]
159 # Check for frame name to see if function needs to be called before showing frame
160 if name == ChoosePositionFrame:
161 self.geometry("400x610")
162 frame.make_title(self)
163 elif name == CheckDate:
164 if frame.in_date:
165 self.geometry("400x610")
166 frame = self.frames[ChoosePositionFrame]
167 frame.make_title(self)
168 else:
169 self.geometry("400x190")
170 elif name == VoteFrame:
171 frame = self.frames[frame.on_open(self)]
172 self.geometry("400x350")
173 frame.make_ui(self, self.current_user.position_chosen)
174 elif name == ViewDetailsFrame:
175 frame.on_open()
176 elif name == LoginFrame:
177 self.geometry("400x230")
178 elif name == ViewResultsFrame:
179 self.geometry("400x350")
180 elif name == ChooseDetailsFrame:
181 self.geometry("400x570")
182 elif name == ManagementInterfaceFrame:
183 self.geometry("400x600")
184 elif name == EditPositionsFrame:
185 self.geometry("400x370")
186 elif name == ChooseDateFrame:
187 self.geometry("400x200")
188 frame.tkraise()
189
190
191class LoginFrame(tk.Frame):
192 def __init__(self, parent, controller):
193 # Create UI
194 tk.Frame.__init__(self, parent)
195 title_label(self, "Login", True)
196
197 sub_text(self, "Login ID:").grid(padx=42, row=1, column=0, sticky=tk.W)
198 self.login_id_entry = tk.Entry(self, width=32)
199 self.login_id_entry.grid(row=1, column=1, sticky=tk.E)
200
201 sub_text(self, "Password:").grid(padx=42, pady=10, row=2, column=0, sticky=tk.W)
202 self.password_entry = tk.Entry(self, width=32, show="*")
203 self.password_entry.grid(row=2, column=1, sticky=tk.E)
204
205 tk.Button(self, text="Submit", command=lambda: self.check_password(controller), height=2, width=10) \
206 .grid(pady=10, row=3, column=1, sticky=tk.E)
207 tk.Button(self, text="Exit", command=controller.destroy, width=10, height=2) \
208 .grid(pady=10, row=3, column=0, sticky=tk.W, padx=(42, 0))
209
210 def check_password(self, controller):
211 # Check if admin details is entered
212 if self.login_id_entry.get() == ADMIN_USERNAME and self.password_entry.get() == ADMIN_PASSWORD:
213 self.password_entry.delete(0, "end")
214 self.login_id_entry.delete(0, "end")
215 controller.show_frame(ManagementInterfaceFrame)
216 return None
217 # Check for details in 'StudentVoters.txt'
218 with open("StudentVoters.txt", "r", newline="") as student_voters:
219 reader = csv.reader(student_voters)
220 if not self.login_id_entry.get() in [item[0] for item in reader]:
221 tk.messagebox.showerror("Incorrect details", "Your user ID was not found.")
222 return None
223 # Reset file reading position
224 student_voters.seek(0)
225 for user in reader:
226 if user[0] == self.login_id_entry.get() and user[3] == self.password_entry.get():
227 controller.current_user = User(user[0], user[1], user[2])
228 self.password_entry.delete(0, "end")
229 self.login_id_entry.delete(0, "end")
230 controller.show_frame(CheckDate)
231 return None
232 tk.messagebox.showerror("Incorrect details", "The login ID or password you entered is incorrect.")
233
234
235class CheckDate(tk.Frame):
236 def __init__(self, parent, controller):
237 tk.Frame.__init__(self, parent)
238 # Set in_date attribute, which is checked when controller.show_frame is called from any class
239 self.in_date = True
240 # Read config file
241 with open("config.txt", "r") as config_file:
242 reader = csv.reader(config_file)
243 for line in reader:
244 if line[0] == "start_date":
245 self.start_date = datetime.datetime.strptime(line[1], "%d-%m-%Y %H:%M")
246 elif line[0] == "end_date":
247 self.end_date = datetime.datetime.strptime(line[1], "%d-%m-%Y %H:%M")
248 # Compare current date to dates set in config
249 self.current_date = datetime.datetime.now()
250 if self.end_date < self.current_date:
251 title_label(self, "Voting session ended", True)
252 tk.Label(self, text="Voting ended on " + self.end_date.strftime("%d/%m/%Y") + ".", width=32, anchor=tk.W,
253 font=SUB_TEXT_FONT) \
254 .grid(padx=42, row=1, column=0, sticky=tk.W)
255 self.in_date = False
256 tk.Button(self, text="View results", command=lambda: controller.show_frame(ViewResultsFrame), height=2,
257 width=10).grid(pady=20, row=2, column=0, sticky=tk.E, padx=42)
258 elif self.start_date >= self.current_date:
259 title_label(self, "Voting session\nnot started yet", True)
260 tk.Label(self, text="Voting will start from " + self.start_date.strftime("%d/%m/%Y") + ".", width=29,
261 anchor=tk.W, font=SUB_TEXT_FONT) \
262 .grid(padx=42, row=1, column=0)
263 self.in_date = False
264
265 tk.Button(self, text="Back", command=lambda: controller.show_frame(LoginFrame), height=2, width=10) \
266 .grid(pady=20, row=2, column=0, sticky=tk.W, padx=42)
267
268
269class EditPositionsFrame(tk.Frame):
270 def __init__(self, parent, controller):
271 tk.Frame.__init__(self, parent)
272 title_label(self, "Edit candidates", True)
273 tk.Label(self, text="Enter 4 new candidates:", width=34, anchor=tk.W, font=SUB_TEXT_FONT) \
274 .grid(row=1, column=0, sticky=tk.W, padx=(42, 0))
275 tk.Label(self, text="WARNING: This will erase all existing candidates", fg="red") \
276 .grid(row=2, column=0, padx=(42, 0), sticky=tk.W)
277 # Generate 4 entry boxes for candidates' names
278 self.entries = []
279 for i in range(4):
280 self.entries.append(tk.Entry(self, width=30))
281 self.entries[i].grid(row=i + 3, column=0, sticky=tk.W, padx=(42, 0), pady=10)
282 tk.Button(self, text="Back", width=10, height=2,
283 command=lambda: controller.show_frame(ManagementInterfaceFrame))\
284 .grid(row=7, column=0, sticky=tk.W, pady=10, padx=(42, 0))
285 tk.Button(self, text="Submit", width=10, height=2, command=lambda: self.submit_candidates(controller))\
286 .grid(row=7, column=0, sticky=tk.E, pady=10)
287
288 def submit_candidates(self, controller):
289 new_candidates = []
290 position = controller.position_admining
291 positions_entered = []
292 # Check input data and extract information
293 for entry in self.entries:
294 positions_entered.append(entry.get())
295 if entry.get().strip() == "":
296 tk.messagebox.showerror("Error", "Please enter a value in all 4 entries")
297 return None
298 if len(entry.get().strip().split()) != 2:
299 tk.messagebox.showerror("Error", "Please enter two names in each field")
300 return None
301 first_name = entry.get().strip().split()[0]
302 last_name = entry.get().strip().split()[1]
303 new_candidates.append([position, first_name, last_name])
304 # Check for duplicates in entry boxes
305 if len(new_candidates) != len([list(i) for i in set(map(tuple, new_candidates))]):
306 tk.messagebox.showerror("Error", "Please ensure there are no duplicates")
307 return None
308 # Check if candidate already existing for another position
309 with open("GSUCandidates.txt", "r") as cand_file:
310 reader = csv.reader(cand_file)
311 for candidate in reader:
312 if candidate[0] != controller.position_admining:
313 if candidate[1] + " " + candidate[2] in positions_entered:
314 tk.messagebox.showerror("Error", "Candidate already exists for a different position.")
315 return None
316 new_candidates.append(candidate)
317 # Write candidates into file
318 with open("GSUCandidates.txt", "w", newline="") as cand_file:
319 writer = csv.writer(cand_file)
320 for candidate in new_candidates:
321 writer.writerow(candidate)
322 for entry in self.entries:
323 entry.delete(0, "end")
324 # Remove votes for position editing
325 votes = []
326 with open("Votes.txt", "r") as cand_file:
327 reader = csv.reader(cand_file)
328 for vote in reader:
329 if vote[5] != position:
330 votes.append(vote)
331 with open("Votes.txt", "w", newline="") as cand_file:
332 writer = csv.writer(cand_file)
333 for vote in votes:
334 writer.writerow(vote)
335 controller.show_frame(ManagementInterfaceFrame)
336
337
338class ManagementInterfaceFrame(tk.Frame):
339 def __init__(self, parent, controller):
340 tk.Frame.__init__(self, parent)
341 title_label(self, "Management", True)
342 tk.Label(self, text="Choose a position to edit:", width=35, anchor=tk.W, font=SUB_TEXT_FONT) \
343 .grid(row=1, column=0, sticky=tk.W, padx=(42, 0))
344 buttons = []
345 # Create button for every position
346 for x, name in enumerate(POSITIONS):
347 buttons.append(tk.Button(self, text=name, height=2, width=23,
348 command=lambda i=name: self.choose_position(i, controller)))
349 buttons[x].grid(row=x + 2, column=0, pady=10, padx=(42, 0))
350 tk.Button(self, text="Edit voting dates", width=23, height=2,
351 command=lambda: controller.show_frame(ChooseDateFrame)).grid(row=len(POSITIONS) + 2, column=0,
352 pady=10, padx=(42, 0))
353 tk.Button(self, text="Back", width=10, height=2, command=lambda: controller.show_frame(LoginFrame)).grid(
354 pady=10, row=len(POSITIONS) + 3, column=0, sticky=tk.W, padx=42)
355
356 def choose_position(self, position, controller):
357 # Set controller attribute for next class to find
358 controller.position_admining = position
359 controller.show_frame(EditPositionsFrame)
360
361
362class ChooseDateFrame(tk.Frame):
363 def __init__(self, parent, controller):
364 tk.Frame.__init__(self, parent)
365 tk.Label(self, text="Choose dates", width=13, anchor=tk.W, font=TITLE_FONT)\
366 .grid(padx=42, row=0, column=0, sticky=tk.W, columnspan=2)
367 tk.Label(self, text="Start date:", font=SUB_TEXT_FONT)\
368 .grid(row=1, column=0, sticky=tk.W, padx=(42, 0))
369 tk.Label(self, text="End date:", font=SUB_TEXT_FONT)\
370 .grid(row=2, column=0, sticky=tk.W, padx=(42, 0))
371 # Get dates from config file and put values in entries
372 with open("config.txt", "r") as config_file:
373 reader = csv.reader(config_file)
374 for line in reader:
375 if line[0] == "start_date":
376 self.start_date = datetime.datetime.strptime(line[1], "%d-%m-%Y %H:%M")
377 self.start_date = datetime.datetime.strftime(self.start_date, "%d-%m-%Y %H:%M")
378 elif line[0] == "end_date":
379 self.end_date = datetime.datetime.strptime(line[1], "%d-%m-%Y %H:%M")
380 self.end_date = datetime.datetime.strftime(self.end_date, "%d-%m-%Y %H:%M")
381 self.start_date_entry = tk.Entry(self)
382 self.start_date_entry.insert(0, self.start_date)
383 self.start_date_entry.grid(row=1, column=1, sticky=tk.EW, pady=5)
384 self.end_date_entry = tk.Entry(self)
385 self.end_date_entry.insert(0, self.end_date)
386 self.end_date_entry.grid(row=2, column=1, sticky=tk.EW, pady=5)
387 tk.Button(self, text="Back", width=10, height=2,
388 command=lambda: controller.show_frame(ManagementInterfaceFrame)) \
389 .grid(row=4, column=0, sticky=tk.W, padx=(42, 0), pady=10)
390 tk.Button(self, text="Submit", width=10, height=2, command=lambda: self.change_date(controller)) \
391 .grid(row=4, column=1, sticky=tk.E, pady=10)
392
393 def change_date(self, controller):
394 # Check format of entry
395 try:
396 datetime.datetime.strptime(self.end_date_entry.get(), "%d-%m-%Y %H:%M")
397 datetime.datetime.strptime(self.start_date_entry.get(), "%d-%m-%Y %H:%M")
398 except ValueError:
399 tk.messagebox.showerror("Error",
400 "Please enter the date and time in the correct format (DD-MM-YYYY HH: MM)")
401 return None
402 # Write dates into config file
403 with open("config.txt", "w", newline="") as config_file:
404 writer = csv.writer(config_file)
405 writer.writerow(["start_date", self.start_date_entry.get()])
406 writer.writerow(["end_date", self.end_date_entry.get()])
407 controller.show_frame(ManagementInterfaceFrame)
408
409
410def make_choices(controller, position):
411 with open("GSUCandidates.txt", "r") as cand_file:
412 reader = csv.reader(cand_file)
413 candidates_voted = controller.current_user.preferences_voted[position]
414 candidates_voted_names = []
415 for candidate in candidates_voted[1]:
416 candidates_voted_names.append(candidate[1] + " " + candidate[2])
417 candidates_available = []
418
419 for candidate in reader:
420 if candidate[0] == position:
421 if not str(candidate[1] + " " + candidate[2]) in candidates_voted_names:
422 candidates_available.append([str(candidate[1] + " " + candidate[2]), [candidate[1], candidate[2]]])
423
424 return candidates_available
425
426
427# Function for first vote
428def cast_vote_1(controller, name, preference, position):
429 cast_vote(controller, name, preference, position)
430 controller.current_user.position_chosen = position
431 controller.show_frame(VoteFrame)
432
433
434# Function to handle 2nd, 3rd and 4th vote
435def cast_vote_2(controller, choice_boxes, position):
436 if len([item[0].get() for item in choice_boxes]) != len(set([item[0].get() for item in choice_boxes])):
437 tk.messagebox.showerror("Invalid options", "You have selected a candidate more than once.")
438 return None
439 for i, choice_box in enumerate(choice_boxes):
440 if not choice_box[0].get() == "N/A" and not choice_box[0].get()[0] == "(":
441 cast_vote(controller, choice_box[0].get(), str(i + 2), position)
442 controller.show_frame(ChoosePositionFrame)
443
444
445# Function to add individual vote into votes file
446def cast_vote(controller, name, preference, position):
447 first_name = name.split()[0]
448 last_name = name.split()[1]
449 with open("Votes.txt", "a", newline="") as vote_file:
450 csv.writer(vote_file).writerow([controller.current_user.first_name, controller.current_user.last_name,
451 controller.current_user.login_id, first_name, last_name, position, preference])
452 controller.current_user.__init__(controller.current_user.login_id, controller.current_user.first_name,
453 controller.current_user.last_name)
454
455
456class ChoosePositionFrame(tk.Frame):
457 def __init__(self, parent, controller):
458 tk.Frame.__init__(self, parent)
459 self.make_title(controller)
460
461 def make_title(self, controller):
462 subtitle = tk.Label(self, text="You have already voted", font=SUB_TEXT_FONT, width=34, anchor=tk.W)
463 subtitle.grid(padx=42, row=1, column=0, sticky=tk.W)
464 # Destroy title and make new one if title already exists
465 if hasattr(self, "title"):
466 self.title.destroy()
467 self.title = title_label(self, "Hey there " + controller.current_user.first_name + "!", False)
468 self.title.grid(column=0, row=0, columnspan=2, sticky=tk.W, padx=40, pady=10)
469 # Change text if current_user can_vote is true
470 if controller.current_user.can_vote:
471 subtitle.config(text="Choose a position to vote for:")
472 self.draw_buttons(controller)
473
474 def cast_vote(self, position, controller):
475 # Change position_chosen to be accessed by other classes
476 controller.current_user.position_chosen = position
477 controller.show_frame(VoteFrame)
478
479 def draw_buttons(self, controller):
480 # Make buttons for each position
481 buttons = []
482 for x, name in enumerate(POSITIONS):
483 buttons.append(tk.Button(self, text=name, height=2, width=25,
484 state=controller.current_user.preferences_voted[name][2],
485 command=lambda i=name: self.cast_vote(i, controller)))
486 buttons[x].grid(row=x + 2, column=0, pady=15)
487 tk.Button(self, text="Back", command=lambda: controller.show_frame(LoginFrame), height=2, width=10).grid(
488 pady=20, row=len(POSITIONS) + 2, column=0, padx=42, sticky=tk.W)
489
490
491# Frame to redirect to the correct frame depending on votes made
492class VoteFrame(tk.Frame):
493 def __init__(self, parent, controller):
494 tk.Frame.__init__(self, parent)
495
496 def on_open(self, controller):
497 if len(make_choices(controller, controller.current_user.position_chosen)) == 4:
498 return VoteFrame1
499 else:
500 return VoteFrame2
501
502
503# Frame for 1st vote
504class VoteFrame1(tk.Frame):
505 def __init__(self, parent, controller):
506 tk.Frame.__init__(self, parent)
507 title_label(self, "Vote", True)
508 tk.Label(self, text="Choose your first preference:", width=29, anchor=tk.W, font=SUB_TEXT_FONT) \
509 .grid(row=1, column=0, padx=42)
510 tk.Button(self, text="Back", width=10, height=2, command=lambda: controller.show_frame(ChoosePositionFrame)) \
511 .grid(row=3, column=0, sticky=tk.W, pady=10, padx=42)
512 self.options = []
513 self.default_value = tk.StringVar(self)
514
515 def make_ui(self, controller, position):
516 self.options = []
517 # Get options available
518 for candidate in make_choices(controller, position):
519 self.options.append(candidate[0])
520 # Remove OptionMenu if already exists in class
521 if hasattr(self, "choice_box"):
522 self.choice_box.destroy()
523
524 self.default_value = tk.StringVar(self)
525 self.default_value.set(self.options[0])
526 self.choice_box = tk.OptionMenu(self, self.default_value, *self.options)
527 self.choice_box.grid(column=0, row=2, padx=42, sticky=tk.W, pady=10)
528 tk.Button(self, text="Submit", height=2, width=10,
529 command=lambda: cast_vote_1(controller, self.default_value.get(), "1", position)) \
530 .grid(pady=10, row=3, column=0, sticky=tk.E)
531
532
533# Frame for 2nd, 3rd and 4th vote
534class VoteFrame2(tk.Frame):
535 def __init__(self, parent, controller):
536 tk.Frame.__init__(self, parent)
537 title_label(self, "Vote", True)
538 tk.Label(self, text="Choose your other preferences:", width=29, anchor=tk.W, font=SUB_TEXT_FONT) \
539 .grid(columnspan=2, row=1, column=0, padx=42, sticky=tk.W)
540 self.options = ["N/A"]
541 self.choice_boxes = []
542 self.choice_labels = []
543
544 def make_ui(self, controller, position):
545 self.options = ["N/A"]
546 self.choice_labels = []
547 # Get candidates
548 for candidate in make_choices(controller, position):
549 self.options.append(candidate[0])
550 if len(self.choice_boxes) == 3:
551 for choice_box in self.choice_boxes:
552 choice_box[1].destroy()
553 self.choice_boxes = []
554 preferences_voted = []
555 for candidate in controller.current_user.preferences_voted[position][1]:
556 preferences_voted.append(candidate[0])
557 # Create choice boxes
558 for x in range(3):
559 sub_text(self, "Pref " + str(x + 2) + ":").grid(column=0, row=x + 3, padx=42, sticky=tk.W)
560 self.choice_boxes.append([])
561 self.choice_boxes[x].append(tk.StringVar(self))
562 # Check if preference already voted for
563 if str(x + 2) in preferences_voted:
564 self.choice_boxes[x].append(tk.OptionMenu(self, self.choice_boxes[x][0], *self.options))
565 self.choice_boxes[x][1].configure(state="disabled")
566 for i, vote in enumerate(controller.current_user.preferences_voted[position][1]):
567 if vote[0] == str(x + 2):
568 self.choice_boxes[x][0].set(controller.current_user.preferences_voted[position][1][i][1:3])
569 else:
570 self.choice_boxes[x].append(tk.OptionMenu(self, self.choice_boxes[x][0], *self.options))
571 self.choice_boxes[x][0].set(self.options[0])
572 self.choice_boxes[x][1].grid(column=1, row=x + 3, padx=0, pady=10, sticky=tk.EW)
573 self.choice_boxes[x][1].config(width=20)
574
575 tk.Button(self, text="Back", width=10, height=2, command=lambda: controller.show_frame(ChoosePositionFrame)) \
576 .grid(column=0, row=6, sticky=tk.W, padx=(42, 0), pady=10, columnspan=2)
577 tk.Button(self, text="Submit", width=10, height=2,
578 command=lambda: cast_vote_2(controller, self.choice_boxes, controller.current_user.position_chosen)) \
579 .grid(column=0, row=6, sticky=tk.E, pady=10, columnspan=2)
580
581
582class Votes:
583 def __init__(self):
584 # self.votes = {
585 # "President": {},
586 # "GSU Officer": {},
587 # "Faculty Officer": {}
588 # }
589 self.votes = {}
590 for position in POSITIONS:
591 self.votes[position] = {}
592 # Add dictionaries with empty lists inside votes attribute
593 with open("GSUCandidates.txt", "r") as cand_file:
594 reader = csv.reader(cand_file)
595 for candidate in reader:
596 self.votes[candidate[0]][str(candidate[1] + " " + candidate[2])] = [0, 0, 0, 0]
597 # Add up votes from votes file
598 with open("Votes.txt", "r") as votes_file:
599 reader = csv.reader(votes_file)
600 for vote in reader:
601 self.votes[vote[5]][str(vote[3] + " " + vote[4])][int(vote[6]) - 1] += 1
602
603 def get_winner(self, position):
604 # Sort candidates by votes
605 votes_formed = []
606 for candidate in self.votes[position].items():
607 votes_formed.append([candidate[1][0], candidate[1][1], candidate[1][2], candidate[1][3], candidate[0]])
608 votes_formed.sort(reverse=True)
609 if votes_formed[0] == votes_formed[1]:
610 tk.messagebox.showerror("No winner", "Unfortunately, there was no winner for "
611 + position + ". A re-election is recommended. ")
612 return votes_formed
613
614
615# Frame with summary statistics of the election
616class ViewResultsFrame(tk.Frame):
617 def __init__(self, parent, controller):
618 tk.Frame.__init__(self, parent)
619 tk.Label(self, text="Results", anchor=tk.W, font=TITLE_FONT, width=13).grid(row=0, column=0, sticky=tk.W,
620 padx=42)
621 for i, position in enumerate(POSITIONS):
622 winner = controller.votes.get_winner(position)
623 text = position + " winner: " + winner[0][4] + "."
624 tk.Label(self, text=text, anchor=tk.W).grid(row=i + 1, column=0, sticky=tk.W, pady=3, padx=(42, 0))
625 tk.Button(self, text="Back", width=10, height=2, command=lambda: controller.show_frame(CheckDate)) \
626 .grid(row=len(POSITIONS) + 1, column=0, sticky=tk.W, padx=(42, 0), pady=10)
627 tk.Button(self, text="Details", width=10, height=2, command=lambda: controller.show_frame(ChooseDetailsFrame)) \
628 .grid(row=len(POSITIONS) + 1, column=0, sticky=tk.E, pady=10)
629 tk.Button(self, text="Print", width=10, height=2,
630 command=lambda: make_printout(controller.votes.votes, controller)) \
631 .grid(row=len(POSITIONS) + 2, column=0, sticky=tk.E, pady=5)
632
633
634# Frame to show details of each position
635class ViewDetailsFrame(tk.Frame):
636 def __init__(self, parent, controller):
637 tk.Frame.__init__(self, parent)
638 self.controller = controller
639 tk.Button(self, text="Back", width=10, height=2, command=self.go_back)\
640 .grid(row=9, column=0, sticky=tk.W, padx=(42, 0), pady=10)
641
642 def on_open(self):
643 # Remove title if already made
644 if hasattr(self, "title"):
645 self.title.destroy()
646 for label in self.summary_labels:
647 label.destroy()
648 position_votes = self.controller.votes.get_winner(self.controller.position_viewing)
649 rows_of_text = [["Candidate", "1st pref", "2nd pref", "3rd pref", "4th pref"]]
650 position_votes = sorted(position_votes, key=lambda o: o[4])
651 candidates = self.controller.votes.get_winner(self.controller.position_viewing)
652 # Create a table using vote data
653 labels = []
654 for vote in position_votes:
655 rows_of_text.append([vote[4], str(vote[0]), str(vote[1]), str(vote[2]), str(vote[3])])
656 for x, row in enumerate(rows_of_text):
657 for i, col in enumerate(row):
658 if i == 0:
659 labels.append(tk.Label(self, text=col, width=10))
660 labels[5*x+i].grid(padx=(42, 0), pady=5, row=x + 1, column=i)
661 else:
662 labels.append(tk.Label(self, text=col, width=10))
663 labels[5*x+i].grid(pady=5, row=x + 1, column=i)
664 # Calculate summary statistics
665 total_votes = 0
666 for vote in candidates:
667 total_votes += vote[0]
668 self.summary_labels = []
669 self.summary_labels.append(tk.Label(self, text="Winner: " + candidates[0][4], font=SUB_TEXT_FONT))
670 self.summary_labels[0].grid(row=6, column=0, columnspan=5, sticky=tk.W, padx=(42, 0), pady=5)
671 text = "Votes received: " + str(candidates[0][0]) + " ("\
672 + str(int(round((candidates[0][0] / total_votes) * 100))) + "%)"
673 self.summary_labels.append(tk.Label(self, text=text, font=SUB_TEXT_FONT))
674 self.summary_labels[1].grid(row=7, column=0, columnspan=5, sticky=tk.W, padx=(42, 0), pady=5)
675 self.summary_labels.append(tk.Label(self, text="Total votes: " + str(total_votes), font=SUB_TEXT_FONT))
676 self.summary_labels[2].grid(row=8, column=0, columnspan=5, sticky=tk.W, padx=(42, 0), pady=5)
677 self.title = tk.Label(self, text=self.controller.position_viewing, anchor=tk.W, font=TITLE_FONT, width=13)
678 self.title.grid(row=0, column=0, sticky=tk.W, padx=42, columnspan=5)
679 # Make window bigger to accompany more data
680 self.controller.geometry("460x410")
681
682 def go_back(self):
683 # Change window size back
684 self.controller.geometry("400x410")
685 self.controller.show_frame(ChooseDetailsFrame)
686
687
688class ChooseDetailsFrame(tk.Frame):
689 def __init__(self, parent, controller):
690 tk.Frame.__init__(self, parent)
691 self.controller = controller
692 tk.Label(self, text="View details", anchor=tk.W, font=TITLE_FONT, width=13)\
693 .grid(row=0, column=0, sticky=tk.W, padx=42)
694 # Create buttons for each position
695 buttons = []
696 for x, name in enumerate(POSITIONS):
697 buttons.append(tk.Button(self, text=name, height=2, width=25,
698 state=controller.current_user.preferences_voted[name][2],
699 command=lambda i=name: self.view_details(i)))
700 buttons[x].grid(row=x + 1, column=0, pady=15, padx=(42, 0))
701 tk.Button(self, text="Back", width=10, height=2, command=lambda: controller.show_frame(ViewResultsFrame))\
702 .grid(row=len(POSITIONS) + 1, column=0, sticky=tk.W, padx=(42, 0), pady=10)
703
704 def view_details(self, position):
705 self.controller.position_viewing = position
706 self.controller.show_frame(ViewDetailsFrame)
707
708
709if __name__ == "__main__":
710 # Create tkinter application
711 app = Application()
712 app.mainloop()