Skip to content

Commit

Permalink
Merge pull request #30 from Phoupraw/check
Browse files Browse the repository at this point in the history
  • Loading branch information
YDX-2147483647 authored Aug 10, 2023
2 parents a4a3e80 + 744c4e9 commit 7db5396
Show file tree
Hide file tree
Showing 25 changed files with 262 additions and 103 deletions.
15 changes: 15 additions & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
env:
browser: true
es2021: true
extends: standard
parserOptions:
ecmaVersion: latest
sourceType: module
rules:
indent:
- error
- 4
camelcase: 0
comma-dangle:
- error
- only-multiline
14 changes: 13 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ repos:
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: "v0.0.264"
rev: "v0.0.284"
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix, --show-fixes]
Expand All @@ -24,6 +24,18 @@ repos:
args:
- --quiet
- id: djlint-django
- repo: https://github.com/pre-commit/mirrors-eslint
rev: v8.46.0
hooks:
- id: eslint
args:
- --fix
additional_dependencies:
- eslint@^8.0.1
- eslint-config-standard@latest
- eslint-plugin-import@^2.25.2
- eslint-plugin-n@^15.0.0 || ^16.0.0
- eslint-plugin-promise@^6.0.0
- repo: https://github.com/crate-ci/typos
rev: v1.15.0
hooks:
Expand Down
1 change: 1 addition & 0 deletions contest/contest/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
""""项目容器"""
3 changes: 1 addition & 2 deletions contest/contest/asgi.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""
ASGI config for contest project.
"""ASGI config for contest project.
It exposes the ASGI callable as a module-level variable named ``application``.
Expand Down
4 changes: 2 additions & 2 deletions contest/contest/settings.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""
Django settings for contest project.
"""Django settings for contest project.
Generated by 'django-admin startproject' using Django 4.1.2.
Expand All @@ -9,6 +8,7 @@
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.1/ref/settings/
"""
from __future__ import annotations

from pathlib import Path
from shutil import which
Expand Down
5 changes: 4 additions & 1 deletion contest/contest/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.1/topics/http/urls/
Examples:
Examples
--------
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Expand All @@ -12,6 +14,7 @@
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path
Expand Down
3 changes: 1 addition & 2 deletions contest/contest/wsgi.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""
WSGI config for contest project.
"""WSGI config for contest project.
It exposes the WSGI callable as a module-level variable named ``application``.
Expand Down
4 changes: 4 additions & 0 deletions contest/js/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""JavaScript 程序及构建系统
请参考 doc/js.md。
"""
28 changes: 14 additions & 14 deletions contest/js/static_src/rollup.config.dev.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'

export default {
input: {
status: './src/status.js',
contest: './src/contest.js',
toggle_mobile_menu: './src/toggle_mobile_menu.js',
},
output: {
dir: '../static/js/dist/',
format: 'es',
entryFileNames: '[name].js',
},
plugins: [
commonjs(),
resolve(),
],
input: {
status: './src/status.js',
contest: './src/contest.js',
toggle_mobile_menu: './src/toggle_mobile_menu.js',
},
output: {
dir: '../static/js/dist/',
format: 'es',
entryFileNames: '[name].js',
},
plugins: [
commonjs(),
resolve(),
],
}
22 changes: 11 additions & 11 deletions contest/js/static_src/rollup.config.prod.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import terser from '@rollup/plugin-terser'
import dev_config from './rollup.config.dev.mjs'

export default {
input: dev_config.input,
output: {
...dev_config.output,
compact: true,
},
plugins: [
commonjs(),
resolve(),
babel({ babelHelpers: 'bundled' }),
terser(),
],
input: dev_config.input,
output: {
...dev_config.output,
compact: true,
},
plugins: [
commonjs(),
resolve(),
babel({ babelHelpers: 'bundled' }),
terser(),
],
}
18 changes: 9 additions & 9 deletions contest/js/static_src/src/contest.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import Swal from 'sweetalert2'

import { get_data } from "./util"
import { get_data } from './util'

/** 时间允差,毫秒 */
const TIME_MARGIN = 10 * 1e3

const update_url = get_data("update-url")
const deadline = Date.parse(get_data("deadline"))
const deadline_duration_seconds = get_data("deadline-duration")
const update_url = get_data('update-url')
const deadline = Date.parse(get_data('deadline'))
const deadline_duration_seconds = get_data('deadline-duration')

//! 作答

const form = document.querySelector("form")
const form = document.querySelector('form')
const filed_sets = Array.from(form.querySelectorAll('fieldset'))
const inputs = filed_sets.map(set => Array.from(set.querySelectorAll('input')))
/** @type {HTMLDivElement} */
Expand All @@ -31,9 +31,9 @@ async function update_contest_progress () {
method: 'POST',
body: new FormData(form)
})
if (!response.ok && response.status == 403) {
if (!response.ok && response.status === 403) {
// 锁定表单
filed_sets.forEach(set => set.disabled = true)
filed_sets.forEach(set => { set.disabled = true })

// 提示提交
const result = await Swal.fire({
Expand Down Expand Up @@ -75,7 +75,7 @@ setTimeout(update_contest_progress_periodically, deadline - Date.now() - TIME_MA

/** 时间进度 */
class TimeProgress {
constructor() {
constructor () {
/** @type {HTMLDivElement} */
this.bar = document.querySelector('div#time-progress-bar')
/** @type {HTMLSpanElement} */
Expand Down Expand Up @@ -112,7 +112,7 @@ class TimeProgress {
setTimeout(() => { this.update() }, 100)
}
} else {
this.bar.style.width = `100%`
this.bar.style.width = '100%'
this.text.textContent = '0秒'
}

Expand Down
2 changes: 1 addition & 1 deletion contest/js/static_src/src/status.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { get_data } from './util'
/** @type {"not taking" | "taking contest" | "deadline passed" | ""} */
const status = get_data('status')

if (status == 'deadline passed') {
if (status === 'deadline passed') {
Swal.fire({
title: '上次作答已超时',
text: '截止前作答部分已尽量保存。建议下次早些提交。',
Expand Down
4 changes: 4 additions & 0 deletions contest/quiz/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""答题
主应用。
"""
7 changes: 4 additions & 3 deletions contest/quiz/auth_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@


class CASBackend(AbstractCASBackend):
"""Authentication backend for CAS"""

def configure_user(self, user: User) -> User:
"""CAS 自动创建 User 后,继续建立 Student"""

# todo: Not all users are students.
Student.objects.create(
user=user,
user=user, # type: ignore[misc]
name=user.first_name + user.last_name or user.username,
) # type: ignore
)
# `AbstractCASBackend`中`User`被标注为标准`User`,
# 而`Student`需要自定义的,导致虚警。
# `AbstractCASBackend`实际会用`django.contrib.auth.get_user_model`,
Expand Down
14 changes: 14 additions & 0 deletions contest/quiz/constants.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
"""常量
视图、模板中的所有常量。
"""
from dataclasses import dataclass
from datetime import timedelta
from typing import NamedTuple


class PageMeta(NamedTuple):
"""页面的元数据
用于 ROUTES。
"""

title: str
"""标题用于`<title>`、`<h1>`等"""

Expand All @@ -13,6 +22,11 @@ class PageMeta(NamedTuple):

@dataclass(frozen=True)
class ConstantsNamespace:
"""常量的命名空间
为了避免修改,使用 dataclass 而非 dict。
"""

DEADLINE_DURATION = timedelta(minutes=15)
"""作答限时"""

Expand Down
48 changes: 24 additions & 24 deletions contest/quiz/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,25 @@ class User(AbstractUser):
class Question(models.Model):
content = models.CharField("题干内容", max_length=200)

def __str__(self) -> str:
return self.content

class Meta:
verbose_name_plural = verbose_name = "题目"

def __str__(self) -> str:
return self.content


class Choice(models.Model):
content = models.CharField("选项内容", max_length=200)
correct = models.BooleanField("是否应选")

question = models.ForeignKey(Question, verbose_name="题干", on_delete=models.CASCADE)

def __str__(self) -> str:
return self.content

class Meta:
verbose_name_plural = verbose_name = "选项"

def __str__(self) -> str:
return self.content


class Student(models.Model):
user = models.OneToOneField(
Expand All @@ -48,23 +48,26 @@ class Student(models.Model):
verbose_name="用户",
)

@admin.display(description="最终得分")
def final_score(self) -> float:
return max([0] + [r.score() for r in self.response_set.all()])

name = models.CharField("姓名", max_length=50)

class Meta:
verbose_name_plural = verbose_name = "学生"

def __str__(self) -> str:
return self.name

class Meta:
verbose_name_plural = verbose_name = "学生"
@admin.display(description="最终得分")
def final_score(self) -> float:
return max([0] + [r.score() for r in self.response_set.all()])


class Response(models.Model):
submit_at = models.DateTimeField("提交时刻")
student = models.ForeignKey(Student, verbose_name="作答者", on_delete=models.CASCADE)

class Meta:
verbose_name_plural = verbose_name = "答卷"

def __str__(self) -> str:
return f"{self.student.name}{self.submit_at} 提交的答卷"

Expand All @@ -78,9 +81,6 @@ def score(self) -> float:
constants.SCORE * len(self.answer_set.filter(choice__correct=True)) / n_answers
)

class Meta:
verbose_name_plural = verbose_name = "答卷"


class Answer(models.Model):
response = models.ForeignKey(Response, verbose_name="答卷", on_delete=models.CASCADE)
Expand All @@ -89,12 +89,12 @@ class Answer(models.Model):
Choice, verbose_name="所选选项", blank=True, null=True, on_delete=models.CASCADE
)

def __str__(self) -> str:
return f"“{self.question}” → “{self.choice}”"

class Meta:
verbose_name_plural = verbose_name = "回答"

def __str__(self) -> str:
return f"“{self.question}” → “{self.choice}”"


class DraftResponse(models.Model):
student = models.OneToOneField(
Expand All @@ -105,12 +105,12 @@ class DraftResponse(models.Model):
)
deadline = models.DateTimeField("截止时刻")

def __str__(self) -> str:
return f"{self.student.name} 的答卷草稿"

class Meta:
verbose_name_plural = verbose_name = "答卷草稿"

def __str__(self) -> str:
return f"{self.student.name} 的答卷草稿"

def finalize(self, submit_at: datetime) -> tuple[Response, list[Answer]]:
"""转换为正式 Response
Expand Down Expand Up @@ -142,12 +142,12 @@ class DraftAnswer(models.Model):
Choice, verbose_name="所选选项", blank=True, null=True, on_delete=models.CASCADE
)

def __str__(self) -> str:
return f"“{self.question}” → “{self.choice}”"

class Meta:
verbose_name_plural = verbose_name = "回答草稿"

def __str__(self) -> str:
return f"“{self.question}” → “{self.choice}”"

def finalize(self, response: Response) -> Answer:
"""转换为正式 Answer
Expand Down
Loading

0 comments on commit 7db5396

Please sign in to comment.