PageRenderTime 56ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/automate/csci101/grade-interactive.py

https://gitlab.com/rshipp/mines-grading
Python | 194 lines | 122 code | 18 blank | 54 comment | 35 complexity | 116a7bd023a08c502e4c0c68988362ea MD5 | raw file
  1. #!/usr/bin/env python
  2. # This is a not-very-automated script for interacting with Hellman's grading
  3. # site for CSCI101. You'll have to log in using your Mymail (Google) info.
  4. # Navigating his site required a lot of back-and-forth between various pages
  5. # when all I really wanted was to batch download and grade submissions. This
  6. # mostly accomplished that, but I have no idea if/how well it currently works.
  7. # Anyone is free to test/modify/butcher this code as they please, but use it at
  8. # your own risk. I take no responsiblity for any terrible consequences that may
  9. # result from you running this code, but I'll happily take credit for anything
  10. # good that happens.
  11. # Everything should work with Python 2.7+, although I haven't explicitly tested
  12. # it on 3+. The logging/debugging stuff makes use of the BeautifulSoup library
  13. # for parsing HTML (http://www.crummy.com/software/BeautifulSoup), but it's not
  14. # technically required so I've left that code commented out. However, you will
  15. # need Mechanize from http://wwwsearch.sourceforge.net/mechanize if you don't
  16. # have it already.
  17. import mechanize, getpass, re, time, zipfile, glob, os, subprocess, sys
  18. from bs4 import BeautifulSoup
  19. # Logging: Write BeautifulSoup HTML to file
  20. def writeHTML(page, filename):
  21. with open(filename + '.html', 'w') as f:
  22. f.write(page.prettify().encode('utf8'))
  23. # Present the items in a list with their indices, user selects one
  24. def chooseOne(items):
  25. choice = -1
  26. while True:
  27. for index, item in enumerate(items):
  28. print "{}:\t{}".format(index, item[(item.rfind('/') + 1):])
  29. try:
  30. choice = input("Your choice: ")
  31. if int(choice) in xrange(len(items)):
  32. break
  33. else:
  34. raise Exception
  35. except KeyboardInterrupt:
  36. break
  37. except:
  38. print "Invalid choice. Try again."
  39. return choice
  40. # Run the given script and return the result
  41. def runScript(script):
  42. try:
  43. print
  44. result = subprocess.call(["python", "-u", script])
  45. except Exception as e:
  46. print e
  47. sys.stdout.flush()
  48. # Allow time for flushing output before continuing
  49. time.sleep(1)
  50. finally:
  51. print
  52. return result
  53. # Create a browser
  54. browser = mechanize.Browser(factory=mechanize.RobustFactory())
  55. browser.set_handle_robots(False)
  56. browser.set_handle_refresh(False)
  57. # Navigate to grading website
  58. print "Loading https://cs.mcprogramming.com/djintro/entry/"
  59. initialResponse = browser.open("https://cs.mcprogramming.com/djintro/entry/")
  60. homeHTML = BeautifulSoup(initialResponse.get_data())
  61. # writeHTML(homeHTML, 'home')
  62. # Find the login link using BeautifulSoup and follow it
  63. loginLink = homeHTML.find('a', 'mymaillogin')
  64. loginResponse = browser.follow_link(browser.find_link(text=loginLink.get_text()))
  65. # Log in via Google
  66. isLoggedIn = False
  67. while not isLoggedIn :
  68. try:
  69. # Select the first form on the page (should be the login form)
  70. browser.select_form(nr=0)
  71. # Checkbox to stay signed in
  72. # browser.form['PersistentCookie']
  73. email = raw_input("Email: ")
  74. pw = getpass.getpass()
  75. browser.form['Email'] = email
  76. browser.form['Passwd'] = pw
  77. print "Authenticating..."
  78. googleResponse = browser.submit()
  79. except mechanize._mechanize.FormNotFoundError:
  80. # loginHTML = BeautifulSoup(loginResponse.get_data())
  81. # writeHTML(loginHTML, 'login')
  82. print "No form, check login.html"
  83. except Exception as e:
  84. print e
  85. exit(1)
  86. # Check if login was successful
  87. loggedInHTML = BeautifulSoup(googleResponse.get_data())
  88. if loggedInHTML.title.get_text() == "Google Accounts":
  89. print "Error signing in, try again."
  90. continue
  91. else:
  92. print "Success. Loading assignments page..."
  93. isLoggedIn = True
  94. # writeHTML(loggedInHTML, 'loggedIn')
  95. # Find assignments page link using Beautiful Soup and follow it
  96. assignmentsLink = loggedInHTML.find('a')
  97. assignmentsResponse = browser.follow_link(browser.find_link(text=assignmentsLink.get_text()))
  98. # BeautifulSoup doesn't like the <meta> tag specifying us-ascii encoding, so get rid of it
  99. # badAssignmentsHTML = assignmentsResponse.get_data()
  100. # metaTag = re.compile('<meta .+/>')
  101. # fixedAssignmentsHTML = metaTag.sub('', badAssignmentsHTML)
  102. # assignmentsPageHTML = BeautifulSoup(fixedAssignmentsHTML)
  103. # writeHTML(assignmentsPageHTML, 'assignmentsPage')
  104. # Ask for a specific assignment and navigate to its submissions page
  105. validAssignmentName = False
  106. while not validAssignmentName:
  107. try:
  108. assignmentName = raw_input("Assignment name: ").strip()
  109. submissionsLink = browser.find_link(text=assignmentName)
  110. except mechanize._mechanize.LinkNotFoundError:
  111. print "Invalid assignment name, try again."
  112. continue
  113. except Exception as e:
  114. print e
  115. exit(1)
  116. validAssignmentName = True
  117. submissionsResponse = browser.follow_link(submissionsLink)
  118. # badSubmissionsHTML = submissionsResponse.get_data()
  119. # fixedSubmissionsHTML = metaTag.sub('', badSubmissionsHTML)
  120. # submissionsHTML = BeautifulSoup(fixedSubmissionsHTML)
  121. # writeHTML(submissionsHTML, 'submissions')
  122. # Determine download URL
  123. downloadLink = browser.find_link(url_regex=re.compile('../downloadgrading/\?pgk='))
  124. downloadPath = re.compile('/grading/.+')
  125. fullDownloadPath = downloadPath.sub(downloadLink.url[2:], downloadLink.base_url)
  126. # Ask where to save the file
  127. downloadLoc = raw_input("Enter download location: ")
  128. while not os.path.isdir(downloadLoc):
  129. downloadLoc = raw_input("Location must be a directory. Try again: ")
  130. if downloadLoc[-1:] != '/':
  131. downloadLoc += '/'
  132. downloadLoc += '{}_{}'.format(time.strftime("%Y-%m-%d_%H:%M:%S", time.gmtime()), assignmentName)
  133. print "Downloading {} to {}".format(fullDownloadPath, downloadLoc)
  134. # Download
  135. submission = browser.retrieve(fullDownloadPath, filename=downloadLoc)
  136. # Extract to 'submission' folder
  137. z = zipfile.ZipFile(submission[0])
  138. z.extractall('submission')
  139. # Get submission ID
  140. submissionID = fullDownloadPath[(fullDownloadPath.find('=') + 1):].replace('%3A', ':').replace('%40', '@')
  141. print "ID is", submissionID
  142. # Build list of python scripts
  143. pythonScripts = []
  144. correctFilename = True
  145. for script in glob.glob("submission/*.py"):
  146. if os.path.isfile(script):
  147. pythonScripts.append(script)
  148. # If there were no python scripts, list all files and select one to run
  149. if len(pythonScripts) == 0:
  150. correctFilename = False
  151. print "No python scripts. Other files:"
  152. files = [item for item in glob.glob("submission/*") if os.path.isfile(item)]
  153. choice = chooseOne(files)
  154. result = runScript(files[choice])
  155. # Otherwise, select a script to run
  156. else:
  157. if len(pythonScripts) > 1:
  158. print "Multiple python scripts are present:"
  159. choice = chooseOne(pythonScripts)
  160. else:
  161. choice = 0
  162. result = runScript(pythonScripts[choice])
  163. # Enter a grade for this submission?
  164. gradeThis = raw_input("Grade this submission? (y/N)\n")
  165. try:
  166. # This line *sometimes* fails with "OPTION outside of SELECT". I have no idea why.
  167. browser.select_form(nr=0)
  168. if gradeThis.lower() in ['y', 'yes']:
  169. print "Will grade"
  170. else:
  171. print "No grade"
  172. print browser.form.find_control(name=(submissionID + 'deferred'))
  173. except Exception as e:
  174. print e
  175. with open('troublesome.html', 'w') as f:
  176. f.write(submissionsResponse.get_data())