Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow use of word ids for grading links #1397

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ itsdangerous==0.24
cssmin==0.2.0
jsmin==2.2.1
hashids==1.2.0
word-identifiers==1.0.0
pygments==2.2.0
humanize==0.5.1
markdown>=2.6,<2.7
Expand Down
12 changes: 12 additions & 0 deletions server/controllers/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,18 @@ def grading(bid):

return grading_view(backup, form=form)

@admin.route('/grading_word/<wordid:bid>')
@is_staff()
def grading_wordid(bid):
return grading(bid)


@admin.route('/grading_get_word/<hashid:bid>')
@is_staff()
def grading_get_wordid(bid):
return "word = {!r}".format(utils.encode_word_id(bid))


@admin.route('/composition/<hashid:bid>')
@is_staff()
def composition(bid):
Expand Down
11 changes: 11 additions & 0 deletions server/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ def to_python(self, value):
def to_url(self, value):
return utils.encode_id(value)

class WordidConverter(BaseConverter):
def to_python(self, value):
try:
return utils.decode_word_id(value)
except (TypeError, ValueError) as e:
raise ValidationError(str(e))

def to_url(self, value):
return utils.encode_word_id(value)

name_part = '[^/]+'

# TODO: Move the regexes to constants.py
Expand All @@ -46,5 +56,6 @@ class AssignmentNameConverter(BaseConverter):
def init_app(app):
app.url_map.converters['bool'] = BoolConverter
app.url_map.converters['hashid'] = HashidConverter
app.url_map.converters['wordid'] = WordidConverter
app.url_map.converters['offering'] = OfferingConverter
app.url_map.converters['assignment_name'] = AssignmentNameConverter
41 changes: 41 additions & 0 deletions server/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from urllib.parse import urlparse, urljoin
from functools import lru_cache

import base64
import bleach
from flask import render_template, url_for, Markup
from hashids import Hashids
Expand All @@ -20,6 +21,8 @@
import sendgrid
import sendgrid.helpers.mail as sg_helpers

from word_identifiers import words_to_id, id_to_words

from server import constants

logger = logging.getLogger(__name__)
Expand All @@ -40,6 +43,44 @@ def decode_id(value):
raise ValueError('Could not decode hash {0} into ID'.format(value))
return numbers[0]

base62_forward = {}
base62_forward.update({i : chr(ord("0") + i) for i in range(10)})
base62_forward.update({i + 10 : chr(ord("A") + i) for i in range(26)})
base62_forward.update({i + 36 : chr(ord("a") + i) for i in range(26)})
base62_backward = {c : i for i, c in base62_forward.items()}

def hashid_to_int_direct(value):
"""
direct translation of hashid --> int in 1-padded base 62, without hashing.
"""
result = 1
for c in value:
result *= 62
result += base62_backward[c]
return result

def int_to_hashid_direct(number):
"""
direct translation of int --> hashid in 1-padded base 62, without hashing.
"""
result = []
while number > 0:
result.append(base62_forward[number % 62])
number //= 62
result.reverse()
if result.pop(0) != "1":
raise ValueError("Invalid number in 1-padded base 62")
return "".join(result)

def encode_word_id(id_number):
hashid = encode_id(id_number)
integer_id = hashid_to_int_direct(hashid)
return "-".join(id_to_words(integer_id))

def decode_word_id(word_id):
integer_id = words_to_id(word_id.split("-"))
hashid = int_to_hashid_direct(integer_id)
return decode_id(hashid)

def convert_markdown(text):
# https://pythonadventures.wordpress.com/tag/markdown/
Expand Down
24 changes: 24 additions & 0 deletions tests/test_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import datetime
import json
import os
import re
import signal
import time
import urllib.parse
Expand Down Expand Up @@ -381,6 +382,29 @@ def test_assignment_send_backup_to_ag(self):
self.driver.find_element_by_id('autograde-button').click()
self.assertIn("Submitted to the autograder", self.driver.page_source)

def test_assignment_send_backup_to_ag(self):
self._login(role="admin")
self.assignment.autograding_key = "test" # Autograder will respond with 200
models.db.session.commit()

# find a backup
backup = models.Backup(
submitter_id=self.user1.id,
assignment=self.assignment,
)
models.db.session.add(backup)
models.db.session.commit()

bid = utils.encode_id(backup.id)
bid_word = utils.encode_word_id(backup.id)
self.page_load(self.get_server_url() + "/admin/grading/" + bid)
content = self.driver.page_source
self.page_load(self.get_server_url() + "/admin/grading_get_word/" + bid)
extract_word = [x.group(1) for x in re.finditer("word = '(.*)'", self.driver.page_source)]
self.assertEqual([bid_word], extract_word)
self.page_load(self.get_server_url() + "/admin/grading_word/" + bid_word)
self.assertEqual(content, self.driver.page_source)

def test_admin_enrollment(self):
self._login(role="admin")
self.page_load(self.get_server_url() + "/admin/course/1/enrollment")
Expand Down