Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'beta'

Conflicts:
	api.py
	backstage.py
	config.example.py
	contest.py
	forum.py
	home.py
	judge/__init__.py
	judge/base/__init__.py
	judge/filters/__init__.py
	judge/utils/__init__.py
	lang.py
	main.py
	member.py
	problem.py
	static/css/style.css
	tpl/base/sidebar.html
	tpl/contest.html
	tpl/contest_list.html
	tpl/home.html
	tpl/member.html
	tpl/problem.html
	tpl/problem_list.html
	tpl/settings.html
	tpl/settings_changepass.html
	tpl/signin.html
	tpl/signup.html
	tpl/submit.html
	tpl/topic.html
	tpl/topic_create.html
  • Loading branch information...
commit 9448e968973073c98231b22663bbebb2a452dcd7 2 parents 0c96959 + 85647d2
@fanzeyi authored
Showing with 15,378 additions and 0 deletions.
  1. +5 −0 .gitignore
  2. +24 −0 LICENSES
  3. +9 −0 Makefile
  4. +27 −0 api.py
  5. +253 −0 backstage.py
  6. +35 −0 config.example.py
  7. +74 −0 contest.py
  8. +3 −0  daemons-rq/README.md
  9. +17 −0 daemons-rq/config.example.py
  10. +60 −0 daemons-rq/daemons.py
  11. +18 −0 daemons-rq/run.sh
  12. +199 −0 daemons-rq/tasks.py
  13. +11 −0 daemons/celeryconfig.example.py
  14. +17 −0 daemons/config.example.py
  15. +56 −0 daemons/daemons.py
  16. +200 −0 daemons/tasks.py
  17. +174 −0 forum.py
  18. +49 −0 handlers.py
  19. +32 −0 home.py
  20. +473 −0 i18n/message.po
  21. +473 −0 i18n/message_cn.po
  22. +473 −0 i18n/zh_CN/LC_MESSAGES/message_cn.po
  23. BIN  i18n/zh_CN/LC_MESSAGES/vulpix.mo
  24. +474 −0 i18n/zh_TW/LC_MESSAGES/message_tw.po
  25. BIN  i18n/zh_TW/LC_MESSAGES/vulpix.mo
  26. +3 −0  judge/__init__.py
  27. +215 −0 judge/base/__init__.py
  28. +282 −0 judge/db/__init__.py
  29. +189 −0 judge/db/models.py
  30. +171 −0 judge/filters/__init__.py
  31. +10 −0 judge/utils/__init__.py
  32. +25 −0 lang.py
  33. +28 −0 less/bootstrap/accordion.less
  34. +70 −0 less/bootstrap/alerts.less
  35. +62 −0 less/bootstrap/bootstrap.less
  36. +22 −0 less/bootstrap/breadcrumbs.less
  37. +148 −0 less/bootstrap/button-groups.less
  38. +183 −0 less/bootstrap/buttons.less
  39. +121 −0 less/bootstrap/carousel.less
  40. +18 −0 less/bootstrap/close.less
  41. +57 −0 less/bootstrap/code.less
  42. +18 −0 less/bootstrap/component-animations.less
  43. +130 −0 less/bootstrap/dropdowns.less
  44. +522 −0 less/bootstrap/forms.less
  45. +8 −0 less/bootstrap/grid.less
  46. +20 −0 less/bootstrap/hero-unit.less
  47. +32 −0 less/bootstrap/labels.less
  48. +17 −0 less/bootstrap/layouts.less
  49. +590 −0 less/bootstrap/mixins.less
  50. +83 −0 less/bootstrap/modals.less
  51. +299 −0 less/bootstrap/navbar.less
  52. +353 −0 less/bootstrap/navs.less
  53. +30 −0 less/bootstrap/pager.less
  54. +55 −0 less/bootstrap/pagination.less
  55. +49 −0 less/bootstrap/popovers.less
  56. +95 −0 less/bootstrap/progress-bars.less
  57. +126 −0 less/bootstrap/reset.less
  58. +327 −0 less/bootstrap/responsive.less
  59. +29 −0 less/bootstrap/scaffolding.less
  60. +158 −0 less/bootstrap/sprites.less
  61. +150 −0 less/bootstrap/tables.less
  62. +35 −0 less/bootstrap/thumbnails.less
  63. +35 −0 less/bootstrap/tooltip.less
  64. +218 −0 less/bootstrap/type.less
  65. +23 −0 less/bootstrap/utilities.less
  66. +107 −0 less/bootstrap/variables.less
  67. +17 −0 less/bootstrap/wells.less
  68. +98 −0 less/forum.less
  69. +505 −0 less/style.less
  70. +22 −0 less/vars.less
  71. +62 −0 main.py
  72. +238 −0 member.py
  73. +201 −0 problem.py
  74. +17 −0 runner.sh
  75. +4,451 −0 static/css/style.css
  76. +119 −0 tpl/backstage/add_contest.html
  77. +23 −0 tpl/backstage/add_judger.html
  78. +20 −0 tpl/backstage/add_node.html
  79. +92 −0 tpl/backstage/add_problem.html
  80. +35 −0 tpl/backstage/judger.html
  81. +11 −0 tpl/base.html
  82. +16 −0 tpl/base/breadcrumb.html
  83. +9 −0 tpl/base/head.html
  84. +144 −0 tpl/base/sidebar.html
  85. +118 −0 tpl/contest.html
  86. +34 −0 tpl/contest_list.html
  87. 0  tpl/form.html
  88. +81 −0 tpl/home.html
  89. +105 −0 tpl/macros.html
  90. +51 −0 tpl/member.html
  91. +34 −0 tpl/member_list.html
  92. +88 −0 tpl/problem.html
  93. +36 −0 tpl/problem_list.html
  94. +47 −0 tpl/settings.html
  95. +18 −0 tpl/settings_changepass.html
  96. +49 −0 tpl/signin.html
  97. +53 −0 tpl/signup.html
  98. +122 −0 tpl/submit.html
  99. +38 −0 tpl/submit_list.html
  100. +67 −0 tpl/topic.html
  101. +19 −0 tpl/topic_create.html
  102. +56 −0 tpl/topic_list.html
  103. +12 −0 tpl/widget/count.html
  104. +1 −0  tpl/widget/notice.html
View
5 .gitignore
@@ -1,3 +1,8 @@
*.pyc
config.py
+celeryconfig.py
+privekey.key
upload/
+test.py
+*.log
+dbconvert.py
View
24 LICENSES
@@ -0,0 +1,24 @@
+Copyright (c) 2012, Zeray Rice & Bob Robot.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the <organization> nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
View
9 Makefile
@@ -0,0 +1,9 @@
+# Makefile console for config Vulpix
+# Author: Zeray Rice <fanzeyi1994@gmail.com>
+
+runner=$(EUID)
+
+less:
+ lessc less/style.less > static/css/style.css
+run:
+ ./runner.sh
View
27 api.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# AUTHOR: Zeray Rice <fanzeyi1994@gmail.com>
+# FILE: api.py
+# CREATED: 00:26:38 16/03/2012
+# MODIFIED: 00:30:53 16/03/2012
+
+from tornado.web import HTTPError
+from tornado.web import authenticated
+
+from judge.db import Problem
+from judge.db import ProblemDBMixin
+from judge.base import BaseHandler
+
+class GetProblemHandler(BaseHandler, ProblemDBMixin):
+ @authenticated
+ def get(self, pid):
+ try:
+ pid = int(pid)
+ except ValueError:
+ raise HTTPError(404)
+ problem = self.select_problem_by_id(pid)
+ if not problem:
+ raise HTTPError(404)
+ self.write(problem.title)
+ self.finish()
+
+__all__ = ["GetProblemHandler"]
View
253 backstage.py
@@ -0,0 +1,253 @@
+# -*- coding: utf-8 -*-
+# AUTHOR: Zeray Rice <fanzeyi1994@gmail.com>
+# FILE: backstage.py
+# CREATED: 02:43:49 15/03/2012
+# MODIFIED: 18:31:05 18/04/2012
+
+import re
+import datetime
+import functools
+from sqlalchemy.orm.exc import NoResultFound
+
+from tornado.web import HTTPError
+
+from judge.db import Node
+from judge.db import Judger
+from judge.db import Contest
+from judge.db import Problem
+from judge.db import ForumDBMixin
+from judge.db import JudgerDBMixin
+from judge.db import ContestDBMixin
+from judge.db import ProblemDBMixin
+from judge.base import BaseHandler
+
+def backstage(method):
+ """Decorate methods with this to require that user be NOT logged in"""
+ @functools.wraps(method)
+ def wrapper(self, *args, **kwargs):
+ if self.current_user:
+ if self.current_user.admin:
+ return method(self, *args, **kwargs)
+ raise HTTPError(404)
+ return wrapper
+
+class AddProblemHandler(BaseHandler, ProblemDBMixin):
+ @backstage
+ def get(self):
+ title = self._("Add Problem")
+ pid = self.get_argument("pid", default = None)
+ problem = None
+ if pid:
+ title = self._("Edit Problem")
+ problem = self.select_problem_by_id(pid)
+ if not problem:
+ raise HTTPError(404)
+ problem.content = self.xhtml_escape(problem.content)
+ tagquery = self.select_problem_tag_by_problem_id(problem.id)
+ tags = []
+ for tag in tagquery:
+ tags.append(tag.tagname)
+ self.render("backstage/add_problem.html", locals())
+ @backstage
+ def post(self):
+ probtitle = self.get_argument('probtitle', default = None)
+ shortname = self.get_argument('shortname', default = None)
+ timelimit = self.get_argument('timelimit', default = 1000)
+ memlimit = self.get_argument('memlimit', default = 128)
+ testpoint = self.get_argument('testpoint', default = None)
+ invisible = self.get_argument('invisible', default = 0)
+ content = self.get_argument('content', default = None)
+ pid = self.get_argument('pid', default = 0)
+ tags = self.get_arguments('tags[]')
+ tags = map(self.xhtml_escape, tags)
+ problem = Problem()
+ error = []
+ error.extend(self.check_text_value(probtitle, self._("Title"), required = True))
+ error.extend(self.check_text_value(shortname, self._("Short Name"), required = True))
+ error.extend(self.check_text_value(timelimit, self._("Time Limit"), required = True, is_num = True))
+ error.extend(self.check_text_value(memlimit, self._("Memory Limit"), required = True, is_num = True))
+ error.extend(self.check_text_value(testpoint, self._("Testpoint"), required = True, is_num = True))
+ error.extend(self.check_text_value(invisible, self._("Invisible"), is_num = True, vaild = [0, 1]))
+ error.extend(self.check_text_value(content, self._("Content"), max = 1000000, required = True))
+ error.extend(self.check_text_value(testpoint, self._("Test Point"), required = True))
+ problem.id = int(pid)
+ problem.title = self.xhtml_escape(probtitle)
+ problem.shortname = self.xhtml_escape(shortname)
+ problem.timelimit = self.xhtml_escape(timelimit)
+ problem.memlimit = self.xhtml_escape(memlimit)
+ problem.testpoint = self.xhtml_escape(testpoint)
+ problem.invisible = self.xhtml_escape(invisible)
+ if error:
+ problem.content = self.xhtml_escape(content)
+ title = self._("Edit Problem")
+ self.render("backstage/add_problem.html", locals())
+ return
+ problem.content = content
+ if problem.id:
+ self.update_problem(problem)
+ self.delete_problem_tag_by_problem_id(problem.id)
+ else:
+ self.insert_problem(problem)
+ for tag in tags:
+ self.insert_problem_tag(tag, problem.id)
+ self.redirect('/problem/%d' % problem.id)
+
+class AddContestHandler(BaseHandler, ProblemDBMixin, ContestDBMixin):
+ @backstage
+ def get(self):
+ title = self._("Add Contest")
+ cid = self.get_argument("cid", default = 0)
+ contest = None
+ if cid:
+ contest = self.select_contest_by_id(cid)
+ title = self._("Edit Contest")
+ related_problem = self.select_contest_problem_by_contest_id(contest.id)
+ self.render("backstage/add_contest.html", locals())
+ @backstage
+ def post(self):
+ title = self.get_argument("title", default = "")
+ description = self.get_argument("description", default = "")
+ start_time = self.get_argument("start_time", default = "")
+ end_time = self.get_argument("end_time", default = "")
+ invisible = self.get_argument("invisible", default = 0)
+ cid = self.get_argument("cid", default = None)
+ related_problem = self.get_arguments("link_problem[]")
+ contest = Contest()
+ contest.id = cid
+ error = []
+ error.extend(self.check_text_value(title, self._("Title"), required = True, max = 100))
+ error.extend(self.check_text_value(description, self._("Description"), max = 20000))
+ try:
+ start_datetime = datetime.datetime.strptime(start_time, "%m/%d/%Y %H:%M")
+ end_datetime = datetime.datetime.strptime(end_time, "%m/%d/%Y %H:%M")
+ except ValueError:
+ error.append(self._("Start/End Time Format is Invalid."))
+ else:
+ if start_datetime > end_datetime:
+ error.append(self._("Start/End Time is invalid."))
+ contest.title = self.xhtml_escape(title)
+ contest.description = self.xhtml_escape(description)
+ contest.start_time = self.xhtml_escape(start_time)
+ contest.end_time = self.xhtml_escape(end_time)
+ contest.invisible = self.xhtml_escape(invisible)
+ contest.related_problem = related_problem
+ if error:
+ title = self._("Add Contest")
+ related_problem = []
+ for pid in contest.related_problem:
+ related_problem.append(self.select_problem_by_id(pid))
+ self.render("backstage/contest_create.html", locals())
+ return
+ contest.start_time = start_datetime
+ contest.end_time = end_datetime
+ if contest.id:
+ self.update_contest(contest)
+ self.delete_contest_problem_by_contest_id(contest.id)
+ else:
+ self.insert_contest(contest)
+ for pid in contest.related_problem:
+ self.insert_contest_problem(contest.id, pid)
+ self.redirect("/contest/%d" % int(contest.id))
+
+class AddNodeHandler(BaseHandler, ForumDBMixin):
+ @backstage
+ def get(self):
+ title = self._("Add Node")
+ nid = self.get_argument("nid", default = 0)
+ node = None
+ try:
+ nid = int(nid)
+ except ValueError:
+ raise HTTPError(404)
+ if nid:
+ node = self.select_node_by_id(nid)
+ if not node:
+ raise HTTPError(404)
+ title = self._("Edit Node")
+ self.render("backstage/add_node.html", locals())
+ @backstage
+ def post(self):
+ name = self.get_argument("name", default = "")
+ link = self.get_argument("link", default = "").lower()
+ nid = self.get_argument("nid", default = 0)
+ error = []
+ node = Node()
+ error.extend(self.check_text_value(name, self._("Name"), required = True, max = 100))
+ error.extend(self.check_text_value(link, self._("Link"), required = True, max = 100))
+ if not error:
+ try:
+ duplinode = self.select_node_by_link(link)
+ except NoResultFound:
+ pass
+ else:
+ error.append(self._("This link have taken."))
+ if nid:
+ node.id = nid
+ node.name = name
+ node.link = link
+ node.description = ""
+ if error:
+ title = self._("Edit Node")
+ self.render("backstage/add_node.html", locals())
+ return
+ if node.id:
+ self.update_node(node)
+ else:
+ self.insert_node(node)
+ self.redirect("/go/%s" % node.link)
+
+class ManageJudgerHandler(BaseHandler, JudgerDBMixin):
+ @backstage
+ def get(self):
+ title = self._("Judger Manage")
+ judgers = self.select_judgers()
+ self.render("backstage/judger.html", locals())
+
+class AddJudgerHandler(BaseHandler, JudgerDBMixin):
+ @backstage
+ def get(self):
+ title = self._("Add Judger")
+ jid = self.get_argument("jid", default = 0)
+ judger = None
+ try:
+ jid = int(jid)
+ except ValueError:
+ raise HTTPError(404)
+ if jid:
+ judger = self.select_judger_by_id(jid)
+ if not judger:
+ raise HTTPError(404)
+ title = self._("Edit Judger")
+ self.render("backstage/add_judger.html", locals())
+ @backstage
+ def post(self):
+ name = self.get_argument("name", default = "")
+ description = self.get_argument("description", default = "")
+ path = self.get_argument("path", default = "")
+ priority = self.get_argument("priority", default = 0)
+ pubkey = self.get_argument("pubkey", default = "")
+ jid = self.get_argument("jid", default = 0)
+ judger = Judger()
+ error = []
+ error.extend(self.check_text_value(name, self._("Name"), required = True, min = 1, max = 100))
+ error.extend(self.check_text_value(description, self._("Description"), max = 5000))
+ error.extend(self.check_text_value(path, self._("Path"), required = True, regex = re.compile(r"^http://(.*)"), max = 200))
+ error.extend(self.check_text_value(priority, self._("Priority"), is_num = True))
+ error.extend(self.check_text_value(pubkey, self._("RSA Public Key"), required = True))
+ judger.id = jid
+ judger.name = self.xhtml_escape(name)
+ judger.description = self.xhtml_escape(description)
+ judger.path = self.xhtml_escape(path)
+ judger.priority = priority
+ judger.pubkey = self.xhtml_escape(pubkey)
+ if error:
+ title = self._("Edit Judger")
+ self.render("backstage/add_judger.html", locals())
+ return
+ if judger.id:
+ self.update_judger(judger)
+ else:
+ self.insert_judger(judger)
+ self.redirect("/backstage/judger")
+
+__all__ = ["backstage", "AddProblemHandler", "AddContestHandler", "ManageJudgerHandler", "AddJudgerHandler", "AddNodeHandler"]
View
35 config.example.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+# AUTHOR: Zeray Rice <fanzeyi1994@gmail.com>
+# FILE: config.example.py
+# CREATED: 02:35:50 08/03/2012
+# MODIFIED: 02:36:05 08/03/2012
+# DESCRIPTION: site config
+
+import os
+
+mysql_config = {
+ 'mysql_host' : '',
+ 'mysql_user' : '',
+ 'mysql_password' : '',
+ 'mysql_database' : ''
+}
+
+accept_lang = {
+ 'zh_cn' : 'zh_CN',
+ 'en' : 'en_US',
+}
+
+site_config = {
+ 'site_title' : u'Online Judge',
+ 'base_domain' : '',
+ 'login_url' : '/signin',
+ 'template_path' : os.path.join(os.path.dirname(__file__), 'tpl'),
+ 'static_path' : os.path.join(os.path.dirname(__file__), "static"),
+ 'i18n_path' : os.path.join(os.path.dirname(__file__), 'i18n'),
+ 'xsrf_cookies' : True,
+ 'cookie_secret' : '', #
+ 'bcrypt_salt' : '', # import bcrypt; bcrypt.gensalt(log_rounds=4)
+ 'default_mail' : 'no-reply@fanhe.org',
+ 'mail_server' : '127.0.0.1',
+}
+
View
74 contest.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+# AUTHOR: Zeray Rice <fanzeyi1994@gmail.com>
+# FILE: contest.py
+# CREATED: 15:46:17 15/03/2012
+# MODIFIED: 14:50:32 16/03/2012
+
+import datetime
+
+from tornado.web import HTTPError
+from tornado.web import authenticated
+
+from judge.db import Contest
+from judge.db import ContestDBMixin
+from judge.base import BaseHandler
+
+def get_contest_status(contest):
+ status = 0
+ now = datetime.datetime.now()
+ if now >= contest.start_time and now <= contest.end_time:
+ status = 1 #"Running"
+ elif now <= contest.start_time:
+ status = 2 #"Waiting"
+ elif now >= contest.end_time:
+ status = 3 #"Finished"
+ if contest.invisible:
+ status = 4 #"Invisible"
+ if not status:
+ status = 5 #"Unknown"
+ return status
+
+class ViewContestHandler(BaseHandler, ContestDBMixin):
+ @authenticated
+ def get(self, cid):
+ try:
+ cid = int(cid)
+ except ValueError:
+ raise HTTPError(404)
+ contest = self.select_contest_by_id(cid)
+ if not contest:
+ raise HTTPError(404)
+ now = datetime.datetime.now()
+ contest.status = get_contest_status(contest)
+ problems = self.select_contest_problem_by_contest_id(cid)
+ for problem in problems:
+ problem.submit = self.select_contest_submit_by_contest_id_problem_id_user_id(cid, problem.problem_id)
+ breadcrumb = []
+ breadcrumb.append((self._('Home'), '/'))
+ breadcrumb.append((self._('Contest'), '/contest'))
+ breadcrumb.append((contest.title, '/contest/%d' % contest.id))
+ self.render("contest.html", locals())
+
+class ListContestHandlder(BaseHandler, ContestDBMixin):
+ def get(self):
+ start = self.get_argument("start", default = 0)
+ title = self._("Contest List")
+ try:
+ start = int(start)
+ except ValueError:
+ start = 0
+ if self.current_user and self.current_user.admin:
+ count = self.count_contest()
+ contests = self.select_contest(start = start)
+ else:
+ count = self.count_visible_contest()
+ contests = self.select_visible_contest(start = start)
+ breadcrumb = []
+ breadcrumb.append((self._('Home'), '/'))
+ breadcrumb.append((self._('Contest'), '/contest'))
+ pages = self.get_page_count(count)
+ for contest in contests:
+ contest.status = get_contest_status(contest)
+ self.render("contest_list.html", locals())
+
+__all__ = ["ViewContestHandler", "ListContestHandlder"]
View
3  daemons-rq/README.md
@@ -0,0 +1,3 @@
+此评测进程依赖 [RQ](https://github.com/nvie/rq) 作为任务队列系统,相比 Celery 更轻便,只依赖 [Redis](http://redis.io/)。
+
+同时可以通过 [rq-dashboard](https://github.com/nvie/rq-dashboard) 来监测队列执行情况。
View
17 daemons-rq/config.example.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+# AUTHOR: Zeray Rice <fanzeyi1994@gmail.com>
+# FILE: config.py
+# CREATED: 02:21:18 09/06/2012
+# MODIFIED: 02:26:57 09/06/2012
+
+COMPILE_DIR = "/home/fanzeyi/cogs_data/comp/"
+TESTDATA_DIR = "/home/fanzeyi/cogs_data/testdata/"
+CARETAKER_PATH = "/usr/local/bin/taker"
+DAEMON_PORT = 8889
+SALT = ""
+
+MYSQL_HOST = "localhost"
+MYSQL_PORT = 3306
+MYSQL_USER = "root"
+MYSQL_PASS = ""
+MYSQL_DB = ""
View
60 daemons-rq/daemons.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+# AUTHOR: Zeray Rice <fanzeyi1994@gmail.com>
+# FILE: daemons.py
+# CREATED: 02:48:06 18/03/2012
+# MODIFIED: 02:23:16 09/06/2012
+
+import time
+import hashlib
+import logging
+import simplejson as json
+from rq import Queue
+from rq import use_connection
+from operator import itemgetter
+
+import tornado.web
+import tornado.ioloop
+import tornado.options
+from tornado.web import HTTPError
+
+tornado.options.parse_command_line()
+
+from tasks import judge
+from config import SALT
+from config import DAEMON_PORT
+
+REQURED_ARGS = ["code", "lang", "filename", "shortname", "timelimit", "memlimit", "testpoint", "sign", "time"]
+use_connection()
+
+class ReceiveQueryHandler(tornado.web.RequestHandler):
+ def post(self):
+ query = self.get_argument("query", default = None)
+ if not query:
+ logging.error("Invalid Post Query.")
+ raise HTTPError(404)
+ query = json.loads(query)
+ for key in REQURED_ARGS:
+ if key not in query.keys():
+ logging.error("Require key %s is missing" % key)
+ raise HTTPError(404)
+ query_sign = query["sign"]
+ query.pop("sign")
+ now = time.time()
+ if abs(now - query["time"]) > 300:
+ logging.error("Time is invalid!")
+ raise HTTPError(404)
+ query = dict(sorted(query.iteritems(), key=itemgetter(1)))
+ sign = hashlib.sha1(json.dumps(query) + SALT).hexdigest()
+ if not sign == query_sign:
+ logging.error("Signature is invalid")
+ raise HTTPError(404)
+ q = Queue('judge')
+ q.enqueue(judge, query)
+
+application = tornado.web.Application([
+ (r"/", ReceiveQueryHandler),
+])
+
+if __name__ == "__main__":
+ application.listen(int(DAEMON_PORT))
+ tornado.ioloop.IOLoop.instance().start()
View
18 daemons-rq/run.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+# AUTHOR: Zeray Rice <fanzeyi1994@gmail.com>
+# FILE: run.sh
+# CREATED: 02:25:51 09/06/2012
+# MODIFIED: 18:17:17 09/06/2012
+
+if [ "$(id -u)" != "0" ]; then
+ echo "This script must be run as root" 1>&2
+ exit 1
+fi
+
+if [ "$(ps -A | grep redis-server | wc -l)" == "0" ]; then
+ echo "Redis Server is not running!" 1>&2
+ exit 1
+fi
+
+rqworker judge 2>&1 >> judge.log &
+python2 daemons.py 2>&1 >> daemons.log &
View
199 daemons-rq/tasks.py
@@ -0,0 +1,199 @@
+# -*- coding: utf-8 -*-
+# AUTHOR: Zeray Rice <fanzeyi1994@gmail.com>
+# FILE: tasks.py
+# CREATED: 02:27:12 17/03/2012
+# MODIFIED: 02:20:05 09/06/2012
+
+import os
+import MySQLdb
+import subprocess
+from time import sleep
+from MySQLdb import escape_string
+
+from config import COMPILE_DIR
+from config import TESTDATA_DIR
+from config import CARETAKER_PATH
+
+from config import MYSQL_DB
+from config import MYSQL_HOST
+from config import MYSQL_PORT
+from config import MYSQL_USER
+from config import MYSQL_PASS
+
+STATUS_DICT = {
+ "A" : 1,
+ "W" : 2,
+ "T" : 3,
+ "M" : 4,
+ "R" : 5,
+ "C" : 6,
+ "N" : 7,
+}
+_e = lambda a: escape_string(str(a))
+
+def _clean(dir_path):
+ for root, dirs, files in os.walk(dir_path):
+ for name in files:
+ os.remove(os.path.join(root, name))
+
+def _compile_cmd(lang, filename):
+ lang = int(lang)
+ if lang == 1:
+ return "fpc %s -So -XS -v0 -O1 -o\"a.out\"" % filename
+ elif lang == 2:
+ return "gcc %s -lm -w -static -o a.out" % filename
+ elif lang == 3:
+ return "g++ %s -lm -static -o a.out" % filename
+
+def _compile(result, query):
+ _clean(COMPILE_DIR)
+ os.chdir(COMPILE_DIR)
+ with open(COMPILE_DIR + query['filename'], "w+") as code:
+ code.write(query['code'].encode("utf-8"))
+ cmd = " ".join(["timeout 30", _compile_cmd(query['lang'], query['filename'])])
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
+ (stdoutput,erroutput) = proc.communicate()
+ result['msg'] = "".join(["STDOUT:\n--------\n", stdoutput, "\n--------\nSTDERR\n--------\n", erroutput])
+ result['cmd'] = cmd
+ if proc.returncode != 0:
+ result["compilesucc"] = 0
+ else:
+ result["compilesucc"] = 1
+ return result
+
+def _get_input_file(tp, shortname):
+ with open(os.path.join(TESTDATA_DIR, shortname, shortname + str(tp+1) + ".in")) as input_data_p:
+ input_data = input_data_p.read()
+ with open(os.path.join(COMPILE_DIR, shortname + ".in"), "w") as input_data_p:
+ input_data_p.write(input_data)
+
+def _run(result, query, tp):
+ shortname = query["shortname"]
+ os.chdir(COMPILE_DIR)
+ cmd = " ".join([CARETAKER_PATH, "--input=%s.in" % query["shortname"], \
+ "--output=%s.out" % query["shortname"], \
+ "--time=%s" % str(query["timelimit"]), \
+ "--memory=%s" % str(query["memlimit"] * 1024), \
+ "a.out"])
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
+ (stdoutput,erroutput) = proc.communicate()
+ testresult = ""
+ wexit = 0
+ time = 0
+ memory = 0
+ if proc.returncode:
+ if proc.returncode == 251:
+ testresult = "T"
+ elif proc.returncode == 252:
+ testresult = "M"
+ elif proc.returncode == 253:
+ testresult = "R"
+ else:
+ wexit, time, memory = stdoutput.split("\n")[-2].split(" ")
+ wexit = int(wexit)
+ time = int(time)
+ memory = int(memory)
+ if wexit != 0:
+ testresult = "W"
+ else:
+ # cmp output
+ with open(os.path.join(TESTDATA_DIR, shortname, shortname + str(tp+1) + ".ans")) as answer_fp:
+ answer = answer_fp.read()
+ try:
+ with open(os.path.join(COMPILE_DIR, shortname + ".out")) as prg_answer_fp:
+ prg_answer = prg_answer_fp.read()
+ except IOError:
+ testresult = "N"
+ else:
+ if _compare(answer, prg_answer):
+ testresult = "A"
+ result["score"] = result["score"] + 10
+ else:
+ testresult = "W"
+ result["testpoint"].append((testresult, time, memory))
+
+def _compare(fout, fans):
+ out = [line.strip() for line in fout if line.strip()]
+ ans = [line.strip() for line in fans if line.strip()]
+ return ans == out
+
+def _get_status(testpoint):
+ for tp in testpoint:
+ if tp != "A":
+ return STATUS_DICT[tp]
+ return STATUS_DICT["A"]
+
+def _return_result(result, query):
+ # connect to server
+ conn = MySQLdb.connect(host = MYSQL_HOST, user = MYSQL_USER, passwd = MYSQL_PASS, db = MYSQL_DB)
+ cur = conn.cursor()
+ testpoint = ""
+ timecost = ""
+ memorycost = ""
+ totaltime = 0
+ totalmemory = 0
+ if result["compilesucc"] == 0:
+ status = 6
+ else:
+ # update
+ testpoint = []
+ timecost = []
+ memorycost = []
+ totaltime = 0
+ totalmemory = 0
+ for tp in result["testpoint"]:
+ testpoint.append(tp[0])
+ timecost.append(str(tp[1]))
+ memorycost.append(str(tp[2]))
+ totaltime = totaltime + tp[1]
+ totalmemory = totalmemory + tp[2]
+ testpoint = "".join(testpoint)
+ timecost = ",".join(timecost)
+ memorycost = ",".join(memorycost)
+ status = _get_status(testpoint)
+ cur.execute("""UPDATE `submit` SET `status` = %s,
+ `testpoint` = %s,
+ `testpoint_time` = %s,
+ `testpoint_memory` = %s,
+ `score` = %s,
+ `costtime` = %s,
+ `costmemory` = %s,
+ `msg` = %s
+ WHERE `id` = %s """, (_e(status), _e(testpoint), _e(timecost), _e(memorycost), \
+ _e(result["score"]), _e(totaltime), _e(totalmemory), \
+ _e(result["msg"]), _e(query["id"])))
+ conn.commit()
+ cur.close()
+
+'''
+
+Query String Format:
+
+id - Required. Submit id.
+code - Required. Code file content.
+lang - Required. Code language. ( 1 - Pascal, 2 - C, 3 - C++ )
+filename - Required. Code file filename.
+shortname - Required. Problem shortname.
+timelimit - Required. Problem time limit. (MB)
+memlimit - Required. Problem memory limit.(ms)
+testpoint - Required. Problem testpoint number.
+sign - Signature.
+time - For signature.
+
+'''
+
+def judge(query):
+ result = {}
+ _compile(result, query)
+ if result["compilesucc"] == 0:
+ result["score"] = 0
+ _return_result(result, query)
+ result["testpoint"] = []
+ result["score"] = 0
+ if result["compilesucc"]:
+ for tp in range(query["testpoint"]):
+ _get_input_file(tp, query["shortname"])
+ _run(result, query, tp)
+ _return_result(result, query)
+ return 0
+
View
11 daemons/celeryconfig.example.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# AUTHOR: Zeray Rice <fanzeyi1994@gmail.com>
+# FILE: celeryconfig.py
+# CREATED: 02:26:56 17/03/2012
+# MODIFIED: 02:58:41 17/03/2012
+
+CELERY_RESULT_BACKEND = "amqp"
+CELERYD_CONCURRENCY = 1
+BROKER_URL = "amqp://guest:guest@localhost:5672/"
+
+CELERY_IMPORTS = ("tasks", )
View
17 daemons/config.example.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+# AUTHOR: Zeray Rice <fanzeyi1994@gmail.com>
+# FILE: config.py
+# CREATED: 02:35:44 18/03/2012
+# MODIFIED: 00:04:42 19/03/2012
+
+COMPILE_DIR = "/home/fanzeyi/cogs_data/comp/"
+TESTDATA_DIR = "/home/fanzeyi/cogs_data/testdata/"
+CARETAKER_PATH = "/usr/local/bin/taker"
+DAEMON_PORT = 8889
+SALT = ""
+
+MYSQL_HOST = ""
+MYSQL_PORT = 3306
+MYSQL_USER = ""
+MYSQL_PASS = ""
+MYSQL_DB = ""
View
56 daemons/daemons.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+# AUTHOR: Zeray Rice <fanzeyi1994@gmail.com>
+# FILE: daemons.py
+# CREATED: 02:48:06 18/03/2012
+# MODIFIED: 01:35:35 19/03/2012
+
+import time
+import hashlib
+import logging
+import simplejson as json
+from operator import itemgetter
+
+import tornado.web
+import tornado.ioloop
+import tornado.options
+from tornado.web import HTTPError
+
+tornado.options.parse_command_line()
+
+from tasks import judge
+from config import SALT
+from config import DAEMON_PORT
+
+REQURED_ARGS = ["code", "lang", "filename", "shortname", "timelimit", "memlimit", "testpoint", "sign", "time"]
+
+class ReceiveQueryHandler(tornado.web.RequestHandler):
+ def post(self):
+ query = self.get_argument("query", default = None)
+ if not query:
+ logging.error("Invalid Post Query.")
+ raise HTTPError(404)
+ query = json.loads(query)
+ for key in REQURED_ARGS:
+ if key not in query.keys():
+ logging.error("Require key %s is missing" % key)
+ raise HTTPError(404)
+ query_sign = query["sign"]
+ query.pop("sign")
+ now = time.time()
+ if abs(now - query["time"]) > 300:
+ logging.error("Time is invalid!")
+ raise HTTPError(404)
+ query = dict(sorted(query.iteritems(), key=itemgetter(1)))
+ sign = hashlib.sha1(json.dumps(query) + SALT).hexdigest()
+ if not sign == query_sign:
+ logging.error("Signature is invalid")
+ raise HTTPError(404)
+ judge.delay(query)
+
+application = tornado.web.Application([
+ (r"/", ReceiveQueryHandler),
+])
+
+if __name__ == "__main__":
+ application.listen(int(DAEMON_PORT))
+ tornado.ioloop.IOLoop.instance().start()
View
200 daemons/tasks.py
@@ -0,0 +1,200 @@
+# -*- coding: utf-8 -*-
+# AUTHOR: Zeray Rice <fanzeyi1994@gmail.com>
+# FILE: tasks.py
+# CREATED: 02:27:12 17/03/2012
+# MODIFIED: 03:02:52 18/04/2012
+
+import os
+import MySQLdb
+import subprocess
+from time import sleep
+from celery.task import task
+from MySQLdb import escape_string
+
+from config import COMPILE_DIR
+from config import TESTDATA_DIR
+from config import CARETAKER_PATH
+
+from config import MYSQL_DB
+from config import MYSQL_HOST
+from config import MYSQL_PORT
+from config import MYSQL_USER
+from config import MYSQL_PASS
+
+STATUS_DICT = {
+ "A" : 1,
+ "W" : 2,
+ "T" : 3,
+ "M" : 4,
+ "R" : 5,
+ "C" : 6,
+ "N" : 7,
+}
+_e = lambda a: escape_string(str(a))
+
+def _clean(dir_path):
+ for root, dirs, files in os.walk(dir_path):
+ for name in files:
+ os.remove(os.path.join(root, name))
+
+def _compile_cmd(lang, filename):
+ lang = int(lang)
+ if lang == 1:
+ return "fpc %s -So -XS -v0 -O1 -o\"a.out\"" % filename
+ elif lang == 2:
+ return "gcc %s -lm -w -static -o a.out" % filename
+ elif lang == 3:
+ return "g++ %s -lm -static -o a.out" % filename
+
+def _compile(result, query):
+ _clean(COMPILE_DIR)
+ os.chdir(COMPILE_DIR)
+ with open(COMPILE_DIR + query['filename'], "w+") as code:
+ code.write(query['code'].encode("utf-8"))
+ cmd = " ".join(["timeout 30", _compile_cmd(query['lang'], query['filename'])])
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
+ (stdoutput,erroutput) = proc.communicate()
+ result['msg'] = "".join(["STDOUT:\n--------\n", stdoutput, "\n--------\nSTDERR\n--------\n", erroutput])
+ result['cmd'] = cmd
+ if proc.returncode != 0:
+ result["compilesucc"] = 0
+ else:
+ result["compilesucc"] = 1
+ return result
+
+def _get_input_file(tp, shortname):
+ with open(os.path.join(TESTDATA_DIR, shortname, shortname + str(tp+1) + ".in")) as input_data_p:
+ input_data = input_data_p.read()
+ with open(os.path.join(COMPILE_DIR, shortname + ".in"), "w") as input_data_p:
+ input_data_p.write(input_data)
+
+def _run(result, query, tp):
+ shortname = query["shortname"]
+ os.chdir(COMPILE_DIR)
+ cmd = " ".join([CARETAKER_PATH, "--input=%s.in" % query["shortname"], \
+ "--output=%s.out" % query["shortname"], \
+ "--time=%s" % str(query["timelimit"]), \
+ "--memory=%s" % str(query["memlimit"] * 1024), \
+ "a.out"])
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
+ (stdoutput,erroutput) = proc.communicate()
+ testresult = ""
+ wexit = 0
+ time = 0
+ memory = 0
+ if proc.returncode:
+ if proc.returncode == 251:
+ testresult = "T"
+ elif proc.returncode == 252:
+ testresult = "M"
+ elif proc.returncode == 253:
+ testresult = "R"
+ else:
+ wexit, time, memory = stdoutput.split("\n")[-2].split(" ")
+ wexit = int(wexit)
+ time = int(time)
+ memory = int(memory)
+ if wexit != 0:
+ testresult = "W"
+ else:
+ # cmp output
+ with open(os.path.join(TESTDATA_DIR, shortname, shortname + str(tp+1) + ".ans")) as answer_fp:
+ answer = answer_fp.read()
+ try:
+ with open(os.path.join(COMPILE_DIR, shortname + ".out")) as prg_answer_fp:
+ prg_answer = prg_answer_fp.read()
+ except IOError:
+ testresult = "N"
+ else:
+ if _compare(answer, prg_answer):
+ testresult = "A"
+ result["score"] = result["score"] + 10
+ else:
+ testresult = "W"
+ result["testpoint"].append((testresult, time, memory))
+
+def _compare(fout, fans):
+ out = [line.strip() for line in fout if line.strip()]
+ ans = [line.strip() for line in fans if line.strip()]
+ return ans == out
+
+def _get_status(testpoint):
+ for tp in testpoint:
+ if tp != "A":
+ return STATUS_DICT[tp]
+ return STATUS_DICT["A"]
+
+def _return_result(result, query):
+ # connect to server
+ conn = MySQLdb.connect(host = MYSQL_HOST, user = MYSQL_USER, passwd = MYSQL_PASS, db = MYSQL_DB)
+ cur = conn.cursor()
+ testpoint = ""
+ timecost = ""
+ memorycost = ""
+ totaltime = 0
+ totalmemory = 0
+ if result["compilesucc"] == 0:
+ status = 6
+ else:
+ # update
+ testpoint = []
+ timecost = []
+ memorycost = []
+ totaltime = 0
+ totalmemory = 0
+ for tp in result["testpoint"]:
+ testpoint.append(tp[0])
+ timecost.append(str(tp[1]))
+ memorycost.append(str(tp[2]))
+ totaltime = totaltime + tp[1]
+ totalmemory = totalmemory + tp[2]
+ testpoint = "".join(testpoint)
+ timecost = ",".join(timecost)
+ memorycost = ",".join(memorycost)
+ status = _get_status(testpoint)
+ cur.execute("""UPDATE `submit` SET `status` = %s,
+ `testpoint` = %s,
+ `testpoint_time` = %s,
+ `testpoint_memory` = %s,
+ `score` = %s,
+ `costtime` = %s,
+ `costmemory` = %s,
+ `msg` = %s
+ WHERE `id` = %s """, (_e(status), _e(testpoint), _e(timecost), _e(memorycost), \
+ _e(result["score"]), _e(totaltime), _e(totalmemory), \
+ _e(result["msg"]), _e(query["id"])))
+ conn.commit()
+ cur.close()
+
+'''
+
+Query String Format:
+
+id - Required. Submit id.
+code - Required. Code file content.
+lang - Required. Code language. ( 1 - Pascal, 2 - C, 3 - C++ )
+filename - Required. Code file filename.
+shortname - Required. Problem shortname.
+timelimit - Required. Problem time limit. (MB)
+memlimit - Required. Problem memory limit.(ms)
+testpoint - Required. Problem testpoint number.
+sign - Signature.
+time - For signature.
+
+'''
+
+@task
+def judge(query):
+ result = {}
+ _compile(result, query)
+ if result["compilesucc"] == 0:
+ result["score"] = 0
+ _return_result(result, query)
+ result["testpoint"] = []
+ result["score"] = 0
+ if result["compilesucc"]:
+ for tp in range(query["testpoint"]):
+ _get_input_file(tp, query["shortname"])
+ _run(result, query, tp)
+ _return_result(result, query)
+ return 0
View
174 forum.py
@@ -0,0 +1,174 @@
+# -*- coding: utf-8 -*-
+# AUTHOR: Zeray Rice <fanzeyi1994@gmail.com>
+# FILE: forum.py
+# CREATED: 22:39:44 17/04/2012
+# MODIFIED: 18:52:37 18/04/2012
+
+import datetime
+from tornado.web import HTTPError
+from tornado.web import authenticated
+from sqlalchemy.orm.exc import NoResultFound
+
+from judge.db import Node
+from judge.db import Reply
+from judge.db import Topic
+from judge.db import ForumDBMixin
+from judge.base import BaseHandler
+
+class ViewNodeHandler(BaseHandler, ForumDBMixin):
+ def get(self, link):
+ node = self.select_node_by_link(link.lower())
+ if not node:
+ raise HTTPError(404)
+ start = self.get_argument("start", default = 0)
+ try:
+ start = int(start)
+ except ValueError:
+ start = 0
+ topics = self.select_topic_by_node_id(node.id, start = 0)
+ breadcrumb = []
+ breadcrumb.append((self._('Home'), '/'))
+ breadcrumb.append((self._('Forum'), '/forum'))
+ breadcrumb.append((node.name, '/go/%s' % node.link))
+ title = node.name
+ for topic in topics:
+ topic.reply_count = self.count_reply_by_topic_id(topic.id)
+ self.render("topic_list.html", locals())
+
+class ViewForumHandler(BaseHandler, ForumDBMixin):
+ def get(self):
+ start = self.get_argument("start", default = 0)
+ try:
+ start = int(start)
+ except ValueError:
+ start = 0
+ topics = self.select_lates_topic(start = 0)
+ breadcrumb = []
+ breadcrumb.append((self._('Home'), '/'))
+ breadcrumb.append((self._('Forum'), '/forum'))
+ title = self._("Forum")
+ index = True
+ for topic in topics:
+ topic.reply_count = self.count_reply_by_topic_id(topic.id)
+ self.render("topic_list.html", locals())
+
+class CreateTopicHandler(BaseHandler, ForumDBMixin):
+ @authenticated
+ def get(self, link):
+ node = self.select_node_by_link(link.lower())
+ if not node:
+ raise HTTPError(404)
+ topic = None
+ if self.current_user.admin:
+ tid = self.get_argument("tid", default = 0)
+ try:
+ tid = int(tid)
+ except ValueError:
+ raise HTTPError(404)
+ topic = self.select_topic_by_id(tid)
+ breadcrumb = []
+ breadcrumb.append((self._('Home'), '/'))
+ breadcrumb.append((self._('Forum'), '/forum'))
+ breadcrumb.append((node.name, '/go/%s' % node.link))
+ breadcrumb.append((self._("Create Topic"), '/new/%s' % node.link))
+ title = self._("Create Topic")
+ self.render("topic_create.html", locals())
+ @authenticated
+ def post(self, link):
+ try:
+ node = self.select_node_by_link(link.lower())
+ except NoResultFound:
+ raise HTTPError(404)
+ title = self.get_argument("title", default = "")
+ content = self.get_argument("content", default = "")
+ error = []
+ topic = Topic()
+ if self.current_user.admin:
+ tid = self.get_argument("tid", default = 0)
+ try:
+ tid = int(tid)
+ except ValueError:
+ raise HTTPError(404)
+ if tid:
+ topic.id = tid
+ error.extend(self.check_text_value(title, self._("Topic Title"), required = True, max = 255))
+ error.extend(self.check_text_value(content, self._("Topic Content"), required = True, min = 5, max = 100000))
+ topic.title = self.xhtml_escape(title)
+ topic.content = self.xhtml_escape(content)
+ if error:
+ breadcrumb = []
+ breadcrumb.append((self._('Home'), '/'))
+ breadcrumb.append((self._('Forum'), '/forum'))
+ breadcrumb.append((node.name, '/go/%s' % node.link))
+ breadcrumb.append((self._("Create Topic"), '/new/%s' % node.link))
+ title = self._("Create Topic")
+ self.render("topic_create.html", locals())
+ return
+ topic.node_id = node.id
+ topic.member_id = self.current_user.id
+ if topic.id:
+ self.update_topic(topic)
+ else:
+ #self.insert_topic(topic)
+ topic.create = datetime.datetime.now()
+ topic.last_reply = datetime.datetime.now()
+ self.db.add(topic)
+ self.db.commit()
+ self.db.flush()
+ self.redirect("/t/%s" % topic.id)
+
+class ViewTopicHandler(BaseHandler, ForumDBMixin):
+ def get(self, topic_id):
+ try:
+ topic_id = int(topic_id)
+ except ValueError:
+ raise HTTPError(404)
+ topic = self.select_topic_by_id(topic_id)
+ if not topic:
+ raise HTTPError(404)
+ replies = self.select_reply_by_topic_id(topic_id)
+ topic.reply_count = len(replies)
+ title = topic.title
+ breadcrumb = []
+ breadcrumb.append((self._('Home'), '/'))
+ breadcrumb.append((self._('Forum'), '/forum'))
+ breadcrumb.append((topic.node.name, '/go/%s' % topic.node.link))
+ breadcrumb.append((topic.title, '/t/%d' % topic.id))
+ self.render("topic.html", locals())
+ @authenticated
+ def post(self, topic_id):
+ try:
+ topic_id = int(topic_id)
+ except ValueError:
+ raise HTTPError(404)
+ topic = self.select_topic_by_id(topic_id)
+ if not topic:
+ raise HTTPError(404)
+ content = self.get_argument("content", default = "")
+ error = []
+ error.extend(self.check_text_value(content, self._("Reply Content"), required = True, min = 5))
+ if error:
+ replies = self.select_reply_by_topic_id(topic_id)
+ topic.reply_count = len(replies)
+ title = topic.title
+ content = self.xhtml_escape(content)
+ breadcrumb = []
+ breadcrumb.append((self._('Home'), '/'))
+ breadcrumb.append((self._('Forum'), '/forum'))
+ breadcrumb.append((topic.node.name, '/go/%s' % topic.node.link))
+ breadcrumb.append((topic.title, '/t/%d' % topic.id))
+ self.render("topic.html", locals())
+ return
+ reply = Reply()
+ reply.content = self.xhtml_escape(content)
+ reply.topic_id = topic.id
+ reply.member_id = self.current_user.id
+ reply.create = datetime.datetime.now()
+ self.db.add(reply)
+ self.db.commit()
+ topic.last_reply = datetime.datetime.now()
+ self.db.commit()
+ self.redirect("/t/%d" % topic_id)
+
+
+__all__ = ["ViewNodeHandler", "CreateTopicHandler", "ViewTopicHandler", "ViewForumHandler"]
View
49 handlers.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+# AUTHOR: Zeray Rice <fanzeyi1994@gmail.com>
+# FILE: handlers.py
+# CREATED: 01:41:06 08/03/2012
+# MODIFIED: 20:28:26 18/04/2012
+# DESCRIPTION: URL Route
+
+from api import *
+from home import *
+from lang import *
+from forum import *
+from member import *
+from problem import *
+from contest import *
+from backstage import *
+
+'''
+'' Handler 命名规范: [动宾结构 / 名词] + Handler
+'''
+
+handlers = [
+ (r'/', HomeHandler),
+ (r'/signin', SigninHandler),
+ (r'/signup', SignupHandler),
+ (r'/signout', SignoutHandler),
+ (r'/settings', SettingsHandler),
+ (r'/settings/changepass', ChangePasswordHandler),
+ (r'/member', ListMemberHandler),
+ (r'/member/(.*)', MemberHandler),
+ (r'/lang/(.*)', SetLanguageHandler),
+ (r'/problem', ListProblemHandler),
+ (r'/problem/([\d]*)', ViewProblemHandler),
+ (r'/tag/(.*)', ViewTagHandler),
+ (r'/submit', ListSubmitHandler),
+ (r'/submit/(.*)', ViewSubmitHandler),
+ (r'/backstage/problem/add', AddProblemHandler),
+ (r'/backstage/contest/add', AddContestHandler),
+ (r'/backstage/node/add', AddNodeHandler),
+ (r'/backstage/judger', ManageJudgerHandler),
+ (r'/backstage/judger/add', AddJudgerHandler),
+ (r'/contest', ListContestHandlder),
+ (r'/contest/([\d]*)', ViewContestHandler),
+ (r'/go/(.*)', ViewNodeHandler),
+ (r'/t/([\d]*)', ViewTopicHandler),
+ (r'/new/(.*)', CreateTopicHandler),
+ (r'/forum', ViewForumHandler),
+ (r'/test', TestHandler),
+ (r'/api/problem/get/([\d]*)', GetProblemHandler),
+]
View
32 home.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+# AUTHOR: Zeray Rice <fanzeyi1994@gmail.com>
+# FILE: home.py
+# CREATED: 02:00:16 08/03/2012
+# MODIFIED: 18:26:17 18/04/2012
+# DESCRIPTION: Home handler
+
+from contest import get_contest_status
+
+from judge.db import ForumDBMixin
+from judge.db import MemberDBMixin
+from judge.db import ContestDBMixin
+from judge.db import ProblemDBMixin
+from judge.base import BaseHandler
+
+class HomeHandler(BaseHandler, MemberDBMixin, ProblemDBMixin, ContestDBMixin, ForumDBMixin):
+ def get(self):
+ title = self._("Home")
+ breadcrumb = []
+ breadcrumb.append((self._("Home"), "/"))
+ latest_problem = self.select_latest_visible_problem_order_by_id(count = 5)
+ latest_contest = self.select_visible_contest(count = 5)
+ latest_submit = self.select_submit_order_by_id(count = 5)
+ for contest in latest_contest:
+ contest.status = get_contest_status(contest)
+ latest_topic = []
+ latest_node = self.select_latest_node()
+ count_problem = self.count_visible_problem()
+ count_member = self.count_member()
+ self.render("home.html", locals())
+
+__all__ = ["HomeHandler"]
View
473 i18n/message.po
@@ -0,0 +1,473 @@
+# Vulpix Translate File.
+# Copyright (C) 2012 Zeray Rice <fanzeyi1994@gmail.com>
+# This file is distributed under the same license as the Vulpix package.
+# Zeray Rice <fanzeyi1994@gmail.com>, 2012.
+#
+#,
+msgid ""
+msgstr ""
+"Project-Id-Version: 1.0 beta\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-03-22 19:52+0800\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: problem.py:45 problem.py:82 problem.py:119 problem.py:142 problem.py:160
+#: contest.py:47 contest.py:67 home.py:16 home.py:18 member.py:107
+#: member.py:134 member.py:177 tpl/base/sidebar.html:34
+msgid "Home"
+msgstr ""
+
+#: problem.py:46 problem.py:83 problem.py:120 problem.py:121
+#: tpl/submit.html:13 tpl/submit_list.html:12 tpl/base/sidebar.html:40
+#: tpl/contest.html:76
+msgid "Problem"
+msgstr ""
+
+#: problem.py:68
+msgid "Please Choose Your Code File"
+msgstr ""
+
+#: problem.py:72
+msgid "File Type Error!"
+msgstr ""
+
+#: problem.py:75
+msgid "Error Code Language Select"
+msgstr ""
+
+#: problem.py:79
+msgid "No Judger Seted!"
+msgstr ""
+
+#: problem.py:143 problem.py:161 tpl/backstage/add_judger.html:18
+#: tpl/backstage/add_problem.html:40 tpl/backstage/add_contest.html:53
+#: tpl/problem.html:71 tpl/base/sidebar.html:52 tpl/contest.html:103
+msgid "Submit"
+msgstr ""
+
+#: problem.py:144 tpl/submit_list.html:5
+msgid "Submit List"
+msgstr ""
+
+#: problem.py:163
+#, python-format
+msgid "Submit #%d - %s"
+msgstr ""
+
+#: contest.py:48 contest.py:68 tpl/base/sidebar.html:46
+msgid "Contest"
+msgstr ""
+
+#: contest.py:55 tpl/contest_list.html:5
+msgid "Contest List"
+msgstr ""
+
+#: backstage.py:34 tpl/base/sidebar.html:93
+msgid "Add Problem"
+msgstr ""
+
+#: backstage.py:38 backstage.py:79
+msgid "Edit Problem"
+msgstr ""
+
+#: backstage.py:62 backstage.py:115 tpl/backstage/add_problem.html:16
+#: tpl/backstage/add_contest.html:16 tpl/contest_list.html:11
+#: tpl/problem_list.html:11
+msgid "Title"
+msgstr ""
+
+#: backstage.py:63 tpl/backstage/add_problem.html:17 tpl/problem.html:24
+#: tpl/problem_list.html:14 tpl/contest.html:43
+msgid "Short Name"
+msgstr ""
+
+#: backstage.py:64 tpl/backstage/add_problem.html:18
+msgid "Time Limit"
+msgstr ""
+
+#: backstage.py:65 tpl/backstage/add_problem.html:19
+msgid "Memory Limit"
+msgstr ""
+
+#: backstage.py:66 tpl/submit_list.html:17
+msgid "Testpoint"
+msgstr ""
+
+#: backstage.py:67 tpl/backstage/add_problem.html:21
+#: tpl/backstage/add_contest.html:29
+msgid "Invisible"
+msgstr ""
+
+#: backstage.py:68 tpl/backstage/add_problem.html:22
+msgid "Content"
+msgstr ""
+
+#: backstage.py:69 tpl/backstage/add_problem.html:20 tpl/submit.html:68
+msgid "Test Point"
+msgstr ""
+
+#: backstage.py:95 backstage.py:132 tpl/base/sidebar.html:99
+msgid "Add Contest"
+msgstr ""
+
+#: backstage.py:100
+msgid "Edit Contest"
+msgstr ""
+
+#: backstage.py:116 backstage.py:183 tpl/backstage/add_judger.html:16
+#: tpl/backstage/add_contest.html:30
+msgid "Description"
+msgstr ""
+
+#: backstage.py:121
+msgid "Start/End Time Format is Invalid."
+msgstr ""
+
+#: backstage.py:124
+msgid "Start/End Time is invalid."
+msgstr ""
+
+#: backstage.py:152 tpl/base/sidebar.html:111
+msgid "Judger Manage"
+msgstr ""
+
+#: backstage.py:159 tpl/backstage/judger.html:6
+msgid "Add Judger"
+msgstr ""
+
+#: backstage.py:170 backstage.py:194
+msgid "Edit Judger"
+msgstr ""
+
+#: backstage.py:182 tpl/backstage/add_judger.html:13
+#: tpl/backstage/judger.html:13 tpl/contest.html:42
+msgid "Name"
+msgstr ""
+
+#: backstage.py:184 tpl/backstage/add_judger.html:14
+#: tpl/backstage/judger.html:14
+msgid "Path"
+msgstr ""
+
+#: backstage.py:185 tpl/backstage/add_judger.html:15
+#: tpl/backstage/judger.html:15
+msgid "Priority"
+msgstr ""
+
+#: backstage.py:186 tpl/backstage/add_judger.html:17
+msgid "RSA Public Key"
+msgstr ""
+
+#: judge/base/__init__.py:122
+#, python-format
+msgid "%s is required"
+msgstr ""
+
+#: judge/base/__init__.py:128
+#, python-format
+msgid "%s must be a number."
+msgstr ""
+
+#: judge/base/__init__.py:131 judge/base/__init__.py:142
+#: judge/base/__init__.py:144
+#, python-format
+msgid "%s is invalid."
+msgstr ""
+
+#: judge/base/__init__.py:134
+#, python-format
+msgid "%s is too long."
+msgstr ""
+
+#: judge/base/__init__.py:136
+#, python-format
+msgid "%s is too short."
+msgstr ""
+
+#: judge/base/__init__.py:148
+msgid "Username"
+msgstr ""
+
+#: judge/base/__init__.py:150
+msgid "A username can only contain letters and digits."
+msgstr ""
+
+#: judge/base/__init__.py:154
+msgid "That username is taken. Please choose another."
+msgstr ""
+
+#: judge/base/__init__.py:157
+msgid "Password"
+msgstr ""
+
+#: judge/base/__init__.py:160
+msgid "E-mail"
+msgstr ""
+
+#: judge/base/__init__.py:162
+msgid "Your Email address is invalid."
+msgstr ""
+
+#: judge/base/__init__.py:166
+msgid "That Email is taken. Please choose another."
+msgstr ""
+
+#: member.py:36
+msgid "Wrong Username and password combination."
+msgstr ""
+
+#: member.py:101 member.py:109 member.py:132 member.py:136
+#: tpl/base/sidebar.html:77
+msgid "Settings"
+msgstr ""
+
+#: member.py:121 tpl/settings.html:20
+msgid "Website"
+msgstr ""
+
+#: member.py:123 tpl/settings.html:21
+msgid "Tagline"
+msgstr ""
+
+#: member.py:124 tpl/settings.html:23
+msgid "Code Language"
+msgstr ""
+
+#: member.py:125 tpl/settings.html:32
+msgid "Bio"
+msgstr ""
+
+#: member.py:141
+msgid "Settings Updated."
+msgstr ""
+
+#: member.py:154
+msgid "Wrong Passowrd"
+msgstr ""
+
+#: member.py:156 tpl/settings_changepass.html:10
+#: tpl/settings_changepass.html:15 tpl/settings.html:39 tpl/settings.html:44
+msgid "Change Password"
+msgstr ""
+
+#: member.py:166
+msgid "Password Updated."
+msgstr ""
+
+#: tpl/backstage/judger.html:7
+msgid "Judger List"
+msgstr ""
+
+#: tpl/backstage/judger.html:16
+msgid "Queue"
+msgstr ""
+
+#: tpl/backstage/judger.html:17 tpl/backstage/judger.html:27
+msgid "Edit"
+msgstr ""
+
+#: tpl/backstage/add_problem.html:24
+msgid "Problem Tag"
+msgstr ""
+
+#: tpl/backstage/add_problem.html:27 tpl/backstage/add_contest.html:35
+msgid "Add"
+msgstr ""
+
+#: tpl/backstage/add_contest.html:18 tpl/contest_list.html:12
+#: tpl/contest.html:19
+msgid "Start Time"
+msgstr ""
+
+#: tpl/backstage/add_contest.html:24 tpl/contest.html:23
+msgid "End Time"
+msgstr ""
+
+#: tpl/backstage/add_contest.html:32
+msgid "Related Problem"
+msgstr ""
+
+#: tpl/member.html:12
+msgid "ID:"
+msgstr ""
+
+#: tpl/member.html:12
+msgid "Joined at"
+msgstr ""
+
+#: tpl/problem.html:7
+msgid "This Problem is Invisible!"
+msgstr ""
+
+#: tpl/problem.html:28 tpl/submit.html:75 tpl/submit_list.html:15
+#: tpl/problem_list.html:12
+msgid "Time"
+msgstr ""
+
+#: tpl/problem.html:32 tpl/submit.html:76 tpl/submit_list.html:16
+#: tpl/problem_list.html:13
+msgid "Memory"
+msgstr ""
+
+#: tpl/problem.html:36
+msgid "Tags"
+msgstr ""
+
+#: tpl/problem.html:53 tpl/contest.html:73
+msgid "Submit Problem"
+msgstr ""
+
+#: tpl/problem.html:57 tpl/contest.html:89
+msgid "Code File"
+msgstr ""
+
+#: tpl/problem.html:63 tpl/submit.html:25 tpl/submit_list.html:14
+#: tpl/contest.html:44 tpl/contest.html:95
+msgid "Language"
+msgstr ""
+
+#: tpl/submit.html:5
+msgid "Submit Detail"
+msgstr ""
+
+#: tpl/submit.html:17
+msgid "Member"
+msgstr ""
+
+#: tpl/submit.html:21 tpl/submit.html:74 tpl/submit_list.html:11
+#: tpl/contest_list.html:14 tpl/problem_list.html:15 tpl/contest.html:27
+#: tpl/contest.html:46
+msgid "Status"
+msgstr ""
+
+#: tpl/submit.html:29 tpl/contest.html:47
+msgid "Score"
+msgstr ""
+
+#: tpl/submit.html:33
+msgid "Cost Time"
+msgstr ""
+
+#: tpl/submit.html:37
+msgid "Cost Memory"
+msgstr ""
+
+#: tpl/submit.html:41 tpl/contest.html:45
+msgid "Submit Time"
+msgstr ""
+
+#: tpl/submit.html:46
+msgid "IP"
+msgstr ""
+
+#: tpl/submit.html:50
+msgid "User-Agent"
+msgstr ""
+
+#: tpl/submit.html:59
+msgid "Compile Output"
+msgstr ""
+
+#: tpl/submit.html:95
+msgid "Code"
+msgstr ""
+
+#: tpl/submit.html:100
+msgid "You dont have permissions to view this code."
+msgstr ""
+
+#: tpl/home.html:10
+msgid "Latest Problem"
+msgstr ""
+
+#: tpl/home.html:16
+msgid "Latest Contest"
+msgstr ""
+
+#: tpl/home.html:22
+msgid "Latest Discuss"
+msgstr ""
+
+#: tpl/home.html:31
+msgid "Notice"
+msgstr ""
+
+#: tpl/home.html:37
+msgid "Site Count"
+msgstr ""
+
+#: tpl/submit_list.html:13
+msgid "User"
+msgstr ""
+
+#: tpl/settings_changepass.html:13 tpl/settings.html:42
+msgid "Current Password"
+msgstr ""
+
+#: tpl/settings_changepass.html:14 tpl/settings.html:43
+msgid "New Password"
+msgstr ""
+
+#: tpl/contest_list.html:13
+msgid "End time"
+msgstr ""
+
+#: tpl/base/sidebar.html:58
+msgid "Forum"
+msgstr ""
+
+#: tpl/base/sidebar.html:64
+msgid "List"
+msgstr ""
+
+#: tpl/base/sidebar.html:71
+msgid "Notes"
+msgstr ""
+
+#: tpl/base/sidebar.html:83
+msgid "Sign Out"
+msgstr ""
+
+#: tpl/base/sidebar.html:88
+msgid "Backstage"
+msgstr ""
+
+#: tpl/base/sidebar.html:105
+msgid "Add Node"
+msgstr ""
+
+#: tpl/base/sidebar.html:119 tpl/signup.html:46 tpl/signin.html:5
+#: tpl/signin.html:39
+msgid "Sign In"
+msgstr ""
+
+#: tpl/base/sidebar.html:125 tpl/signup.html:5 tpl/signup.html:43
+#: tpl/signin.html:42
+msgid "Sign Up"
+msgstr ""
+
+#: tpl/base/sidebar.html:136
+msgid "Help"
+msgstr ""
+
+#: tpl/settings.html:16
+msgid "Basic Settings"
+msgstr ""
+
+#: tpl/settings.html:19
+msgid "Email"
+msgstr ""
+
+#: tpl/settings.html:33
+msgid "Save Changes"
+msgstr ""
+
+#: tpl/problem_list.html:5 tpl/contest.html:37
+msgid "Problem List"
+msgstr ""
View
473 i18n/message_cn.po
@@ -0,0 +1,473 @@
+# Vulpix Translate File.
+# Copyright (C) 2012 Zeray Rice <fanzeyi1994@gmail.com>
+# This file is distributed under the same license as the Vulpix package.
+# Zeray Rice <fanzeyi1994@gmail.com>, 2012.
+#
+#,
+msgid ""
+msgstr ""
+"Project-Id-Version: 1.0 beta\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-03-22 19:52+0800\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: problem.py:45 problem.py:82 problem.py:119 problem.py:142 problem.py:160
+#: contest.py:47 contest.py:67 home.py:16 home.py:18 member.py:107
+#: member.py:134 member.py:177 tpl/base/sidebar.html:34
+msgid "Home"
+msgstr "首页"
+
+#: problem.py:46 problem.py:83 problem.py:120 problem.py:121
+#: tpl/submit.html:13 tpl/submit_list.html:12 tpl/base/sidebar.html:40
+#: tpl/contest.html:76
+msgid "Problem"
+msgstr "题目"
+
+#: problem.py:68
+msgid "Please Choose Your Code File"
+msgstr "请选择一个代码文件"
+
+#: problem.py:72
+msgid "File Type Error!"
+msgstr "文件类型错误"
+
+#: problem.py:75
+msgid "Error Code Language Select"
+msgstr "语言选项错误"
+
+#: problem.py:79
+msgid "No Judger Seted!"
+msgstr "没有设置评测机"
+
+#: problem.py:143 problem.py:161 tpl/backstage/add_judger.html:18
+#: tpl/backstage/add_problem.html:40 tpl/backstage/add_contest.html:53
+#: tpl/problem.html:71 tpl/base/sidebar.html:52 tpl/contest.html:103
+msgid "Submit"
+msgstr "提交"
+
+#: problem.py:144 tpl/submit_list.html:5
+msgid "Submit List"
+msgstr "提交列表"
+
+#: problem.py:163
+#, python-format
+msgid "Submit #%d - %s"
+msgstr "提交 #%d - %s"
+
+#: contest.py:48 contest.py:68 tpl/base/sidebar.html:46
+msgid "Contest"
+msgstr "比赛"
+
+#: contest.py:55 tpl/contest_list.html:5
+msgid "Contest List"
+msgstr "比赛列表"
+
+#: backstage.py:34 tpl/base/sidebar.html:93
+msgid "Add Problem"
+msgstr "添加题目"
+
+#: backstage.py:38 backstage.py:79
+msgid "Edit Problem"
+msgstr "编辑题目"
+
+#: backstage.py:62 backstage.py:115 tpl/backstage/add_problem.html:16
+#: tpl/backstage/add_contest.html:16 tpl/contest_list.html:11
+#: tpl/problem_list.html:11
+msgid "Title"
+msgstr "标题"
+
+#: backstage.py:63 tpl/backstage/add_problem.html:17 tpl/problem.html:24
+#: tpl/problem_list.html:14 tpl/contest.html:43
+msgid "Short Name"
+msgstr "名称"
+
+#: backstage.py:64 tpl/backstage/add_problem.html:18
+msgid "Time Limit"
+msgstr "时间限制"
+
+#: backstage.py:65 tpl/backstage/add_problem.html:19
+msgid "Memory Limit"
+msgstr "空间限制"
+
+#: backstage.py:66 tpl/submit_list.html:17
+msgid "Testpoint"
+msgstr "测试点数"
+
+#: backstage.py:67 tpl/backstage/add_problem.html:21
+#: tpl/backstage/add_contest.html:29
+msgid "Invisible"
+msgstr "不可见"
+
+#: backstage.py:68 tpl/backstage/add_problem.html:22
+msgid "Content"
+msgstr "正文"
+
+#: backstage.py:69 tpl/backstage/add_problem.html:20 tpl/submit.html:68
+msgid "Test Point"
+msgstr "测试点"
+
+#: backstage.py:95 backstage.py:132 tpl/base/sidebar.html:99
+msgid "Add Contest"
+msgstr "添加比赛"
+
+#: backstage.py:100
+msgid "Edit Contest"
+msgstr "编辑比赛"
+
+#: backstage.py:116 backstage.py:183 tpl/backstage/add_judger.html:16
+#: tpl/backstage/add_contest.html:30
+msgid "Description"
+msgstr "描述"
+
+#: backstage.py:121
+msgid "Start/End Time Format is Invalid."
+msgstr "开始/结束时间格式不正确。"
+
+#: backstage.py:124
+msgid "Start/End Time is invalid."
+msgstr "开始/结束时间不正确。"
+
+#: backstage.py:152 tpl/base/sidebar.html:111
+msgid "Judger Manage"
+msgstr "评测机管理"
+
+#: backstage.py:159 tpl/backstage/judger.html:6
+msgid "Add Judger"
+msgstr "添加评测机"
+
+#: backstage.py:170 backstage.py:194
+msgid "Edit Judger"
+msgstr "编辑评测机"
+
+#: backstage.py:182 tpl/backstage/add_judger.html:13
+#: tpl/backstage/judger.html:13 tpl/contest.html:42
+msgid "Name"
+msgstr "名称"
+
+#: backstage.py:184 tpl/backstage/add_judger.html:14
+#: tpl/backstage/judger.html:14
+msgid "Path"
+msgstr "路径"
+
+#: backstage.py:185 tpl/backstage/add_judger.html:15
+#: tpl/backstage/judger.html:15
+msgid "Priority"
+msgstr "优先级"
+
+#: backstage.py:186 tpl/backstage/add_judger.html:17
+msgid "RSA Public Key"
+msgstr "RSA 公钥"
+
+#: judge/base/__init__.py:122
+#, python-format
+msgid "%s is required"
+msgstr "要求填写 %s。"
+
+#: judge/base/__init__.py:128
+#, python-format
+msgid "%s must be a number."
+msgstr "%s 必须为数字。"
+
+#: judge/base/__init__.py:131 judge/base/__init__.py:142
+#: judge/base/__init__.py:144
+#, python-format
+msgid "%s is invalid."
+msgstr "%s "
+
+#: judge/base/__init__.py:134
+#, python-format
+msgid "%s is too long."
+msgstr "%s 过长。"
+
+#: judge/base/__init__.py:136
+#, python-format
+msgid "%s is too short."
+msgstr "%s 过短。"
+
+#: judge/base/__init__.py:148
+msgid "Username"
+msgstr "用户名"
+
+#: judge/base/__init__.py:150
+msgid "A username can only contain letters and digits."
+msgstr "用户名只允许包含数字和字母。"
+
+#: judge/base/__init__.py:154
+msgid "That username is taken. Please choose another."
+msgstr "用户名已被使用,请换一个。"
+
+#: judge/base/__init__.py:157
+msgid "Password"
+msgstr "密码"
+
+#: judge/base/__init__.py:160
+msgid "E-mail"
+msgstr "邮箱"
+
+#: judge/base/__init__.py:162
+msgid "Your Email address is invalid."
+msgstr "邮箱地址不正确。"
+
+#: judge/base/__init__.py:166
+msgid "That Email is taken. Please choose another."
+msgstr "邮箱已被使用,请换一个。"
+
+#: member.py:36
+msgid "Wrong Username and password combination."
+msgstr "用户名或密码错误。"
+
+#: member.py:101 member.py:109 member.py:132 member.py:136
+#: tpl/base/sidebar.html:77
+msgid "Settings"
+msgstr "设置"
+
+#: member.py:121 tpl/settings.html:20
+msgid "Website"
+msgstr "网站"
+
+#: member.py:123 tpl/settings.html:21
+msgid "Tagline"
+msgstr "签名"
+
+#: member.py:124 tpl/settings.html:23
+msgid "Code Language"
+msgstr "编程语言"
+
+#: member.py:125 tpl/settings.html:32
+msgid "Bio"
+msgstr "个人信息"
+
+#: member.py:141
+msgid "Settings Updated."
+msgstr "设置已更新。"
+
+#: member.py:154
+msgid "Wrong Passowrd"
+msgstr "密码错误"
+
+#: member.py:156 tpl/settings_changepass.html:10
+#: tpl/settings_changepass.html:15 tpl/settings.html:39 tpl/settings.html:44
+msgid "Change Password"
+msgstr "更改密码"
+
+#: member.py:166
+msgid "Password Updated."
+msgstr "密码已更新"
+
+#: tpl/backstage/judger.html:7
+msgid "Judger List"
+msgstr "评测机列表"
+
+#: tpl/backstage/judger.html:16
+msgid "Queue"
+msgstr "队列"
+
+#: tpl/backstage/judger.html:17 tpl/backstage/judger.html:27
+msgid "Edit"
+msgstr "编辑"
+
+#: tpl/backstage/add_problem.html:24
+msgid "Problem Tag"
+msgstr "题目标签"
+
+#: tpl/backstage/add_problem.html:27 tpl/backstage/add_contest.html:35
+msgid "Add"
+msgstr "添加"
+
+#: tpl/backstage/add_contest.html:18 tpl/contest_list.html:12
+#: tpl/contest.html:19
+msgid "Start Time"
+msgstr "开始时间"
+
+#: tpl/backstage/add_contest.html:24 tpl/contest.html:23
+msgid "End Time"
+msgstr "结束时间"
+
+#: tpl/backstage/add_contest.html:32
+msgid "Related Problem"
+msgstr "关联题目"
+
+#: tpl/member.html:12
+msgid "ID:"
+msgstr "编号:"
+
+#: tpl/member.html:12
+msgid "Joined at"
+msgstr "加入时间"
+
+#: tpl/problem.html:7
+msgid "This Problem is Invisible!"
+msgstr "题目不可用!"
+
+#: tpl/problem.html:28 tpl/submit.html:75 tpl/submit_list.html:15
+#: tpl/problem_list.html:12
+msgid "Time"
+msgstr "时间"
+
+#: tpl/problem.html:32 tpl/submit.html:76 tpl/submit_list.html:16
+#: tpl/problem_list.html:13
+msgid "Memory"
+msgstr "内存"
+
+#: tpl/problem.html:36
+msgid "Tags"
+msgstr "标签"
+
+#: tpl/problem.html:53 tpl/contest.html:73
+msgid "Submit Problem"
+msgstr "提交题目"
+
+#: tpl/problem.html:57 tpl/contest.html:89
+msgid "Code File"
+msgstr "代码文件"
+
+#: tpl/problem.html:63 tpl/submit.html:25 tpl/submit_list.html:14
+#: tpl/contest.html:44 tpl/contest.html:95
+msgid "Language"
+msgstr "编程语言"
+
+#: tpl/submit.html:5
+msgid "Submit Detail"
+msgstr "提交详情"
+
+#: tpl/submit.html:17
+msgid "Member"
+msgstr "成员"
+
+#: tpl/submit.html:21 tpl/submit.html:74 tpl/submit_list.html:11
+#: tpl/contest_list.html:14 tpl/problem_list.html:15 tpl/contest.html:27
+#: tpl/contest.html:46
+msgid "Status"
+msgstr "状态"
+
+#: tpl/submit.html:29 tpl/contest.html:47
+msgid "Score"
+msgstr "得分"
+
+#: tpl/submit.html:33
+msgid "Cost Time"
+msgstr "用时"
+
+#: tpl/submit.html:37
+msgid "Cost Memory"
+msgstr "内存使用"
+
+#: tpl/submit.html:41 tpl/contest.html:45
+msgid "Submit Time"
+msgstr "提交时间"
+
+#: tpl/submit.html:46
+msgid "IP"
+msgstr "IP 地址"
+
+#: tpl/submit.html:50
+msgid "User-Agent"
+msgstr "访问信息"
+
+#: tpl/submit.html:59
+msgid "Compile Output"
+msgstr "编译输出"
+
+#: tpl/submit.html:95
+msgid "Code"
+msgstr "代码"
+
+#: tpl/submit.html:100
+msgid "You dont have permissions to view this code."
+msgstr "你没有权限查看此代码。"
+
+#: tpl/home.html:10
+msgid "Latest Problem"
+msgstr "最新题目"
+
+#: tpl/home.html:16
+msgid "Latest Contest"
+msgstr "最近比赛"
+
+#: tpl/home.html:22
+msgid "Latest Discuss"
+msgstr "最新讨论"
+
+#: tpl/home.html:31
+msgid "Notice"
+msgstr "公告"
</