class CommonRequestHandler(RequestHandler): """Encapsulates shared RequestHandler functionality. """ # Whether the login cookie duration has to be refreshed when # this handler is called. Useful to filter asynchronous # requests. refresh_cookie = True def __init__(self, *args, **kwargs): super(CommonRequestHandler, self).__init__(*args, **kwargs) self.timestamp = None self.sql_session = None self.r_params = None self.contest = None self.url = None def prepare(self): """This method is executed at the beginning of each request. """ super(CommonRequestHandler, self).prepare() self.timestamp = make_datetime() self.set_header("Cache-Control", "no-cache, must-revalidate") self.sql_session = Session() self.sql_session.expire_all() self.url = create_url_builder(get_url_root(self.request.path)) @property def service(self): return self.application.service
class CommonRequestHandler(RequestHandler): """Encapsulates shared RequestHandler functionality. """ # Whether the login cookie duration has to be refreshed when # this handler is called. Useful to filter asynchronous # requests. refresh_cookie = True def __init__(self, *args, **kwargs): super(CommonRequestHandler, self).__init__(*args, **kwargs) self.timestamp = None self.sql_session = None self.r_params = None self.contest = None self.url = None def prepare(self): """This method is executed at the beginning of each request. """ super(CommonRequestHandler, self).prepare() self.timestamp = make_datetime() self.set_header("Cache-Control", "no-cache, must-revalidate") self.sql_session = Session() self.sql_session.expire_all() self.url = create_url_builder(get_url_root(self.request.path)) def finish(self, *args, **kwargs): """Finish this response, ending the HTTP request. We override this method in order to properly close the database. TODO - Now that we have greenlet support, this method could be refactored in terms of context manager or something like that. So far I'm leaving it to minimize changes. """ if self.sql_session is not None: try: self.sql_session.close() except Exception as error: logger.warning("Couldn't close SQL connection: %r", error) try: super(CommonRequestHandler, self).finish(*args, **kwargs) except IOError: # When the client closes the connection before we reply, # Tornado raises an IOError exception, that would pollute # our log with unnecessarily critical messages logger.debug("Connection closed before our reply.") @property def service(self): return self.application.service
class CommonRequestHandler(RequestHandler): """Encapsulates shared RequestHandler functionality. """ # Whether the login cookie duration has to be refreshed when # this handler is called. Useful to filter asynchronous # requests. refresh_cookie = True def __init__(self, *args, **kwargs): super(CommonRequestHandler, self).__init__(*args, **kwargs) self.timestamp = None self.sql_session = None self.r_params = None self.contest = None def prepare(self): """This method is executed at the beginning of each request. """ super(CommonRequestHandler, self).prepare() self.timestamp = make_datetime() self.set_header("Cache-Control", "no-cache, must-revalidate") self.sql_session = Session() self.sql_session.expire_all() @property def service(self): return self.application.service def redirect(self, url): url = get_url_root(self.request.path) + url # We would prefer to just use this: # tornado.web.RequestHandler.redirect(self, url) # but unfortunately that assumes it knows the full path to the current # page to generate an absolute URL. This may not be the case if we are # hidden behind a proxy which is remapping part of its URL space to us. self.set_status(302) self.set_header("Location", url) self.finish()
class BaseHandler(CommonRequestHandler): """Base RequestHandler for this application. All the RequestHandler classes in this application should be a child of this class. """ def try_commit(self): """Try to commit the current session. If not successful display a warning in the webpage. return (bool): True if commit was successful, False otherwise. """ try: self.sql_session.commit() except IntegrityError as error: self.application.service.add_notification( make_datetime(), "Operation failed.", "%s" % error) return False else: self.application.service.add_notification( make_datetime(), "Operation successful.", "") return True def safe_get_item(self, cls, ident, session=None): """Get item from database of class cls and id ident, using session if given, or self.sql_session if not given. If id is not found, raise a 404. cls (type): class of object to retrieve. ident (string): id of object. session (Session|None): session to use. return (object): the object with the given id. raise (HTTPError): 404 if not found. """ if session is None: session = self.sql_session entity = cls.get_from_id(ident, session) if entity is None: raise tornado.web.HTTPError(404) return entity def prepare(self): """This method is executed at the beginning of each request. """ # Attempt to update the contest and all its references # If this fails, the request terminates. self.set_header("Cache-Control", "no-cache, must-revalidate") self.sql_session = Session() self.sql_session.expire_all() self.contest = None def render_params(self): """Return the default render params used by almost all handlers. return (dict): default render params """ params = {} params["timestamp"] = make_datetime() params["contest"] = self.contest params["url_root"] = get_url_root(self.request.path) if self.contest is not None: params["phase"] = self.contest.phase(params["timestamp"]) # Keep "== None" in filter arguments. SQLAlchemy does not # understand "is None". params["unanswered"] = self.sql_session.query(Question)\ .join(Participation)\ .filter(Participation.contest_id == self.contest.id)\ .filter(Question.reply_timestamp == None)\ .filter(Question.ignored == False)\ .count() # noqa # TODO: not all pages require all these data. params["contest_list"] = self.sql_session.query(Contest).all() params["task_list"] = self.sql_session.query(Task).all() params["user_list"] = self.sql_session.query(User).all() return params def finish(self, *args, **kwds): """Finish this response, ending the HTTP request. We override this method in order to properly close the database. TODO - Now that we have greenlet support, this method could be refactored in terms of context manager or something like that. So far I'm leaving it to minimize changes. """ self.sql_session.close() try: tornado.web.RequestHandler.finish(self, *args, **kwds) except IOError: # When the client closes the connection before we reply, # Tornado raises an IOError exception, that would pollute # our log with unnecessarily critical messages logger.debug("Connection closed before our reply.") def write_error(self, status_code, **kwargs): if "exc_info" in kwargs and \ kwargs["exc_info"][0] != tornado.web.HTTPError: exc_info = kwargs["exc_info"] logger.error( "Uncaught exception (%r) while processing a request: %s", exc_info[1], ''.join(traceback.format_exception(*exc_info))) # Most of the handlers raise a 404 HTTP error before r_params # is defined. If r_params is not defined we try to define it # here, and if it fails we simply return a basic textual error notice. if self.r_params is None: try: self.r_params = self.render_params() except: self.write("A critical error has occurred :-(") self.finish() return self.render("error.html", status_code=status_code, **self.r_params) get_string = argument_reader(lambda a: a, empty="") # When a checkbox isn't active it's not sent at all, making it # impossible to distinguish between missing and False. def get_bool(self, dest, name): """Parse a boolean. dest (dict): a place to store the result. name (string): the name of the argument and of the item. """ value = self.get_argument(name, False) try: dest[name] = bool(value) except: raise ValueError("Can't cast %s to bool." % value) get_int = argument_reader(parse_int) get_timedelta_sec = argument_reader(parse_timedelta_sec) get_timedelta_min = argument_reader(parse_timedelta_min) get_datetime = argument_reader(parse_datetime) get_ip_address_or_subnet = argument_reader(parse_ip_address_or_subnet) def get_submission_format(self, dest): """Parse the submission format. Using the two arguments "submission_format_choice" and "submission_format" set the "submission_format" item of the given dictionary. dest (dict): a place to store the result. """ choice = self.get_argument("submission_format_choice", "other") if choice == "simple": filename = "%s.%%l" % dest["name"] format_ = [SubmissionFormatElement(filename)] elif choice == "other": value = self.get_argument("submission_format", "[]") if value == "": value = "[]" format_ = [] try: for filename in json.loads(value): format_ += [SubmissionFormatElement(filename)] except ValueError: raise ValueError("Submission format not recognized.") else: raise ValueError("Submission format not recognized.") dest["submission_format"] = format_ def get_time_limit(self, dest, field): """Parse the time limit. Read the argument with the given name and use its value to set the "time_limit" item of the given dictionary. dest (dict): a place to store the result. field (string): the name of the argument to use. """ value = self.get_argument(field, None) if value is None: return if value == "": dest["time_limit"] = None else: try: value = float(value) except: raise ValueError("Can't cast %s to float." % value) if not 0 <= value < float("+inf"): raise ValueError("Time limit out of range.") dest["time_limit"] = value def get_memory_limit(self, dest, field): """Parse the memory limit. Read the argument with the given name and use its value to set the "memory_limit" item of the given dictionary. dest (dict): a place to store the result. field (string): the name of the argument to use. """ value = self.get_argument(field, None) if value is None: return if value == "": dest["memory_limit"] = None else: try: value = int(value) except: raise ValueError("Can't cast %s to float." % value) if not 0 < value: raise ValueError("Invalid memory limit.") dest["memory_limit"] = value def get_task_type(self, dest, name, params): """Parse the task type. Parse the arguments to get the task type and its parameters, and fill them in the "task_type" and "task_type_parameters" items of the given dictionary. dest (dict): a place to store the result. name (string): the name of the argument that holds the task type name. params (string): the prefix of the names of the arguments that hold the parameters. """ name = self.get_argument(name, None) if name is None: raise ValueError("Task type not found.") try: class_ = get_task_type_class(name) except KeyError: raise ValueError("Task type not recognized: %s." % name) params = json.dumps(class_.parse_handler(self, params + name + "_")) dest["task_type"] = name dest["task_type_parameters"] = params def get_score_type(self, dest, name, params): """Parse the score type. Parse the arguments to get the score type and its parameters, and fill them in the "score_type" and "score_type_parameters" items of the given dictionary. dest (dict): a place to store the result. name (string): the name of the argument that holds the score type name. params (string): the name of the argument that hold the parameters. """ name = self.get_argument(name, None) if name is None: raise ValueError("Score type not found.") try: get_score_type_class(name) except KeyError: raise ValueError("Score type not recognized: %s." % name) params = self.get_argument(params, None) if params is None: raise ValueError("Score type parameters not found.") dest["score_type"] = name dest["score_type_parameters"] = params
class BaseHandler(CommonRequestHandler): """Base RequestHandler for this application. All the RequestHandler classes in this application should be a child of this class. """ def try_commit(self): """Try to commit the current session. If not successful display a warning in the webpage. return (bool): True if commit was successful, False otherwise. """ try: self.sql_session.commit() except IntegrityError as error: self.application.service.add_notification(make_datetime(), "Operation failed.", "%s" % error) return False else: self.application.service.add_notification(make_datetime(), "Operation successful.", "") return True def safe_get_item(self, cls, ident, session=None): """Get item from database of class cls and id ident, using session if given, or self.sql_session if not given. If id is not found, raise a 404. cls (type): class of object to retrieve. ident (string): id of object. session (Session|None): session to use. return (object): the object with the given id. raise (HTTPError): 404 if not found. """ if session is None: session = self.sql_session entity = cls.get_from_id(ident, session) if entity is None: raise tornado.web.HTTPError(404) return entity def prepare(self): """This method is executed at the beginning of each request. """ # Attempt to update the contest and all its references # If this fails, the request terminates. self.set_header("Cache-Control", "no-cache, must-revalidate") self.sql_session = Session() self.sql_session.expire_all() self.contest = None def render_params(self): """Return the default render params used by almost all handlers. return (dict): default render params """ params = {} params["timestamp"] = make_datetime() params["contest"] = self.contest params["url_root"] = get_url_root(self.request.path) if self.contest is not None: params["phase"] = self.contest.phase(params["timestamp"]) # Keep "== None" in filter arguments. SQLAlchemy does not # understand "is None". params["unanswered"] = self.sql_session.query(Question)\ .join(Participation)\ .filter(Participation.contest_id == self.contest.id)\ .filter(Question.reply_timestamp == None)\ .filter(Question.ignored == False)\ .count() # noqa # TODO: not all pages require all these data. params["contest_list"] = self.sql_session.query(Contest).all() params["task_list"] = self.sql_session.query(Task).all() params["user_list"] = self.sql_session.query(User).all() params["team_list"] = self.sql_session.query(Team).all() return params def finish(self, *args, **kwds): """Finish this response, ending the HTTP request. We override this method in order to properly close the database. TODO - Now that we have greenlet support, this method could be refactored in terms of context manager or something like that. So far I'm leaving it to minimize changes. """ self.sql_session.close() try: tornado.web.RequestHandler.finish(self, *args, **kwds) except IOError: # When the client closes the connection before we reply, # Tornado raises an IOError exception, that would pollute # our log with unnecessarily critical messages logger.debug("Connection closed before our reply.") def write_error(self, status_code, **kwargs): if "exc_info" in kwargs and \ kwargs["exc_info"][0] != tornado.web.HTTPError: exc_info = kwargs["exc_info"] logger.error( "Uncaught exception (%r) while processing a request: %s", exc_info[1], ''.join(traceback.format_exception(*exc_info))) # Most of the handlers raise a 404 HTTP error before r_params # is defined. If r_params is not defined we try to define it # here, and if it fails we simply return a basic textual error notice. if self.r_params is None: try: self.r_params = self.render_params() except: self.write("A critical error has occurred :-(") self.finish() return self.render("error.html", status_code=status_code, **self.r_params) get_string = argument_reader(lambda a: a, empty="") # When a checkbox isn't active it's not sent at all, making it # impossible to distinguish between missing and False. def get_bool(self, dest, name): """Parse a boolean. dest (dict): a place to store the result. name (string): the name of the argument and of the item. """ value = self.get_argument(name, False) try: dest[name] = bool(value) except: raise ValueError("Can't cast %s to bool." % value) get_int = argument_reader(parse_int) get_timedelta_sec = argument_reader(parse_timedelta_sec) get_timedelta_min = argument_reader(parse_timedelta_min) get_datetime = argument_reader(parse_datetime) get_ip_address_or_subnet = argument_reader(parse_ip_address_or_subnet) def get_submission_format(self, dest): """Parse the submission format. Using the two arguments "submission_format_choice" and "submission_format" set the "submission_format" item of the given dictionary. dest (dict): a place to store the result. """ choice = self.get_argument("submission_format_choice", "other") if choice == "simple": filename = "%s.%%l" % dest["name"] format_ = [SubmissionFormatElement(filename)] elif choice == "other": value = self.get_argument("submission_format", "[]") if value == "": value = "[]" format_ = [] try: for filename in json.loads(value): format_ += [SubmissionFormatElement(filename)] except ValueError: raise ValueError("Submission format not recognized.") else: raise ValueError("Submission format not recognized.") dest["submission_format"] = format_ def get_time_limit(self, dest, field): """Parse the time limit. Read the argument with the given name and use its value to set the "time_limit" item of the given dictionary. dest (dict): a place to store the result. field (string): the name of the argument to use. """ value = self.get_argument(field, None) if value is None: return if value == "": dest["time_limit"] = None else: try: value = float(value) except: raise ValueError("Can't cast %s to float." % value) if not 0 <= value < float("+inf"): raise ValueError("Time limit out of range.") dest["time_limit"] = value def get_memory_limit(self, dest, field): """Parse the memory limit. Read the argument with the given name and use its value to set the "memory_limit" item of the given dictionary. dest (dict): a place to store the result. field (string): the name of the argument to use. """ value = self.get_argument(field, None) if value is None: return if value == "": dest["memory_limit"] = None else: try: value = int(value) except: raise ValueError("Can't cast %s to float." % value) if not 0 < value: raise ValueError("Invalid memory limit.") dest["memory_limit"] = value def get_task_type(self, dest, name, params): """Parse the task type. Parse the arguments to get the task type and its parameters, and fill them in the "task_type" and "task_type_parameters" items of the given dictionary. dest (dict): a place to store the result. name (string): the name of the argument that holds the task type name. params (string): the prefix of the names of the arguments that hold the parameters. """ name = self.get_argument(name, None) if name is None: raise ValueError("Task type not found.") try: class_ = get_task_type_class(name) except KeyError: raise ValueError("Task type not recognized: %s." % name) params = json.dumps(class_.parse_handler(self, params + name + "_")) dest["task_type"] = name dest["task_type_parameters"] = params def get_score_type(self, dest, name, params): """Parse the score type. Parse the arguments to get the score type and its parameters, and fill them in the "score_type" and "score_type_parameters" items of the given dictionary. dest (dict): a place to store the result. name (string): the name of the argument that holds the score type name. params (string): the name of the argument that hold the parameters. """ name = self.get_argument(name, None) if name is None: raise ValueError("Score type not found.") try: get_score_type_class(name) except KeyError: raise ValueError("Score type not recognized: %s." % name) params = self.get_argument(params, None) if params is None: raise ValueError("Score type parameters not found.") dest["score_type"] = name dest["score_type_parameters"] = params def render_params_for_submissions(self, query, page, page_size=50): """Add data about the requested submissions to r_params. submission_query (sqlalchemy.orm.query.Query): the query giving back all interesting submissions. page (int): the index of the page to display. page_size(int): the number of submissions per page. """ query = query\ .options(joinedload(Submission.task))\ .options(joinedload(Submission.participation))\ .options(joinedload(Submission.files))\ .options(joinedload(Submission.token))\ .options(joinedload(Submission.results) .joinedload(SubmissionResult.evaluations))\ .order_by(Submission.timestamp.desc()) offset = page * page_size count = query.count() if self.r_params is None: self.r_params = self.render_params() # A page showing paginated submissions can use these # parameters: total number of submissions, submissions to # display in this page, index of the current page, total # number of pages. self.r_params["submission_count"] = count self.r_params["submissions"] = \ query.slice(offset, offset + page_size).all() self.r_params["submission_page"] = page self.r_params["submission_pages"] = \ (count + page_size - 1) // page_size