Coverage for mongo/engine.py: 97%
215 statements
« prev ^ index » next coverage.py v7.9.2, created at 2026-07-24 16:42 +0000
« 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
7from config import settings
9__all__ = [*mongoengine.__all__]
11MONGO_HOST = settings.MONGO_HOST
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)
26class ZipField(FileField):
28 def __init__(self, max_size=0, **ks):
29 super().__init__(**ks)
30 self.max_size = max_size
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 )
52class IntEnumField(IntField):
54 def __init__(self, enum: IntEnum, **ks):
55 super().__init__(**ks)
56 self.enum = enum
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}')
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 )
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 )
102class Duration(EmbeddedDocument):
103 start = DateTimeField(default=datetime.now)
104 end = DateTimeField(default=datetime(2111, 10, 10))
106 def __contains__(self, other) -> bool:
107 if not isinstance(other, datetime):
108 return False
109 return self.start <= other <= self.end
112class User(Document):
114 class Role(IntEnum):
115 ADMIN = 0
116 TEACHER = 1
117 STUDENT = 2
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')
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 }
151class Homework(Document):
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')
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')
190class Number(Document):
191 name = StringField(
192 max_length=64,
193 primary_key=True,
194 )
195 number = IntField(default=1)
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')
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 )
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 )
242class Problem(Document):
244 class Visibility:
245 SHOW = 0
246 HIDDEN = 1
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 )
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 )
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)
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)
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)
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)
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)
411class Post(Document):
412 post_name = StringField(default='', required=True, max_length=64)
413 thread = ReferenceField('PostThread', db_field='postThread')
416class Config(Document):
417 meta = {
418 'allow_inheritance': True,
419 }
420 name = StringField(required=True, max_length=64, primary_key=True)
423class Sandbox(EmbeddedDocument):
424 name = StringField(required=True)
425 url = StringField(required=True)
426 token = StringField(required=True)
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 )
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)