From 6eac010448253294d1d9ba404c8093cde8419e35 Mon Sep 17 00:00:00 2001 From: Miguel Barao Date: Tue, 20 Nov 2018 14:34:48 +0000 Subject: [PATCH] changed how files (images) are served. Not it is serving the whole file, still asynchronous, --- BUGS.md | 2 ++ README.md | 6 +++--- app.py | 2 +- questions.py | 3 +-- serve.py | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------- test.py | 19 +++++++------------ 6 files changed, 88 insertions(+), 52 deletions(-) diff --git a/BUGS.md b/BUGS.md index 1d4521b..1d92387 100644 --- a/BUGS.md +++ b/BUGS.md @@ -11,9 +11,11 @@ ou usar push (websockets?) - submissao faz um post ajax. - eventos unfocus? - servidor nao esta a lidar com eventos scroll/resize. ignorar? +- Test.reset_answers() unused. # TODO +- fazer package para instalar perguntations com pip. - adicionar opcao para eliminar um teste em curso. - gerar teste qd o prof autoriza? melhor nao, pode apagar o teste em curso. gerar previamente e manter uma pool de testes gerados? - enviar resposta de cada pergunta individualmente. diff --git a/README.md b/README.md index 448a5cd..1c67331 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ### 1.1 Requirements -The webserver is a python application and requires `python3.6` and `pip` to be installed, plus the following additional packages: +The webserver is a python application and requires `python3.7` and `pip` to be installed, plus the following additional packages: - tornado - mistune @@ -42,13 +42,13 @@ I personally prefer python packages to be installed for a single user, but if a Linux: ```bash -apt-get install py36-tornado py36-mistune? py36-yaml py36-pygments... +apt-get install py37-tornado py37-mistune? py37-yaml py37-pygments... ``` macOS macports: ```bash -port install py36-py36-tornado py36-mistune? py36-yaml py36-pygments... +port install py37-py37-tornado py37-mistune? py37-yaml py37-pygments... ``` diff --git a/app.py b/app.py index 33fff7f..5cc74a6 100644 --- a/app.py +++ b/app.py @@ -67,7 +67,7 @@ class App(object): try: self.testfactory = test.TestFactory(testconf) except test.TestFactoryException: - logger.critical('Can\'t create test factory.') + logger.critical('Cannot create test factory.') raise AppException() # connect to database and check registered students diff --git a/questions.py b/questions.py index f871fb0..c2107d2 100644 --- a/questions.py +++ b/questions.py @@ -332,8 +332,7 @@ class QuestionTextArea(Question): 'correct': '' # trying to execute this will fail => grade 0.0 }) - self['correct'] = path.join(self['path'], self['correct']) - # self['correct'] = path.abspath(path.normpath(path.join(self['path'], self['correct']))) # abspath will prepend cwd, which is plain wrong... + self['correct'] = path.abspath(path.normpath(path.join(self['path'], self['correct']))) # abspath will prepend cwd, which is plain wrong... #------------------------------------------------------------------------ # can return negative values for wrong answers diff --git a/serve.py b/serve.py index 5809783..a60089d 100755 --- a/serve.py +++ b/serve.py @@ -131,7 +131,6 @@ class RootHandler(BaseHandler): # ---------------------------------------------------------------------------- class FileHandler(BaseHandler): SUPPORTED_METHODS = ['GET'] - chunk_size = 512 * 1024 # serve up to 512 KiB multiple times @tornado.web.authenticated async def get(self): @@ -139,40 +138,79 @@ class FileHandler(BaseHandler): ref = self.get_query_argument('ref', None) image = self.get_query_argument('image', None) - # FIXME does not work when user 0 is reviewing a test + if uid != '0': + t = self.testapp.get_student_test(uid) + else: + logging.error('FIXME Cannot serve images for review.') + raise tornado.web.HTTPError(404) # FIXME admin + + if t is None: + raise tornado.web.HTTPError(404) # Not Found + + for q in t['questions']: + if q['ref'] == ref: + filepath = path.join(q['path'], 'public', image) + + try: + f = open(filepath, 'rb') + except FileNotFoundError: + logging.error(f'File not found: {filepath}') + except PermissionError: + logging.error(f'No permission: {filepath}') + else: + content_type = mimetypes.guess_type(image) + self.set_header("Content-Type", content_type[0]) + with f: + self.write(f.read()) + await self.flush() - t = self.testapp.get_student_test(uid) - if t is not None: - for q in t['questions']: - if q['ref'] == ref: - filepath = path.join(q['path'], 'public', image) - - try: - f = open(filepath, 'rb') - except FileNotFoundError: - logging.error(f'File not found: {filepath}') - except PermissionError: - logging.error(f'No permission: {filepath}') - else: - content_type = mimetypes.guess_type(image) - self.set_header("Content-Type", content_type[0]) - - # divide the file into chunks and write one chunk at a time, so - # that the write does not block the ioloop for very long. - with f: - chunk = f.read(self.chunk_size) - while chunk: - try: - self.write(chunk) # write the cunk to response - await self.flush() # flush the current chunk to socket - except iostream.StreamClosedError: - break # client closed the connection - finally: - del chunk - await asyncio.sleep(0) - chunk = f.read(self.chunk_size) - - raise tornado.web.HTTPError(status_code=404) + + + +# class FileHandler(BaseHandler): +# SUPPORTED_METHODS = ['GET'] +# chunk_size = 512 * 1024 # serve up to 512 KiB multiple times + +# @tornado.web.authenticated +# async def get(self): +# uid = self.current_user +# ref = self.get_query_argument('ref', None) +# image = self.get_query_argument('image', None) + +# # FIXME does not work when user 0 is reviewing a test + +# t = self.testapp.get_student_test(uid) +# if t is not None: +# for q in t['questions']: +# if q['ref'] == ref: +# filepath = path.join(q['path'], 'public', image) + +# try: +# f = open(filepath, 'rb') +# except FileNotFoundError: +# logging.error(f'File not found: {filepath}') +# except PermissionError: +# logging.error(f'No permission: {filepath}') +# else: +# content_type = mimetypes.guess_type(image) +# self.set_header("Content-Type", content_type[0]) + +# # divide the file into chunks and write one chunk at a time, so +# # that the write does not block the ioloop for very long. +# with f: +# chunk = f.read(self.chunk_size) +# while chunk: +# try: +# self.write(chunk) # write the cunk to response +# await self.flush() # flush the current chunk to socket +# except iostream.StreamClosedError: +# break # client closed the connection +# finally: +# del chunk +# await asyncio.sleep(0) +# chunk = f.read(self.chunk_size) + +# raise tornado.web.HTTPError(status_code=404) # Not Found # ------------------------------------------------------------------------- @@ -254,6 +292,8 @@ class TestHandler(BaseHandler): # --- REVIEW ------------------------------------------------------------- class ReviewHandler(BaseHandler): + SUPPORTED_METHODS = ['GET'] + _templates = { 'radio': 'review-question-radio.html', 'checkbox': 'review-question-checkbox.html', diff --git a/test.py b/test.py index ea5e2fa..a259f36 100644 --- a/test.py +++ b/test.py @@ -50,8 +50,6 @@ class TestFactory(dict): logger.info(f'Checking question "{r}".') try: self.question_factory.generate(r) - # except questions.QuestionFactoryException: - # logger.critical(f'Can\'t generate question "{r}".') except: logger.critical(f'Can\'t generate question "{r}".') errors_found = True @@ -220,10 +218,10 @@ class Test(dict): # ----------------------------------------------------------------------- # Removes all answers from the test (clean) - def reset_answers(self): - for q in self['questions']: - q['answer'] = None - logger.info(f'Student {self["student"]["number"]}: all answers cleared.') + # def reset_answers(self): + # for q in self['questions']: + # q['answer'] = None + # logger.info(f'Student {self["student"]["number"]}: all answers cleared.') # ----------------------------------------------------------------------- # Given a dictionary ans={index: 'some answer'} updates the @@ -238,14 +236,11 @@ class Test(dict): async def correct(self): self['finish_time'] = datetime.now() self['state'] = 'FINISHED' - grade = 0.0 for q in self['questions']: grade += await q.correct_async() * q['points'] - - self['grade'] = max(0, round(grade, 1)) # avoid negative final grades - - logger.info(f'Student {self["student"]["number"]}: correction gave {self["grade"]} points.') + self['grade'] = max(0, round(grade, 1)) # avoid negative grade + logger.info(f'Student {self["student"]["number"]}: {self["grade"]} points.') return self['grade'] # ----------------------------------------------------------------------- @@ -259,5 +254,5 @@ class Test(dict): # ----------------------------------------------------------------------- def save_json(self, filepath): with open(path.expanduser(filepath), 'w') as f: - json.dump(self, f, indent=2, default=str) # HACK default=str required for datetime objects + json.dump(self, f, indent=2, default=str) # default=str required for datetime objects logger.info(f'Student {self["student"]["number"]}: saved JSON file.') -- libgit2 0.21.2