· 7 years ago · Oct 14, 2018, 09:26 PM
1# EDU-MARKER - TEACHER MODULE
2# DANIEL PERKS - 175038
3
4# Requirements:
5# nltk.download('all')
6
7
8# Imports - Importing required modules
9import sqlite3 # Used for database structuring and read
10import socket # Used for peer-to-peer connection via LAN
11import sys # Used for a few system functions, mainly sys.exit - should remove
12import datetime # Used to gather and print the current date/time for log
13import threading # Used to thread a function for accepting students
14import time # Used for its .sleep function to add pauses to code
15import random # Used to randomly generate the port/password for tests
16import getpass # Gets the users Windows username
17import nltk
18from nltk.downloader import Downloader # Used for word analysis
19import urllib # Used to parse web URLs
20import requests # Used to retrieve a webpages content
21import wikipedia # Used to query wikipedia
22import threading # Used to thread multiple fucntions at the same time
23#from fuzzy import start # The file containing the fuzzy logic marking programm
24from bs4 import BeautifulSoup # Used to parse and extract data from website requests
25from easygui import * # Used for the GUI
26#from fuzzy import mark as fuzzy
27
28from tkinter import Tk
29from contextlib import contextmanager
30
31@contextmanager
32def tk(timeout=5):
33 root = Tk() # default root
34 root.withdraw() # remove from the screen
35
36 # destroy all widgets in `timeout` seconds
37 func_id = root.after(int(1000*timeout), root.quit)
38 try:
39 yield root
40 finally: # cleanup
41 root.after_cancel(func_id) # cancel callback
42 root.destroy()
43
44class Encoder:
45
46 @staticmethod
47 def decoder(string):
48 try:
49 newstring = string[1:]
50 newstring = bytearray.fromhex(newstring).decode()
51 except (ValueError, TypeError):
52 newstring = string
53 return newstring
54
55
56 @staticmethod
57 def encoder(string):
58 string = "T" + (string.encode('utf-8')).hex()
59 return string
60
61
62class Network: # Selection of networking tools
63 def __init__(self):
64 self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
65 self.host = str(socket.gethostbyname(socket.gethostname()))
66 self.port = random.randint(9999, 50000)
67 self.names = {}
68 self.server.bind((self.host, self.port))
69 self.clients = {}
70 self.users = []
71 self.portOpen = 1
72
73 def start(self, test, group): # Starts a server on the local IP with a random port
74 roomno = self.host.split(".")
75 roomno = roomno[2] + "." + roomno[3]
76 #roomno = ".".join(roomno)
77 msg = "You are running the test named: " + \
78 test.capitalize() + "\nYou are running this for class: " + \
79 group + "\n\nStudents should use these login details:" \
80 "\nRoom Number: " + str(roomno) + "\nPassword: " + str(self.port) + "\n\n\n" \
81 "Press start to start accepting students..."
82 # todo Make the live student update
83 clr()
84 print("Room Number: " + roomno + " Password: " + str(self.port))
85 print("Press enter to start the test once all students are connected.")
86 print("IP: " + str(self.host))
87 print("\nConnected Students: ")
88 msgbox(msg, "EduMarker - Starting...", ok_button="Start")
89 self.server.listen(3)
90 acceptThread = threading.Thread(target=self.accept)
91 acceptThread.start()
92 input()
93 self.portOpen = 0
94 questions = []
95 databasedata = database.read_questions(database.testsDB.cursor(), test)
96 for question in databasedata:
97 questions.append(question[1])
98 send = "startx " + str(questions)
99 send = send.encode()
100 for user in self.users:
101 conn = self.clients[user]
102 conn.sendall(send)
103 answers = self.recieveAnswers()
104 name = str(group) + "-" + str(test)
105 database.saveAnswers(name,answers)
106 msgbox("All students have completed the test, and their results have been recorded.\n"
107 "Please use the 'Mark a Test' option on the main menu to mark this test\n\n"
108 "You will now be sent back to the main menu",
109 "EduMarker - Test Complete", "Continue")
110 main.home()
111 return
112
113 def recieveAnswers(self):
114 clients = self.clients
115 users = self.users
116 answers = []
117 while True:
118 for user in users:
119 conn = clients[user]
120 answer = ""
121 try:
122 answer = (conn.recv(1024)).decode()
123 except ConnectionResetError:
124 msgbox("Student " + str(self.names[user]) + " has left the room.")
125 if answer != None and answer != "":
126 #print("Data: " + str(answer))
127 answers.append(answer)
128 # answer = answer[1:-1].split(",")
129 # user = (answer[0])[1:-1]
130 # question = (answer[1])[1:-1]
131 # answer = (answer[2])[1:-1]
132 # print(user)
133 # print(question)
134 # print(answer)
135 # print("")
136 else:
137 clients.pop(user)
138 users.pop(users.index(user))
139 if len(users) == 0:
140 return answers
141
142
143 def accept(self): # Accepts clients for an open server
144 conn = ""
145 add = ""
146 if self.portOpen == 1:
147 conn, add = self.server.accept()
148 if self.portOpen == 1:
149 user = (conn.recv(1024)).decode()
150 user = user.split(";")
151 username = user[0]
152 fullname = user[1]
153 statement = ('Student ' + fullname + ' connected from user ' + username)
154 print(statement)
155 self.users.append(username)
156 self.names[username] = fullname
157 self.clients[username] = conn
158 conn.sendall(b'Connected')
159 acceptThread = threading.Thread(target=self.accept)
160 acceptThread.start()
161
162
163class Data: # The tools to send and receive questions and answers
164 def __init__(self):
165 self.students = []
166 self.questions = []
167 self.answers = []
168 self.testReg = ""
169 self.testCon = ""
170 self.groupNo = 0
171 try:
172 self.testsDB = sqlite3.connect('questions.db')
173 self.resultsDB = sqlite3.connect('results.db')
174 except sqlite3.DatabaseError:
175 msg = "Unfortunately, a quiz cannot be started at this time.\n" \
176 "This is likely because the program is already in use on this computer\n" \
177 "Please close any programs using the file and try again\nDo you want to try again?"
178 if ynbox(msg, "EduMarker - Error") is True:
179 main.home()
180
181 def commit(self):
182 self.testsDB.commit()
183 self.resultsDB.commit()
184
185 def __del__(self):
186 self.commit()
187
188 def read_questions_start(self): # reads the questions from the file
189 cur = self.testsDB.cursor()
190 cur.execute("SELECT name FROM sqlite_master WHERE type='table'")
191 quizoptions = cur.fetchall()
192 quizoptionsconv = []
193 for test in quizoptions:
194 if test != ('sqlite_sequence',):
195 test = Encoder.decoder(str(test)[2:-3])
196 quizoptionsconv.append(test)
197 if not quizoptionsconv:
198 msgbox("There are no tests available right now.\nMake one from the main menu.")
199 main.home()
200 else:
201 quizoptionsconv.append("\n")
202 quizoptionsconv.append("Make a new test...")
203 values = choicebox("What test would you like to start?", "EduMark - Setup", quizoptionsconv)
204 if values == "\n" or values == None or values.strip() == "":
205 if ynbox("Would you like to cancel the test?", "EduMarker - Exit") is True:
206 main.home()
207 else:
208 self.read_questions_start()
209 else:
210 index = quizoptionsconv.index(values)
211 values = quizoptions[index][0]
212 self.testReg = values
213 self.testCon = Encoder.decoder(self.testReg)
214 try:
215 cur = self.testsDB.cursor()
216 cur.execute("SELECT * FROM " + values)
217 self.questions = cur.fetchall()
218 except sqlite3.OperationalError:
219 msg = "Unfortunately, there is not a test with this name. Please try again..."
220 msgbox(msg, "EduMarker - Error")
221 self.read_questions_start()
222 else:
223 group = multenterbox("Please enter the class taking this test...", "EduMark - Setup", ["Class"])
224 if group == None:
225 if ynbox("Are you sure you want to cancel the test?", "EduMarker - Quit") is True:
226 main.home()
227 return
228 else:
229 self.read_questions_start()
230 if any([letter == "-" for letter in group[0]]) is True:
231 msgbox("You used an invalid character: '-'\nPlease choose a name without this character.")
232 self.read_questions_start()
233 return
234 if group is not None:
235 return group[0]
236 else:
237 if ynbox("Would you like to exit the test creator?", "EduMarker - Exit") is True:
238 main.home()
239
240 def read_questions(self, db, test):
241 cursor = db
242 test = Encoder.encoder(test)
243 cursor.execute("SELECT * FROM " + test)
244 data = cursor.fetchall()
245 return data
246
247 def read_tables(self, db):
248 cursor = db.cursor()
249 cursor.execute("SELECT name FROM sqlite_master WHERE type is 'table'")
250 data = cursor.fetchall()
251 return data
252
253 def read_data(self, db, table):
254 cursor = db.cursor()
255 cursor.execute("SELECT * FROM " + table)
256 data = cursor.fetchall()
257 return data
258
259 def saveAnswers(self,name,answers):
260 cursor = self.resultsDB.cursor()
261 print(answers)
262 data = {}
263 for k in answers:
264 k = eval(k)
265 try:
266 data[k[0]] += [(int(k[1]), k[2])]
267 except KeyError:
268 data[k[0]] = [(int(k[1]), k[2])]
269 # data = str(data)
270 name = encoder.encoder(name)
271 cursor.execute("CREATE TABLE " + name + " (`studName` TEXT,`qNo` INTEGER,`qAns` TEXT,"
272 "PRIMARY KEY(`studName`,`qNo`));")
273 for stud in data:
274 for k in data[stud]:
275 cursor.execute("INSERT INTO " + str(name) + "('studName', 'qNo', 'qAns') VALUES ("''+ str(stud) + "'," + str(k[0]) + ",'" + str(k[1]) + "');")
276
277
278class Editor:
279 def __init__(self):
280 self.test = None
281 self.testid = 0
282 self.testen = ""
283
284 def create(self):
285 cursor = database.testsDB.cursor()
286 title = "EduMark - Test Creation"
287 name = (multenterbox("What is the name of this test?", title, ["Test Name"]))
288 if name is None:
289 if ynbox("Would you like to exit the test creator?", "EduMarker - Exit") is True:
290 main.home()
291 else:
292 self.create()
293 sys.exit()
294 elif name == "":
295 msgbox("You did not enter a test name\n Try again...")
296 self.create()
297 elif any([letter == "-" for letter in name]) is True:
298 msgbox("You used an invalid character: '-'\nPlease choose a name without this character.")
299 self.create()
300 return
301 else:
302 name = Encoder.encoder(name[0])
303 try:
304 cursor.execute("CREATE TABLE " + name + " (`QID` INTEGER PRIMARY KEY UNIQUE,"
305 "`Question` TEXT, `Answer` TEXT );")
306 except sqlite3.OperationalError:
307 msgbox("This test already exsists.\nPlease use a different name or edit the exsisting test.")
308 self.create()
309
310 questions = None
311 while questions is None:
312 questions = (multenterbox("How many questions will be in this test?"
313 "", title, ["Number of Questions"]))[0]
314 if questions is None:
315 if ynbox("You did not enter a number of questions.\nDo you want to quit the test creator?",
316 "EduMarker - Quit") is True:
317 main.home()
318 else:
319 self.create()
320 if questions.isdigit() is False:
321 if ynbox("You did not enter a number of questions.\nDo you want to quit the test creator?",
322 "EduMarker - Quit") is True:
323 main.home()
324 else:
325 self.create()
326 elif int(questions[0]) < 0:
327 if ynbox("You did not enter a valid number of questions.\nDo you want to quit the test creator?",
328 "EduMarker - Quit") is True:
329 main.home()
330 else:
331 self.create()
332 else:
333 questions = questions[0].lower()
334 for questionNo in range(int(questions)):
335 entry = None
336 while entry is None:
337 entry = multenterbox("Enter question " + str(questionNo+1) +
338 "'s details:", title, ["Question", "Answer(s)"])
339 if entry is None:
340 if ynbox("Do you want to quit the test editor?", "EduMarker - Quit") is True:
341 main.home()
342 if entry != None: # Does not work with 'is' like suggested
343 exsisting = database.read_tables(database.testsDB)
344 if name in exsisting:
345 msgbox("A test with this name already exsists.\n"
346 "Please pick a different name or edit the exsisting test.")
347 self.create()
348 else:
349 cursor.execute("INSERT INTO " + name + " ('Question', 'Answer') "
350 "VALUES ('" + entry[0] + "', '" + entry[1] + "')")
351 database.commit()
352 else:
353 msgbox("You did not enter a question and answer for this test\nPlease try again...")
354 msgbox("Your test has been made successfully\nYou will now go back to the main menu",
355 "EduMarker - Test Created", ok_button="Continue")
356 main.home()
357
358 def editstart(self):
359 msg = "Pick which test you want to edit?"
360 title = "EduMark - Test Editor"
361 choices = database.read_tables(database.testsDB)
362 choices_new = []
363 self.test = None
364 for loop in range(len(choices)):
365 choice = choices[loop][0]
366 if choice != "sqlite_sequence":
367 choice = encoder.decoder(str(choice))
368 choices_new.append(choice)
369 if not choices_new:
370 msgbox("There are no tests available.\nPlease create one from the main menu", "EduMarker - Error")
371 main.home()
372 elif len(choices_new) is 1:
373 self.test = choices_new[0]
374 msgbox("There is only one test available now\nTherefore you will now begin editing this test"
375 "\n\nTest name: " + str(self.test))
376 else:
377 self.test = (choicebox(msg, title, choices_new))
378 if self.test is None:
379 if ynbox("You did not pick a test.\nDo you want to quit the test editor?",
380 "EduMarker - Quit") is True:
381 main.home()
382 else:
383 self.editstart()
384 self.testid = choices_new.index(self.test)
385 self.edit()
386
387 def edit(self):
388 db = database.testsDB.cursor()
389 questions = database.read_questions(db, self.test)
390 questions_new = []
391 for question in questions:
392 questions_new.append(str(question[1]) + " - " + str(question[2]))
393 if questions_new is []:
394 questions_new.append("No questions added, click here to add one!")
395 if len(questions_new) == 1:
396 questions_new.extend(["", "Add new question...", "Delete this test..."])
397 else:
398 questions_new.extend(["", "Add new question...", "Delete a question...", "Delete this test..."])
399 question = ""
400 while question == "":
401 question = choicebox("Which question would you like to edit?", "EduMarker - Edit Test", questions_new)
402 if question is None:
403 if ynbox("Are you sure you want to go back to the main menu?", "EduMarker - Quit") is True:
404 main.home()
405 return
406 else:
407 self.edit()
408 return
409 quest = question.split(" - ")
410 if question == "Add new question..." or question == "No questions added, click here to add one!":
411 self.addquestion(self.test)
412 elif question == "Delete a question...":
413 self.deleteq(self.test)
414 elif question == "Delete this test...":
415 self.deletet(self.test)
416 else:
417 sql = None
418 new_question = multenterbox("Please enter the new question and/or answers...",
419 "EduMarker - Edit Test", ["Question", "Answer"], [quest[0], quest[1]])
420 if self.test[0] != "T":
421 self.testen = Encoder.encoder(self.test)
422 questid = str(questions[questions_new.index(question)][0])
423 if new_question is None:
424 if ynbox("Are you sure you want to leave this unedited?", "EduMarker - Close") is True:
425 main.home()
426 return
427 else:
428 self.edit()
429 return
430 if new_question[0] is quest[0] and new_question[1] is quest[1]:
431 msgbox("You did not change either the question or answer\n"
432 "Try again or cancel by closing the window.")
433 elif new_question[0] is quest[0]:
434 sql = "UPDATE " + str(self.testen) + " SET answer ='" + \
435 str(new_question[1]) + "' WHERE QID = " + questid
436 elif new_question[1] is quest[1]:
437 sql = "UPDATE " + str(self.testen) + " SET question='" + \
438 str(new_question[0]) + "' WHERE QID = " + questid
439 elif new_question[0] is None and new_question[1] is None:
440 msgbox("Both the question and answer box were empty\n"
441 "Try again or cancel by closing the window.")
442 self.edit()
443 else:
444 sql = "UPDATE " + str(self.testen) + " SET question='" + \
445 str(new_question[0]) + "', answer ='" + \
446 str(new_question[1]) + "' WHERE QID = " + questid
447 db.execute(sql)
448 database.commit()
449 self.edit()
450
451 def addquestion(self, table):
452 cursor = database.testsDB.cursor()
453 entry = multenterbox("Enter a new question:", "EduMarker - Add Question", ["Question", "Answer(s)"])
454 if entry is None:
455 if ynbox("You didn't enter anything.\nDid you want to quit?"):
456 main.home()
457 return
458 else:
459 self.edit()
460 return
461
462 table = Encoder.encoder(table)
463 statement = "INSERT INTO " + table + " ('Question', 'Answer') " \
464 "VALUES ('" + entry[0] + "', '" + entry[1] + "')"
465 cursor.execute(statement)
466 database.commit()
467 self.edit()
468
469 def deleteq(self, test):
470 question = None
471 while question is None:
472 db = database.testsDB.cursor()
473 questions = database.read_questions(db, self.test)
474 questions_new = []
475 for item in questions:
476 item = (item[1] + " - " + item[2])
477 questions_new.append(item)
478 question = choicebox("Which question would you like to delete?", "EduMark - Delete Question", questions_new)
479 if question is None:
480 if ynbox("You didn't choice a question to delete.\nDo you still want to delete a question?"):
481 self.deleteq(self.test)
482 return
483 else:
484 main.home()
485 return
486 sql = "DELETE FROM " + Encoder.encoder(test) + " WHERE QID='" + \
487 str(questions[questions_new.index(question)][0]) + "';"
488 db.execute(sql)
489 database.commit()
490 self.edit()
491
492 def deletet(self, table):
493 cursor = database.testsDB.cursor()
494 table = Encoder.encoder(table)
495 sql = "DROP TABLE IF EXISTS " + table
496 if ynbox("Are you sure you want to delete this table?", "EduMarker - Confirm Delete"):
497 cursor.execute(sql)
498 database.commit()
499 self.editstart()
500
501
502class Marking:
503
504 def markAnswer(self, results, testname, fullname):
505 answers = {}
506 questions = database.read_questions(database.testsDB.cursor(), testname)
507 for i in results:
508 try:
509 answers[i[0]][i[1]] = i[2]
510 except KeyError:
511 answers[i[0]] = {i[1]: i[2]}
512 print(questions)
513 print(answers)
514
515 def chooseTest(self):
516 classes = database.read_tables(database.resultsDB)
517 newclasses = [encoder.decoder(element[0]) for element in classes]
518 newclasses = [element.split("-")[0] for element in newclasses]
519 newclasses = list(set(newclasses))
520 newclasses.append("")
521 newclasses.append("Create A New Class")
522 testClass = choicebox("Choose which Class you want to mark...","EduMarker - Choose Class",newclasses)
523 while testClass is "":
524 testClass = choicebox("Choose which Class you want to mark...","EduMarker - Choose Class",newclasses)
525 if testClass == "Create A New Class":
526 main.home()
527 testClass = classes[newclasses.index(testClass)][0]
528 tests = [element[0] for element in classes]
529 testsEn = []
530 testsDe = []
531 testNamesDe = []
532 for element in tests:
533 if testClass in element:
534 testsEn.append(element)
535 testsDe.append(Encoder.decoder(element))
536 testNamesDe.append((Encoder.decoder(element)).split("-")[1])
537 testsPlus = testNamesDe.copy()
538 testsPlus.append("")
539 testsPlus.append("Start or Create A New Test")
540 test = choicebox("Choose which Test you want to mark...","EduMarker - Choose Test",testsPlus)
541 while test is "":
542 test = choicebox("Choose which Test you want to mark...","EduMarker - Choose Test",testsPlus)
543 if test == "Start or Create A New Test":
544 main.home()
545 else:
546 fullName = testsDe[testNamesDe.index(test)]
547 testName = test
548 results = database.read_data(database.resultsDB, testClass)
549 return results, testName, fullName
550
551 def getWebResults(self,query,marks,level):
552 pass
553
554
555class MainMenu:
556 def __init__(self):
557 self.user = getpass.getuser()
558
559 def home(self):
560 log("Main menu accessed")
561 msg = "Welcome to EduMarker, " + self.user + "!\nWhat would you like to do?"
562 title = "EduMarker - Main Menu"
563 choices = ["Start A Test - Begin a existing test for an existing class",
564 "Mark A Test - Mark a completed test for a class", "",
565 "Create a Test - Create a new test from scratch",
566 "Edit a Test - Edit the Questions and Answers of an existing test",
567 "", "Exit the program"]
568 choice = choicebox(msg, title, choices)
569 if choice == choices[0]: # Start a test
570 log("Test Setup Begun")
571 group = database.read_questions_start() # Read the Questions
572 network.start(database.testCon, group) # Start the test
573 elif choice == choices[1]: # Mark a test
574 log("Marking Begun")
575 a, b, c = marking.chooseTest()
576 marking.markAnswer(a,b,c)
577 elif choice == choices[3]: # Create a test
578 log("Test Creation Begun")
579 editor.create()
580 elif choice == choices[4]: # Edit a test
581 log("Test Editing Begun")
582 editor.editstart()
583 elif choice == choices[6]: # Exit the program
584 msg = "Are you sure you want to exit?"
585 if ynbox(msg, "EduMarker - Close") is True:
586 log("Program Closed via Main Menu option")
587 sys.exit()
588 else:
589 self.home()
590 elif choice is None:
591 log("Program Closed by Close Button")
592 sys.exit()
593 elif choice == "":
594 self.home()
595 else:
596 msgbox("Honestly, I have no idea how you got here.\nThis is technically unreachable", "The Impossible Error")
597
598
599def clr():
600 print("\n"*80) # Todo remove if unused at end
601
602def log(statement):
603 logfile = open("log.txt","a")
604 timeRec = datetime.datetime.now()
605 string = "\n- " + str(timeRec) + " - " + str(statement)
606 logfile.write(string)
607 logfile.close()
608
609def startEduMark():
610 logfile = open("log.txt", "a")
611 timeRec = datetime.datetime.now()
612 string = "\n- " + str(timeRec) + " - NEW INSTANCE OF PROGRAM STARTED"
613 logfile.write(string)
614 logfile.close()
615 #marking.chooseTest()
616 #try:
617 main.home()
618 #except Exception as e:
619 #msgbox("An error occured, please try again...","EduMarker - Error")
620 #log("ERROR: Crash occured: " + str(e))
621
622 # TODO PEP8
623
624def download():
625 nltk.download('punkt')
626 nltk.download('averaged_perceptron_tagger')
627
628network = Network()
629database = Data()
630editor = Editor()
631marking = Marking()
632encoder = Encoder()
633main = MainMenu()
634d = Downloader()
635if not d.is_installed('punkt') or not d.is_installed('averaged_perceptron_tagger'):
636 p1 = threading.Thread(target=download, daemon=True)
637 p1.start()
638 msgbox(" PERFORMING A ONE TIME INITAL SETUP\n ------------------------------------------------\n\nPlease wait while some packages are downloaded that are required for this program to run.\nThe download should not take more than a few minutes.\n\nYou can close this popupm, the program will start when the download is complete.")
639 while True:
640 if p1.is_alive() is False:
641 startEduMark()
642 break
643else:
644 startEduMark()
645
646# ORDER OF RUNNING #
647# Retrieve Q&As
648# Start server
649# Connect students
650# Run test
651
652# todo add exits for all easygui
653# todo fix missing test when starting one