Skip to content

Commit fac3a93

Browse files
committed
String PID support
1 parent ed03355 commit fac3a93

File tree

8 files changed

+46
-31
lines changed

8 files changed

+46
-31
lines changed

vj4/handler/contest.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from vj4.util import pagination
2525

2626

27-
@app.route('/contest', 'contest_main')
27+
@app.route('/c', 'contest_main')
2828
class ContestMainHandler(contest.ContestMixin, base.Handler):
2929
CONTESTS_PER_PAGE = 20
3030

@@ -47,7 +47,7 @@ async def get(self, *, rule: int=0, page: int=1):
4747
tdocs=tdocs, tsdict=tsdict)
4848

4949

50-
@app.route('/contest/{tid:\w{24}}', 'contest_detail')
50+
@app.route('/c/{tid:\w{24}}', 'contest_detail')
5151
class ContestDetailHandler(contest.ContestMixin, base.OperationHandler):
5252
DISCUSSIONS_PER_PAGE = 15
5353

@@ -105,7 +105,7 @@ async def post_attend(self, *, tid: objectid.ObjectId):
105105
self.json_or_redirect(self.url)
106106

107107

108-
@app.route('/contest/{tid:\w{24}}/code', 'contest_code')
108+
@app.route('/c/{tid:\w{24}}/code', 'contest_code')
109109
class ContestCodeHandler(base.OperationHandler):
110110
@base.limit_rate('contest_code', 3600, 60)
111111
@base.route_argument
@@ -132,7 +132,7 @@ async def get(self, *, tid: objectid.ObjectId):
132132
file_name='{}.zip'.format(tdoc['title']))
133133

134134

135-
@app.route('/contest/{tid}/{pid:-?\d+|\w{24}}', 'contest_detail_problem')
135+
@app.route('/c/{tid}/p/{pid:[a-zA-Z0-9]+}', 'contest_detail_problem')
136136
class ContestDetailProblemHandler(contest.ContestMixin, base.Handler):
137137
@base.route_argument
138138
@base.require_perm(builtin.PERM_VIEW_CONTEST)
@@ -163,7 +163,7 @@ async def get(self, *, tid: objectid.ObjectId, pid: document.convert_doc_id):
163163
page_title=pdoc['title'], path_components=path_components)
164164

165165

166-
@app.route('/contest/{tid}/{pid}/submit', 'contest_detail_problem_submit')
166+
@app.route('/c/{tid}/p/{pid}/submit', 'contest_detail_problem_submit')
167167
class ContestDetailProblemSubmitHandler(contest.ContestMixin, base.Handler):
168168
@base.route_argument
169169
@base.require_perm(builtin.PERM_VIEW_CONTEST)
@@ -233,7 +233,7 @@ async def post(self, *, tid: objectid.ObjectId, pid: document.convert_doc_id,
233233
self.json_or_redirect(self.reverse_url('record_detail', rid=rid))
234234

235235

236-
@app.route('/contest/{tid}/scoreboard', 'contest_scoreboard')
236+
@app.route('/c/{tid}/scoreboard', 'contest_scoreboard')
237237
class ContestScoreboardHandler(contest.ContestMixin, base.Handler):
238238
@base.route_argument
239239
@base.require_perm(builtin.PERM_VIEW_CONTEST)
@@ -251,7 +251,7 @@ async def get(self, *, tid: objectid.ObjectId):
251251
page_title=page_title, path_components=path_components)
252252

253253

254-
@app.route('/contest/{tid}/scoreboard/download/{ext}', 'contest_scoreboard_download')
254+
@app.route('/c/{tid}/scoreboard/download/{ext}', 'contest_scoreboard_download')
255255
class ContestScoreboardDownloadHandler(contest.ContestMixin, base.Handler):
256256
def _export_status_as_csv(self, rows):
257257
# \r\n for notepad compatibility
@@ -321,7 +321,7 @@ async def post(self, *, title: str, content: str, rule: int,
321321
self.json_or_redirect(self.reverse_url('contest_detail', tid=tid))
322322

323323

324-
@app.route('/contest/{tid}/edit', 'contest_edit')
324+
@app.route('/c/{tid}/edit', 'contest_edit')
325325
class ContestEditHandler(contest.ContestMixin, base.Handler):
326326
@base.route_argument
327327
@base.require_priv(builtin.PRIV_USER_PROFILE)

vj4/handler/homework.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def _format_penalty_rules_yaml(penalty_rules):
5151
return yaml_doc
5252

5353

54-
@app.route('/homework', 'homework_main')
54+
@app.route('/h', 'homework_main')
5555
class HomeworkMainHandler(contest.ContestMixin, base.Handler):
5656
@base.require_perm(builtin.PERM_VIEW_HOMEWORK)
5757
async def get(self):
@@ -72,7 +72,7 @@ async def get(self):
7272
self.render('homework_main.html', tdocs=tdocs, calendar_tdocs=calendar_tdocs)
7373

7474

75-
@app.route('/homework/{tid:\w{24}}', 'homework_detail')
75+
@app.route('/h/{tid:\w{24}}', 'homework_detail')
7676
class HomeworkDetailHandler(contest.ContestMixin, base.OperationHandler):
7777
DISCUSSIONS_PER_PAGE = 15
7878

@@ -130,7 +130,7 @@ async def post_attend(self, *, tid: objectid.ObjectId):
130130
self.json_or_redirect(self.url)
131131

132132

133-
@app.route('/homework/{tid:\w{24}}/code', 'homework_code')
133+
@app.route('/h/{tid:\w{24}}/code', 'homework_code')
134134
class HomeworkCodeHandler(base.OperationHandler):
135135
@base.limit_rate('homework_code', 3600, 60)
136136
@base.route_argument
@@ -157,7 +157,7 @@ async def get(self, *, tid: objectid.ObjectId):
157157
file_name='{}.zip'.format(tdoc['title']))
158158

159159

160-
@app.route('/homework/{tid}/{pid:-?\d+|\w{24}}', 'homework_detail_problem')
160+
@app.route('/h/{tid}/p/{pid:[a-zA-Z0-9]+}', 'homework_detail_problem')
161161
class HomeworkDetailProblemHandler(contest.ContestMixin, base.Handler):
162162
@base.route_argument
163163
@base.require_perm(builtin.PERM_VIEW_HOMEWORK)
@@ -188,7 +188,7 @@ async def get(self, *, tid: objectid.ObjectId, pid: document.convert_doc_id):
188188
page_title=pdoc['title'], path_components=path_components)
189189

190190

191-
@app.route('/homework/{tid}/{pid}/submit', 'homework_detail_problem_submit')
191+
@app.route('/h/{tid}/p/{pid}/submit', 'homework_detail_problem_submit')
192192
class HomeworkDetailProblemSubmitHandler(contest.ContestMixin, base.Handler):
193193
@base.route_argument
194194
@base.require_perm(builtin.PERM_VIEW_HOMEWORK)
@@ -257,7 +257,7 @@ async def post(self, *, tid: objectid.ObjectId, pid: document.convert_doc_id,
257257
self.json_or_redirect(self.reverse_url('record_detail', rid=rid))
258258

259259

260-
@app.route('/homework/{tid}/scoreboard', 'homework_scoreboard')
260+
@app.route('/h/{tid}/scoreboard', 'homework_scoreboard')
261261
class HomeworkScoreboardHandler(contest.ContestMixin, base.Handler):
262262
@base.route_argument
263263
@base.require_perm(builtin.PERM_VIEW_HOMEWORK)
@@ -275,7 +275,7 @@ async def get(self, *, tid: objectid.ObjectId):
275275
page_title=page_title, path_components=path_components)
276276

277277

278-
@app.route('/homework/{tid}/scoreboard/download/{ext}', 'homework_scoreboard_download')
278+
@app.route('/h/{tid}/scoreboard/download/{ext}', 'homework_scoreboard_download')
279279
class HomeworkScoreboardDownloadHandler(contest.ContestMixin, base.Handler):
280280
def _export_status_as_csv(self, rows):
281281
# \r\n for notepad compatibility
@@ -357,7 +357,7 @@ async def post(self, *, title: str, content: str,
357357
self.json_or_redirect(self.reverse_url('homework_detail', tid=tid))
358358

359359

360-
@app.route('/homework/{tid}/edit', 'homework_edit')
360+
@app.route('/h/{tid}/edit', 'homework_edit')
361361
class HomeworkEditHandler(contest.ContestMixin, base.Handler):
362362
@base.require_priv(builtin.PRIV_USER_PROFILE)
363363
@base.require_perm(builtin.PERM_EDIT_HOMEWORK)

vj4/handler/problem.py

+7-8
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ async def star_unstar(self, *, pid: document.convert_doc_id, star: bool):
9090
post_unstar = functools.partialmethod(star_unstar, star=False)
9191

9292

93-
@app.route('/p/random', 'problem_random')
93+
@app.route('/problem/random', 'problem_random')
9494
class ProblemRandomHandler(base.Handler):
9595
@base.require_perm(builtin.PERM_VIEW_PROBLEM)
9696
@base.route_argument
@@ -185,7 +185,7 @@ async def get(self, *, category: str):
185185
self.json_or_redirect(self.referer_or_main)
186186

187187

188-
@app.route('/p/{pid:-?\d+|\w{24}}', 'problem_detail')
188+
@app.route('/p/{pid:[a-zA-Z0-9]+}', 'problem_detail')
189189
class ProblemDetailHandler(base.OperationHandler):
190190
async def _get_related_trainings(self, pid):
191191
if self.has_perm(builtin.PERM_VIEW_TRAINING):
@@ -568,7 +568,7 @@ async def get(self, *, pid: document.convert_doc_id):
568568
secret=fdoc['metadata']['secret']))
569569

570570

571-
@app.route('/p/create', 'problem_create')
571+
@app.route('/problem/create', 'problem_create')
572572
class ProblemCreateHandler(base.Handler):
573573
@base.require_priv(builtin.PRIV_USER_PROFILE)
574574
@base.require_perm(builtin.PERM_CREATE_PROBLEM)
@@ -580,16 +580,15 @@ async def get(self):
580580
@base.post_argument
581581
@base.require_csrf_token
582582
@base.sanitize
583-
async def post(self, *, title: str, content: str, hidden: bool=False, numeric_pid: bool=False):
584-
pid = None
585-
if numeric_pid:
583+
async def post(self, *, title: str, content: str, hidden: bool=False, pid: str):
584+
if not pid:
586585
pid = await domain.inc_pid_counter(self.domain_id)
587586
pid = await problem.add(self.domain_id, title, content, self.user['_id'],
588587
hidden=hidden, pid=pid)
589588
self.json_or_redirect(self.reverse_url('problem_settings', pid=pid))
590589

591590

592-
@app.route('/p/copy', 'problem_copy')
591+
@app.route('/problem/copy', 'problem_copy')
593592
class ProblemCopyHandler(base.Handler):
594593
MAX_PROBLEMS_PER_REQUEST = 20
595594

@@ -791,7 +790,7 @@ async def get(self, *, pid: document.convert_doc_id):
791790
page_title=pdoc['title'], path_components=path_components)
792791

793792

794-
@app.route('/p/search', 'problem_search')
793+
@app.route('/problem/search', 'problem_search')
795794
class ProblemSearchHandler(base.Handler):
796795
@base.get_argument
797796
@base.route_argument

vj4/locale/zh_CN.yaml

+2-1
Original file line numberDiff line numberDiff line change
@@ -773,7 +773,8 @@ display_name: 显示名
773773
home_domain_account: 当前域的设置
774774
'The value `{1}` of {0} already exists.': '{0} 的值 `{1}` 已经存在。'
775775
Display name {1} you want to set is used by others.: 您想要设置的显示名 {1} 已经被其他人使用了。
776-
Numeric PID: 数字题号
776+
Leave blank to use an allocated one.: 留空以自动获取题号
777+
PID: 题号
777778
Only {0} problems can be copied in one request, got {1}.: 一次请求只能复制 {0} 个题目,但是您输入了 {1} 个。
778779
Problem is successfully copied.: 题目复制完成。
779780
problem_copy: 复制题目

vj4/model/adaptor/problem.py

+4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ async def add(domain_id: str, title: str, content: str, owner_uid: int,
3939
category: list=[], tag: list=[], hidden: bool=False):
4040
validator.check_title(title)
4141
validator.check_content(content)
42+
try:
43+
pid = int(pid)
44+
except ValueError:
45+
validator.check_string_pid(pid)
4246
pid = await document.add(domain_id, content, owner_uid, document.TYPE_PROBLEM,
4347
pid, title=title, data=data, category=category, tag=tag,
4448
hidden=hidden, num_submit=0, num_accept=0)

vj4/ui/templates/components/problem.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
{%- endif %}
1111
>
1212
{%- endif %}
13-
{% if pdoc['doc_id']|string|length < 10 %}P{{ pdoc['doc_id'] }} {% endif %}{{ pdoc['title'] }}
13+
{% if pdoc['doc_id'] is number %}P {{ pdoc['doc_id'] }}{% elif pdoc['doc_id']|string|length < 10 %} {{ pdoc['doc_id'] }} {% endif %}{{ pdoc['title'] }}
1414
{%- if not invalid %}
1515
</a>
1616
{%- endif %}

vj4/ui/templates/problem_edit.html

+5-5
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@
66
<div class="section__body">
77
<form method="post">
88
<div class="row">
9-
<div class="medium-8 columns">
9+
<div class="medium-7 columns">
1010
<label>
1111
{{ _('Title') }}
1212
<input name="title" placeholder="{{ _('title') }}" value="{{ pdoc['title']|default('') }}" class="textbox" autofocus>
1313
</label>
1414
</div>
1515
{% if page_name == 'problem_create' %}
16-
<div class="medium-2 columns">
16+
<div class="medium-3 columns">
1717
<label>
18-
{{ _('Settings') }}
18+
{{ _('PID') }}
1919
<br>
20-
<label class="checkbox">
21-
<input type="checkbox" name="numeric_pid" value="on" checked>{{ _('Numeric PID') }}
20+
<label>
21+
<input name="pid" placeholder="{{ _('Leave blank to use an allocated one.') }}" class="textbox">
2222
</label>
2323
</label>
2424
</div>

vj4/util/validator.py

+11
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
ID_RE = re.compile(r'[^\\/\s\u3000]([^\\/\n\r]*[^\\/\s\u3000])?')
1313
ROLE_RE = re.compile(r'[_0-9A-Za-z]{1,256}')
1414
DOMAIN_INVITATION_CODE_RE = re.compile(r'[0-9A-Za-z]{1,64}')
15+
PID_RE1 = re.compile(r'[a-zA-Z0-9]*[a-zA-Z]+[a-zA-Z0-9]*')
16+
PID_RE2 = re.compile(r'[a-zA-Z0-9]{3,23}')
1517

1618

1719
def is_uid(s):
@@ -23,6 +25,15 @@ def check_uid(s):
2325
raise error.ValidationError('uid')
2426

2527

28+
def is_string_pid(s):
29+
return bool(PID_RE1.fullmatch(s)) and bool(PID_RE2.fullmatch(s))
30+
31+
32+
def check_string_pid(s):
33+
if not is_string_pid(s):
34+
raise error.ValidationError('pid')
35+
36+
2637
def is_uname(s):
2738
return bool(UNAME_RE.fullmatch(s))
2839

0 commit comments

Comments
 (0)