diff --git a/.flake8 b/.flake8
deleted file mode 100644
index 0fc0318..0000000
--- a/.flake8
+++ /dev/null
@@ -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
diff --git a/account/decorators.py b/account/decorators.py
index 0b6f236..7102b17 100644
--- a/account/decorators.py
+++ b/account/decorators.py
@@ -2,10 +2,11 @@ import functools
import hashlib
import time
+from contest.models import Contest, ContestRuleType, ContestStatus, ContestType
from problem.models import Problem
-from contest.models import Contest, ContestType, ContestStatus, ContestRuleType
-from utils.api import JSONResponse, APIError
+from utils.api import APIError, JSONResponse
from utils.constants import CONTEST_PASSWORD_SESSION_KEY
+
from .models import ProblemPermission
diff --git a/account/middleware.py b/account/middleware.py
index 91eed27..3fc218c 100644
--- a/account/middleware.py
+++ b/account/middleware.py
@@ -1,10 +1,10 @@
from django.conf import settings
from django.db import connection
-from django.utils.timezone import now
from django.utils.deprecation import MiddlewareMixin
+from django.utils.timezone import now
-from utils.api import JSONResponse
from account.models import User
+from utils.api import JSONResponse
class APITokenAuthMiddleware(MiddlewareMixin):
diff --git a/account/models.py b/account/models.py
index 7a2c8eb..50ed357 100644
--- a/account/models.py
+++ b/account/models.py
@@ -1,6 +1,7 @@
-from django.contrib.auth.models import AbstractBaseUser
from django.conf import settings
+from django.contrib.auth.models import AbstractBaseUser
from django.db import models
+
from utils.models import JSONField
diff --git a/account/serializers.py b/account/serializers.py
index 228c6ca..2c8b513 100644
--- a/account/serializers.py
+++ b/account/serializers.py
@@ -1,6 +1,6 @@
from django import forms
-from utils.api import serializers, UsernameSerializer
+from utils.api import UsernameSerializer, serializers
from .models import AdminType, ProblemPermission, User, UserProfile
diff --git a/account/tasks.py b/account/tasks.py
index 5135999..9f8f25c 100644
--- a/account/tasks.py
+++ b/account/tasks.py
@@ -1,8 +1,9 @@
import logging
+
import dramatiq
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__)
diff --git a/account/tests.py b/account/tests.py
deleted file mode 100644
index c727f03..0000000
--- a/account/tests.py
+++ /dev/null
@@ -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)
diff --git a/account/urls/admin.py b/account/urls/admin.py
index 7390b4a..34ed640 100644
--- a/account/urls/admin.py
+++ b/account/urls/admin.py
@@ -1,6 +1,6 @@
from django.urls import path
-from ..views.admin import UserAdminAPI, GenerateUserAPI, ResetUserPasswordAPI
+from ..views.admin import GenerateUserAPI, ResetUserPasswordAPI, UserAdminAPI
urlpatterns = [
path("user", UserAdminAPI.as_view()),
diff --git a/account/urls/oj.py b/account/urls/oj.py
index 61d9db2..b5f6ac4 100644
--- a/account/urls/oj.py
+++ b/account/urls/oj.py
@@ -1,30 +1,30 @@
from django.urls import path
+from utils.captcha.views import CaptchaAPIView
+
from ..views.oj import (
+ SSOAPI,
ApplyResetPasswordAPI,
- ResetPasswordAPI,
- UserChangePasswordAPI,
+ AvatarUploadAPI,
+ CheckTFARequiredAPI,
Metrics,
- UserRegisterAPI,
+ OpenAPIAppkeyAPI,
+ ProfileProblemDisplayIDRefreshAPI,
+ ResetPasswordAPI,
+ SessionManagementAPI,
+ TwoFactorAuthAPI,
+ UserActivityRankAPI,
UserChangeEmailAPI,
+ UserChangePasswordAPI,
UserLoginAPI,
UserLogoutAPI,
UsernameOrEmailCheck,
- AvatarUploadAPI,
- TwoFactorAuthAPI,
+ UserProblemRankAPI,
UserProfileAPI,
UserRankAPI,
- UserActivityRankAPI,
- UserProblemRankAPI,
- CheckTFARequiredAPI,
- SessionManagementAPI,
- ProfileProblemDisplayIDRefreshAPI,
- OpenAPIAppkeyAPI,
- SSOAPI,
+ UserRegisterAPI,
)
-from utils.captcha.views import CaptchaAPIView
-
urlpatterns = [
path("login", UserLoginAPI.as_view()),
path("logout", UserLogoutAPI.as_view()),
diff --git a/account/views/admin.py b/account/views/admin.py
index d0e666b..95140d4 100644
--- a/account/views/admin.py
+++ b/account/views/admin.py
@@ -1,11 +1,11 @@
import os
import re
-import xlsxwriter
-from django.db import transaction, IntegrityError
-from django.db.models import Q, F
-from django.http import HttpResponse
+import xlsxwriter
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 submission.models import Submission
@@ -16,10 +16,10 @@ from ..decorators import super_admin_required
from ..models import AdminType, ProblemPermission, User, UserProfile
from ..serializers import (
EditUserSerializer,
- UserAdminSerializer,
GenerateUserSerializer,
+ ImportUserSerializer,
+ UserAdminSerializer,
)
-from ..serializers import ImportUserSerializer
# ks251XXX 或者 ks2510XX 返回 251 或者 2510
diff --git a/account/views/oj.py b/account/views/oj.py
index d53a9f9..dbe3727 100644
--- a/account/views/oj.py
+++ b/account/views/oj.py
@@ -2,44 +2,42 @@ import os
from datetime import timedelta
from importlib import import_module
+import qrcode
from django.conf import settings
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.utils import timezone
from django.utils.decorators import method_decorator
from django.utils.timezone import now
-from django.views.decorators.csrf import ensure_csrf_cookie, csrf_exempt
-from django.db.models import Count, Q
-from django.utils import timezone
-
-import qrcode
+from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
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 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.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 ..models import User, UserProfile, AdminType
+from ..models import AdminType, User, UserProfile
from ..serializers import (
ApplyResetPasswordSerializer,
- ResetPasswordSerializer,
- UserChangePasswordSerializer,
- UserLoginSerializer,
- UserRegisterSerializer,
- UsernameOrEmailCheckSerializer,
- RankInfoSerializer,
- UserChangeEmailSerializer,
- SSOSerializer,
-)
-from ..serializers import (
- TwoFactorAuthCodeSerializer,
- UserProfileSerializer,
EditUserProfileSerializer,
ImageUploadForm,
+ RankInfoSerializer,
+ ResetPasswordSerializer,
+ SSOSerializer,
+ TwoFactorAuthCodeSerializer,
+ UserChangeEmailSerializer,
+ UserChangePasswordSerializer,
+ UserLoginSerializer,
+ UsernameOrEmailCheckSerializer,
+ UserProfileSerializer,
+ UserRegisterSerializer,
)
from ..tasks import send_email_async
diff --git a/ai/views/oj.py b/ai/views/oj.py
index 60f7666..a3fae6a 100644
--- a/ai/views/oj.py
+++ b/ai/views/oj.py
@@ -1,27 +1,26 @@
-from collections import defaultdict
-from datetime import datetime, timedelta
import hashlib
import json
+from collections import defaultdict
+from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
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.http import StreamingHttpResponse
from django.utils import timezone
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.openai import get_ai_client
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
DIFFICULTY_MAP = {"Low": "简单", "Mid": "中等", "High": "困难"}
DEFAULT_CLASS_SIZE = 45
@@ -598,7 +597,11 @@ class AIAnalysisAPI(APIView):
client = get_ai_client()
- system_prompt = "你是一个风趣的编程老师,学生使用判题狗平台进行编程练习。请根据学生提供的详细数据和每周数据,给出用户的学习建议,最后写一句鼓励学生的话。请使用 markdown 格式输出,不要在代码块中输出。"
+ system_prompt = (
+ "你是一个风趣的编程老师,学生使用判题狗平台进行编程练习。"
+ "请根据学生提供的详细数据和每周数据,给出用户的学习建议,最后写一句鼓励学生的话。"
+ "请使用 markdown 格式输出,不要在代码块中输出。"
+ )
user_prompt = f"这段时间内的详细数据: {details}\n(其中部分字段含义是 flowcharts:流程图的提交,solved:代码的提交)\n每周或每月的数据: {duration}"
def on_complete(full_text):
diff --git a/announcement/tests.py b/announcement/tests.py
deleted file mode 100644
index 98caa1c..0000000
--- a/announcement/tests.py
+++ /dev/null
@@ -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)
diff --git a/announcement/views/admin.py b/announcement/views/admin.py
index 938ac3c..32bbee6 100644
--- a/announcement/views/admin.py
+++ b/announcement/views/admin.py
@@ -1,12 +1,11 @@
from account.decorators import super_admin_required
-from utils.api import APIView, validate_serializer
-
from announcement.models import Announcement
from announcement.serializers import (
AnnouncementSerializer,
CreateAnnouncementSerializer,
EditAnnouncementSerializer,
)
+from utils.api import APIView, validate_serializer
class AnnouncementAdminAPI(APIView):
diff --git a/announcement/views/oj.py b/announcement/views/oj.py
index e70c0dd..a987c90 100644
--- a/announcement/views/oj.py
+++ b/announcement/views/oj.py
@@ -1,7 +1,6 @@
-from utils.api import APIView
-
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):
diff --git a/class_pk/admin.py b/class_pk/admin.py
index ea5d68b..c3779ed 100644
--- a/class_pk/admin.py
+++ b/class_pk/admin.py
@@ -1,3 +1,2 @@
-from django.contrib import admin
# Register your models here.
diff --git a/class_pk/models.py b/class_pk/models.py
index fcd29fa..5be8fa6 100644
--- a/class_pk/models.py
+++ b/class_pk/models.py
@@ -1,4 +1,3 @@
-from django.db import models
# 如果需要存储班级PK历史记录,可以在这里定义模型
# 目前暂时不需要,因为都是实时计算
diff --git a/class_pk/urls/oj.py b/class_pk/urls/oj.py
index a0410c3..e0de529 100644
--- a/class_pk/urls/oj.py
+++ b/class_pk/urls/oj.py
@@ -1,6 +1,6 @@
from django.urls import path
-from ..views.oj import ClassRankAPI, UserClassRankAPI, ClassPKAPI
+from ..views.oj import ClassPKAPI, ClassRankAPI, UserClassRankAPI
urlpatterns = [
path("class_rank", ClassRankAPI.as_view()),
diff --git a/class_pk/views/oj.py b/class_pk/views/oj.py
index de12117..4f9114e 100644
--- a/class_pk/views/oj.py
+++ b/class_pk/views/oj.py
@@ -1,12 +1,13 @@
-import re
import statistics
from datetime import datetime
-from django.db.models import Sum, Avg
+
+from django.db.models import Avg, Sum
from django.utils import timezone
-from utils.api import APIView
+
from account.decorators import login_required
-from account.models import User, UserProfile, AdminType
-from submission.models import Submission, JudgeStatus
+from account.models import AdminType, User, UserProfile
+from submission.models import JudgeStatus, Submission
+from utils.api import APIView
class ClassRankAPI(APIView):
diff --git a/comment/urls/admin.py b/comment/urls/admin.py
index c0716e9..34139ba 100644
--- a/comment/urls/admin.py
+++ b/comment/urls/admin.py
@@ -2,7 +2,6 @@ from django.urls import path
from ..views.admin import CommentAPI
-
urlpatterns = [
path("comment", CommentAPI.as_view()),
]
diff --git a/comment/urls/oj.py b/comment/urls/oj.py
index c30631f..0282308 100644
--- a/comment/urls/oj.py
+++ b/comment/urls/oj.py
@@ -2,7 +2,6 @@ from django.urls import path
from ..views.oj import CommentAPI, CommentStatisticsAPI
-
urlpatterns = [
path("comment", CommentAPI.as_view()),
path("comment/statistics", CommentStatisticsAPI.as_view()),
diff --git a/comment/views/admin.py b/comment/views/admin.py
index 23d8249..74806c1 100644
--- a/comment/views/admin.py
+++ b/comment/views/admin.py
@@ -1,8 +1,8 @@
from account.decorators import super_admin_required
+from comment.models import Comment
from comment.serializers import CommentListSerializer
from problem.models import Problem
from utils.api import APIView
-from comment.models import Comment
class CommentAPI(APIView):
diff --git a/comment/views/oj.py b/comment/views/oj.py
index deb968c..f361194 100644
--- a/comment/views/oj.py
+++ b/comment/views/oj.py
@@ -1,14 +1,15 @@
from django.core.cache import cache
from django.db.models import Avg, Count
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 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 comment.serializers import CreateCommentSerializer, CommentSerializer
-from submission.models import Submission, JudgeStatus
+from utils.constants import CacheKey
class CommentAPI(APIView):
diff --git a/conf/consumers.py b/conf/consumers.py
index 2e14406..602ab16 100644
--- a/conf/consumers.py
+++ b/conf/consumers.py
@@ -3,6 +3,7 @@ WebSocket consumers for configuration updates
"""
import json
import logging
+
from channels.generic.websocket import AsyncWebsocketConsumer
logger = logging.getLogger(__name__)
diff --git a/conf/tests.py b/conf/tests.py
deleted file mode 100644
index fbb4022..0000000
--- a/conf/tests.py
+++ /dev/null
@@ -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": "test",
- "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": "
",
- "allow_register": True, "submission_list_show_all": False}
- resp = self.client.post(url, data=data)
- self.assertSuccess(resp)
- self.assertEqual(SysOptions.website_footer, '
')
-
- 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)
diff --git a/conf/urls/admin.py b/conf/urls/admin.py
index f33c976..ab268e6 100644
--- a/conf/urls/admin.py
+++ b/conf/urls/admin.py
@@ -2,13 +2,13 @@ from django.urls import path
from ..views import (
SMTPAPI,
- JudgeServerAPI,
- WebsiteConfigAPI,
- TestCasePruneAPI,
- SMTPTestAPI,
- ReleaseNotesAPI,
DashboardInfoAPI,
+ JudgeServerAPI,
RandomUsernameAPI,
+ ReleaseNotesAPI,
+ SMTPTestAPI,
+ TestCasePruneAPI,
+ WebsiteConfigAPI,
)
urlpatterns = [
diff --git a/conf/urls/oj.py b/conf/urls/oj.py
index 73a85cf..c3438c8 100644
--- a/conf/urls/oj.py
+++ b/conf/urls/oj.py
@@ -1,11 +1,11 @@
from django.urls import path
from ..views import (
+ ClassUsernamesAPI,
HitokotoAPI,
JudgeServerHeartbeatAPI,
LanguagesAPI,
WebsiteConfigAPI,
- ClassUsernamesAPI,
)
urlpatterns = [
diff --git a/conf/views.py b/conf/views.py
index 42c63b2..abe268d 100644
--- a/conf/views.py
+++ b/conf/views.py
@@ -22,18 +22,19 @@ from problem.models import Problem
from submission.models import Submission
from utils.api import APIView, CSRFExemptAPIView, validate_serializer
from utils.cache import JsonDataLoader
-from utils.shortcuts import send_email, get_env
-from utils.xss_filter import XSSHtml
+from utils.shortcuts import get_env, send_email
from utils.websocket import push_config_update
+from utils.xss_filter import XSSHtml
+
from .models import JudgeServer
from .serializers import (
CreateEditWebsiteConfigSerializer,
CreateSMTPConfigSerializer,
+ EditJudgeServerSerializer,
EditSMTPConfigSerializer,
JudgeServerHeartbeatSerializer,
JudgeServerSerializer,
TestSMTPConfigSerializer,
- EditJudgeServerSerializer,
)
diff --git a/contest/serializers.py b/contest/serializers.py
index bdca7ed..f96653e 100644
--- a/contest/serializers.py
+++ b/contest/serializers.py
@@ -1,7 +1,6 @@
from utils.api import UsernameSerializer, serializers
-from .models import Contest, ContestAnnouncement, ContestRuleType
-from .models import ACMContestRank, OIContestRank
+from .models import ACMContestRank, Contest, ContestAnnouncement, ContestRuleType, OIContestRank
class CreateConetestSeriaizer(serializers.Serializer):
diff --git a/contest/tests.py b/contest/tests.py
deleted file mode 100644
index a92e09a..0000000
--- a/contest/tests.py
+++ /dev/null
@@ -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)
diff --git a/contest/urls/admin.py b/contest/urls/admin.py
index 20ae6cb..d83c772 100644
--- a/contest/urls/admin.py
+++ b/contest/urls/admin.py
@@ -1,6 +1,6 @@
from django.urls import path
-from ..views.admin import ContestAnnouncementAPI, ContestAPI, ACMContestHelper, DownloadContestSubmissions
+from ..views.admin import ACMContestHelper, ContestAnnouncementAPI, ContestAPI, DownloadContestSubmissions
urlpatterns = [
path("contest", ContestAPI.as_view()),
diff --git a/contest/urls/oj.py b/contest/urls/oj.py
index 7235f4f..7377019 100644
--- a/contest/urls/oj.py
+++ b/contest/urls/oj.py
@@ -1,9 +1,6 @@
from django.urls import path
-from ..views.oj import ContestAnnouncementListAPI
-from ..views.oj import ContestPasswordVerifyAPI, ContestAccessAPI
-from ..views.oj import ContestListAPI, ContestAPI
-from ..views.oj import ContestRankAPI
+from ..views.oj import ContestAccessAPI, ContestAnnouncementListAPI, ContestAPI, ContestListAPI, ContestPasswordVerifyAPI, ContestRankAPI
urlpatterns = [
path("contests", ContestListAPI.as_view()),
diff --git a/contest/views/admin.py b/contest/views/admin.py
index 6cdf49a..e2e4989 100644
--- a/contest/views/admin.py
+++ b/contest/views/admin.py
@@ -6,25 +6,25 @@ from ipaddress import ip_network
import dateutil.parser
from django.http import FileResponse
-from problem.models import Problem
-
from account.decorators import super_admin_required
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.cache import cache
from utils.constants import CacheKey
from utils.shortcuts import rand_str
from utils.tasks import delete_files
-from ..models import Contest, ContestAnnouncement, ACMContestRank
+
+from ..models import ACMContestRank, Contest, ContestAnnouncement
from ..serializers import (
- ContestAnnouncementSerializer,
+ ACMContesHelperSerializer,
ContestAdminSerializer,
+ ContestAnnouncementSerializer,
CreateConetestSeriaizer,
CreateContestAnnouncementSerializer,
EditConetestSeriaizer,
EditContestAnnouncementSerializer,
- ACMContesHelperSerializer,
)
diff --git a/contest/views/oj.py b/contest/views/oj.py
index d388f2a..0cfa551 100644
--- a/contest/views/oj.py
+++ b/contest/views/oj.py
@@ -1,26 +1,23 @@
import io
import xlsxwriter
+from django.core.cache import cache
from django.http import HttpResponse
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 utils.api import APIView, validate_serializer
-from utils.constants import CacheKey, CONTEST_PASSWORD_SESSION_KEY
-from utils.shortcuts import datetime2str, check_is_id
-from account.models import AdminType
-from account.decorators import (
- login_required,
- check_contest_permission,
- check_contest_password,
-)
+from utils.constants import CONTEST_PASSWORD_SESSION_KEY, CacheKey, ContestRuleType, ContestStatus
+from utils.shortcuts import check_is_id, datetime2str
-from utils.constants import ContestRuleType, ContestStatus
-from ..models import ContestAnnouncement, Contest, OIContestRank, ACMContestRank
-from ..serializers import ContestAnnouncementSerializer
-from ..serializers import ContestSerializer, ContestPasswordVerifySerializer
-from ..serializers import OIContestRankSerializer, ACMContestRankSerializer
+from ..models import ACMContestRank, Contest, ContestAnnouncement, OIContestRank
+from ..serializers import ACMContestRankSerializer, ContestAnnouncementSerializer, ContestPasswordVerifySerializer, ContestSerializer, OIContestRankSerializer
class ContestAnnouncementListAPI(APIView):
diff --git a/dev.py b/dev.py
index 0afcca9..fc58265 100644
--- a/dev.py
+++ b/dev.py
@@ -6,13 +6,13 @@ WebSocket 开发服务器启动脚本
"""
import os
-import sys
-import subprocess
import platform
import signal
+import subprocess
+import sys
+import time
from pathlib import Path
from threading import Thread
-import time
def main():
diff --git a/flowchart/consumers.py b/flowchart/consumers.py
index 330e511..315197f 100644
--- a/flowchart/consumers.py
+++ b/flowchart/consumers.py
@@ -3,6 +3,7 @@ WebSocket consumers for flowchart evaluation updates
"""
import json
import logging
+
from channels.generic.websocket import AsyncWebsocketConsumer
logger = logging.getLogger(__name__)
diff --git a/flowchart/models.py b/flowchart/models.py
index 81ce65e..514d47d 100644
--- a/flowchart/models.py
+++ b/flowchart/models.py
@@ -1,7 +1,8 @@
-from django.db import models
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 utils.shortcuts import rand_str
User = get_user_model()
diff --git a/flowchart/serializers.py b/flowchart/serializers.py
index e240d75..96ee9b6 100644
--- a/flowchart/serializers.py
+++ b/flowchart/serializers.py
@@ -1,4 +1,5 @@
from rest_framework import serializers
+
from .models import FlowchartSubmission
diff --git a/flowchart/tasks.py b/flowchart/tasks.py
index 8a3939e..09ad4eb 100644
--- a/flowchart/tasks.py
+++ b/flowchart/tasks.py
@@ -1,12 +1,16 @@
-import dramatiq
import json
import time
+
+import dramatiq
from django.db import transaction
from django.utils import timezone
+
from utils.openai import get_ai_client
from utils.shortcuts import DRAMATIQ_WORKER_ARGS
+
from .models import FlowchartSubmission, FlowchartSubmissionStatus
+
@dramatiq.actor(**DRAMATIQ_WORKER_ARGS(max_retries=3))
def evaluate_flowchart_task(submission_id):
"""异步AI评分任务"""
diff --git a/flowchart/urls/oj.py b/flowchart/urls/oj.py
index 2148a0e..c77b9e5 100644
--- a/flowchart/urls/oj.py
+++ b/flowchart/urls/oj.py
@@ -1,10 +1,11 @@
from django.urls import path
+
from ..views.oj import (
FlowchartSubmissionAPI,
- FlowchartSubmissionListAPI,
- FlowchartSubmissionRetryAPI,
FlowchartSubmissionCurrentAPI,
FlowchartSubmissionDetailAPI,
+ FlowchartSubmissionListAPI,
+ FlowchartSubmissionRetryAPI,
)
urlpatterns = [
diff --git a/flowchart/views.py b/flowchart/views.py
index 91ea44a..b8e4ee0 100644
--- a/flowchart/views.py
+++ b/flowchart/views.py
@@ -1,3 +1,2 @@
-from django.shortcuts import render
# Create your views here.
diff --git a/flowchart/views/oj.py b/flowchart/views/oj.py
index 9100c3c..c8e0566 100644
--- a/flowchart/views/oj.py
+++ b/flowchart/views/oj.py
@@ -1,13 +1,13 @@
-from utils.api import APIView
from account.decorators import login_required
from flowchart.models import FlowchartSubmission, FlowchartSubmissionStatus
from flowchart.serializers import (
CreateFlowchartSubmissionSerializer,
- FlowchartSubmissionSerializer,
FlowchartSubmissionListSerializer,
+ FlowchartSubmissionSerializer,
)
from flowchart.tasks import evaluate_flowchart_task
from problem.models import Problem
+from utils.api import APIView
class FlowchartSubmissionAPI(APIView):
diff --git a/fps/parser.py b/fps/parser.py
index 63d3559..02cb129 100644
--- a/fps/parser.py
+++ b/fps/parser.py
@@ -1,11 +1,11 @@
#!/usr/bin/env python3
import base64
import copy
-import random
-import string
import hashlib
import json
import os
+import random
+import string
import xml.etree.ElementTree as ET
diff --git a/judge/dispatcher.py b/judge/dispatcher.py
index db15832..312ae3b 100644
--- a/judge/dispatcher.py
+++ b/judge/dispatcher.py
@@ -4,12 +4,12 @@ import logging
from urllib.parse import urljoin
import requests
-from django.db import transaction, IntegrityError
+from django.db import IntegrityError, transaction
from django.db.models import F
from account.models import User
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 problem.models import Problem, ProblemRuleType
from problem.utils import parse_problem_template
diff --git a/judge/languages.py b/judge/languages.py
index 0324b13..9627d0a 100644
--- a/judge/languages.py
+++ b/judge/languages.py
@@ -1,6 +1,5 @@
from problem.models import ProblemIOMode
-
default_env = ["LANG=en_US.UTF-8", "LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8"]
_c_lang_config = {
diff --git a/judge/tasks.py b/judge/tasks.py
index 8a1794a..bd13305 100644
--- a/judge/tasks.py
+++ b/judge/tasks.py
@@ -1,8 +1,8 @@
import dramatiq
from account.models import User
-from submission.models import Submission
from judge.dispatcher import JudgeDispatcher
+from submission.models import Submission
from utils.shortcuts import DRAMATIQ_WORKER_ARGS
diff --git a/manage.py b/manage.py
index 16b46a5..46a2483 100755
--- a/manage.py
+++ b/manage.py
@@ -5,8 +5,8 @@ import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "oj.settings")
- from django.core.management import execute_from_command_line
import django
+ from django.core.management import execute_from_command_line
sys.stdout.write("Django VERSION " + str(django.VERSION) + "\n")
execute_from_command_line(sys.argv)
diff --git a/message/models.py b/message/models.py
index 7f90350..a93b0ae 100644
--- a/message/models.py
+++ b/message/models.py
@@ -1,4 +1,5 @@
from django.db import models
+
from account.models import User
from submission.models import Submission
from utils.models import RichTextField
diff --git a/message/serializers.py b/message/serializers.py
index 93b8bc2..c972c6c 100644
--- a/message/serializers.py
+++ b/message/serializers.py
@@ -1,7 +1,7 @@
from submission.serializers import SubmissionSafeModelSerializer
from utils.api import UsernameSerializer, serializers
-from .models import Message
+from .models import Message
class MessageSerializer(serializers.ModelSerializer):
diff --git a/message/views/oj.py b/message/views/oj.py
index 72e6711..e3d200d 100644
--- a/message/views/oj.py
+++ b/message/views/oj.py
@@ -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 message.models import Message
from message.serializers import CreateMessageSerializer, MessageSerializer
from submission.models import Submission
from utils.api import APIView
-
-from message.models import Message
-
from utils.api.api import validate_serializer
diff --git a/oj/asgi.py b/oj/asgi.py
index 69bc4f5..2c221fc 100644
--- a/oj/asgi.py
+++ b/oj/asgi.py
@@ -8,9 +8,10 @@ https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
"""
import os
-from django.core.asgi import get_asgi_application
-from channels.routing import ProtocolTypeRouter, URLRouter
+
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")
@@ -19,7 +20,7 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "oj.settings")
django_asgi_app = get_asgi_application()
# Import routing after Django setup
-from oj.routing import websocket_urlpatterns
+from oj.routing import websocket_urlpatterns # noqa: E402
application = ProtocolTypeRouter(
{
diff --git a/oj/routing.py b/oj/routing.py
index 96cefd6..466ac0c 100644
--- a/oj/routing.py
+++ b/oj/routing.py
@@ -3,9 +3,10 @@ WebSocket URL Configuration for oj project.
"""
from django.urls import path
-from submission.consumers import SubmissionConsumer
+
from conf.consumers import ConfigConsumer
from flowchart.consumers import FlowchartConsumer
+from submission.consumers import SubmissionConsumer
websocket_urlpatterns = [
path("ws/submission/", SubmissionConsumer.as_asgi()),
diff --git a/options/models.py b/options/models.py
index 30db2f6..0adc46f 100644
--- a/options/models.py
+++ b/options/models.py
@@ -1,4 +1,5 @@
from django.db import models
+
from utils.models import JSONField
diff --git a/options/options.py b/options/options.py
index e5373ef..b3c5a05 100644
--- a/options/options.py
+++ b/options/options.py
@@ -3,10 +3,11 @@ import os
import threading
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 utils.shortcuts import rand_str
+
from .models import SysOptions as SysOptionsModel
diff --git a/options/tests.py b/options/tests.py
deleted file mode 100644
index a39b155..0000000
--- a/options/tests.py
+++ /dev/null
@@ -1 +0,0 @@
-# Create your tests here.
diff --git a/problem/models.py b/problem/models.py
index bd1e3dd..f32b336 100644
--- a/problem/models.py
+++ b/problem/models.py
@@ -2,8 +2,8 @@ from django.db import models
from account.models import User
from contest.models import Contest
-from utils.models import RichTextField
from utils.constants import Choices
+from utils.models import RichTextField
class ProblemTag(models.Model):
diff --git a/problem/serializers.py b/problem/serializers.py
index 3a70c00..38073c8 100644
--- a/problem/serializers.py
+++ b/problem/serializers.py
@@ -5,11 +5,11 @@ from django import forms
from utils.api import UsernameSerializer, serializers
from utils.constants import Difficulty
from utils.serializers import (
- LanguageNameMultiChoiceField,
LanguageNameChoiceField,
+ LanguageNameMultiChoiceField,
)
-from .models import Problem, ProblemRuleType, ProblemTag, ProblemIOMode
+from .models import Problem, ProblemIOMode, ProblemRuleType, ProblemTag
from .utils import parse_problem_template
diff --git a/problem/urls/admin.py b/problem/urls/admin.py
index d7953a7..20e735d 100644
--- a/problem/urls/admin.py
+++ b/problem/urls/admin.py
@@ -1,14 +1,14 @@
from django.urls import path
from ..views.admin import (
+ AddContestProblemAPI,
ContestProblemAPI,
+ MakeContestProblemPublicAPIView,
ProblemAPI,
ProblemFlowchartAIGen,
+ ProblemVisibleAPI,
StuckProblemsAPI,
TestCaseAPI,
- MakeContestProblemPublicAPIView,
- AddContestProblemAPI,
- ProblemVisibleAPI,
)
urlpatterns = [
diff --git a/problem/urls/oj.py b/problem/urls/oj.py
index b91d861..7e11893 100644
--- a/problem/urls/oj.py
+++ b/problem/urls/oj.py
@@ -1,12 +1,12 @@
from django.urls import path
from ..views.oj import (
- ProblemSolvedPeopleCount,
- ProblemTagAPI,
- ProblemAPI,
ContestProblemAPI,
PickOneAPI,
+ ProblemAPI,
ProblemAuthorAPI,
+ ProblemSolvedPeopleCount,
+ ProblemTagAPI,
SimilarProblemAPI,
)
diff --git a/problem/utils.py b/problem/utils.py
index c530263..8769e8c 100644
--- a/problem/utils.py
+++ b/problem/utils.py
@@ -1,7 +1,6 @@
import re
from functools import lru_cache
-
TEMPLATE_BASE = """//PREPEND BEGIN
{}
//PREPEND END
diff --git a/problem/views/admin.py b/problem/views/admin.py
index 17b3d74..e71c201 100644
--- a/problem/views/admin.py
+++ b/problem/views/admin.py
@@ -7,28 +7,27 @@ import zipfile
from wsgiref.util import FileWrapper
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.db.models import Count
-
-from account.decorators import problem_permission_required, ensure_created_by, super_admin_required
+from account.decorators import ensure_created_by, problem_permission_required, super_admin_required
from contest.models import Contest, ContestStatus
from submission.models import Submission
-from utils.api import APIView, CSRFExemptAPIView, validate_serializer, APIError
-from utils.shortcuts import rand_str, natural_sort_key
+from utils.api import APIError, APIView, CSRFExemptAPIView, validate_serializer
from utils.openai import get_ai_client
+from utils.shortcuts import natural_sort_key, rand_str
+
from ..models import Problem, ProblemRuleType, ProblemTag
from ..serializers import (
+ AddContestProblemSerializer,
+ ContestProblemMakePublicSerializer,
CreateContestProblemSerializer,
CreateProblemSerializer,
- EditProblemSerializer,
EditContestProblemSerializer,
- ProblemAdminSerializer,
+ EditProblemSerializer,
ProblemAdminListSerializer,
+ ProblemAdminSerializer,
TestCaseUploadForm,
- ContestProblemMakePublicSerializer,
- AddContestProblemSerializer,
)
diff --git a/problem/views/oj.py b/problem/views/oj.py
index 2da9452..02d409d 100644
--- a/problem/views/oj.py
+++ b/problem/views/oj.py
@@ -1,20 +1,23 @@
-from datetime import datetime
import random
-from django.db.models import Q, Count
+from datetime import datetime
+
from django.core.cache import cache
-from account.models import User
-from submission.models import Submission, JudgeStatus
-from utils.api import APIView
+from django.db.models import Count, Q
+
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 ..models import ProblemTag, Problem
+
+from ..models import Problem, ProblemTag
from ..serializers import (
+ ProblemListSerializer,
+ ProblemSafeSerializer,
ProblemSerializer,
TagSerializer,
- ProblemSafeSerializer,
- ProblemListSerializer,
)
-from contest.models import ContestRuleType
class ProblemTagAPI(APIView):
diff --git a/problemset/models.py b/problemset/models.py
index e235700..e0d5c1d 100644
--- a/problemset/models.py
+++ b/problemset/models.py
@@ -1,8 +1,9 @@
from django.db import models
from django.utils.timezone import now
+
from account.models import User
from problem.models import Problem
-from utils.models import RichTextField, JSONField
+from utils.models import JSONField, RichTextField
class ProblemSet(models.Model):
diff --git a/problemset/serializers.py b/problemset/serializers.py
index 526c6bb..3adab99 100644
--- a/problemset/serializers.py
+++ b/problemset/serializers.py
@@ -1,8 +1,9 @@
from utils.api import UsernameSerializer, serializers
+
from .models import (
ProblemSet,
- ProblemSetProblem,
ProblemSetBadge,
+ ProblemSetProblem,
ProblemSetProgress,
UserBadge,
)
diff --git a/problemset/signals.py b/problemset/signals.py
index 265fe82..fe46746 100644
--- a/problemset/signals.py
+++ b/problemset/signals.py
@@ -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
+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__)
diff --git a/problemset/urls/oj.py b/problemset/urls/oj.py
index 3fa8e4d..0f20df8 100644
--- a/problemset/urls/oj.py
+++ b/problemset/urls/oj.py
@@ -1,13 +1,14 @@
from django.urls import path
+
from problemset.views.oj import (
ProblemSetAPI,
+ ProblemSetBadgeAPI,
ProblemSetDetailAPI,
ProblemSetProblemAPI,
ProblemSetProgressAPI,
+ ProblemSetUserProgressAPI,
UserBadgeAPI,
UserProgressAPI,
- ProblemSetBadgeAPI,
- ProblemSetUserProgressAPI,
)
urlpatterns = [
diff --git a/problemset/views/admin.py b/problemset/views/admin.py
index fc09d31..d36e6ac 100644
--- a/problemset/views/admin.py
+++ b/problemset/views/admin.py
@@ -1,28 +1,27 @@
from django.db.models import Q
-from utils.api import APIView, validate_serializer
-from account.decorators import super_admin_required, ensure_created_by
-
+from account.decorators import ensure_created_by, super_admin_required
+from problem.models import Problem
from problemset.models import (
ProblemSet,
- ProblemSetProblem,
ProblemSetBadge,
+ ProblemSetProblem,
ProblemSetProgress,
)
from problemset.serializers import (
- ProblemSetSerializer,
- ProblemSetListSerializer,
- CreateProblemSetSerializer,
- EditProblemSetSerializer,
- ProblemSetProblemSerializer,
AddProblemToSetSerializer,
- EditProblemInSetSerializer,
- ProblemSetBadgeSerializer,
CreateProblemSetBadgeSerializer,
+ CreateProblemSetSerializer,
+ EditProblemInSetSerializer,
EditProblemSetBadgeSerializer,
+ EditProblemSetSerializer,
+ ProblemSetBadgeSerializer,
+ ProblemSetListSerializer,
+ ProblemSetProblemSerializer,
ProblemSetProgressSerializer,
+ ProblemSetSerializer,
)
-from problem.models import Problem
+from utils.api import APIView, validate_serializer
class ProblemSetAdminAPI(APIView):
diff --git a/problemset/views/oj.py b/problemset/views/oj.py
index eb4194b..8bfb7bc 100644
--- a/problemset/views/oj.py
+++ b/problemset/views/oj.py
@@ -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 utils.api import APIView, validate_serializer
-
from account.models import User
-
+from problem.models import Problem
from problemset.models import (
ProblemSet,
- ProblemSetProblem,
ProblemSetBadge,
+ ProblemSetProblem,
ProblemSetProgress,
ProblemSetSubmission,
UserBadge,
)
from problemset.serializers import (
- ProblemSetSerializer,
+ JoinProblemSetSerializer,
+ ProblemSetBadgeSerializer,
ProblemSetListSerializer,
ProblemSetProblemSerializer,
- ProblemSetBadgeSerializer,
ProblemSetProgressSerializer,
- UserBadgeSerializer,
- JoinProblemSetSerializer,
+ ProblemSetSerializer,
UpdateProgressSerializer,
+ UserBadgeSerializer,
)
-
from submission.models import Submission
-from problem.models import Problem
+from utils.api import APIView, validate_serializer
class ProblemSetAPI(APIView):
diff --git a/pyproject.toml b/pyproject.toml
index 1eb6029..aa24de0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,7 +7,6 @@ requires-python = ">=3.12"
dependencies = [
"channels>=4.2.0",
"channels-redis>=4.2.0",
- "coverage==6.5.0",
"daphne>=4.1.2",
"django>=5.2.3",
"django-cas-ng==5.0.1",
@@ -18,9 +17,6 @@ dependencies = [
"dramatiq==1.17.0",
"entrypoints==0.4",
"envelopes==0.4",
- "flake8==7.0.0",
- "flake8-coding==1.3.2",
- "flake8-quotes==3.3.2",
"gunicorn==22.0.0",
"jsonfield==3.1.0",
"openai>=1.108.1",
@@ -33,3 +29,18 @@ dependencies = [
"raven==6.10.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"]
diff --git a/run_test.py b/run_test.py
deleted file mode 100644
index a6c714f..0000000
--- a/run_test.py
+++ /dev/null
@@ -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")
diff --git a/submission/consumers.py b/submission/consumers.py
index b496432..2dadcee 100644
--- a/submission/consumers.py
+++ b/submission/consumers.py
@@ -3,6 +3,7 @@ WebSocket consumers for submission updates
"""
import json
import logging
+
from channels.generic.websocket import AsyncWebsocketConsumer
logger = logging.getLogger(__name__)
diff --git a/submission/models.py b/submission/models.py
index c7ca45d..05dbf13 100644
--- a/submission/models.py
+++ b/submission/models.py
@@ -1,10 +1,9 @@
from django.db import models
+from contest.models import Contest
+from problem.models import Problem
from utils.constants import ContestStatus
from utils.models import JSONField
-from problem.models import Problem
-from contest.models import Contest
-
from utils.shortcuts import rand_str
diff --git a/submission/serializers.py b/submission/serializers.py
index 1f929d2..3e27db0 100644
--- a/submission/serializers.py
+++ b/submission/serializers.py
@@ -2,10 +2,11 @@ from django.db import models
from django.db.models import F
from django.utils import timezone
-from .models import Submission
+from problemset.models import ProblemSetProgress
from utils.api import serializers
from utils.serializers import LanguageNameChoiceField
-from problemset.models import ProblemSetProgress
+
+from .models import Submission
def bulk_fetch_problemset_progress(user, problem_ids):
diff --git a/submission/urls/oj.py b/submission/urls/oj.py
index cafc4d0..c0b03ca 100644
--- a/submission/urls/oj.py
+++ b/submission/urls/oj.py
@@ -1,10 +1,10 @@
from django.urls import path
from ..views.oj import (
- SubmissionAPI,
- SubmissionListAPI,
ContestSubmissionListAPI,
+ SubmissionAPI,
SubmissionExistsAPI,
+ SubmissionListAPI,
SubmissionsTodayCount,
)
diff --git a/submission/views/admin.py b/submission/views/admin.py
index 7371604..b0b36d2 100644
--- a/submission/views/admin.py
+++ b/submission/views/admin.py
@@ -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 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):
if class_name and username.startswith("ks"):
diff --git a/submission/views/oj.py b/submission/views/oj.py
index e72a72b..ee799cb 100644
--- a/submission/views/oj.py
+++ b/submission/views/oj.py
@@ -1,8 +1,8 @@
-from datetime import datetime
import ipaddress
+from datetime import datetime
-from account.decorators import login_required, check_contest_permission
-from contest.models import ContestStatus, ContestRuleType
+from account.decorators import check_contest_permission, login_required
+from contest.models import ContestRuleType, ContestStatus
from judge.tasks import judge_task
from options.options import SysOptions
@@ -12,13 +12,16 @@ from utils.api import APIView, validate_serializer
from utils.cache import cache
from utils.captcha import Captcha
from utils.throttling import TokenBucket
+
from ..models import Submission
from ..serializers import (
CreateSubmissionSerializer,
- SubmissionModelSerializer,
ShareSubmissionSerializer,
+ SubmissionListSerializer,
+ SubmissionModelSerializer,
+ SubmissionSafeModelSerializer,
+ bulk_fetch_problemset_progress,
)
-from ..serializers import SubmissionSafeModelSerializer, SubmissionListSerializer, bulk_fetch_problemset_progress
class SubmissionAPI(APIView):
diff --git a/tutorial/models.py b/tutorial/models.py
index baff521..350dc73 100644
--- a/tutorial/models.py
+++ b/tutorial/models.py
@@ -1,6 +1,8 @@
from django.db import models
+
from account.models import User
+
class Tutorial(models.Model):
TYPE_CHOICES = [
('python', 'Python'),
diff --git a/tutorial/serializers.py b/tutorial/serializers.py
index 0c5ef3a..8e0ef00 100644
--- a/tutorial/serializers.py
+++ b/tutorial/serializers.py
@@ -1,7 +1,9 @@
from rest_framework import serializers
-from .models import Tutorial, Exercise
+
from account.serializers import UserSerializer
+from .models import Exercise, Tutorial
+
class TutorialListSerializer(serializers.ModelSerializer):
created_by = UserSerializer(read_only=True)
diff --git a/tutorial/urls/admin.py b/tutorial/urls/admin.py
index e035689..d4221b7 100644
--- a/tutorial/urls/admin.py
+++ b/tutorial/urls/admin.py
@@ -1,5 +1,6 @@
from django.urls import path
-from ..views.admin import TutorialAdminAPI, TutorialVisibilityAPI, ExerciseAdminAPI
+
+from ..views.admin import ExerciseAdminAPI, TutorialAdminAPI, TutorialVisibilityAPI
urlpatterns = [
path("tutorial", TutorialAdminAPI.as_view()),
diff --git a/tutorial/urls/tutorial.py b/tutorial/urls/tutorial.py
index 78324f7..4a2919c 100644
--- a/tutorial/urls/tutorial.py
+++ b/tutorial/urls/tutorial.py
@@ -1,5 +1,6 @@
from django.urls import path
-from ..views.oj import TutorialAPI, TutorialTitlesAPI, ExerciseAPI
+
+from ..views.oj import ExerciseAPI, TutorialAPI, TutorialTitlesAPI
urlpatterns = [
path("tutorial", TutorialAPI.as_view()),
diff --git a/tutorial/views/admin.py b/tutorial/views/admin.py
index 63eb799..72b135c 100644
--- a/tutorial/views/admin.py
+++ b/tutorial/views/admin.py
@@ -1,16 +1,15 @@
from account.decorators import super_admin_required
-from utils.api import APIView, validate_serializer
-
-from tutorial.models import Tutorial, Exercise
+from tutorial.models import Exercise, Tutorial
from tutorial.serializers import (
- TutorialSerializer,
- TutorialListSerializer,
+ CreateExerciseSerializer,
CreateTutorialSerializer,
+ EditExerciseSerializer,
EditTutorialSerializer,
ExerciseSerializer,
- CreateExerciseSerializer,
- EditExerciseSerializer,
+ TutorialListSerializer,
+ TutorialSerializer,
)
+from utils.api import APIView, validate_serializer
class TutorialAdminAPI(APIView):
diff --git a/tutorial/views/oj.py b/tutorial/views/oj.py
index 176611f..b02d4cb 100644
--- a/tutorial/views/oj.py
+++ b/tutorial/views/oj.py
@@ -1,8 +1,7 @@
+from tutorial.models import Exercise, Tutorial
+from tutorial.serializers import ExerciseSerializer, TutorialSerializer
from utils.api import APIView
-from tutorial.models import Tutorial, Exercise
-from tutorial.serializers import TutorialSerializer, ExerciseSerializer
-
class TutorialAPI(APIView):
def get(self, request):
diff --git a/utils/api/tests.py b/utils/api/tests.py
deleted file mode 100644
index 0f0a79b..0000000
--- a/utils/api/tests.py
+++ /dev/null
@@ -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)
diff --git a/utils/cache.py b/utils/cache.py
index ca4ca13..3d5e74f 100644
--- a/utils/cache.py
+++ b/utils/cache.py
@@ -1,7 +1,7 @@
import json
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.client.default import DefaultClient
diff --git a/utils/captcha/__init__.py b/utils/captcha/__init__.py
index 8b8375c..124404b 100644
--- a/utils/captcha/__init__.py
+++ b/utils/captcha/__init__.py
@@ -12,8 +12,8 @@ limitations under the License.
"""
import os
-import time
import random
+import time
from PIL import Image, ImageDraw, ImageFont
diff --git a/utils/captcha/views.py b/utils/captcha/views.py
index 4642e32..4981db0 100644
--- a/utils/captcha/views.py
+++ b/utils/captcha/views.py
@@ -1,6 +1,6 @@
-from . import Captcha
from ..api import APIView
from ..shortcuts import img2base64
+from . import Captcha
class CaptchaAPIView(APIView):
diff --git a/utils/shortcuts.py b/utils/shortcuts.py
index e21e406..0d6e888 100644
--- a/utils/shortcuts.py
+++ b/utils/shortcuts.py
@@ -1,7 +1,6 @@
import os
-import re
-import datetime
import random
+import re
from base64 import b64encode
from io import BytesIO
diff --git a/utils/tasks.py b/utils/tasks.py
index 26a2180..d431157 100644
--- a/utils/tasks.py
+++ b/utils/tasks.py
@@ -1,4 +1,5 @@
import os
+
import dramatiq
from utils.shortcuts import DRAMATIQ_WORKER_ARGS
diff --git a/utils/urls.py b/utils/urls.py
index 35d955e..5668741 100644
--- a/utils/urls.py
+++ b/utils/urls.py
@@ -1,6 +1,6 @@
from django.urls import path
-from .views import SimditorImageUploadAPIView, SimditorFileUploadAPIView
+from .views import SimditorFileUploadAPIView, SimditorImageUploadAPIView
urlpatterns = [
path("upload_image", SimditorImageUploadAPIView.as_view()),
diff --git a/utils/views.py b/utils/views.py
index ce30c71..17de959 100644
--- a/utils/views.py
+++ b/utils/views.py
@@ -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 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__)
diff --git a/utils/websocket.py b/utils/websocket.py
index c3ce299..b049be2 100644
--- a/utils/websocket.py
+++ b/utils/websocket.py
@@ -2,8 +2,9 @@
WebSocket utility functions for pushing real-time updates
"""
import logging
-from channels.layers import get_channel_layer
+
from asgiref.sync import async_to_sync
+from channels.layers import get_channel_layer
logger = logging.getLogger(__name__)
diff --git a/utils/xss_filter.py b/utils/xss_filter.py
index fe4f7aa..7e4a4b0 100644
--- a/utils/xss_filter.py
+++ b/utils/xss_filter.py
@@ -25,8 +25,8 @@ Python 2.6+ or 3.2+
Cannot defense xss in browser which is belowed IE7
浏览器版本:IE7+ 或其他浏览器,无法防御IE6及以下版本浏览器中的XSS
"""
-import re
import copy
+import re
from html.parser import HTMLParser
diff --git a/uv.lock b/uv.lock
index b3f5ada..f4f97fa 100644
--- a/uv.lock
+++ b/uv.lock
@@ -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" },
]
-[[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]]
name = "cryptography"
version = "46.0.3"
@@ -466,41 +460,6 @@ version = "0.4"
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" }
-[[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]]
name = "gunicorn"
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" },
]
-[[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]]
name = "msgpack"
version = "1.1.2"
@@ -803,7 +753,6 @@ source = { virtual = "." }
dependencies = [
{ name = "channels" },
{ name = "channels-redis" },
- { name = "coverage" },
{ name = "daphne" },
{ name = "django" },
{ name = "django-cas-ng" },
@@ -814,9 +763,6 @@ dependencies = [
{ name = "dramatiq" },
{ name = "entrypoints" },
{ name = "envelopes" },
- { name = "flake8" },
- { name = "flake8-coding" },
- { name = "flake8-quotes" },
{ name = "gunicorn" },
{ name = "jsonfield" },
{ name = "openai" },
@@ -830,11 +776,15 @@ dependencies = [
{ name = "xlsxwriter" },
]
+[package.dev-dependencies]
+dev = [
+ { name = "ruff" },
+]
+
[package.metadata]
requires-dist = [
{ name = "channels", specifier = ">=4.2.0" },
{ name = "channels-redis", specifier = ">=4.2.0" },
- { name = "coverage", specifier = "==6.5.0" },
{ name = "daphne", specifier = ">=4.1.2" },
{ name = "django", specifier = ">=5.2.3" },
{ name = "django-cas-ng", specifier = "==5.0.1" },
@@ -845,9 +795,6 @@ requires-dist = [
{ name = "dramatiq", specifier = "==1.17.0" },
{ name = "entrypoints", 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 = "jsonfield", specifier = "==3.1.0" },
{ name = "openai", specifier = ">=1.108.1" },
@@ -861,6 +808,9 @@ requires-dist = [
{ name = "xlsxwriter", specifier = "==3.2.0" },
]
+[package.metadata.requires-dev]
+dev = [{ name = "ruff", specifier = ">=0.15.11" }]
+
[[package]]
name = "openai"
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" },
]
-[[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]]
name = "pycparser"
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" },
]
-[[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]]
name = "pyopenssl"
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" },
]
+[[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]]
name = "service-identity"
version = "24.2.0"