Coverage for mongo/engine.py: 97%

215 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2026-07-24 16:42 +0000

1from mongoengine import * 

2import mongoengine 

3from enum import IntEnum 

4from datetime import datetime 

5from zipfile import ZipFile, BadZipFile 

6 

7from config import settings 

8 

9__all__ = [*mongoengine.__all__] 

10 

11MONGO_HOST = settings.MONGO_HOST 

12 

13# FIXME: we should use config to check whether is in testing 

14if MONGO_HOST.startswith('mongomock'): 

15 import mongomock 

16 MONGO_HOST = MONGO_HOST.replace('mongomock', 'mongodb') 

17 connect( 

18 'normal-oj', 

19 host=MONGO_HOST, 

20 mongo_client_class=mongomock.MongoClient, 

21 ) 

22else: 

23 connect('normal-oj', host=MONGO_HOST) 

24 

25 

26class ZipField(FileField): 

27 

28 def __init__(self, max_size=0, **ks): 

29 super().__init__(**ks) 

30 self.max_size = max_size 

31 

32 def validate(self, value): 

33 super().validate(value) 

34 # skip check 

35 if not value: 

36 return 

37 try: 

38 with ZipFile(value) as zf: 

39 # the size of original files 

40 size = sum(info.file_size for info in zf.infolist()) 

41 except BadZipFile: 

42 self.error('Only accept zip file.') 

43 # no limit 

44 if self.max_size <= 0: 

45 return 

46 if size > self.max_size: 

47 self.error( 

48 f'{size} bytes exceed the max size limit ({self.max_size} bytes)' 

49 ) 

50 

51 

52class IntEnumField(IntField): 

53 

54 def __init__(self, enum: IntEnum, **ks): 

55 super().__init__(**ks) 

56 self.enum = enum 

57 

58 def validate(self, value): 

59 choices = (*self.enum.__members__.values(), ) 

60 if value not in choices: 

61 self.error(f'Value must be one of {choices}') 

62 

63 

64class Profile(EmbeddedDocument): 

65 displayed_name = StringField( 

66 db_field='displayedName', 

67 default='', 

68 max_length=16, 

69 ) 

70 bio = StringField( 

71 max_length=64, 

72 required=True, 

73 default='', 

74 ) 

75 

76 

77class EditorConfig(EmbeddedDocument): 

78 font_size = IntField(db_field='fontSize', 

79 min_value=8, 

80 max_value=72, 

81 default=14) 

82 theme = StringField( 

83 default='default', 

84 choices=[ 

85 "default", "base16-dark", "base16-light", "dracula", "eclipse", 

86 "material", "monokai" 

87 ], 

88 ) 

89 indent_type = IntField(db_field='indentType', default=1, choices=[0, 1]) 

90 tab_size = IntField( 

91 db_field='tabSize', 

92 default=4, 

93 min_value=1, 

94 max_value=8, 

95 ) 

96 language = IntField( 

97 default=0, 

98 choices=[0, 1, 2], 

99 ) 

100 

101 

102class Duration(EmbeddedDocument): 

103 start = DateTimeField(default=datetime.now) 

104 end = DateTimeField(default=datetime(2111, 10, 10)) 

105 

106 def __contains__(self, other) -> bool: 

107 if not isinstance(other, datetime): 

108 return False 

109 return self.start <= other <= self.end 

110 

111 

112class User(Document): 

113 

114 class Role(IntEnum): 

115 ADMIN = 0 

116 TEACHER = 1 

117 STUDENT = 2 

118 

119 username = StringField(max_length=16, required=True, primary_key=True) 

120 user_id = StringField(db_field='userId', max_length=24, required=True) 

121 user_id2 = StringField(db_field='userId2', max_length=24, default='') 

122 email = EmailField(required=True, unique=True, max_length=128) 

123 md5 = StringField(required=True, max_length=32) 

124 active = BooleanField(default=False) 

125 role = IntEnumField(default=Role.STUDENT, enum=Role) 

126 profile = EmbeddedDocumentField(Profile, default=Profile) 

127 editor_config = EmbeddedDocumentField( 

128 EditorConfig, 

129 db_field='editorConfig', 

130 default=EditorConfig, 

131 null=True, 

132 ) 

133 courses = ListField(ReferenceField('Course')) 

134 submissions = ListField(ReferenceField('Submission')) 

135 last_submit = DateTimeField(default=datetime.min) 

136 AC_problem_ids = ListField(IntField(), default=list) 

137 AC_submission = IntField(default=0) 

138 submission = IntField(default=0) 

139 problem_submission = DictField(db_field='problemSubmission') 

140 

141 @property 

142 def info(self): 

143 return { 

144 'username': self.username, 

145 'displayedName': self.profile.displayed_name, 

146 'md5': self.md5, 

147 'role': self.role, 

148 } 

149 

150 

151class Homework(Document): 

152 

153 homework_name = StringField( 

154 max_length=64, 

155 required=True, 

156 db_field='homeworkName', 

157 unique_with='course_id', 

158 ) 

159 markdown = StringField(max_length=10000, default='') 

160 scoreboard_status = IntField( 

161 default=0, 

162 choices=[0, 1], 

163 db_field='scoreboardStatus', 

164 ) 

165 course_id = StringField(required=True, db_field='courseId') 

166 duration = EmbeddedDocumentField(Duration, default=Duration) 

167 problem_ids = ListField(IntField(), db_field='problemIds') 

168 student_status = DictField(db_field='studentStatus') 

169 ip_filters = ListField(StringField(max_length=64), default=list) 

170 penalty = StringField(max_length=10000, default='score = 0') 

171 

172 

173class Course(Document): 

174 course_name = StringField( 

175 max_length=64, 

176 required=True, 

177 unique=True, 

178 db_field='courseName', 

179 ) 

180 student_nicknames = DictField(db_field='studentNicknames') 

181 course_status = IntField(default=0, choices=[0, 1]) 

182 teacher = ReferenceField('User') 

183 tas = ListField(ReferenceField('User')) 

184 homeworks = ListField(ReferenceField('Homework', reverse_delete_rule=PULL)) 

185 announcements = ListField(ReferenceField('Announcement')) 

186 posts = ListField(ReferenceField('Post'), default=list) 

187 student_scores = DictField(db_field='studentScores') 

188 

189 

190class Number(Document): 

191 name = StringField( 

192 max_length=64, 

193 primary_key=True, 

194 ) 

195 number = IntField(default=1) 

196 

197 

198class ProblemCase(EmbeddedDocument): 

199 task_score = IntField(required=True, db_field='taskScore') 

200 case_count = IntField(required=True, db_field='caseCount') 

201 memory_limit = IntField(required=True, db_field='memoryLimit') 

202 time_limit = IntField(required=True, db_field='timeLimit') 

203 

204 

205class ProblemTestCase(EmbeddedDocument): 

206 language = IntField(choices=[0, 1, 2]) 

207 fill_in_template = StringField(db_field='fillInTemplate', max_length=16000) 

208 tasks = EmbeddedDocumentListField( 

209 ProblemCase, 

210 default=list, 

211 ) 

212 # zip file contains testcase input/output 

213 case_zip = ZipField( 

214 db_field='caseZip', 

215 defautl=None, 

216 null=True, 

217 ) 

218 case_zip_minio_path = StringField( 

219 null=True, 

220 max_length=256, 

221 db_field='caseZipMinioPath', 

222 ) 

223 

224 

225class ProblemDescription(EmbeddedDocument): 

226 description = StringField(max_length=100000) 

227 input = StringField(max_length=100000) 

228 output = StringField(max_length=100000) 

229 hint = StringField(max_length=100000) 

230 sample_input = ListField( 

231 StringField(max_length=1024), 

232 default=list, 

233 db_field='sampleInput', 

234 ) 

235 sample_output = ListField( 

236 StringField(max_length=1024), 

237 default=list, 

238 db_field='sampleOutput', 

239 ) 

240 

241 

242class Problem(Document): 

243 

244 class Visibility: 

245 SHOW = 0 

246 HIDDEN = 1 

247 

248 problem_id = SequenceField( 

249 db_field='problemId', 

250 required=True, 

251 primary_key=True, 

252 ) 

253 courses = ListField(ReferenceField('Course'), default=list) 

254 problem_status = IntField( 

255 default=1, 

256 choices=[Visibility.SHOW, Visibility.HIDDEN], 

257 db_field='problemStatus', 

258 ) 

259 problem_type = IntField( 

260 default=0, 

261 choices=[0, 1, 2], 

262 db_field='problemType', 

263 ) 

264 problem_name = StringField( 

265 db_field='problemName', 

266 max_length=64, 

267 required=True, 

268 ) 

269 description = EmbeddedDocumentField( 

270 ProblemDescription, 

271 default=ProblemDescription, 

272 ) 

273 owner = StringField(max_length=16, required=True) 

274 # pdf = 

275 tags = ListField(StringField(max_length=16)) 

276 test_case = EmbeddedDocumentField( 

277 ProblemTestCase, 

278 db_field='testCase', 

279 default=ProblemTestCase, 

280 ) 

281 ac_user = IntField(db_field='ACUser', default=0) 

282 submitter = IntField(default=0) 

283 homeworks = ListField(ReferenceField('Homework'), default=list) 

284 # user can view stdout/stderr 

285 can_view_stdout = BooleanField(db_field='canViewStdout', default=True) 

286 cpp_report_url = StringField( 

287 db_field='cppReportUrl', 

288 default='', 

289 max_length=128, 

290 ) 

291 python_report_url = StringField( 

292 db_field='pythonReportUrl', 

293 default='', 

294 max_length=128, 

295 ) 

296 # moss_status (not started: 0, processing: 1, done: 2) 

297 moss_status = IntField( 

298 default=0, 

299 choices=[0, 1, 2], 

300 db_field='mossStatus', 

301 ) 

302 # bitmask of allowed languages (c: 1, cpp: 2, py3: 4) 

303 allowed_language = IntField(db_field='allowedLanguage', default=7) 

304 # high score for each student 

305 # Dict[username, score] 

306 high_scores = DictField(db_field='highScore', default={}) 

307 quota = IntField(default=-1) 

308 default_code = StringField( 

309 db_field='defaultCode', 

310 max_length=10**4, 

311 default='', 

312 ) 

313 

314 

315class CaseResult(EmbeddedDocument): 

316 status = IntField(required=True) 

317 exec_time = IntField(required=True, db_field='execTime') 

318 memory_usage = IntField(required=True, db_field='memoryUsage') 

319 output = ZipField( 

320 required=True, 

321 null=True, 

322 max_size=11**9, 

323 ) 

324 output_minio_path = StringField( 

325 null=True, 

326 max_length=256, 

327 db_field='outputMinioPath', 

328 ) 

329 

330 

331class TaskResult(EmbeddedDocument): 

332 status = IntField(default=-1) 

333 exec_time = IntField(default=-1, db_field='execTime') 

334 memory_usage = IntField(default=-1, db_field='memoryUsage') 

335 score = IntField(default=0) 

336 cases = EmbeddedDocumentListField(CaseResult, default=list) 

337 

338 

339class Submission(Document): 

340 meta = { 

341 'indexes': [ 

342 ( 

343 'id', 

344 'user', 

345 'score', 

346 'status', 

347 'problem', 

348 'language', 

349 'timestamp', 

350 ), 

351 ] 

352 } 

353 problem = ReferenceField(Problem, required=True) 

354 user = ReferenceField(User, required=True) 

355 language = IntField( 

356 required=True, 

357 min_value=0, 

358 max_value=3, 

359 db_field='languageType', 

360 ) 

361 timestamp = DateTimeField(required=True) 

362 status = IntField(default=-2) 

363 score = IntField(default=-1) 

364 tasks = EmbeddedDocumentListField(TaskResult, default=list) 

365 exec_time = IntField(default=-1, db_field='runTime') 

366 memory_usage = IntField(default=-1, db_field='memoryUsage') 

367 code = ZipField(null=True, max_size=10**7) 

368 code_minio_path = StringField( 

369 null=True, 

370 max_length=256, 

371 db_field='codeMinioPath', 

372 ) 

373 last_send = DateTimeField(db_field='lastSend', default=datetime.now) 

374 comment = FileField(default=None, null=True) 

375 ip_addr = StringField(default=None, null=True) 

376 

377 

378class Message(Document): 

379 timestamp = DateTimeField(default=datetime.now) 

380 sender = StringField(max_length=16, required=True) 

381 receivers = ListField(StringField(max_length=16), required=True) 

382 status = IntField(default=0, choices=[0, 1]) # not delete / delete 

383 title = StringField(max_length=32, required=True) 

384 markdown = StringField(max_length=100000, required=True) 

385 

386 

387class Announcement(Document): 

388 status = IntField(default=0, choices=[0, 1]) # not delete / delete 

389 title = StringField(max_length=64, required=True) 

390 course = ReferenceField('Course', required=True) 

391 create_time = DateTimeField(db_field='createTime', default=datetime.now) 

392 update_time = DateTimeField(db_field='updateTime', default=datetime.now) 

393 creator = ReferenceField('User', required=True) 

394 updater = ReferenceField('User', required=True) 

395 markdown = StringField(max_length=100000, required=True) 

396 pinned = BooleanField(default=False) 

397 

398 

399class PostThread(Document): 

400 markdown = StringField(default='', required=True, max_length=100000) 

401 author = ReferenceField('User', db_field='author') 

402 course_id = ReferenceField('Course', db_field='courseId') 

403 depth = IntField(default=0) # 0 is top post, 1 is reply to post 

404 created = DateTimeField(required=True) 

405 updated = DateTimeField(required=True) 

406 status = IntField(default=0, choices=[0, 1]) # not delete / delete 

407 reply = ListField(ReferenceField('PostThread', db_field='postThread'), 

408 dafault=list) 

409 

410 

411class Post(Document): 

412 post_name = StringField(default='', required=True, max_length=64) 

413 thread = ReferenceField('PostThread', db_field='postThread') 

414 

415 

416class Config(Document): 

417 meta = { 

418 'allow_inheritance': True, 

419 } 

420 name = StringField(required=True, max_length=64, primary_key=True) 

421 

422 

423class Sandbox(EmbeddedDocument): 

424 name = StringField(required=True) 

425 url = StringField(required=True) 

426 token = StringField(required=True) 

427 

428 

429class SubmissionConfig(Config): 

430 rate_limit = IntField(default=0, db_field='rateLimit') 

431 sandbox_instances = EmbeddedDocumentListField( 

432 Sandbox, 

433 default=[ 

434 Sandbox( 

435 name='Sandbox-0', 

436 url='http://sandbox:1450', 

437 token='KoNoSandboxDa', 

438 ), 

439 ], 

440 db_field='sandboxInstances', 

441 ) 

442 

443 

444class LoginRecords(Document): 

445 user_id = StringField(required=True) 

446 ip_addr = StringField(required=True) 

447 success = BooleanField(required=True, default=False) 

448 timestamp = DateTimeField(required=True, default=datetime.now)