fmt
This commit is contained in:
10
.flake8
10
.flake8
@@ -1,10 +0,0 @@
|
|||||||
[flake8]
|
|
||||||
exclude =
|
|
||||||
xss_filter.py,
|
|
||||||
*/migrations/,
|
|
||||||
*settings.py
|
|
||||||
*/apps.py
|
|
||||||
venv/
|
|
||||||
max-line-length = 180
|
|
||||||
inline-quotes = "
|
|
||||||
no-accept-encodings = True
|
|
||||||
@@ -2,10 +2,11 @@ import functools
|
|||||||
import hashlib
|
import hashlib
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from contest.models import Contest, ContestRuleType, ContestStatus, ContestType
|
||||||
from problem.models import Problem
|
from problem.models import Problem
|
||||||
from contest.models import Contest, ContestType, ContestStatus, ContestRuleType
|
from utils.api import APIError, JSONResponse
|
||||||
from utils.api import JSONResponse, APIError
|
|
||||||
from utils.constants import CONTEST_PASSWORD_SESSION_KEY
|
from utils.constants import CONTEST_PASSWORD_SESSION_KEY
|
||||||
|
|
||||||
from .models import ProblemPermission
|
from .models import ProblemPermission
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.utils.timezone import now
|
|
||||||
from django.utils.deprecation import MiddlewareMixin
|
from django.utils.deprecation import MiddlewareMixin
|
||||||
|
from django.utils.timezone import now
|
||||||
|
|
||||||
from utils.api import JSONResponse
|
|
||||||
from account.models import User
|
from account.models import User
|
||||||
|
from utils.api import JSONResponse
|
||||||
|
|
||||||
|
|
||||||
class APITokenAuthMiddleware(MiddlewareMixin):
|
class APITokenAuthMiddleware(MiddlewareMixin):
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from django.contrib.auth.models import AbstractBaseUser
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.models import AbstractBaseUser
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from utils.models import JSONField
|
from utils.models import JSONField
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from utils.api import serializers, UsernameSerializer
|
from utils.api import UsernameSerializer, serializers
|
||||||
|
|
||||||
from .models import AdminType, ProblemPermission, User, UserProfile
|
from .models import AdminType, ProblemPermission, User, UserProfile
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import dramatiq
|
import dramatiq
|
||||||
|
|
||||||
from options.options import SysOptions
|
from options.options import SysOptions
|
||||||
from utils.shortcuts import send_email, DRAMATIQ_WORKER_ARGS
|
from utils.shortcuts import DRAMATIQ_WORKER_ARGS, send_email
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
646
account/tests.py
646
account/tests.py
@@ -1,646 +0,0 @@
|
|||||||
import time
|
|
||||||
|
|
||||||
from unittest import mock
|
|
||||||
from datetime import timedelta
|
|
||||||
from copy import deepcopy
|
|
||||||
|
|
||||||
from django.contrib import auth
|
|
||||||
from django.utils.timezone import now
|
|
||||||
from otpauth import OtpAuth
|
|
||||||
|
|
||||||
from utils.api.tests import APIClient, APITestCase
|
|
||||||
from utils.shortcuts import rand_str
|
|
||||||
from options.options import SysOptions
|
|
||||||
|
|
||||||
from .models import AdminType, ProblemPermission, User
|
|
||||||
from utils.constants import ContestRuleType
|
|
||||||
|
|
||||||
|
|
||||||
class PermissionDecoratorTest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.regular_user = User.objects.create(username="regular_user")
|
|
||||||
self.admin = User.objects.create(username="admin")
|
|
||||||
self.super_admin = User.objects.create(username="super_admin")
|
|
||||||
self.request = mock.MagicMock()
|
|
||||||
self.request.user.is_authenticated = mock.MagicMock()
|
|
||||||
|
|
||||||
def test_login_required(self):
|
|
||||||
self.request.user.is_authenticated.return_value = False
|
|
||||||
|
|
||||||
def test_admin_required(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_super_admin_required(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DuplicateUserCheckAPITest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
user = self.create_user("test", "test123", login=False)
|
|
||||||
user.email = "test@test.com"
|
|
||||||
user.save()
|
|
||||||
self.url = self.reverse("check_username_or_email")
|
|
||||||
|
|
||||||
def test_duplicate_username(self):
|
|
||||||
resp = self.client.post(self.url, data={"username": "test"})
|
|
||||||
data = resp.data["data"]
|
|
||||||
self.assertEqual(data["username"], True)
|
|
||||||
resp = self.client.post(self.url, data={"username": "Test"})
|
|
||||||
self.assertEqual(resp.data["data"]["username"], True)
|
|
||||||
|
|
||||||
def test_ok_username(self):
|
|
||||||
resp = self.client.post(self.url, data={"username": "test1"})
|
|
||||||
data = resp.data["data"]
|
|
||||||
self.assertFalse(data["username"])
|
|
||||||
|
|
||||||
def test_duplicate_email(self):
|
|
||||||
resp = self.client.post(self.url, data={"email": "test@test.com"})
|
|
||||||
self.assertEqual(resp.data["data"]["email"], True)
|
|
||||||
resp = self.client.post(self.url, data={"email": "Test@Test.com"})
|
|
||||||
self.assertTrue(resp.data["data"]["email"])
|
|
||||||
|
|
||||||
def test_ok_email(self):
|
|
||||||
resp = self.client.post(self.url, data={"email": "aa@test.com"})
|
|
||||||
self.assertFalse(resp.data["data"]["email"])
|
|
||||||
|
|
||||||
|
|
||||||
class TFARequiredCheckAPITest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.url = self.reverse("tfa_required_check")
|
|
||||||
self.create_user("test", "test123", login=False)
|
|
||||||
|
|
||||||
def test_not_required_tfa(self):
|
|
||||||
resp = self.client.post(self.url, data={"username": "test"})
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
self.assertEqual(resp.data["data"]["result"], False)
|
|
||||||
|
|
||||||
def test_required_tfa(self):
|
|
||||||
user = User.objects.first()
|
|
||||||
user.two_factor_auth = True
|
|
||||||
user.save()
|
|
||||||
resp = self.client.post(self.url, data={"username": "test"})
|
|
||||||
self.assertEqual(resp.data["data"]["result"], True)
|
|
||||||
|
|
||||||
|
|
||||||
class UserLoginAPITest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.username = self.password = "test"
|
|
||||||
self.user = self.create_user(username=self.username, password=self.password, login=False)
|
|
||||||
self.login_url = self.reverse("user_login_api")
|
|
||||||
|
|
||||||
def _set_tfa(self):
|
|
||||||
self.user.two_factor_auth = True
|
|
||||||
tfa_token = rand_str(32)
|
|
||||||
self.user.tfa_token = tfa_token
|
|
||||||
self.user.save()
|
|
||||||
return tfa_token
|
|
||||||
|
|
||||||
def test_login_with_correct_info(self):
|
|
||||||
response = self.client.post(self.login_url,
|
|
||||||
data={"username": self.username, "password": self.password})
|
|
||||||
self.assertDictEqual(response.data, {"error": None, "data": "Succeeded"})
|
|
||||||
|
|
||||||
user = auth.get_user(self.client)
|
|
||||||
self.assertTrue(user.is_authenticated)
|
|
||||||
|
|
||||||
def test_login_with_correct_info_upper_username(self):
|
|
||||||
resp = self.client.post(self.login_url, data={"username": self.username.upper(), "password": self.password})
|
|
||||||
self.assertDictEqual(resp.data, {"error": None, "data": "Succeeded"})
|
|
||||||
user = auth.get_user(self.client)
|
|
||||||
self.assertTrue(user.is_authenticated)
|
|
||||||
|
|
||||||
def test_login_with_wrong_info(self):
|
|
||||||
response = self.client.post(self.login_url,
|
|
||||||
data={"username": self.username, "password": "invalid_password"})
|
|
||||||
self.assertDictEqual(response.data, {"error": "error", "data": "Invalid username or password"})
|
|
||||||
|
|
||||||
user = auth.get_user(self.client)
|
|
||||||
self.assertFalse(user.is_authenticated)
|
|
||||||
|
|
||||||
def test_tfa_login(self):
|
|
||||||
token = self._set_tfa()
|
|
||||||
code = OtpAuth(token).totp()
|
|
||||||
if len(str(code)) < 6:
|
|
||||||
code = (6 - len(str(code))) * "0" + str(code)
|
|
||||||
response = self.client.post(self.login_url,
|
|
||||||
data={"username": self.username,
|
|
||||||
"password": self.password,
|
|
||||||
"tfa_code": code})
|
|
||||||
self.assertDictEqual(response.data, {"error": None, "data": "Succeeded"})
|
|
||||||
|
|
||||||
user = auth.get_user(self.client)
|
|
||||||
self.assertTrue(user.is_authenticated)
|
|
||||||
|
|
||||||
def test_tfa_login_wrong_code(self):
|
|
||||||
self._set_tfa()
|
|
||||||
response = self.client.post(self.login_url,
|
|
||||||
data={"username": self.username,
|
|
||||||
"password": self.password,
|
|
||||||
"tfa_code": "qqqqqq"})
|
|
||||||
self.assertDictEqual(response.data, {"error": "error", "data": "Invalid two factor verification code"})
|
|
||||||
|
|
||||||
user = auth.get_user(self.client)
|
|
||||||
self.assertFalse(user.is_authenticated)
|
|
||||||
|
|
||||||
def test_tfa_login_without_code(self):
|
|
||||||
self._set_tfa()
|
|
||||||
response = self.client.post(self.login_url,
|
|
||||||
data={"username": self.username,
|
|
||||||
"password": self.password})
|
|
||||||
self.assertDictEqual(response.data, {"error": "error", "data": "tfa_required"})
|
|
||||||
|
|
||||||
user = auth.get_user(self.client)
|
|
||||||
self.assertFalse(user.is_authenticated)
|
|
||||||
|
|
||||||
def test_user_disabled(self):
|
|
||||||
self.user.is_disabled = True
|
|
||||||
self.user.save()
|
|
||||||
resp = self.client.post(self.login_url, data={"username": self.username,
|
|
||||||
"password": self.password})
|
|
||||||
self.assertDictEqual(resp.data, {"error": "error", "data": "Your account has been disabled"})
|
|
||||||
|
|
||||||
|
|
||||||
class CaptchaTest(APITestCase):
|
|
||||||
def _set_captcha(self, session):
|
|
||||||
captcha = rand_str(4)
|
|
||||||
session["_django_captcha_key"] = captcha
|
|
||||||
session["_django_captcha_expires_time"] = int(time.time()) + 30
|
|
||||||
session.save()
|
|
||||||
return captcha
|
|
||||||
|
|
||||||
|
|
||||||
class UserRegisterAPITest(CaptchaTest):
|
|
||||||
def setUp(self):
|
|
||||||
self.client = APIClient()
|
|
||||||
self.register_url = self.reverse("user_register_api")
|
|
||||||
self.captcha = rand_str(4)
|
|
||||||
|
|
||||||
self.data = {"username": "test_user", "password": "testuserpassword",
|
|
||||||
"real_name": "real_name", "email": "test@qduoj.com",
|
|
||||||
"captcha": self._set_captcha(self.client.session)}
|
|
||||||
|
|
||||||
def test_website_config_limit(self):
|
|
||||||
SysOptions.allow_register = False
|
|
||||||
resp = self.client.post(self.register_url, data=self.data)
|
|
||||||
self.assertDictEqual(resp.data, {"error": "error", "data": "Register function has been disabled by admin"})
|
|
||||||
|
|
||||||
def test_invalid_captcha(self):
|
|
||||||
self.data["captcha"] = "****"
|
|
||||||
response = self.client.post(self.register_url, data=self.data)
|
|
||||||
self.assertDictEqual(response.data, {"error": "error", "data": "Invalid captcha"})
|
|
||||||
|
|
||||||
self.data.pop("captcha")
|
|
||||||
response = self.client.post(self.register_url, data=self.data)
|
|
||||||
self.assertTrue(response.data["error"] is not None)
|
|
||||||
|
|
||||||
def test_register_with_correct_info(self):
|
|
||||||
response = self.client.post(self.register_url, data=self.data)
|
|
||||||
self.assertDictEqual(response.data, {"error": None, "data": "Succeeded"})
|
|
||||||
|
|
||||||
def test_username_already_exists(self):
|
|
||||||
self.test_register_with_correct_info()
|
|
||||||
|
|
||||||
self.data["captcha"] = self._set_captcha(self.client.session)
|
|
||||||
self.data["email"] = "test1@qduoj.com"
|
|
||||||
response = self.client.post(self.register_url, data=self.data)
|
|
||||||
self.assertDictEqual(response.data, {"error": "error", "data": "Username already exists"})
|
|
||||||
|
|
||||||
def test_email_already_exists(self):
|
|
||||||
self.test_register_with_correct_info()
|
|
||||||
|
|
||||||
self.data["captcha"] = self._set_captcha(self.client.session)
|
|
||||||
self.data["username"] = "test_user1"
|
|
||||||
response = self.client.post(self.register_url, data=self.data)
|
|
||||||
self.assertDictEqual(response.data, {"error": "error", "data": "Email already exists"})
|
|
||||||
|
|
||||||
|
|
||||||
class SessionManagementAPITest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.create_user("test", "test123")
|
|
||||||
self.url = self.reverse("session_management_api")
|
|
||||||
# launch a request to provide session data
|
|
||||||
login_url = self.reverse("user_login_api")
|
|
||||||
self.client.post(login_url, data={"username": "test", "password": "test123"})
|
|
||||||
|
|
||||||
def test_get_sessions(self):
|
|
||||||
resp = self.client.get(self.url)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
data = resp.data["data"]
|
|
||||||
self.assertEqual(len(data), 1)
|
|
||||||
|
|
||||||
# def test_delete_session_key(self):
|
|
||||||
# resp = self.client.delete(self.url + "?session_key=" + self.session_key)
|
|
||||||
# self.assertSuccess(resp)
|
|
||||||
|
|
||||||
def test_delete_session_with_invalid_key(self):
|
|
||||||
resp = self.client.delete(self.url + "?session_key=aaaaaaaaaa")
|
|
||||||
self.assertDictEqual(resp.data, {"error": "error", "data": "Invalid session_key"})
|
|
||||||
|
|
||||||
|
|
||||||
class UserProfileAPITest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.url = self.reverse("user_profile_api")
|
|
||||||
|
|
||||||
def test_get_profile_without_login(self):
|
|
||||||
resp = self.client.get(self.url)
|
|
||||||
self.assertDictEqual(resp.data, {"error": None, "data": None})
|
|
||||||
|
|
||||||
def test_get_profile(self):
|
|
||||||
self.create_user("test", "test123")
|
|
||||||
resp = self.client.get(self.url)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
|
|
||||||
def test_update_profile(self):
|
|
||||||
self.create_user("test", "test123")
|
|
||||||
update_data = {"real_name": "zemal", "submission_number": 233, "language": "en-US"}
|
|
||||||
resp = self.client.put(self.url, data=update_data)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
data = resp.data["data"]
|
|
||||||
self.assertEqual(data["real_name"], "zemal")
|
|
||||||
self.assertEqual(data["submission_number"], 0)
|
|
||||||
self.assertEqual(data["language"], "en-US")
|
|
||||||
|
|
||||||
|
|
||||||
class TwoFactorAuthAPITest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.url = self.reverse("two_factor_auth_api")
|
|
||||||
self.create_user("test", "test123")
|
|
||||||
|
|
||||||
def _get_tfa_code(self):
|
|
||||||
user = User.objects.first()
|
|
||||||
code = OtpAuth(user.tfa_token).totp()
|
|
||||||
if len(str(code)) < 6:
|
|
||||||
code = (6 - len(str(code))) * "0" + str(code)
|
|
||||||
return code
|
|
||||||
|
|
||||||
def test_get_image(self):
|
|
||||||
resp = self.client.get(self.url)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
|
|
||||||
def test_open_tfa_with_invalid_code(self):
|
|
||||||
self.test_get_image()
|
|
||||||
resp = self.client.post(self.url, data={"code": "000000"})
|
|
||||||
self.assertDictEqual(resp.data, {"error": "error", "data": "Invalid code"})
|
|
||||||
|
|
||||||
def test_open_tfa_with_correct_code(self):
|
|
||||||
self.test_get_image()
|
|
||||||
code = self._get_tfa_code()
|
|
||||||
resp = self.client.post(self.url, data={"code": code})
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
user = User.objects.first()
|
|
||||||
self.assertEqual(user.two_factor_auth, True)
|
|
||||||
|
|
||||||
def test_close_tfa_with_invalid_code(self):
|
|
||||||
self.test_open_tfa_with_correct_code()
|
|
||||||
resp = self.client.post(self.url, data={"code": "000000"})
|
|
||||||
self.assertDictEqual(resp.data, {"error": "error", "data": "Invalid code"})
|
|
||||||
|
|
||||||
def test_close_tfa_with_correct_code(self):
|
|
||||||
self.test_open_tfa_with_correct_code()
|
|
||||||
code = self._get_tfa_code()
|
|
||||||
resp = self.client.put(self.url, data={"code": code})
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
user = User.objects.first()
|
|
||||||
self.assertEqual(user.two_factor_auth, False)
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch("account.views.oj.send_email_async.send")
|
|
||||||
class ApplyResetPasswordAPITest(CaptchaTest):
|
|
||||||
def setUp(self):
|
|
||||||
self.create_user("test", "test123", login=False)
|
|
||||||
user = User.objects.first()
|
|
||||||
user.email = "test@oj.com"
|
|
||||||
user.save()
|
|
||||||
self.url = self.reverse("apply_reset_password_api")
|
|
||||||
self.data = {"email": "test@oj.com", "captcha": self._set_captcha(self.client.session)}
|
|
||||||
|
|
||||||
def _refresh_captcha(self):
|
|
||||||
self.data["captcha"] = self._set_captcha(self.client.session)
|
|
||||||
|
|
||||||
def test_apply_reset_password(self, send_email_send):
|
|
||||||
resp = self.client.post(self.url, data=self.data)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
send_email_send.assert_called()
|
|
||||||
|
|
||||||
def test_apply_reset_password_twice_in_20_mins(self, send_email_send):
|
|
||||||
self.test_apply_reset_password()
|
|
||||||
send_email_send.reset_mock()
|
|
||||||
self._refresh_captcha()
|
|
||||||
resp = self.client.post(self.url, data=self.data)
|
|
||||||
self.assertDictEqual(resp.data, {"error": "error", "data": "You can only reset password once per 20 minutes"})
|
|
||||||
send_email_send.assert_not_called()
|
|
||||||
|
|
||||||
def test_apply_reset_password_again_after_20_mins(self, send_email_send):
|
|
||||||
self.test_apply_reset_password()
|
|
||||||
user = User.objects.first()
|
|
||||||
user.reset_password_token_expire_time = now() - timedelta(minutes=21)
|
|
||||||
user.save()
|
|
||||||
self._refresh_captcha()
|
|
||||||
self.test_apply_reset_password()
|
|
||||||
|
|
||||||
|
|
||||||
class ResetPasswordAPITest(CaptchaTest):
|
|
||||||
def setUp(self):
|
|
||||||
self.create_user("test", "test123", login=False)
|
|
||||||
self.url = self.reverse("reset_password_api")
|
|
||||||
user = User.objects.first()
|
|
||||||
user.reset_password_token = "online_judge?"
|
|
||||||
user.reset_password_token_expire_time = now() + timedelta(minutes=20)
|
|
||||||
user.save()
|
|
||||||
self.data = {"token": user.reset_password_token,
|
|
||||||
"captcha": self._set_captcha(self.client.session),
|
|
||||||
"password": "test456"}
|
|
||||||
|
|
||||||
def test_reset_password_with_correct_token(self):
|
|
||||||
resp = self.client.post(self.url, data=self.data)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
self.assertTrue(self.client.login(username="test", password="test456"))
|
|
||||||
|
|
||||||
def test_reset_password_with_invalid_token(self):
|
|
||||||
self.data["token"] = "aaaaaaaaaaa"
|
|
||||||
resp = self.client.post(self.url, data=self.data)
|
|
||||||
self.assertDictEqual(resp.data, {"error": "error", "data": "Token does not exist"})
|
|
||||||
|
|
||||||
def test_reset_password_with_expired_token(self):
|
|
||||||
user = User.objects.first()
|
|
||||||
user.reset_password_token_expire_time = now() - timedelta(seconds=30)
|
|
||||||
user.save()
|
|
||||||
resp = self.client.post(self.url, data=self.data)
|
|
||||||
self.assertDictEqual(resp.data, {"error": "error", "data": "Token has expired"})
|
|
||||||
|
|
||||||
|
|
||||||
class UserChangeEmailAPITest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.url = self.reverse("user_change_email_api")
|
|
||||||
self.user = self.create_user("test", "test123")
|
|
||||||
self.new_mail = "test@oj.com"
|
|
||||||
self.data = {"password": "test123", "new_email": self.new_mail}
|
|
||||||
|
|
||||||
def test_change_email_success(self):
|
|
||||||
resp = self.client.post(self.url, data=self.data)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
|
|
||||||
def test_wrong_password(self):
|
|
||||||
self.data["password"] = "aaaa"
|
|
||||||
resp = self.client.post(self.url, data=self.data)
|
|
||||||
self.assertDictEqual(resp.data, {"error": "error", "data": "Wrong password"})
|
|
||||||
|
|
||||||
def test_duplicate_email(self):
|
|
||||||
u = self.create_user("aa", "bb", login=False)
|
|
||||||
u.email = self.new_mail
|
|
||||||
u.save()
|
|
||||||
resp = self.client.post(self.url, data=self.data)
|
|
||||||
self.assertDictEqual(resp.data, {"error": "error", "data": "The email is owned by other account"})
|
|
||||||
|
|
||||||
|
|
||||||
class UserChangePasswordAPITest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.url = self.reverse("user_change_password_api")
|
|
||||||
|
|
||||||
# Create user at first
|
|
||||||
self.username = "test_user"
|
|
||||||
self.old_password = "testuserpassword"
|
|
||||||
self.new_password = "new_password"
|
|
||||||
self.user = self.create_user(username=self.username, password=self.old_password, login=False)
|
|
||||||
|
|
||||||
self.data = {"old_password": self.old_password, "new_password": self.new_password}
|
|
||||||
|
|
||||||
def _get_tfa_code(self):
|
|
||||||
user = User.objects.first()
|
|
||||||
code = OtpAuth(user.tfa_token).totp()
|
|
||||||
if len(str(code)) < 6:
|
|
||||||
code = (6 - len(str(code))) * "0" + str(code)
|
|
||||||
return code
|
|
||||||
|
|
||||||
def test_login_required(self):
|
|
||||||
response = self.client.post(self.url, data=self.data)
|
|
||||||
self.assertEqual(response.data, {"error": "permission-denied", "data": "Please login first"})
|
|
||||||
|
|
||||||
def test_valid_ola_password(self):
|
|
||||||
self.assertTrue(self.client.login(username=self.username, password=self.old_password))
|
|
||||||
response = self.client.post(self.url, data=self.data)
|
|
||||||
self.assertEqual(response.data, {"error": None, "data": "Succeeded"})
|
|
||||||
self.assertTrue(self.client.login(username=self.username, password=self.new_password))
|
|
||||||
|
|
||||||
def test_invalid_old_password(self):
|
|
||||||
self.assertTrue(self.client.login(username=self.username, password=self.old_password))
|
|
||||||
self.data["old_password"] = "invalid"
|
|
||||||
response = self.client.post(self.url, data=self.data)
|
|
||||||
self.assertEqual(response.data, {"error": "error", "data": "Invalid old password"})
|
|
||||||
|
|
||||||
def test_tfa_code_required(self):
|
|
||||||
self.user.two_factor_auth = True
|
|
||||||
self.user.tfa_token = "tfa_token"
|
|
||||||
self.user.save()
|
|
||||||
self.assertTrue(self.client.login(username=self.username, password=self.old_password))
|
|
||||||
self.data["tfa_code"] = rand_str(6)
|
|
||||||
resp = self.client.post(self.url, data=self.data)
|
|
||||||
self.assertEqual(resp.data, {"error": "error", "data": "Invalid two factor verification code"})
|
|
||||||
|
|
||||||
self.data["tfa_code"] = self._get_tfa_code()
|
|
||||||
resp = self.client.post(self.url, data=self.data)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
|
|
||||||
|
|
||||||
class UserRankAPITest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.url = self.reverse("user_rank_api")
|
|
||||||
self.create_user("test1", "test123", login=False)
|
|
||||||
self.create_user("test2", "test123", login=False)
|
|
||||||
test1 = User.objects.get(username="test1")
|
|
||||||
profile1 = test1.userprofile
|
|
||||||
profile1.submission_number = 10
|
|
||||||
profile1.accepted_number = 10
|
|
||||||
profile1.total_score = 240
|
|
||||||
profile1.save()
|
|
||||||
|
|
||||||
test2 = User.objects.get(username="test2")
|
|
||||||
profile2 = test2.userprofile
|
|
||||||
profile2.submission_number = 15
|
|
||||||
profile2.accepted_number = 10
|
|
||||||
profile2.total_score = 700
|
|
||||||
profile2.save()
|
|
||||||
|
|
||||||
def test_get_acm_rank(self):
|
|
||||||
resp = self.client.get(self.url, data={"rule": ContestRuleType.ACM})
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
data = resp.data["data"]["results"]
|
|
||||||
self.assertEqual(data[0]["user"]["username"], "test1")
|
|
||||||
self.assertEqual(data[1]["user"]["username"], "test2")
|
|
||||||
|
|
||||||
def test_get_oi_rank(self):
|
|
||||||
resp = self.client.get(self.url, data={"rule": ContestRuleType.OI})
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
data = resp.data["data"]["results"]
|
|
||||||
self.assertEqual(data[0]["user"]["username"], "test2")
|
|
||||||
self.assertEqual(data[1]["user"]["username"], "test1")
|
|
||||||
|
|
||||||
def test_admin_role_filted(self):
|
|
||||||
self.create_admin("admin", "admin123")
|
|
||||||
admin = User.objects.get(username="admin")
|
|
||||||
profile1 = admin.userprofile
|
|
||||||
profile1.submission_number = 20
|
|
||||||
profile1.accepted_number = 5
|
|
||||||
profile1.total_score = 300
|
|
||||||
profile1.save()
|
|
||||||
resp = self.client.get(self.url, data={"rule": ContestRuleType.ACM})
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
self.assertEqual(len(resp.data["data"]), 2)
|
|
||||||
|
|
||||||
resp = self.client.get(self.url, data={"rule": ContestRuleType.OI})
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
self.assertEqual(len(resp.data["data"]), 2)
|
|
||||||
|
|
||||||
|
|
||||||
class ProfileProblemDisplayIDRefreshAPITest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class AdminUserTest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.user = self.create_super_admin(login=True)
|
|
||||||
self.username = self.password = "test"
|
|
||||||
self.regular_user = self.create_user(username=self.username, password=self.password, login=False)
|
|
||||||
self.url = self.reverse("user_admin_api")
|
|
||||||
self.data = {"id": self.regular_user.id, "username": self.username, "real_name": "test_name",
|
|
||||||
"email": "test@qq.com", "admin_type": AdminType.REGULAR_USER,
|
|
||||||
"problem_permission": ProblemPermission.OWN, "open_api": True,
|
|
||||||
"two_factor_auth": False, "is_disabled": False}
|
|
||||||
|
|
||||||
def test_user_list(self):
|
|
||||||
response = self.client.get(self.url)
|
|
||||||
self.assertSuccess(response)
|
|
||||||
|
|
||||||
def test_edit_user_successfully(self):
|
|
||||||
response = self.client.put(self.url, data=self.data)
|
|
||||||
self.assertSuccess(response)
|
|
||||||
resp_data = response.data["data"]
|
|
||||||
self.assertEqual(resp_data["username"], self.username)
|
|
||||||
self.assertEqual(resp_data["email"], "test@qq.com")
|
|
||||||
self.assertEqual(resp_data["open_api"], True)
|
|
||||||
self.assertEqual(resp_data["two_factor_auth"], False)
|
|
||||||
self.assertEqual(resp_data["is_disabled"], False)
|
|
||||||
self.assertEqual(resp_data["problem_permission"], ProblemPermission.NONE)
|
|
||||||
|
|
||||||
self.assertTrue(self.regular_user.check_password("test"))
|
|
||||||
|
|
||||||
def test_edit_user_password(self):
|
|
||||||
data = self.data
|
|
||||||
new_password = "testpassword"
|
|
||||||
data["password"] = new_password
|
|
||||||
response = self.client.put(self.url, data=data)
|
|
||||||
self.assertSuccess(response)
|
|
||||||
user = User.objects.get(id=self.regular_user.id)
|
|
||||||
self.assertFalse(user.check_password(self.password))
|
|
||||||
self.assertTrue(user.check_password(new_password))
|
|
||||||
|
|
||||||
def test_edit_user_tfa(self):
|
|
||||||
data = self.data
|
|
||||||
self.assertIsNone(self.regular_user.tfa_token)
|
|
||||||
data["two_factor_auth"] = True
|
|
||||||
response = self.client.put(self.url, data=data)
|
|
||||||
self.assertSuccess(response)
|
|
||||||
resp_data = response.data["data"]
|
|
||||||
# if `tfa_token` is None, a new value will be generated
|
|
||||||
self.assertTrue(resp_data["two_factor_auth"])
|
|
||||||
token = User.objects.get(id=self.regular_user.id).tfa_token
|
|
||||||
self.assertIsNotNone(token)
|
|
||||||
|
|
||||||
response = self.client.put(self.url, data=data)
|
|
||||||
self.assertSuccess(response)
|
|
||||||
resp_data = response.data["data"]
|
|
||||||
# if `tfa_token` is not None, the value is not changed
|
|
||||||
self.assertTrue(resp_data["two_factor_auth"])
|
|
||||||
self.assertEqual(User.objects.get(id=self.regular_user.id).tfa_token, token)
|
|
||||||
|
|
||||||
def test_edit_user_openapi(self):
|
|
||||||
data = self.data
|
|
||||||
self.assertIsNone(self.regular_user.open_api_appkey)
|
|
||||||
data["open_api"] = True
|
|
||||||
response = self.client.put(self.url, data=data)
|
|
||||||
self.assertSuccess(response)
|
|
||||||
resp_data = response.data["data"]
|
|
||||||
# if `open_api_appkey` is None, a new value will be generated
|
|
||||||
self.assertTrue(resp_data["open_api"])
|
|
||||||
key = User.objects.get(id=self.regular_user.id).open_api_appkey
|
|
||||||
self.assertIsNotNone(key)
|
|
||||||
|
|
||||||
response = self.client.put(self.url, data=data)
|
|
||||||
self.assertSuccess(response)
|
|
||||||
resp_data = response.data["data"]
|
|
||||||
# if `openapi_app_key` is not None, the value is not changed
|
|
||||||
self.assertTrue(resp_data["open_api"])
|
|
||||||
self.assertEqual(User.objects.get(id=self.regular_user.id).open_api_appkey, key)
|
|
||||||
|
|
||||||
def test_import_users(self):
|
|
||||||
data = {"users": [["user1", "pass1", "eami1@e.com", "user1"],
|
|
||||||
["user2", "pass3", "eamil3@e.com", "user2"]]
|
|
||||||
}
|
|
||||||
resp = self.client.post(self.url, data)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
# successfully created 2 users
|
|
||||||
self.assertEqual(User.objects.all().count(), 4)
|
|
||||||
|
|
||||||
def test_import_duplicate_user(self):
|
|
||||||
data = {"users": [["user1", "pass1", "eami1@e.com", "user1"],
|
|
||||||
["user1", "pass1", "eami1@e.com", "user1"]]
|
|
||||||
}
|
|
||||||
resp = self.client.post(self.url, data)
|
|
||||||
self.assertFailed(resp, "DETAIL: Key (username)=(user1) already exists.")
|
|
||||||
# no user is created
|
|
||||||
self.assertEqual(User.objects.all().count(), 2)
|
|
||||||
|
|
||||||
def test_delete_users(self):
|
|
||||||
self.test_import_users()
|
|
||||||
user_ids = User.objects.filter(username__in=["user1", "user2"]).values_list("id", flat=True)
|
|
||||||
user_ids = ",".join([str(id) for id in user_ids])
|
|
||||||
resp = self.client.delete(self.url + "?id=" + user_ids)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
self.assertEqual(User.objects.all().count(), 2)
|
|
||||||
|
|
||||||
|
|
||||||
class GenerateUserAPITest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.create_super_admin()
|
|
||||||
self.url = self.reverse("generate_user_api")
|
|
||||||
self.data = {
|
|
||||||
"number_from": 100, "number_to": 105,
|
|
||||||
"prefix": "pre", "suffix": "suf",
|
|
||||||
"default_email": "test@test.com",
|
|
||||||
"password_length": 8
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_error_case(self):
|
|
||||||
data = deepcopy(self.data)
|
|
||||||
data["prefix"] = "t" * 16
|
|
||||||
data["suffix"] = "s" * 14
|
|
||||||
resp = self.client.post(self.url, data=data)
|
|
||||||
self.assertEqual(resp.data["data"], "Username should not more than 32 characters")
|
|
||||||
|
|
||||||
data2 = deepcopy(self.data)
|
|
||||||
data2["number_from"] = 106
|
|
||||||
resp = self.client.post(self.url, data=data2)
|
|
||||||
self.assertEqual(resp.data["data"], "Start number must be lower than end number")
|
|
||||||
|
|
||||||
@mock.patch("account.views.admin.xlsxwriter.Workbook")
|
|
||||||
def test_generate_user_success(self, mock_workbook):
|
|
||||||
resp = self.client.post(self.url, data=self.data)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
mock_workbook.assert_called()
|
|
||||||
|
|
||||||
|
|
||||||
class OpenAPIAppkeyAPITest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.user = self.create_super_admin()
|
|
||||||
self.url = self.reverse("open_api_appkey_api")
|
|
||||||
|
|
||||||
def test_reset_appkey(self):
|
|
||||||
resp = self.client.post(self.url, data={})
|
|
||||||
self.assertFailed(resp)
|
|
||||||
|
|
||||||
self.user.open_api = True
|
|
||||||
self.user.save()
|
|
||||||
resp = self.client.post(self.url, data={})
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
self.assertEqual(resp.data["data"]["appkey"], User.objects.get(username=self.user.username).open_api_appkey)
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from ..views.admin import UserAdminAPI, GenerateUserAPI, ResetUserPasswordAPI
|
from ..views.admin import GenerateUserAPI, ResetUserPasswordAPI, UserAdminAPI
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("user", UserAdminAPI.as_view()),
|
path("user", UserAdminAPI.as_view()),
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
from utils.captcha.views import CaptchaAPIView
|
||||||
|
|
||||||
from ..views.oj import (
|
from ..views.oj import (
|
||||||
|
SSOAPI,
|
||||||
ApplyResetPasswordAPI,
|
ApplyResetPasswordAPI,
|
||||||
ResetPasswordAPI,
|
AvatarUploadAPI,
|
||||||
UserChangePasswordAPI,
|
CheckTFARequiredAPI,
|
||||||
Metrics,
|
Metrics,
|
||||||
UserRegisterAPI,
|
OpenAPIAppkeyAPI,
|
||||||
|
ProfileProblemDisplayIDRefreshAPI,
|
||||||
|
ResetPasswordAPI,
|
||||||
|
SessionManagementAPI,
|
||||||
|
TwoFactorAuthAPI,
|
||||||
|
UserActivityRankAPI,
|
||||||
UserChangeEmailAPI,
|
UserChangeEmailAPI,
|
||||||
|
UserChangePasswordAPI,
|
||||||
UserLoginAPI,
|
UserLoginAPI,
|
||||||
UserLogoutAPI,
|
UserLogoutAPI,
|
||||||
UsernameOrEmailCheck,
|
UsernameOrEmailCheck,
|
||||||
AvatarUploadAPI,
|
UserProblemRankAPI,
|
||||||
TwoFactorAuthAPI,
|
|
||||||
UserProfileAPI,
|
UserProfileAPI,
|
||||||
UserRankAPI,
|
UserRankAPI,
|
||||||
UserActivityRankAPI,
|
UserRegisterAPI,
|
||||||
UserProblemRankAPI,
|
|
||||||
CheckTFARequiredAPI,
|
|
||||||
SessionManagementAPI,
|
|
||||||
ProfileProblemDisplayIDRefreshAPI,
|
|
||||||
OpenAPIAppkeyAPI,
|
|
||||||
SSOAPI,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from utils.captcha.views import CaptchaAPIView
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("login", UserLoginAPI.as_view()),
|
path("login", UserLoginAPI.as_view()),
|
||||||
path("logout", UserLogoutAPI.as_view()),
|
path("logout", UserLogoutAPI.as_view()),
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import xlsxwriter
|
|
||||||
|
|
||||||
from django.db import transaction, IntegrityError
|
import xlsxwriter
|
||||||
from django.db.models import Q, F
|
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.contrib.auth.hashers import make_password
|
from django.contrib.auth.hashers import make_password
|
||||||
|
from django.db import IntegrityError, transaction
|
||||||
|
from django.db.models import F, Q
|
||||||
|
from django.http import HttpResponse
|
||||||
from django.utils.crypto import get_random_string
|
from django.utils.crypto import get_random_string
|
||||||
|
|
||||||
from submission.models import Submission
|
from submission.models import Submission
|
||||||
@@ -16,10 +16,10 @@ from ..decorators import super_admin_required
|
|||||||
from ..models import AdminType, ProblemPermission, User, UserProfile
|
from ..models import AdminType, ProblemPermission, User, UserProfile
|
||||||
from ..serializers import (
|
from ..serializers import (
|
||||||
EditUserSerializer,
|
EditUserSerializer,
|
||||||
UserAdminSerializer,
|
|
||||||
GenerateUserSerializer,
|
GenerateUserSerializer,
|
||||||
|
ImportUserSerializer,
|
||||||
|
UserAdminSerializer,
|
||||||
)
|
)
|
||||||
from ..serializers import ImportUserSerializer
|
|
||||||
|
|
||||||
|
|
||||||
# ks251XXX 或者 ks2510XX 返回 251 或者 2510
|
# ks251XXX 或者 ks2510XX 返回 251 或者 2510
|
||||||
|
|||||||
@@ -2,44 +2,42 @@ import os
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
|
import qrcode
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import auth
|
from django.contrib import auth
|
||||||
|
from django.core.cache import cache
|
||||||
|
from django.db.models import Count, Q
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
|
||||||
from django.db.models import Count, Q
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
import qrcode
|
|
||||||
from otpauth import OtpAuth
|
from otpauth import OtpAuth
|
||||||
|
|
||||||
from problem.models import Problem
|
|
||||||
from submission.models import Submission, JudgeStatus
|
|
||||||
from django.core.cache import cache
|
|
||||||
from utils.constants import ContestRuleType, CacheKey
|
|
||||||
from options.options import SysOptions
|
from options.options import SysOptions
|
||||||
from utils.api import APIView, validate_serializer, CSRFExemptAPIView
|
from problem.models import Problem
|
||||||
|
from submission.models import JudgeStatus, Submission
|
||||||
|
from utils.api import APIView, CSRFExemptAPIView, validate_serializer
|
||||||
from utils.captcha import Captcha
|
from utils.captcha import Captcha
|
||||||
from utils.shortcuts import rand_str, img2base64, datetime2str
|
from utils.constants import CacheKey, ContestRuleType
|
||||||
|
from utils.shortcuts import datetime2str, img2base64, rand_str
|
||||||
|
|
||||||
from ..decorators import login_required
|
from ..decorators import login_required
|
||||||
from ..models import User, UserProfile, AdminType
|
from ..models import AdminType, User, UserProfile
|
||||||
from ..serializers import (
|
from ..serializers import (
|
||||||
ApplyResetPasswordSerializer,
|
ApplyResetPasswordSerializer,
|
||||||
ResetPasswordSerializer,
|
|
||||||
UserChangePasswordSerializer,
|
|
||||||
UserLoginSerializer,
|
|
||||||
UserRegisterSerializer,
|
|
||||||
UsernameOrEmailCheckSerializer,
|
|
||||||
RankInfoSerializer,
|
|
||||||
UserChangeEmailSerializer,
|
|
||||||
SSOSerializer,
|
|
||||||
)
|
|
||||||
from ..serializers import (
|
|
||||||
TwoFactorAuthCodeSerializer,
|
|
||||||
UserProfileSerializer,
|
|
||||||
EditUserProfileSerializer,
|
EditUserProfileSerializer,
|
||||||
ImageUploadForm,
|
ImageUploadForm,
|
||||||
|
RankInfoSerializer,
|
||||||
|
ResetPasswordSerializer,
|
||||||
|
SSOSerializer,
|
||||||
|
TwoFactorAuthCodeSerializer,
|
||||||
|
UserChangeEmailSerializer,
|
||||||
|
UserChangePasswordSerializer,
|
||||||
|
UserLoginSerializer,
|
||||||
|
UsernameOrEmailCheckSerializer,
|
||||||
|
UserProfileSerializer,
|
||||||
|
UserRegisterSerializer,
|
||||||
)
|
)
|
||||||
from ..tasks import send_email_async
|
from ..tasks import send_email_async
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,26 @@
|
|||||||
from collections import defaultdict
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
|
from collections import defaultdict
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.db.models import Min, Count
|
from django.db.models import Count, Min
|
||||||
from django.db.models.functions import TruncDate
|
from django.db.models.functions import TruncDate
|
||||||
from django.http import StreamingHttpResponse
|
from django.http import StreamingHttpResponse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.dateparse import parse_datetime
|
from django.utils.dateparse import parse_datetime
|
||||||
|
|
||||||
|
from account.decorators import login_required
|
||||||
|
from account.models import User
|
||||||
|
from ai.models import AIAnalysis
|
||||||
|
from flowchart.models import FlowchartSubmission, FlowchartSubmissionStatus
|
||||||
|
from problem.models import Problem
|
||||||
|
from submission.models import JudgeStatus, Submission
|
||||||
from utils.api import APIView
|
from utils.api import APIView
|
||||||
from utils.openai import get_ai_client
|
from utils.openai import get_ai_client
|
||||||
from utils.shortcuts import datetime2str
|
from utils.shortcuts import datetime2str
|
||||||
|
|
||||||
from account.models import User
|
|
||||||
from problem.models import Problem
|
|
||||||
from submission.models import Submission, JudgeStatus
|
|
||||||
from flowchart.models import FlowchartSubmission, FlowchartSubmissionStatus
|
|
||||||
from account.decorators import login_required
|
|
||||||
from ai.models import AIAnalysis
|
|
||||||
|
|
||||||
CACHE_TIMEOUT = 300
|
CACHE_TIMEOUT = 300
|
||||||
DIFFICULTY_MAP = {"Low": "简单", "Mid": "中等", "High": "困难"}
|
DIFFICULTY_MAP = {"Low": "简单", "Mid": "中等", "High": "困难"}
|
||||||
DEFAULT_CLASS_SIZE = 45
|
DEFAULT_CLASS_SIZE = 45
|
||||||
@@ -598,7 +597,11 @@ class AIAnalysisAPI(APIView):
|
|||||||
|
|
||||||
client = get_ai_client()
|
client = get_ai_client()
|
||||||
|
|
||||||
system_prompt = "你是一个风趣的编程老师,学生使用判题狗平台进行编程练习。请根据学生提供的详细数据和每周数据,给出用户的学习建议,最后写一句鼓励学生的话。请使用 markdown 格式输出,不要在代码块中输出。"
|
system_prompt = (
|
||||||
|
"你是一个风趣的编程老师,学生使用判题狗平台进行编程练习。"
|
||||||
|
"请根据学生提供的详细数据和每周数据,给出用户的学习建议,最后写一句鼓励学生的话。"
|
||||||
|
"请使用 markdown 格式输出,不要在代码块中输出。"
|
||||||
|
)
|
||||||
user_prompt = f"这段时间内的详细数据: {details}\n(其中部分字段含义是 flowcharts:流程图的提交,solved:代码的提交)\n每周或每月的数据: {duration}"
|
user_prompt = f"这段时间内的详细数据: {details}\n(其中部分字段含义是 flowcharts:流程图的提交,solved:代码的提交)\n每周或每月的数据: {duration}"
|
||||||
|
|
||||||
def on_complete(full_text):
|
def on_complete(full_text):
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
from utils.api.tests import APITestCase
|
|
||||||
|
|
||||||
from .models import Announcement
|
|
||||||
|
|
||||||
|
|
||||||
class AnnouncementAdminTest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.user = self.create_super_admin()
|
|
||||||
self.url = self.reverse("announcement_admin_api")
|
|
||||||
|
|
||||||
def test_announcement_list(self):
|
|
||||||
response = self.client.get(self.url)
|
|
||||||
self.assertSuccess(response)
|
|
||||||
|
|
||||||
def create_announcement(self):
|
|
||||||
return self.client.post(self.url, data={"title": "test", "content": "test", "visible": True})
|
|
||||||
|
|
||||||
def test_create_announcement(self):
|
|
||||||
resp = self.create_announcement()
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
return resp
|
|
||||||
|
|
||||||
def test_edit_announcement(self):
|
|
||||||
data = {"id": self.create_announcement().data["data"]["id"], "title": "ahaha", "content": "test content",
|
|
||||||
"visible": False}
|
|
||||||
resp = self.client.put(self.url, data=data)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
resp_data = resp.data["data"]
|
|
||||||
self.assertEqual(resp_data["title"], "ahaha")
|
|
||||||
self.assertEqual(resp_data["content"], "test content")
|
|
||||||
self.assertEqual(resp_data["visible"], False)
|
|
||||||
|
|
||||||
def test_delete_announcement(self):
|
|
||||||
id = self.test_create_announcement().data["data"]["id"]
|
|
||||||
resp = self.client.delete(self.url + "?id=" + str(id))
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
self.assertFalse(Announcement.objects.filter(id=id).exists())
|
|
||||||
|
|
||||||
|
|
||||||
class AnnouncementAPITest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.user = self.create_super_admin()
|
|
||||||
Announcement.objects.create(title="title", content="content", visible=True, created_by=self.user)
|
|
||||||
self.url = self.reverse("announcement_api")
|
|
||||||
|
|
||||||
def test_get_announcement_list(self):
|
|
||||||
resp = self.client.get(self.url)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
from account.decorators import super_admin_required
|
from account.decorators import super_admin_required
|
||||||
from utils.api import APIView, validate_serializer
|
|
||||||
|
|
||||||
from announcement.models import Announcement
|
from announcement.models import Announcement
|
||||||
from announcement.serializers import (
|
from announcement.serializers import (
|
||||||
AnnouncementSerializer,
|
AnnouncementSerializer,
|
||||||
CreateAnnouncementSerializer,
|
CreateAnnouncementSerializer,
|
||||||
EditAnnouncementSerializer,
|
EditAnnouncementSerializer,
|
||||||
)
|
)
|
||||||
|
from utils.api import APIView, validate_serializer
|
||||||
|
|
||||||
|
|
||||||
class AnnouncementAdminAPI(APIView):
|
class AnnouncementAdminAPI(APIView):
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from utils.api import APIView
|
|
||||||
|
|
||||||
from announcement.models import Announcement
|
from announcement.models import Announcement
|
||||||
from announcement.serializers import AnnouncementSerializer, AnnouncementListSerializer
|
from announcement.serializers import AnnouncementListSerializer, AnnouncementSerializer
|
||||||
|
from utils.api import APIView
|
||||||
|
|
||||||
|
|
||||||
class AnnouncementAPI(APIView):
|
class AnnouncementAPI(APIView):
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
# Register your models here.
|
# Register your models here.
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
from django.db import models
|
|
||||||
|
|
||||||
# 如果需要存储班级PK历史记录,可以在这里定义模型
|
# 如果需要存储班级PK历史记录,可以在这里定义模型
|
||||||
# 目前暂时不需要,因为都是实时计算
|
# 目前暂时不需要,因为都是实时计算
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from ..views.oj import ClassRankAPI, UserClassRankAPI, ClassPKAPI
|
from ..views.oj import ClassPKAPI, ClassRankAPI, UserClassRankAPI
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("class_rank", ClassRankAPI.as_view()),
|
path("class_rank", ClassRankAPI.as_view()),
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import re
|
|
||||||
import statistics
|
import statistics
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from django.db.models import Sum, Avg
|
|
||||||
|
from django.db.models import Avg, Sum
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from utils.api import APIView
|
|
||||||
from account.decorators import login_required
|
from account.decorators import login_required
|
||||||
from account.models import User, UserProfile, AdminType
|
from account.models import AdminType, User, UserProfile
|
||||||
from submission.models import Submission, JudgeStatus
|
from submission.models import JudgeStatus, Submission
|
||||||
|
from utils.api import APIView
|
||||||
|
|
||||||
|
|
||||||
class ClassRankAPI(APIView):
|
class ClassRankAPI(APIView):
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from django.urls import path
|
|||||||
|
|
||||||
from ..views.admin import CommentAPI
|
from ..views.admin import CommentAPI
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("comment", CommentAPI.as_view()),
|
path("comment", CommentAPI.as_view()),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from django.urls import path
|
|||||||
|
|
||||||
from ..views.oj import CommentAPI, CommentStatisticsAPI
|
from ..views.oj import CommentAPI, CommentStatisticsAPI
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("comment", CommentAPI.as_view()),
|
path("comment", CommentAPI.as_view()),
|
||||||
path("comment/statistics", CommentStatisticsAPI.as_view()),
|
path("comment/statistics", CommentStatisticsAPI.as_view()),
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
from account.decorators import super_admin_required
|
from account.decorators import super_admin_required
|
||||||
|
from comment.models import Comment
|
||||||
from comment.serializers import CommentListSerializer
|
from comment.serializers import CommentListSerializer
|
||||||
from problem.models import Problem
|
from problem.models import Problem
|
||||||
from utils.api import APIView
|
from utils.api import APIView
|
||||||
from comment.models import Comment
|
|
||||||
|
|
||||||
|
|
||||||
class CommentAPI(APIView):
|
class CommentAPI(APIView):
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.db.models import Avg, Count
|
from django.db.models import Avg, Count
|
||||||
from django.db.models.functions import Round
|
from django.db.models.functions import Round
|
||||||
from comment.models import Comment
|
|
||||||
from problem.models import Problem
|
|
||||||
from utils.api import APIView
|
|
||||||
from utils.constants import CacheKey
|
|
||||||
from account.decorators import login_required
|
from account.decorators import login_required
|
||||||
|
from comment.models import Comment
|
||||||
|
from comment.serializers import CommentSerializer, CreateCommentSerializer
|
||||||
|
from problem.models import Problem
|
||||||
|
from submission.models import JudgeStatus, Submission
|
||||||
|
from utils.api import APIView
|
||||||
from utils.api.api import validate_serializer
|
from utils.api.api import validate_serializer
|
||||||
from comment.serializers import CreateCommentSerializer, CommentSerializer
|
from utils.constants import CacheKey
|
||||||
from submission.models import Submission, JudgeStatus
|
|
||||||
|
|
||||||
|
|
||||||
class CommentAPI(APIView):
|
class CommentAPI(APIView):
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ WebSocket consumers for configuration updates
|
|||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from channels.generic.websocket import AsyncWebsocketConsumer
|
from channels.generic.websocket import AsyncWebsocketConsumer
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
185
conf/tests.py
185
conf/tests.py
@@ -1,185 +0,0 @@
|
|||||||
import hashlib
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from options.options import SysOptions
|
|
||||||
from utils.api.tests import APITestCase
|
|
||||||
from .models import JudgeServer
|
|
||||||
|
|
||||||
|
|
||||||
class SMTPConfigTest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.user = self.create_super_admin()
|
|
||||||
self.url = self.reverse("smtp_admin_api")
|
|
||||||
self.password = "testtest"
|
|
||||||
|
|
||||||
def test_create_smtp_config(self):
|
|
||||||
data = {"server": "smtp.test.com", "email": "test@test.com", "port": 465,
|
|
||||||
"tls": True, "password": self.password}
|
|
||||||
resp = self.client.post(self.url, data=data)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
self.assertTrue("password" not in resp.data)
|
|
||||||
return resp
|
|
||||||
|
|
||||||
def test_edit_without_password(self):
|
|
||||||
self.test_create_smtp_config()
|
|
||||||
data = {"server": "smtp1.test.com", "email": "test2@test.com", "port": 465,
|
|
||||||
"tls": True}
|
|
||||||
resp = self.client.put(self.url, data=data)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
|
|
||||||
def test_edit_without_password1(self):
|
|
||||||
self.test_create_smtp_config()
|
|
||||||
data = {"server": "smtp.test.com", "email": "test@test.com", "port": 465,
|
|
||||||
"tls": True, "password": ""}
|
|
||||||
resp = self.client.put(self.url, data=data)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
|
|
||||||
def test_edit_with_password(self):
|
|
||||||
self.test_create_smtp_config()
|
|
||||||
data = {"server": "smtp1.test.com", "email": "test2@test.com", "port": 465,
|
|
||||||
"tls": True, "password": "newpassword"}
|
|
||||||
resp = self.client.put(self.url, data=data)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
|
|
||||||
@mock.patch("conf.views.send_email")
|
|
||||||
def test_test_smtp(self, mocked_send_email):
|
|
||||||
url = self.reverse("smtp_test_api")
|
|
||||||
self.test_create_smtp_config()
|
|
||||||
resp = self.client.post(url, data={"email": "test@test.com"})
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
mocked_send_email.assert_called_once()
|
|
||||||
|
|
||||||
|
|
||||||
class WebsiteConfigAPITest(APITestCase):
|
|
||||||
def test_create_website_config(self):
|
|
||||||
self.create_super_admin()
|
|
||||||
url = self.reverse("website_config_api")
|
|
||||||
data = {"website_base_url": "http://test.com", "website_name": "test name",
|
|
||||||
"website_name_shortcut": "test oj", "website_footer": "<a>test</a>",
|
|
||||||
"allow_register": True, "submission_list_show_all": False}
|
|
||||||
resp = self.client.post(url, data=data)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
|
|
||||||
def test_edit_website_config(self):
|
|
||||||
self.create_super_admin()
|
|
||||||
url = self.reverse("website_config_api")
|
|
||||||
data = {"website_base_url": "http://test.com", "website_name": "test name",
|
|
||||||
"website_name_shortcut": "test oj", "website_footer": "<img onerror=alert(1) src=#>",
|
|
||||||
"allow_register": True, "submission_list_show_all": False}
|
|
||||||
resp = self.client.post(url, data=data)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
self.assertEqual(SysOptions.website_footer, '<img src="#" />')
|
|
||||||
|
|
||||||
def test_get_website_config(self):
|
|
||||||
# do not need to login
|
|
||||||
url = self.reverse("website_info_api")
|
|
||||||
resp = self.client.get(url)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
|
|
||||||
|
|
||||||
class JudgeServerHeartbeatTest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.url = self.reverse("judge_server_heartbeat_api")
|
|
||||||
self.data = {"hostname": "testhostname", "judger_version": "1.0.4", "cpu_core": 4,
|
|
||||||
"cpu": 90.5, "memory": 80.3, "action": "heartbeat", "service_url": "http://127.0.0.1"}
|
|
||||||
self.token = "test"
|
|
||||||
self.hashed_token = hashlib.sha256(self.token.encode("utf-8")).hexdigest()
|
|
||||||
SysOptions.judge_server_token = self.token
|
|
||||||
self.headers = {"HTTP_X_JUDGE_SERVER_TOKEN": self.hashed_token, settings.IP_HEADER: "1.2.3.4"}
|
|
||||||
|
|
||||||
def test_new_heartbeat(self):
|
|
||||||
resp = self.client.post(self.url, data=self.data, **self.headers)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
server = JudgeServer.objects.first()
|
|
||||||
self.assertEqual(server.ip, "127.0.0.1")
|
|
||||||
|
|
||||||
def test_update_heartbeat(self):
|
|
||||||
self.test_new_heartbeat()
|
|
||||||
data = self.data
|
|
||||||
data["judger_version"] = "2.0.0"
|
|
||||||
resp = self.client.post(self.url, data=data, **self.headers)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
self.assertEqual(JudgeServer.objects.get(hostname=self.data["hostname"]).judger_version, data["judger_version"])
|
|
||||||
|
|
||||||
|
|
||||||
class JudgeServerAPITest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.server = JudgeServer.objects.create(**{"hostname": "testhostname", "judger_version": "1.0.4",
|
|
||||||
"cpu_core": 4, "cpu_usage": 90.5, "memory_usage": 80.3,
|
|
||||||
"last_heartbeat": timezone.now()})
|
|
||||||
self.url = self.reverse("judge_server_api")
|
|
||||||
self.create_super_admin()
|
|
||||||
|
|
||||||
def test_get_judge_server(self):
|
|
||||||
resp = self.client.get(self.url)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
self.assertEqual(len(resp.data["data"]["servers"]), 1)
|
|
||||||
|
|
||||||
def test_delete_judge_server(self):
|
|
||||||
resp = self.client.delete(self.url + "?hostname=testhostname")
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
self.assertFalse(JudgeServer.objects.filter(hostname="testhostname").exists())
|
|
||||||
|
|
||||||
def test_disabled_judge_server(self):
|
|
||||||
resp = self.client.put(self.url, data={"is_disabled": True, "id": self.server.id})
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
self.assertTrue(JudgeServer.objects.get(id=self.server.id).is_disabled)
|
|
||||||
|
|
||||||
|
|
||||||
class LanguageListAPITest(APITestCase):
|
|
||||||
def test_get_languages(self):
|
|
||||||
resp = self.client.get(self.reverse("language_list_api"))
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
|
|
||||||
|
|
||||||
class TestCasePruneAPITest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.url = self.reverse("prune_test_case_api")
|
|
||||||
self.create_super_admin()
|
|
||||||
|
|
||||||
def test_get_isolated_test_case(self):
|
|
||||||
resp = self.client.get(self.url)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
|
|
||||||
@mock.patch("conf.views.TestCasePruneAPI.delete_one")
|
|
||||||
@mock.patch("conf.views.os.listdir")
|
|
||||||
@mock.patch("conf.views.Problem")
|
|
||||||
def test_delete_test_case(self, mocked_problem, mocked_listdir, mocked_delete_one):
|
|
||||||
valid_id = "1172980672983b2b49820be3a741b109"
|
|
||||||
mocked_problem.return_value = [valid_id, ]
|
|
||||||
mocked_listdir.return_value = [valid_id, ".test", "aaa"]
|
|
||||||
resp = self.client.delete(self.url)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
mocked_delete_one.assert_called_once_with(valid_id)
|
|
||||||
|
|
||||||
|
|
||||||
class ReleaseNoteAPITest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.url = self.reverse("get_release_notes_api")
|
|
||||||
self.create_super_admin()
|
|
||||||
self.latest_data = {"update": [
|
|
||||||
{
|
|
||||||
"version": "2099-12-25",
|
|
||||||
"level": 1,
|
|
||||||
"title": "Update at 2099-12-25",
|
|
||||||
"details": ["test get", ]
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
|
|
||||||
def test_get_versions(self):
|
|
||||||
resp = self.client.get(self.url)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
|
|
||||||
|
|
||||||
class DashboardInfoAPITest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.url = self.reverse("dashboard_info_api")
|
|
||||||
self.create_admin()
|
|
||||||
|
|
||||||
def test_get_info(self):
|
|
||||||
resp = self.client.get(self.url)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
self.assertEqual(resp.data["data"]["user_count"], 1)
|
|
||||||
@@ -2,13 +2,13 @@ from django.urls import path
|
|||||||
|
|
||||||
from ..views import (
|
from ..views import (
|
||||||
SMTPAPI,
|
SMTPAPI,
|
||||||
JudgeServerAPI,
|
|
||||||
WebsiteConfigAPI,
|
|
||||||
TestCasePruneAPI,
|
|
||||||
SMTPTestAPI,
|
|
||||||
ReleaseNotesAPI,
|
|
||||||
DashboardInfoAPI,
|
DashboardInfoAPI,
|
||||||
|
JudgeServerAPI,
|
||||||
RandomUsernameAPI,
|
RandomUsernameAPI,
|
||||||
|
ReleaseNotesAPI,
|
||||||
|
SMTPTestAPI,
|
||||||
|
TestCasePruneAPI,
|
||||||
|
WebsiteConfigAPI,
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from ..views import (
|
from ..views import (
|
||||||
|
ClassUsernamesAPI,
|
||||||
HitokotoAPI,
|
HitokotoAPI,
|
||||||
JudgeServerHeartbeatAPI,
|
JudgeServerHeartbeatAPI,
|
||||||
LanguagesAPI,
|
LanguagesAPI,
|
||||||
WebsiteConfigAPI,
|
WebsiteConfigAPI,
|
||||||
ClassUsernamesAPI,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|||||||
@@ -22,18 +22,19 @@ from problem.models import Problem
|
|||||||
from submission.models import Submission
|
from submission.models import Submission
|
||||||
from utils.api import APIView, CSRFExemptAPIView, validate_serializer
|
from utils.api import APIView, CSRFExemptAPIView, validate_serializer
|
||||||
from utils.cache import JsonDataLoader
|
from utils.cache import JsonDataLoader
|
||||||
from utils.shortcuts import send_email, get_env
|
from utils.shortcuts import get_env, send_email
|
||||||
from utils.xss_filter import XSSHtml
|
|
||||||
from utils.websocket import push_config_update
|
from utils.websocket import push_config_update
|
||||||
|
from utils.xss_filter import XSSHtml
|
||||||
|
|
||||||
from .models import JudgeServer
|
from .models import JudgeServer
|
||||||
from .serializers import (
|
from .serializers import (
|
||||||
CreateEditWebsiteConfigSerializer,
|
CreateEditWebsiteConfigSerializer,
|
||||||
CreateSMTPConfigSerializer,
|
CreateSMTPConfigSerializer,
|
||||||
|
EditJudgeServerSerializer,
|
||||||
EditSMTPConfigSerializer,
|
EditSMTPConfigSerializer,
|
||||||
JudgeServerHeartbeatSerializer,
|
JudgeServerHeartbeatSerializer,
|
||||||
JudgeServerSerializer,
|
JudgeServerSerializer,
|
||||||
TestSMTPConfigSerializer,
|
TestSMTPConfigSerializer,
|
||||||
EditJudgeServerSerializer,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from utils.api import UsernameSerializer, serializers
|
from utils.api import UsernameSerializer, serializers
|
||||||
|
|
||||||
from .models import Contest, ContestAnnouncement, ContestRuleType
|
from .models import ACMContestRank, Contest, ContestAnnouncement, ContestRuleType, OIContestRank
|
||||||
from .models import ACMContestRank, OIContestRank
|
|
||||||
|
|
||||||
|
|
||||||
class CreateConetestSeriaizer(serializers.Serializer):
|
class CreateConetestSeriaizer(serializers.Serializer):
|
||||||
|
|||||||
162
contest/tests.py
162
contest/tests.py
@@ -1,162 +0,0 @@
|
|||||||
import copy
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from utils.api.tests import APITestCase
|
|
||||||
|
|
||||||
from .models import ContestAnnouncement, ContestRuleType, Contest
|
|
||||||
|
|
||||||
DEFAULT_CONTEST_DATA = {"title": "test title", "description": "test description",
|
|
||||||
"start_time": timezone.localtime(timezone.now()),
|
|
||||||
"end_time": timezone.localtime(timezone.now()) + timedelta(days=1),
|
|
||||||
"rule_type": ContestRuleType.ACM,
|
|
||||||
"password": "123",
|
|
||||||
"allowed_ip_ranges": [],
|
|
||||||
"visible": True, "real_time_rank": True}
|
|
||||||
|
|
||||||
|
|
||||||
class ContestAdminAPITest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.create_super_admin()
|
|
||||||
self.url = self.reverse("contest_admin_api")
|
|
||||||
self.data = copy.deepcopy(DEFAULT_CONTEST_DATA)
|
|
||||||
|
|
||||||
def test_create_contest(self):
|
|
||||||
response = self.client.post(self.url, data=self.data)
|
|
||||||
self.assertSuccess(response)
|
|
||||||
return response
|
|
||||||
|
|
||||||
def test_create_contest_with_invalid_cidr(self):
|
|
||||||
self.data["allowed_ip_ranges"] = ["127.0.0"]
|
|
||||||
resp = self.client.post(self.url, data=self.data)
|
|
||||||
self.assertTrue(resp.data["data"].endswith("is not a valid cidr network"))
|
|
||||||
|
|
||||||
def test_update_contest(self):
|
|
||||||
id = self.test_create_contest().data["data"]["id"]
|
|
||||||
update_data = {"id": id, "title": "update title",
|
|
||||||
"description": "update description",
|
|
||||||
"password": "12345",
|
|
||||||
"visible": False, "real_time_rank": False}
|
|
||||||
data = copy.deepcopy(self.data)
|
|
||||||
data.update(update_data)
|
|
||||||
response = self.client.put(self.url, data=data)
|
|
||||||
self.assertSuccess(response)
|
|
||||||
response_data = response.data["data"]
|
|
||||||
for k in data.keys():
|
|
||||||
if isinstance(data[k], datetime):
|
|
||||||
continue
|
|
||||||
self.assertEqual(response_data[k], data[k])
|
|
||||||
|
|
||||||
def test_get_contests(self):
|
|
||||||
self.test_create_contest()
|
|
||||||
response = self.client.get(self.url)
|
|
||||||
self.assertSuccess(response)
|
|
||||||
|
|
||||||
def test_get_one_contest(self):
|
|
||||||
id = self.test_create_contest().data["data"]["id"]
|
|
||||||
response = self.client.get("{}?id={}".format(self.url, id))
|
|
||||||
self.assertSuccess(response)
|
|
||||||
|
|
||||||
|
|
||||||
class ContestAPITest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
user = self.create_admin()
|
|
||||||
self.contest = Contest.objects.create(created_by=user, **DEFAULT_CONTEST_DATA)
|
|
||||||
self.url = self.reverse("contest_api") + "?id=" + str(self.contest.id)
|
|
||||||
|
|
||||||
def test_get_contest_list(self):
|
|
||||||
url = self.reverse("contest_list_api")
|
|
||||||
response = self.client.get(url + "?limit=10")
|
|
||||||
self.assertSuccess(response)
|
|
||||||
self.assertEqual(len(response.data["data"]["results"]), 1)
|
|
||||||
|
|
||||||
def test_get_one_contest(self):
|
|
||||||
resp = self.client.get(self.url)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
|
|
||||||
def test_regular_user_validate_contest_password(self):
|
|
||||||
self.create_user("test", "test123")
|
|
||||||
url = self.reverse("contest_password_api")
|
|
||||||
resp = self.client.post(url, {"contest_id": self.contest.id, "password": "error_password"})
|
|
||||||
self.assertDictEqual(resp.data, {"error": "error", "data": "Wrong password or password expired"})
|
|
||||||
|
|
||||||
resp = self.client.post(url, {"contest_id": self.contest.id, "password": DEFAULT_CONTEST_DATA["password"]})
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
|
|
||||||
def test_regular_user_access_contest(self):
|
|
||||||
self.create_user("test", "test123")
|
|
||||||
url = self.reverse("contest_access_api")
|
|
||||||
resp = self.client.get(url + "?contest_id=" + str(self.contest.id))
|
|
||||||
self.assertFalse(resp.data["data"]["access"])
|
|
||||||
|
|
||||||
password_url = self.reverse("contest_password_api")
|
|
||||||
resp = self.client.post(password_url,
|
|
||||||
{"contest_id": self.contest.id, "password": DEFAULT_CONTEST_DATA["password"]})
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
resp = self.client.get(self.url)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
|
|
||||||
|
|
||||||
class ContestAnnouncementAdminAPITest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.create_super_admin()
|
|
||||||
self.url = self.reverse("contest_announcement_admin_api")
|
|
||||||
contest_id = self.create_contest().data["data"]["id"]
|
|
||||||
self.data = {"title": "test title", "content": "test content", "contest_id": contest_id, "visible": True}
|
|
||||||
|
|
||||||
def create_contest(self):
|
|
||||||
url = self.reverse("contest_admin_api")
|
|
||||||
data = DEFAULT_CONTEST_DATA
|
|
||||||
return self.client.post(url, data=data)
|
|
||||||
|
|
||||||
def test_create_contest_announcement(self):
|
|
||||||
response = self.client.post(self.url, data=self.data)
|
|
||||||
self.assertSuccess(response)
|
|
||||||
return response
|
|
||||||
|
|
||||||
def test_delete_contest_announcement(self):
|
|
||||||
id = self.test_create_contest_announcement().data["data"]["id"]
|
|
||||||
response = self.client.delete("{}?id={}".format(self.url, id))
|
|
||||||
self.assertSuccess(response)
|
|
||||||
self.assertFalse(ContestAnnouncement.objects.filter(id=id).exists())
|
|
||||||
|
|
||||||
def test_get_contest_announcements(self):
|
|
||||||
self.test_create_contest_announcement()
|
|
||||||
response = self.client.get(self.url + "?contest_id=" + str(self.data["contest_id"]))
|
|
||||||
self.assertSuccess(response)
|
|
||||||
|
|
||||||
def test_get_one_contest_announcement(self):
|
|
||||||
id = self.test_create_contest_announcement().data["data"]["id"]
|
|
||||||
response = self.client.get("{}?id={}".format(self.url, id))
|
|
||||||
self.assertSuccess(response)
|
|
||||||
|
|
||||||
|
|
||||||
class ContestAnnouncementListAPITest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.create_super_admin()
|
|
||||||
self.url = self.reverse("contest_announcement_api")
|
|
||||||
|
|
||||||
def create_contest_announcements(self):
|
|
||||||
contest_id = self.client.post(self.reverse("contest_admin_api"), data=DEFAULT_CONTEST_DATA).data["data"]["id"]
|
|
||||||
url = self.reverse("contest_announcement_admin_api")
|
|
||||||
self.client.post(url, data={"title": "test title1", "content": "test content1", "contest_id": contest_id})
|
|
||||||
self.client.post(url, data={"title": "test title2", "content": "test content2", "contest_id": contest_id})
|
|
||||||
return contest_id
|
|
||||||
|
|
||||||
def test_get_contest_announcement_list(self):
|
|
||||||
contest_id = self.create_contest_announcements()
|
|
||||||
response = self.client.get(self.url, data={"contest_id": contest_id})
|
|
||||||
self.assertSuccess(response)
|
|
||||||
|
|
||||||
|
|
||||||
class ContestRankAPITest(APITestCase):
|
|
||||||
def setUp(self):
|
|
||||||
user = self.create_admin()
|
|
||||||
self.acm_contest = Contest.objects.create(created_by=user, **DEFAULT_CONTEST_DATA)
|
|
||||||
self.create_user("test", "test123")
|
|
||||||
self.url = self.reverse("contest_rank_api")
|
|
||||||
|
|
||||||
def get_contest_rank(self):
|
|
||||||
resp = self.client.get(self.url + "?contest_id=" + self.acm_contest.id)
|
|
||||||
self.assertSuccess(resp)
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from ..views.admin import ContestAnnouncementAPI, ContestAPI, ACMContestHelper, DownloadContestSubmissions
|
from ..views.admin import ACMContestHelper, ContestAnnouncementAPI, ContestAPI, DownloadContestSubmissions
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("contest", ContestAPI.as_view()),
|
path("contest", ContestAPI.as_view()),
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from ..views.oj import ContestAnnouncementListAPI
|
from ..views.oj import ContestAccessAPI, ContestAnnouncementListAPI, ContestAPI, ContestListAPI, ContestPasswordVerifyAPI, ContestRankAPI
|
||||||
from ..views.oj import ContestPasswordVerifyAPI, ContestAccessAPI
|
|
||||||
from ..views.oj import ContestListAPI, ContestAPI
|
|
||||||
from ..views.oj import ContestRankAPI
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("contests", ContestListAPI.as_view()),
|
path("contests", ContestListAPI.as_view()),
|
||||||
|
|||||||
@@ -6,25 +6,25 @@ from ipaddress import ip_network
|
|||||||
import dateutil.parser
|
import dateutil.parser
|
||||||
from django.http import FileResponse
|
from django.http import FileResponse
|
||||||
|
|
||||||
from problem.models import Problem
|
|
||||||
|
|
||||||
from account.decorators import super_admin_required
|
from account.decorators import super_admin_required
|
||||||
from account.models import User
|
from account.models import User
|
||||||
from submission.models import Submission, JudgeStatus
|
from problem.models import Problem
|
||||||
|
from submission.models import JudgeStatus, Submission
|
||||||
from utils.api import APIView, validate_serializer
|
from utils.api import APIView, validate_serializer
|
||||||
from utils.cache import cache
|
from utils.cache import cache
|
||||||
from utils.constants import CacheKey
|
from utils.constants import CacheKey
|
||||||
from utils.shortcuts import rand_str
|
from utils.shortcuts import rand_str
|
||||||
from utils.tasks import delete_files
|
from utils.tasks import delete_files
|
||||||
from ..models import Contest, ContestAnnouncement, ACMContestRank
|
|
||||||
|
from ..models import ACMContestRank, Contest, ContestAnnouncement
|
||||||
from ..serializers import (
|
from ..serializers import (
|
||||||
ContestAnnouncementSerializer,
|
ACMContesHelperSerializer,
|
||||||
ContestAdminSerializer,
|
ContestAdminSerializer,
|
||||||
|
ContestAnnouncementSerializer,
|
||||||
CreateConetestSeriaizer,
|
CreateConetestSeriaizer,
|
||||||
CreateContestAnnouncementSerializer,
|
CreateContestAnnouncementSerializer,
|
||||||
EditConetestSeriaizer,
|
EditConetestSeriaizer,
|
||||||
EditContestAnnouncementSerializer,
|
EditContestAnnouncementSerializer,
|
||||||
ACMContesHelperSerializer,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,23 @@
|
|||||||
import io
|
import io
|
||||||
|
|
||||||
import xlsxwriter
|
import xlsxwriter
|
||||||
|
from django.core.cache import cache
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.core.cache import cache
|
|
||||||
|
|
||||||
|
from account.decorators import (
|
||||||
|
check_contest_password,
|
||||||
|
check_contest_permission,
|
||||||
|
login_required,
|
||||||
|
)
|
||||||
|
from account.models import AdminType
|
||||||
from problem.models import Problem
|
from problem.models import Problem
|
||||||
from utils.api import APIView, validate_serializer
|
from utils.api import APIView, validate_serializer
|
||||||
from utils.constants import CacheKey, CONTEST_PASSWORD_SESSION_KEY
|
from utils.constants import CONTEST_PASSWORD_SESSION_KEY, CacheKey, ContestRuleType, ContestStatus
|
||||||
from utils.shortcuts import datetime2str, check_is_id
|
from utils.shortcuts import check_is_id, datetime2str
|
||||||
from account.models import AdminType
|
|
||||||
from account.decorators import (
|
|
||||||
login_required,
|
|
||||||
check_contest_permission,
|
|
||||||
check_contest_password,
|
|
||||||
)
|
|
||||||
|
|
||||||
from utils.constants import ContestRuleType, ContestStatus
|
from ..models import ACMContestRank, Contest, ContestAnnouncement, OIContestRank
|
||||||
from ..models import ContestAnnouncement, Contest, OIContestRank, ACMContestRank
|
from ..serializers import ACMContestRankSerializer, ContestAnnouncementSerializer, ContestPasswordVerifySerializer, ContestSerializer, OIContestRankSerializer
|
||||||
from ..serializers import ContestAnnouncementSerializer
|
|
||||||
from ..serializers import ContestSerializer, ContestPasswordVerifySerializer
|
|
||||||
from ..serializers import OIContestRankSerializer, ACMContestRankSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class ContestAnnouncementListAPI(APIView):
|
class ContestAnnouncementListAPI(APIView):
|
||||||
|
|||||||
6
dev.py
6
dev.py
@@ -6,13 +6,13 @@ WebSocket 开发服务器启动脚本
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import subprocess
|
|
||||||
import platform
|
import platform
|
||||||
import signal
|
import signal
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ WebSocket consumers for flowchart evaluation updates
|
|||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from channels.generic.websocket import AsyncWebsocketConsumer
|
from channels.generic.websocket import AsyncWebsocketConsumer
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
from django.db import models
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from utils.shortcuts import rand_str
|
from django.db import models
|
||||||
|
|
||||||
from problem.models import Problem
|
from problem.models import Problem
|
||||||
|
from utils.shortcuts import rand_str
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from .models import FlowchartSubmission
|
from .models import FlowchartSubmission
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import dramatiq
|
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import dramatiq
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from utils.openai import get_ai_client
|
from utils.openai import get_ai_client
|
||||||
from utils.shortcuts import DRAMATIQ_WORKER_ARGS
|
from utils.shortcuts import DRAMATIQ_WORKER_ARGS
|
||||||
|
|
||||||
from .models import FlowchartSubmission, FlowchartSubmissionStatus
|
from .models import FlowchartSubmission, FlowchartSubmissionStatus
|
||||||
|
|
||||||
|
|
||||||
@dramatiq.actor(**DRAMATIQ_WORKER_ARGS(max_retries=3))
|
@dramatiq.actor(**DRAMATIQ_WORKER_ARGS(max_retries=3))
|
||||||
def evaluate_flowchart_task(submission_id):
|
def evaluate_flowchart_task(submission_id):
|
||||||
"""异步AI评分任务"""
|
"""异步AI评分任务"""
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from ..views.oj import (
|
from ..views.oj import (
|
||||||
FlowchartSubmissionAPI,
|
FlowchartSubmissionAPI,
|
||||||
FlowchartSubmissionListAPI,
|
|
||||||
FlowchartSubmissionRetryAPI,
|
|
||||||
FlowchartSubmissionCurrentAPI,
|
FlowchartSubmissionCurrentAPI,
|
||||||
FlowchartSubmissionDetailAPI,
|
FlowchartSubmissionDetailAPI,
|
||||||
|
FlowchartSubmissionListAPI,
|
||||||
|
FlowchartSubmissionRetryAPI,
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
from django.shortcuts import render
|
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
from utils.api import APIView
|
|
||||||
from account.decorators import login_required
|
from account.decorators import login_required
|
||||||
from flowchart.models import FlowchartSubmission, FlowchartSubmissionStatus
|
from flowchart.models import FlowchartSubmission, FlowchartSubmissionStatus
|
||||||
from flowchart.serializers import (
|
from flowchart.serializers import (
|
||||||
CreateFlowchartSubmissionSerializer,
|
CreateFlowchartSubmissionSerializer,
|
||||||
FlowchartSubmissionSerializer,
|
|
||||||
FlowchartSubmissionListSerializer,
|
FlowchartSubmissionListSerializer,
|
||||||
|
FlowchartSubmissionSerializer,
|
||||||
)
|
)
|
||||||
from flowchart.tasks import evaluate_flowchart_task
|
from flowchart.tasks import evaluate_flowchart_task
|
||||||
from problem.models import Problem
|
from problem.models import Problem
|
||||||
|
from utils.api import APIView
|
||||||
|
|
||||||
|
|
||||||
class FlowchartSubmissionAPI(APIView):
|
class FlowchartSubmissionAPI(APIView):
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import base64
|
import base64
|
||||||
import copy
|
import copy
|
||||||
import random
|
|
||||||
import string
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
|
import string
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import logging
|
|||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from django.db import transaction, IntegrityError
|
from django.db import IntegrityError, transaction
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
|
|
||||||
from account.models import User
|
from account.models import User
|
||||||
from conf.models import JudgeServer
|
from conf.models import JudgeServer
|
||||||
from contest.models import ContestRuleType, ACMContestRank, OIContestRank, ContestStatus
|
from contest.models import ACMContestRank, ContestRuleType, ContestStatus, OIContestRank
|
||||||
from options.options import SysOptions
|
from options.options import SysOptions
|
||||||
from problem.models import Problem, ProblemRuleType
|
from problem.models import Problem, ProblemRuleType
|
||||||
from problem.utils import parse_problem_template
|
from problem.utils import parse_problem_template
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from problem.models import ProblemIOMode
|
from problem.models import ProblemIOMode
|
||||||
|
|
||||||
|
|
||||||
default_env = ["LANG=en_US.UTF-8", "LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8"]
|
default_env = ["LANG=en_US.UTF-8", "LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8"]
|
||||||
|
|
||||||
_c_lang_config = {
|
_c_lang_config = {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import dramatiq
|
import dramatiq
|
||||||
|
|
||||||
from account.models import User
|
from account.models import User
|
||||||
from submission.models import Submission
|
|
||||||
from judge.dispatcher import JudgeDispatcher
|
from judge.dispatcher import JudgeDispatcher
|
||||||
|
from submission.models import Submission
|
||||||
from utils.shortcuts import DRAMATIQ_WORKER_ARGS
|
from utils.shortcuts import DRAMATIQ_WORKER_ARGS
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import sys
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "oj.settings")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "oj.settings")
|
||||||
|
|
||||||
from django.core.management import execute_from_command_line
|
|
||||||
import django
|
import django
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
sys.stdout.write("Django VERSION " + str(django.VERSION) + "\n")
|
sys.stdout.write("Django VERSION " + str(django.VERSION) + "\n")
|
||||||
|
|
||||||
execute_from_command_line(sys.argv)
|
execute_from_command_line(sys.argv)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from account.models import User
|
from account.models import User
|
||||||
from submission.models import Submission
|
from submission.models import Submission
|
||||||
from utils.models import RichTextField
|
from utils.models import RichTextField
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from submission.serializers import SubmissionSafeModelSerializer
|
from submission.serializers import SubmissionSafeModelSerializer
|
||||||
from utils.api import UsernameSerializer, serializers
|
from utils.api import UsernameSerializer, serializers
|
||||||
from .models import Message
|
|
||||||
|
|
||||||
|
from .models import Message
|
||||||
|
|
||||||
|
|
||||||
class MessageSerializer(serializers.ModelSerializer):
|
class MessageSerializer(serializers.ModelSerializer):
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
from account.decorators import super_admin_required, login_required
|
from account.decorators import login_required, super_admin_required
|
||||||
from account.models import User
|
from account.models import User
|
||||||
|
from message.models import Message
|
||||||
from message.serializers import CreateMessageSerializer, MessageSerializer
|
from message.serializers import CreateMessageSerializer, MessageSerializer
|
||||||
from submission.models import Submission
|
from submission.models import Submission
|
||||||
from utils.api import APIView
|
from utils.api import APIView
|
||||||
|
|
||||||
from message.models import Message
|
|
||||||
|
|
||||||
from utils.api.api import validate_serializer
|
from utils.api.api import validate_serializer
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from django.core.asgi import get_asgi_application
|
|
||||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
|
||||||
from channels.auth import AuthMiddlewareStack
|
from channels.auth import AuthMiddlewareStack
|
||||||
|
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "oj.settings")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "oj.settings")
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "oj.settings")
|
|||||||
django_asgi_app = get_asgi_application()
|
django_asgi_app = get_asgi_application()
|
||||||
|
|
||||||
# Import routing after Django setup
|
# Import routing after Django setup
|
||||||
from oj.routing import websocket_urlpatterns
|
from oj.routing import websocket_urlpatterns # noqa: E402
|
||||||
|
|
||||||
application = ProtocolTypeRouter(
|
application = ProtocolTypeRouter(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ WebSocket URL Configuration for oj project.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from submission.consumers import SubmissionConsumer
|
|
||||||
from conf.consumers import ConfigConsumer
|
from conf.consumers import ConfigConsumer
|
||||||
from flowchart.consumers import FlowchartConsumer
|
from flowchart.consumers import FlowchartConsumer
|
||||||
|
from submission.consumers import SubmissionConsumer
|
||||||
|
|
||||||
websocket_urlpatterns = [
|
websocket_urlpatterns = [
|
||||||
path("ws/submission/", SubmissionConsumer.as_asgi()),
|
path("ws/submission/", SubmissionConsumer.as_asgi()),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from utils.models import JSONField
|
from utils.models import JSONField
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import os
|
|||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from django.db import transaction, IntegrityError
|
from django.db import IntegrityError, transaction
|
||||||
|
|
||||||
from utils.shortcuts import rand_str
|
|
||||||
from judge.languages import languages
|
from judge.languages import languages
|
||||||
|
from utils.shortcuts import rand_str
|
||||||
|
|
||||||
from .models import SysOptions as SysOptionsModel
|
from .models import SysOptions as SysOptionsModel
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
# Create your tests here.
|
|
||||||
@@ -2,8 +2,8 @@ from django.db import models
|
|||||||
|
|
||||||
from account.models import User
|
from account.models import User
|
||||||
from contest.models import Contest
|
from contest.models import Contest
|
||||||
from utils.models import RichTextField
|
|
||||||
from utils.constants import Choices
|
from utils.constants import Choices
|
||||||
|
from utils.models import RichTextField
|
||||||
|
|
||||||
|
|
||||||
class ProblemTag(models.Model):
|
class ProblemTag(models.Model):
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ from django import forms
|
|||||||
from utils.api import UsernameSerializer, serializers
|
from utils.api import UsernameSerializer, serializers
|
||||||
from utils.constants import Difficulty
|
from utils.constants import Difficulty
|
||||||
from utils.serializers import (
|
from utils.serializers import (
|
||||||
LanguageNameMultiChoiceField,
|
|
||||||
LanguageNameChoiceField,
|
LanguageNameChoiceField,
|
||||||
|
LanguageNameMultiChoiceField,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .models import Problem, ProblemRuleType, ProblemTag, ProblemIOMode
|
from .models import Problem, ProblemIOMode, ProblemRuleType, ProblemTag
|
||||||
from .utils import parse_problem_template
|
from .utils import parse_problem_template
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from ..views.admin import (
|
from ..views.admin import (
|
||||||
|
AddContestProblemAPI,
|
||||||
ContestProblemAPI,
|
ContestProblemAPI,
|
||||||
|
MakeContestProblemPublicAPIView,
|
||||||
ProblemAPI,
|
ProblemAPI,
|
||||||
ProblemFlowchartAIGen,
|
ProblemFlowchartAIGen,
|
||||||
|
ProblemVisibleAPI,
|
||||||
StuckProblemsAPI,
|
StuckProblemsAPI,
|
||||||
TestCaseAPI,
|
TestCaseAPI,
|
||||||
MakeContestProblemPublicAPIView,
|
|
||||||
AddContestProblemAPI,
|
|
||||||
ProblemVisibleAPI,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from ..views.oj import (
|
from ..views.oj import (
|
||||||
ProblemSolvedPeopleCount,
|
|
||||||
ProblemTagAPI,
|
|
||||||
ProblemAPI,
|
|
||||||
ContestProblemAPI,
|
ContestProblemAPI,
|
||||||
PickOneAPI,
|
PickOneAPI,
|
||||||
|
ProblemAPI,
|
||||||
ProblemAuthorAPI,
|
ProblemAuthorAPI,
|
||||||
|
ProblemSolvedPeopleCount,
|
||||||
|
ProblemTagAPI,
|
||||||
SimilarProblemAPI,
|
SimilarProblemAPI,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
|
||||||
|
|
||||||
TEMPLATE_BASE = """//PREPEND BEGIN
|
TEMPLATE_BASE = """//PREPEND BEGIN
|
||||||
{}
|
{}
|
||||||
//PREPEND END
|
//PREPEND END
|
||||||
|
|||||||
@@ -7,28 +7,27 @@ import zipfile
|
|||||||
from wsgiref.util import FileWrapper
|
from wsgiref.util import FileWrapper
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import Q
|
from django.db.models import Count, Q
|
||||||
from django.http import StreamingHttpResponse
|
from django.http import StreamingHttpResponse
|
||||||
|
|
||||||
from django.db.models import Count
|
from account.decorators import ensure_created_by, problem_permission_required, super_admin_required
|
||||||
|
|
||||||
from account.decorators import problem_permission_required, ensure_created_by, super_admin_required
|
|
||||||
from contest.models import Contest, ContestStatus
|
from contest.models import Contest, ContestStatus
|
||||||
from submission.models import Submission
|
from submission.models import Submission
|
||||||
from utils.api import APIView, CSRFExemptAPIView, validate_serializer, APIError
|
from utils.api import APIError, APIView, CSRFExemptAPIView, validate_serializer
|
||||||
from utils.shortcuts import rand_str, natural_sort_key
|
|
||||||
from utils.openai import get_ai_client
|
from utils.openai import get_ai_client
|
||||||
|
from utils.shortcuts import natural_sort_key, rand_str
|
||||||
|
|
||||||
from ..models import Problem, ProblemRuleType, ProblemTag
|
from ..models import Problem, ProblemRuleType, ProblemTag
|
||||||
from ..serializers import (
|
from ..serializers import (
|
||||||
|
AddContestProblemSerializer,
|
||||||
|
ContestProblemMakePublicSerializer,
|
||||||
CreateContestProblemSerializer,
|
CreateContestProblemSerializer,
|
||||||
CreateProblemSerializer,
|
CreateProblemSerializer,
|
||||||
EditProblemSerializer,
|
|
||||||
EditContestProblemSerializer,
|
EditContestProblemSerializer,
|
||||||
ProblemAdminSerializer,
|
EditProblemSerializer,
|
||||||
ProblemAdminListSerializer,
|
ProblemAdminListSerializer,
|
||||||
|
ProblemAdminSerializer,
|
||||||
TestCaseUploadForm,
|
TestCaseUploadForm,
|
||||||
ContestProblemMakePublicSerializer,
|
|
||||||
AddContestProblemSerializer,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
from datetime import datetime
|
|
||||||
import random
|
import random
|
||||||
from django.db.models import Q, Count
|
from datetime import datetime
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from account.models import User
|
from django.db.models import Count, Q
|
||||||
from submission.models import Submission, JudgeStatus
|
|
||||||
from utils.api import APIView
|
|
||||||
from account.decorators import check_contest_permission
|
from account.decorators import check_contest_permission
|
||||||
|
from account.models import User
|
||||||
|
from contest.models import ContestRuleType
|
||||||
|
from submission.models import JudgeStatus, Submission
|
||||||
|
from utils.api import APIView
|
||||||
from utils.constants import CacheKey
|
from utils.constants import CacheKey
|
||||||
from ..models import ProblemTag, Problem
|
|
||||||
|
from ..models import Problem, ProblemTag
|
||||||
from ..serializers import (
|
from ..serializers import (
|
||||||
|
ProblemListSerializer,
|
||||||
|
ProblemSafeSerializer,
|
||||||
ProblemSerializer,
|
ProblemSerializer,
|
||||||
TagSerializer,
|
TagSerializer,
|
||||||
ProblemSafeSerializer,
|
|
||||||
ProblemListSerializer,
|
|
||||||
)
|
)
|
||||||
from contest.models import ContestRuleType
|
|
||||||
|
|
||||||
|
|
||||||
class ProblemTagAPI(APIView):
|
class ProblemTagAPI(APIView):
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
|
||||||
from account.models import User
|
from account.models import User
|
||||||
from problem.models import Problem
|
from problem.models import Problem
|
||||||
from utils.models import RichTextField, JSONField
|
from utils.models import JSONField, RichTextField
|
||||||
|
|
||||||
|
|
||||||
class ProblemSet(models.Model):
|
class ProblemSet(models.Model):
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
from utils.api import UsernameSerializer, serializers
|
from utils.api import UsernameSerializer, serializers
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
ProblemSet,
|
ProblemSet,
|
||||||
ProblemSetProblem,
|
|
||||||
ProblemSetBadge,
|
ProblemSetBadge,
|
||||||
|
ProblemSetProblem,
|
||||||
ProblemSetProgress,
|
ProblemSetProgress,
|
||||||
UserBadge,
|
UserBadge,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
# 题单应用信号处理
|
# 题单应用信号处理
|
||||||
from django.db.models.signals import post_save, post_delete
|
|
||||||
from django.dispatch import receiver
|
|
||||||
from .models import ProblemSetProblem, ProblemSetProgress, ProblemSetBadge, UserBadge
|
|
||||||
from django.db import transaction
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from django.db import transaction
|
||||||
|
from django.db.models.signals import post_delete, post_save
|
||||||
|
from django.dispatch import receiver
|
||||||
|
|
||||||
|
from .models import ProblemSetBadge, ProblemSetProblem, ProblemSetProgress, UserBadge
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from problemset.views.oj import (
|
from problemset.views.oj import (
|
||||||
ProblemSetAPI,
|
ProblemSetAPI,
|
||||||
|
ProblemSetBadgeAPI,
|
||||||
ProblemSetDetailAPI,
|
ProblemSetDetailAPI,
|
||||||
ProblemSetProblemAPI,
|
ProblemSetProblemAPI,
|
||||||
ProblemSetProgressAPI,
|
ProblemSetProgressAPI,
|
||||||
|
ProblemSetUserProgressAPI,
|
||||||
UserBadgeAPI,
|
UserBadgeAPI,
|
||||||
UserProgressAPI,
|
UserProgressAPI,
|
||||||
ProblemSetBadgeAPI,
|
|
||||||
ProblemSetUserProgressAPI,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|||||||
@@ -1,28 +1,27 @@
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from utils.api import APIView, validate_serializer
|
from account.decorators import ensure_created_by, super_admin_required
|
||||||
from account.decorators import super_admin_required, ensure_created_by
|
from problem.models import Problem
|
||||||
|
|
||||||
from problemset.models import (
|
from problemset.models import (
|
||||||
ProblemSet,
|
ProblemSet,
|
||||||
ProblemSetProblem,
|
|
||||||
ProblemSetBadge,
|
ProblemSetBadge,
|
||||||
|
ProblemSetProblem,
|
||||||
ProblemSetProgress,
|
ProblemSetProgress,
|
||||||
)
|
)
|
||||||
from problemset.serializers import (
|
from problemset.serializers import (
|
||||||
ProblemSetSerializer,
|
|
||||||
ProblemSetListSerializer,
|
|
||||||
CreateProblemSetSerializer,
|
|
||||||
EditProblemSetSerializer,
|
|
||||||
ProblemSetProblemSerializer,
|
|
||||||
AddProblemToSetSerializer,
|
AddProblemToSetSerializer,
|
||||||
EditProblemInSetSerializer,
|
|
||||||
ProblemSetBadgeSerializer,
|
|
||||||
CreateProblemSetBadgeSerializer,
|
CreateProblemSetBadgeSerializer,
|
||||||
|
CreateProblemSetSerializer,
|
||||||
|
EditProblemInSetSerializer,
|
||||||
EditProblemSetBadgeSerializer,
|
EditProblemSetBadgeSerializer,
|
||||||
|
EditProblemSetSerializer,
|
||||||
|
ProblemSetBadgeSerializer,
|
||||||
|
ProblemSetListSerializer,
|
||||||
|
ProblemSetProblemSerializer,
|
||||||
ProblemSetProgressSerializer,
|
ProblemSetProgressSerializer,
|
||||||
|
ProblemSetSerializer,
|
||||||
)
|
)
|
||||||
from problem.models import Problem
|
from utils.api import APIView, validate_serializer
|
||||||
|
|
||||||
|
|
||||||
class ProblemSetAdminAPI(APIView):
|
class ProblemSetAdminAPI(APIView):
|
||||||
|
|||||||
@@ -1,31 +1,28 @@
|
|||||||
from django.db.models import Q, Avg, Count, Prefetch
|
from django.db.models import Avg, Count, Prefetch, Q
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from utils.api import APIView, validate_serializer
|
|
||||||
|
|
||||||
from account.models import User
|
from account.models import User
|
||||||
|
from problem.models import Problem
|
||||||
from problemset.models import (
|
from problemset.models import (
|
||||||
ProblemSet,
|
ProblemSet,
|
||||||
ProblemSetProblem,
|
|
||||||
ProblemSetBadge,
|
ProblemSetBadge,
|
||||||
|
ProblemSetProblem,
|
||||||
ProblemSetProgress,
|
ProblemSetProgress,
|
||||||
ProblemSetSubmission,
|
ProblemSetSubmission,
|
||||||
UserBadge,
|
UserBadge,
|
||||||
)
|
)
|
||||||
from problemset.serializers import (
|
from problemset.serializers import (
|
||||||
ProblemSetSerializer,
|
JoinProblemSetSerializer,
|
||||||
|
ProblemSetBadgeSerializer,
|
||||||
ProblemSetListSerializer,
|
ProblemSetListSerializer,
|
||||||
ProblemSetProblemSerializer,
|
ProblemSetProblemSerializer,
|
||||||
ProblemSetBadgeSerializer,
|
|
||||||
ProblemSetProgressSerializer,
|
ProblemSetProgressSerializer,
|
||||||
UserBadgeSerializer,
|
ProblemSetSerializer,
|
||||||
JoinProblemSetSerializer,
|
|
||||||
UpdateProgressSerializer,
|
UpdateProgressSerializer,
|
||||||
|
UserBadgeSerializer,
|
||||||
)
|
)
|
||||||
|
|
||||||
from submission.models import Submission
|
from submission.models import Submission
|
||||||
from problem.models import Problem
|
from utils.api import APIView, validate_serializer
|
||||||
|
|
||||||
|
|
||||||
class ProblemSetAPI(APIView):
|
class ProblemSetAPI(APIView):
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ requires-python = ">=3.12"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"channels>=4.2.0",
|
"channels>=4.2.0",
|
||||||
"channels-redis>=4.2.0",
|
"channels-redis>=4.2.0",
|
||||||
"coverage==6.5.0",
|
|
||||||
"daphne>=4.1.2",
|
"daphne>=4.1.2",
|
||||||
"django>=5.2.3",
|
"django>=5.2.3",
|
||||||
"django-cas-ng==5.0.1",
|
"django-cas-ng==5.0.1",
|
||||||
@@ -18,9 +17,6 @@ dependencies = [
|
|||||||
"dramatiq==1.17.0",
|
"dramatiq==1.17.0",
|
||||||
"entrypoints==0.4",
|
"entrypoints==0.4",
|
||||||
"envelopes==0.4",
|
"envelopes==0.4",
|
||||||
"flake8==7.0.0",
|
|
||||||
"flake8-coding==1.3.2",
|
|
||||||
"flake8-quotes==3.3.2",
|
|
||||||
"gunicorn==22.0.0",
|
"gunicorn==22.0.0",
|
||||||
"jsonfield==3.1.0",
|
"jsonfield==3.1.0",
|
||||||
"openai>=1.108.1",
|
"openai>=1.108.1",
|
||||||
@@ -33,3 +29,18 @@ dependencies = [
|
|||||||
"raven==6.10.0",
|
"raven==6.10.0",
|
||||||
"xlsxwriter==3.2.0",
|
"xlsxwriter==3.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"ruff>=0.15.11",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 180
|
||||||
|
exclude = ["*/migrations/*", "*settings.py", "*/apps.py", ".venv"]
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
quote-style = "double"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = ["E", "F", "I"]
|
||||||
|
|||||||
27
run_test.py
27
run_test.py
@@ -1,27 +0,0 @@
|
|||||||
import getopt
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
opts, args = getopt.getopt(sys.argv[1:], "cm:", ["coverage=", "module="])
|
|
||||||
|
|
||||||
is_coverage = False
|
|
||||||
test_module = ""
|
|
||||||
setting = "oj.settings"
|
|
||||||
|
|
||||||
for opt, arg in opts:
|
|
||||||
if opt in ["-c", "--coverage"]:
|
|
||||||
is_coverage = True
|
|
||||||
if opt in ["-m", "--module"]:
|
|
||||||
test_module = arg
|
|
||||||
|
|
||||||
print("Coverage: {cov}".format(cov=is_coverage))
|
|
||||||
print("Module: {mod}".format(mod=(test_module if test_module else "All")))
|
|
||||||
|
|
||||||
print("running flake8...")
|
|
||||||
if os.system("flake8 --statistics ."):
|
|
||||||
exit()
|
|
||||||
|
|
||||||
ret = os.system('coverage run --include="$PWD/*" manage.py test {module} --settings={setting}'.format(module=test_module, setting=setting))
|
|
||||||
|
|
||||||
if not ret and is_coverage:
|
|
||||||
os.system("coverage html && open htmlcov/index.html")
|
|
||||||
@@ -3,6 +3,7 @@ WebSocket consumers for submission updates
|
|||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from channels.generic.websocket import AsyncWebsocketConsumer
|
from channels.generic.websocket import AsyncWebsocketConsumer
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
from contest.models import Contest
|
||||||
|
from problem.models import Problem
|
||||||
from utils.constants import ContestStatus
|
from utils.constants import ContestStatus
|
||||||
from utils.models import JSONField
|
from utils.models import JSONField
|
||||||
from problem.models import Problem
|
|
||||||
from contest.models import Contest
|
|
||||||
|
|
||||||
from utils.shortcuts import rand_str
|
from utils.shortcuts import rand_str
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ from django.db import models
|
|||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from .models import Submission
|
from problemset.models import ProblemSetProgress
|
||||||
from utils.api import serializers
|
from utils.api import serializers
|
||||||
from utils.serializers import LanguageNameChoiceField
|
from utils.serializers import LanguageNameChoiceField
|
||||||
from problemset.models import ProblemSetProgress
|
|
||||||
|
from .models import Submission
|
||||||
|
|
||||||
|
|
||||||
def bulk_fetch_problemset_progress(user, problem_ids):
|
def bulk_fetch_problemset_progress(user, problem_ids):
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from ..views.oj import (
|
from ..views.oj import (
|
||||||
SubmissionAPI,
|
|
||||||
SubmissionListAPI,
|
|
||||||
ContestSubmissionListAPI,
|
ContestSubmissionListAPI,
|
||||||
|
SubmissionAPI,
|
||||||
SubmissionExistsAPI,
|
SubmissionExistsAPI,
|
||||||
|
SubmissionListAPI,
|
||||||
SubmissionsTodayCount,
|
SubmissionsTodayCount,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
from account.decorators import super_admin_required
|
|
||||||
from judge.tasks import judge_task
|
|
||||||
|
|
||||||
from utils.api import APIView
|
|
||||||
from ..models import Submission, JudgeStatus
|
|
||||||
from account.models import User, AdminType
|
|
||||||
from problem.models import Problem
|
|
||||||
from django.db.models import Count, Q
|
from django.db.models import Count, Q
|
||||||
|
|
||||||
|
from account.decorators import super_admin_required
|
||||||
|
from account.models import AdminType, User
|
||||||
|
from judge.tasks import judge_task
|
||||||
|
from problem.models import Problem
|
||||||
|
from utils.api import APIView
|
||||||
|
|
||||||
|
from ..models import JudgeStatus, Submission
|
||||||
|
|
||||||
|
|
||||||
def get_real_name(username, class_name):
|
def get_real_name(username, class_name):
|
||||||
if class_name and username.startswith("ks"):
|
if class_name and username.startswith("ks"):
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
from datetime import datetime
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from account.decorators import login_required, check_contest_permission
|
from account.decorators import check_contest_permission, login_required
|
||||||
from contest.models import ContestStatus, ContestRuleType
|
from contest.models import ContestRuleType, ContestStatus
|
||||||
from judge.tasks import judge_task
|
from judge.tasks import judge_task
|
||||||
from options.options import SysOptions
|
from options.options import SysOptions
|
||||||
|
|
||||||
@@ -12,13 +12,16 @@ from utils.api import APIView, validate_serializer
|
|||||||
from utils.cache import cache
|
from utils.cache import cache
|
||||||
from utils.captcha import Captcha
|
from utils.captcha import Captcha
|
||||||
from utils.throttling import TokenBucket
|
from utils.throttling import TokenBucket
|
||||||
|
|
||||||
from ..models import Submission
|
from ..models import Submission
|
||||||
from ..serializers import (
|
from ..serializers import (
|
||||||
CreateSubmissionSerializer,
|
CreateSubmissionSerializer,
|
||||||
SubmissionModelSerializer,
|
|
||||||
ShareSubmissionSerializer,
|
ShareSubmissionSerializer,
|
||||||
|
SubmissionListSerializer,
|
||||||
|
SubmissionModelSerializer,
|
||||||
|
SubmissionSafeModelSerializer,
|
||||||
|
bulk_fetch_problemset_progress,
|
||||||
)
|
)
|
||||||
from ..serializers import SubmissionSafeModelSerializer, SubmissionListSerializer, bulk_fetch_problemset_progress
|
|
||||||
|
|
||||||
|
|
||||||
class SubmissionAPI(APIView):
|
class SubmissionAPI(APIView):
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from account.models import User
|
from account.models import User
|
||||||
|
|
||||||
|
|
||||||
class Tutorial(models.Model):
|
class Tutorial(models.Model):
|
||||||
TYPE_CHOICES = [
|
TYPE_CHOICES = [
|
||||||
('python', 'Python'),
|
('python', 'Python'),
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from .models import Tutorial, Exercise
|
|
||||||
from account.serializers import UserSerializer
|
from account.serializers import UserSerializer
|
||||||
|
|
||||||
|
from .models import Exercise, Tutorial
|
||||||
|
|
||||||
|
|
||||||
class TutorialListSerializer(serializers.ModelSerializer):
|
class TutorialListSerializer(serializers.ModelSerializer):
|
||||||
created_by = UserSerializer(read_only=True)
|
created_by = UserSerializer(read_only=True)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from ..views.admin import TutorialAdminAPI, TutorialVisibilityAPI, ExerciseAdminAPI
|
|
||||||
|
from ..views.admin import ExerciseAdminAPI, TutorialAdminAPI, TutorialVisibilityAPI
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("tutorial", TutorialAdminAPI.as_view()),
|
path("tutorial", TutorialAdminAPI.as_view()),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from ..views.oj import TutorialAPI, TutorialTitlesAPI, ExerciseAPI
|
|
||||||
|
from ..views.oj import ExerciseAPI, TutorialAPI, TutorialTitlesAPI
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("tutorial", TutorialAPI.as_view()),
|
path("tutorial", TutorialAPI.as_view()),
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
from account.decorators import super_admin_required
|
from account.decorators import super_admin_required
|
||||||
from utils.api import APIView, validate_serializer
|
from tutorial.models import Exercise, Tutorial
|
||||||
|
|
||||||
from tutorial.models import Tutorial, Exercise
|
|
||||||
from tutorial.serializers import (
|
from tutorial.serializers import (
|
||||||
TutorialSerializer,
|
CreateExerciseSerializer,
|
||||||
TutorialListSerializer,
|
|
||||||
CreateTutorialSerializer,
|
CreateTutorialSerializer,
|
||||||
|
EditExerciseSerializer,
|
||||||
EditTutorialSerializer,
|
EditTutorialSerializer,
|
||||||
ExerciseSerializer,
|
ExerciseSerializer,
|
||||||
CreateExerciseSerializer,
|
TutorialListSerializer,
|
||||||
EditExerciseSerializer,
|
TutorialSerializer,
|
||||||
)
|
)
|
||||||
|
from utils.api import APIView, validate_serializer
|
||||||
|
|
||||||
|
|
||||||
class TutorialAdminAPI(APIView):
|
class TutorialAdminAPI(APIView):
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
|
from tutorial.models import Exercise, Tutorial
|
||||||
|
from tutorial.serializers import ExerciseSerializer, TutorialSerializer
|
||||||
from utils.api import APIView
|
from utils.api import APIView
|
||||||
|
|
||||||
from tutorial.models import Tutorial, Exercise
|
|
||||||
from tutorial.serializers import TutorialSerializer, ExerciseSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class TutorialAPI(APIView):
|
class TutorialAPI(APIView):
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
from django.urls import reverse
|
|
||||||
from django.test.testcases import TestCase
|
|
||||||
from rest_framework.test import APIClient
|
|
||||||
|
|
||||||
from account.models import AdminType, ProblemPermission, User, UserProfile
|
|
||||||
|
|
||||||
|
|
||||||
class APITestCase(TestCase):
|
|
||||||
client_class = APIClient
|
|
||||||
|
|
||||||
def create_user(self, username, password, admin_type=AdminType.REGULAR_USER, login=True,
|
|
||||||
problem_permission=ProblemPermission.NONE):
|
|
||||||
user = User.objects.create(username=username, admin_type=admin_type, problem_permission=problem_permission)
|
|
||||||
user.set_password(password)
|
|
||||||
UserProfile.objects.create(user=user)
|
|
||||||
user.save()
|
|
||||||
if login:
|
|
||||||
self.client.login(username=username, password=password)
|
|
||||||
return user
|
|
||||||
|
|
||||||
def create_admin(self, username="admin", password="admin", login=True):
|
|
||||||
return self.create_user(username=username, password=password, admin_type=AdminType.ADMIN,
|
|
||||||
problem_permission=ProblemPermission.OWN,
|
|
||||||
login=login)
|
|
||||||
|
|
||||||
def create_super_admin(self, username="root", password="root", login=True):
|
|
||||||
return self.create_user(username=username, password=password, admin_type=AdminType.SUPER_ADMIN,
|
|
||||||
problem_permission=ProblemPermission.ALL, login=login)
|
|
||||||
|
|
||||||
def reverse(self, url_name, *args, **kwargs):
|
|
||||||
return reverse(url_name, *args, **kwargs)
|
|
||||||
|
|
||||||
def assertSuccess(self, response):
|
|
||||||
if not response.data["error"] is None:
|
|
||||||
raise AssertionError("response with errors, response: " + str(response.data))
|
|
||||||
|
|
||||||
def assertFailed(self, response, msg=None):
|
|
||||||
self.assertTrue(response.data["error"] is not None)
|
|
||||||
if msg:
|
|
||||||
self.assertEqual(response.data["data"], msg)
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from django.core.cache import cache
|
|
||||||
|
|
||||||
|
from django.core.cache import cache
|
||||||
from django_redis.cache import RedisCache
|
from django_redis.cache import RedisCache
|
||||||
from django_redis.client.default import DefaultClient
|
from django_redis.client.default import DefaultClient
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ limitations under the License.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
import random
|
import random
|
||||||
|
import time
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from . import Captcha
|
|
||||||
from ..api import APIView
|
from ..api import APIView
|
||||||
from ..shortcuts import img2base64
|
from ..shortcuts import img2base64
|
||||||
|
from . import Captcha
|
||||||
|
|
||||||
|
|
||||||
class CaptchaAPIView(APIView):
|
class CaptchaAPIView(APIView):
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import datetime
|
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
import dramatiq
|
import dramatiq
|
||||||
|
|
||||||
from utils.shortcuts import DRAMATIQ_WORKER_ARGS
|
from utils.shortcuts import DRAMATIQ_WORKER_ARGS
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from .views import SimditorImageUploadAPIView, SimditorFileUploadAPIView
|
from .views import SimditorFileUploadAPIView, SimditorImageUploadAPIView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("upload_image", SimditorImageUploadAPIView.as_view()),
|
path("upload_image", SimditorImageUploadAPIView.as_view()),
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import os
|
|
||||||
from django.conf import settings
|
|
||||||
from account.serializers import ImageUploadForm, FileUploadForm
|
|
||||||
from utils.shortcuts import rand_str
|
|
||||||
from utils.api import CSRFExemptAPIView
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from account.serializers import FileUploadForm, ImageUploadForm
|
||||||
|
from utils.api import CSRFExemptAPIView
|
||||||
|
from utils.shortcuts import rand_str
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
WebSocket utility functions for pushing real-time updates
|
WebSocket utility functions for pushing real-time updates
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from channels.layers import get_channel_layer
|
|
||||||
from asgiref.sync import async_to_sync
|
from asgiref.sync import async_to_sync
|
||||||
|
from channels.layers import get_channel_layer
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ Python 2.6+ or 3.2+
|
|||||||
Cannot defense xss in browser which is belowed IE7
|
Cannot defense xss in browser which is belowed IE7
|
||||||
浏览器版本:IE7+ 或其他浏览器,无法防御IE6及以下版本浏览器中的XSS
|
浏览器版本:IE7+ 或其他浏览器,无法防御IE6及以下版本浏览器中的XSS
|
||||||
"""
|
"""
|
||||||
import re
|
|
||||||
import copy
|
import copy
|
||||||
|
import re
|
||||||
from html.parser import HTMLParser
|
from html.parser import HTMLParser
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
109
uv.lock
generated
109
uv.lock
generated
@@ -281,12 +281,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/b8/40/c199d095151addf69efdb4b9ca3a4f20f70e20508d6222bffb9b76f58573/constantly-23.10.4-py3-none-any.whl", hash = "sha256:3fd9b4d1c3dc1ec9757f3c52aef7e53ad9323dbe39f51dfd4c43853b68dfa3f9", size = 13547, upload-time = "2023-10-28T23:18:23.038Z" },
|
{ url = "https://files.pythonhosted.org/packages/b8/40/c199d095151addf69efdb4b9ca3a4f20f70e20508d6222bffb9b76f58573/constantly-23.10.4-py3-none-any.whl", hash = "sha256:3fd9b4d1c3dc1ec9757f3c52aef7e53ad9323dbe39f51dfd4c43853b68dfa3f9", size = 13547, upload-time = "2023-10-28T23:18:23.038Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "coverage"
|
|
||||||
version = "6.5.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/5c/66/38d1870cb7cf62da49add1d6803fdbcdef632b2808b5c80bcac35b7634d8/coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84", size = 775224, upload-time = "2022-09-29T20:05:58.509Z" }
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cryptography"
|
name = "cryptography"
|
||||||
version = "46.0.3"
|
version = "46.0.3"
|
||||||
@@ -466,41 +460,6 @@ version = "0.4"
|
|||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/2e/ac/0aaba34d717868729428bf4dca601c93cd6b0f9123894f2509911027b0dd/Envelopes-0.4.tar.gz", hash = "sha256:a4a02b4dc21467794d3a646f946d99a8c5b3311b2df8e211f96ca9e0b838e7e0", size = 33450, upload-time = "2013-11-13T20:02:09.033Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/2e/ac/0aaba34d717868729428bf4dca601c93cd6b0f9123894f2509911027b0dd/Envelopes-0.4.tar.gz", hash = "sha256:a4a02b4dc21467794d3a646f946d99a8c5b3311b2df8e211f96ca9e0b838e7e0", size = 33450, upload-time = "2013-11-13T20:02:09.033Z" }
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "flake8"
|
|
||||||
version = "7.0.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "mccabe" },
|
|
||||||
{ name = "pycodestyle" },
|
|
||||||
{ name = "pyflakes" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/40/3c/3464b567aa367b221fa610bbbcce8015bf953977d21e52f2d711b526fb48/flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132", size = 48219, upload-time = "2024-01-05T00:41:52.142Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e3/01/cc8cdec7b61db0315c2ab62d80677a138ef06832ec17f04d87e6ef858f7f/flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3", size = 57570, upload-time = "2024-01-05T00:41:49.837Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "flake8-coding"
|
|
||||||
version = "1.3.2"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "flake8" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/e8/0e/cbba2b2da4e0ccf4098e8bd333d39531ff0a6aed91d187bc762ac6b9d263/flake8-coding-1.3.2.tar.gz", hash = "sha256:b8f4d5157a8f74670e6cfea732c3d9f4291a4e994c8701d2c55f787c6e6cb741", size = 7308, upload-time = "2019-06-16T13:42:53.782Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/83/a8/0de26928c40727ec29289b4f5c751a75e4cdd639faed9ab01b835fd0883c/flake8_coding-1.3.2-py2.py3-none-any.whl", hash = "sha256:79704112c44d09d4ab6c8965e76a20c3f7073d52146db60303bce777d9612260", size = 7570, upload-time = "2019-06-16T13:42:56.32Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "flake8-quotes"
|
|
||||||
version = "3.3.2"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "flake8" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/88/89/7c5a8e671c17ae49e4b89b06d9d2db5608eea7c66b04d70d27390da9ddb9/flake8-quotes-3.3.2.tar.gz", hash = "sha256:6e26892b632dacba517bf27219c459a8396dcfac0f5e8204904c5a4ba9b480e1", size = 12530, upload-time = "2022-12-19T16:48:09.308Z" }
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gunicorn"
|
name = "gunicorn"
|
||||||
version = "22.0.0"
|
version = "22.0.0"
|
||||||
@@ -743,15 +702,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/92/aa/df863bcc39c5e0946263454aba394de8a9084dbaff8ad143846b0d844739/lxml-6.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:bb4c1847b303835d89d785a18801a883436cdfd5dc3d62947f9c49e24f0f5a2c", size = 3822205, upload-time = "2025-09-22T04:03:36.249Z" },
|
{ url = "https://files.pythonhosted.org/packages/92/aa/df863bcc39c5e0946263454aba394de8a9084dbaff8ad143846b0d844739/lxml-6.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:bb4c1847b303835d89d785a18801a883436cdfd5dc3d62947f9c49e24f0f5a2c", size = 3822205, upload-time = "2025-09-22T04:03:36.249Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mccabe"
|
|
||||||
version = "0.7.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "msgpack"
|
name = "msgpack"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
@@ -803,7 +753,6 @@ source = { virtual = "." }
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "channels" },
|
{ name = "channels" },
|
||||||
{ name = "channels-redis" },
|
{ name = "channels-redis" },
|
||||||
{ name = "coverage" },
|
|
||||||
{ name = "daphne" },
|
{ name = "daphne" },
|
||||||
{ name = "django" },
|
{ name = "django" },
|
||||||
{ name = "django-cas-ng" },
|
{ name = "django-cas-ng" },
|
||||||
@@ -814,9 +763,6 @@ dependencies = [
|
|||||||
{ name = "dramatiq" },
|
{ name = "dramatiq" },
|
||||||
{ name = "entrypoints" },
|
{ name = "entrypoints" },
|
||||||
{ name = "envelopes" },
|
{ name = "envelopes" },
|
||||||
{ name = "flake8" },
|
|
||||||
{ name = "flake8-coding" },
|
|
||||||
{ name = "flake8-quotes" },
|
|
||||||
{ name = "gunicorn" },
|
{ name = "gunicorn" },
|
||||||
{ name = "jsonfield" },
|
{ name = "jsonfield" },
|
||||||
{ name = "openai" },
|
{ name = "openai" },
|
||||||
@@ -830,11 +776,15 @@ dependencies = [
|
|||||||
{ name = "xlsxwriter" },
|
{ name = "xlsxwriter" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[package.dev-dependencies]
|
||||||
|
dev = [
|
||||||
|
{ name = "ruff" },
|
||||||
|
]
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "channels", specifier = ">=4.2.0" },
|
{ name = "channels", specifier = ">=4.2.0" },
|
||||||
{ name = "channels-redis", specifier = ">=4.2.0" },
|
{ name = "channels-redis", specifier = ">=4.2.0" },
|
||||||
{ name = "coverage", specifier = "==6.5.0" },
|
|
||||||
{ name = "daphne", specifier = ">=4.1.2" },
|
{ name = "daphne", specifier = ">=4.1.2" },
|
||||||
{ name = "django", specifier = ">=5.2.3" },
|
{ name = "django", specifier = ">=5.2.3" },
|
||||||
{ name = "django-cas-ng", specifier = "==5.0.1" },
|
{ name = "django-cas-ng", specifier = "==5.0.1" },
|
||||||
@@ -845,9 +795,6 @@ requires-dist = [
|
|||||||
{ name = "dramatiq", specifier = "==1.17.0" },
|
{ name = "dramatiq", specifier = "==1.17.0" },
|
||||||
{ name = "entrypoints", specifier = "==0.4" },
|
{ name = "entrypoints", specifier = "==0.4" },
|
||||||
{ name = "envelopes", specifier = "==0.4" },
|
{ name = "envelopes", specifier = "==0.4" },
|
||||||
{ name = "flake8", specifier = "==7.0.0" },
|
|
||||||
{ name = "flake8-coding", specifier = "==1.3.2" },
|
|
||||||
{ name = "flake8-quotes", specifier = "==3.3.2" },
|
|
||||||
{ name = "gunicorn", specifier = "==22.0.0" },
|
{ name = "gunicorn", specifier = "==22.0.0" },
|
||||||
{ name = "jsonfield", specifier = "==3.1.0" },
|
{ name = "jsonfield", specifier = "==3.1.0" },
|
||||||
{ name = "openai", specifier = ">=1.108.1" },
|
{ name = "openai", specifier = ">=1.108.1" },
|
||||||
@@ -861,6 +808,9 @@ requires-dist = [
|
|||||||
{ name = "xlsxwriter", specifier = "==3.2.0" },
|
{ name = "xlsxwriter", specifier = "==3.2.0" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[package.metadata.requires-dev]
|
||||||
|
dev = [{ name = "ruff", specifier = ">=0.15.11" }]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openai"
|
name = "openai"
|
||||||
version = "2.14.0"
|
version = "2.14.0"
|
||||||
@@ -995,15 +945,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" },
|
{ url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pycodestyle"
|
|
||||||
version = "2.11.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/34/8f/fa09ae2acc737b9507b5734a9aec9a2b35fa73409982f57db1b42f8c3c65/pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f", size = 38974, upload-time = "2023-10-12T23:39:39.762Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b1/90/a998c550d0ddd07e38605bb5c455d00fcc177a800ff9cc3dafdcb3dd7b56/pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67", size = 31132, upload-time = "2023-10-12T23:39:38.242Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pycparser"
|
name = "pycparser"
|
||||||
version = "2.23"
|
version = "2.23"
|
||||||
@@ -1099,15 +1040,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" },
|
{ url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pyflakes"
|
|
||||||
version = "3.2.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/57/f9/669d8c9c86613c9d568757c7f5824bd3197d7b1c6c27553bc5618a27cce2/pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f", size = 63788, upload-time = "2024-01-05T00:28:47.703Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d4/d7/f1b7db88d8e4417c5d47adad627a93547f44bdc9028372dbd2313f34a855/pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a", size = 62725, upload-time = "2024-01-05T00:28:45.903Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyopenssl"
|
name = "pyopenssl"
|
||||||
version = "25.3.0"
|
version = "25.3.0"
|
||||||
@@ -1191,6 +1123,31 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ruff"
|
||||||
|
version = "0.15.11"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e4/8d/192f3d7103816158dfd5ea50d098ef2aec19194e6cbccd4b3485bdb2eb2d/ruff-0.15.11.tar.gz", hash = "sha256:f092b21708bf0e7437ce9ada249dfe688ff9a0954fc94abab05dcea7dcd29c33", size = 4637264, upload-time = "2026-04-16T18:46:26.58Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/1e/6aca3427f751295ab011828e15e9bf452200ac74484f1db4be0197b8170b/ruff-0.15.11-py3-none-linux_armv6l.whl", hash = "sha256:e927cfff503135c558eb581a0c9792264aae9507904eb27809cdcff2f2c847b7", size = 10607943, upload-time = "2026-04-16T18:46:05.967Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e7/26/1341c262e74f36d4e84f3d6f4df0ac68cd53331a66bfc5080daa17c84c0b/ruff-0.15.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7a1b5b2938d8f890b76084d4fa843604d787a912541eae85fd7e233398bbb73e", size = 10988592, upload-time = "2026-04-16T18:46:00.742Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/03/71/850b1d6ffa9564fbb6740429bad53df1094082fe515c8c1e74b6d8d05f18/ruff-0.15.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d4176f3d194afbdaee6e41b9ccb1a2c287dba8700047df474abfbe773825d1cb", size = 10338501, upload-time = "2026-04-16T18:46:03.723Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f2/11/cc1284d3e298c45a817a6aadb6c3e1d70b45c9b36d8d9cce3387b495a03a/ruff-0.15.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b17c886fb88203ced3afe7f14e8d5ae96e9d2f4ccc0ee66aa19f2c2675a27e4", size = 10670693, upload-time = "2026-04-16T18:46:41.941Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/9e/f8288b034ab72b371513c13f9a41d9ba3effac54e24bfb467b007daee2ca/ruff-0.15.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49fafa220220afe7758a487b048de4c8f9f767f37dfefad46b9dd06759d003eb", size = 10416177, upload-time = "2026-04-16T18:46:21.717Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/85/71/504d79abfd3d92532ba6bbe3d1c19fada03e494332a59e37c7c2dabae427/ruff-0.15.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2ab8427e74a00d93b8bda1307b1e60970d40f304af38bccb218e056c220120d", size = 11221886, upload-time = "2026-04-16T18:46:15.086Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/43/5a/947e6ab7a5ad603d65b474be15a4cbc6d29832db5d762cd142e4e3a74164/ruff-0.15.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:195072c0c8e1fc8f940652073df082e37a5d9cb43b4ab1e4d0566ab8977a13b7", size = 12075183, upload-time = "2026-04-16T18:46:07.944Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/a1/0b7bb6268775fdd3a0818aee8efd8f5b4e231d24dd4d528ced2534023182/ruff-0.15.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a0996d486af3920dec930a2e7daed4847dfc12649b537a9335585ada163e9e", size = 11516575, upload-time = "2026-04-16T18:46:31.687Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/30/c3/bb5168fc4d233cc06e95f482770d0f3c87945a0cd9f614b90ea8dc2f2833/ruff-0.15.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bef2cb556d509259f1fe440bb9cd33c756222cf0a7afe90d15edf0866702431", size = 11306537, upload-time = "2026-04-16T18:46:36.988Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/92/4cfae6441f3967317946f3b788136eecf093729b94d6561f963ed810c82e/ruff-0.15.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:030d921a836d7d4a12cf6e8d984a88b66094ccb0e0f17ddd55067c331191bf19", size = 11296813, upload-time = "2026-04-16T18:46:24.182Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/43/26/972784c5dde8313acde8ac71ba8ac65475b85db4a2352a76c9934361f9bc/ruff-0.15.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e783b599b4577788dbbb66b9addcef87e9a8832f4ce0c19e34bf55543a2f890", size = 10633136, upload-time = "2026-04-16T18:46:39.802Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5b/53/3985a4f185020c2f367f2e08a103032e12564829742a1b417980ce1514a0/ruff-0.15.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ae90592246625ba4a34349d68ec28d4400d75182b71baa196ddb9f82db025ef5", size = 10424701, upload-time = "2026-04-16T18:46:10.381Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d3/57/bf0dfb32241b56c83bb663a826133da4bf17f682ba8c096973065f6e6a68/ruff-0.15.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1f111d62e3c983ed20e0ca2e800f8d77433a5b1161947df99a5c2a3fb60514f0", size = 10873887, upload-time = "2026-04-16T18:46:29.157Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/05/e48076b2a57dc33ee8c7a957296f97c744ca891a8ffb4ffb1aaa3b3f517d/ruff-0.15.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:06f483d6646f59eaffba9ae30956370d3a886625f511a3108994000480621d1c", size = 11404316, upload-time = "2026-04-16T18:46:19.462Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/27/0195d15fe7a897cbcba0904792c4b7c9fdd958456c3a17d2ea6093716a9a/ruff-0.15.11-py3-none-win32.whl", hash = "sha256:476a2aa56b7da0b73a3ee80b6b2f0e19cce544245479adde7baa65466664d5f3", size = 10655535, upload-time = "2026-04-16T18:46:12.47Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3a/5e/c927b325bd4c1d3620211a4b96f47864633199feed60fa936025ab27e090/ruff-0.15.11-py3-none-win_amd64.whl", hash = "sha256:8b6756d88d7e234fb0c98c91511aae3cd519d5e3ed271cae31b20f39cb2a12a3", size = 11779692, upload-time = "2026-04-16T18:46:17.268Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/63/b6/aeadee5443e49baa2facd51131159fd6301cc4ccfc1541e4df7b021c37dd/ruff-0.15.11-py3-none-win_arm64.whl", hash = "sha256:063fed18cc1bbe0ee7393957284a6fe8b588c6a406a285af3ee3f46da2391ee4", size = 11032614, upload-time = "2026-04-16T18:46:34.487Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "service-identity"
|
name = "service-identity"
|
||||||
version = "24.2.0"
|
version = "24.2.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user