Merge branch '2.0' of github.com:QingdaoU/OnlineJudge into 2.0
This commit is contained in:
@@ -12,3 +12,5 @@ script:
|
|||||||
- flake8 .
|
- flake8 .
|
||||||
- coverage run --include="$PWD/*" manage.py test
|
- coverage run --include="$PWD/*" manage.py test
|
||||||
- coverage report
|
- coverage report
|
||||||
|
notifications:
|
||||||
|
slack: onlinejudgeteam:BzBz8UFgmS5crpiblof17K2W
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import functools
|
import functools
|
||||||
|
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
|
|
||||||
from utils.api import JSONResponse
|
from utils.api import JSONResponse
|
||||||
|
|
||||||
from .models import ProblemPermission
|
from .models import ProblemPermission
|
||||||
@@ -22,10 +20,10 @@ class BasePermissionDecorator(object):
|
|||||||
|
|
||||||
if self.check_permission():
|
if self.check_permission():
|
||||||
if self.request.user.is_disabled:
|
if self.request.user.is_disabled:
|
||||||
return self.error(_("Your account is disabled"))
|
return self.error("Your account is disabled")
|
||||||
return self.func(*args, **kwargs)
|
return self.func(*args, **kwargs)
|
||||||
else:
|
else:
|
||||||
return self.error(_("Please login in first"))
|
return self.error("Please login in first")
|
||||||
|
|
||||||
def check_permission(self):
|
def check_permission(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|||||||
@@ -44,3 +44,23 @@ class EditUserSerializer(serializers.Serializer):
|
|||||||
open_api = serializers.BooleanField()
|
open_api = serializers.BooleanField()
|
||||||
two_factor_auth = serializers.BooleanField()
|
two_factor_auth = serializers.BooleanField()
|
||||||
is_disabled = serializers.BooleanField()
|
is_disabled = serializers.BooleanField()
|
||||||
|
|
||||||
|
|
||||||
|
class ApplyResetPasswordSerializer(serializers.Serializer):
|
||||||
|
email = serializers.EmailField()
|
||||||
|
captcha = serializers.CharField(max_length=4, min_length=4)
|
||||||
|
|
||||||
|
|
||||||
|
class ResetPasswordSerializer(serializers.Serializer):
|
||||||
|
token = serializers.CharField(min_length=1, max_length=40)
|
||||||
|
password = serializers.CharField(min_length=6, max_length=30)
|
||||||
|
captcha = serializers.CharField(max_length=4, min_length=4)
|
||||||
|
|
||||||
|
|
||||||
|
class SSOSerializer(serializers.Serializer):
|
||||||
|
appkey = serializers.CharField(max_length=35)
|
||||||
|
token = serializers.CharField(max_length=40)
|
||||||
|
|
||||||
|
|
||||||
|
class TwoFactorAuthCodeSerializer(serializers.Serializer):
|
||||||
|
code = serializers.IntegerField()
|
||||||
|
|||||||
8
account/tasks.py
Normal file
8
account/tasks.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from celery import shared_task
|
||||||
|
|
||||||
|
from utils.shortcuts import send_email
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def send_email_async(from_name, to_email, to_name, subject, content):
|
||||||
|
send_email(from_name, to_email, to_name, subject, content)
|
||||||
78
account/templates/reset_password_email.html
Normal file
78
account/templates/reset_password_email.html
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<table cellpadding="0" cellspacing="0" align="center" style="text-align:left;font-family:'微软雅黑','黑体',arial;"
|
||||||
|
width="742">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table cellpadding="0" cellspacing="0"
|
||||||
|
style="text-align:left;border:1px solid #50a5e6;color:#fff;font-size:18px;" width="740">
|
||||||
|
<tbody>
|
||||||
|
<tr height="39" style="background-color:#50a5e6;">
|
||||||
|
<td style="padding-left:15px;font-family:'微软雅黑','黑体',arial;">
|
||||||
|
{{ website_name }} 登录信息找回
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<table cellpadding="0" cellspacing="0"
|
||||||
|
style="text-align:left;border:1px solid #f0f0f0;border-top:none;color:#585858;background-color:#fafafa;"
|
||||||
|
width="740">
|
||||||
|
<tbody>
|
||||||
|
<tr height="25">
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr height="40">
|
||||||
|
<td style="padding-left:25px;padding-right:25px;font-size:18px;font-family:'微软雅黑','黑体',arial;">
|
||||||
|
Hello, {{ username }}:
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr height="15">
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr height="30">
|
||||||
|
<td style="padding-left:55px;padding-right:55px;font-family:'微软雅黑','黑体',arial;font-size:14px;">
|
||||||
|
您刚刚在 {{ website_name }} 申请了找回登录信息服务。
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr height="30">
|
||||||
|
<td style="padding-left:55px;padding-right:55px;font-family:'微软雅黑','黑体',arial;font-size:14px;">
|
||||||
|
请在<span style="color:rgb(255,0,0)">30分钟</span>内点击下面链接设置您的新密码:
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr height="60">
|
||||||
|
<td style="padding-left:55px;padding-right:55px;font-family:'微软雅黑','黑体',arial;font-size:14px;">
|
||||||
|
<a href="{{ link }}" target="_blank"
|
||||||
|
style="color: rgb(255,255,255);text-decoration: none;display: block;min-height: 39px;width: 158px;line-height: 39px;background-color:rgb(80,165,230);font-size:20px;text-align:center;">重置密码</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr height="10">
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr height="20">
|
||||||
|
<td style="padding-left:55px;padding-right:55px;font-family:'微软雅黑','黑体',arial;font-size:12px;">
|
||||||
|
如果上面的链接点击无效,请复制以下链接至浏览器的地址栏直接打开。
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr height="30">
|
||||||
|
<td style="padding-left:55px;padding-right:65px;font-family:'微软雅黑','黑体',arial;">
|
||||||
|
<a href="{{ link }}" target="_blank" style="color:#0c94de;font-size:12px;">
|
||||||
|
{{ link }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr height="20">
|
||||||
|
<td style="padding-left:55px;padding-right:55px;font-family:'微软雅黑','黑体',arial;font-size:12px;">
|
||||||
|
如果您没有提出过该申请,请忽略此邮件。有可能是其他用户误填了您的邮件地址,我们不会对你的帐户进行任何修改。
|
||||||
|
请不要向他人透露本邮件的内容,否则可能会导致您的账号被盗。
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr height="20">
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
@@ -2,7 +2,6 @@ import time
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from django.contrib import auth
|
from django.contrib import auth
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
from otpauth import OtpAuth
|
from otpauth import OtpAuth
|
||||||
|
|
||||||
from utils.api.tests import APIClient, APITestCase
|
from utils.api.tests import APIClient, APITestCase
|
||||||
@@ -45,7 +44,7 @@ class UserLoginAPITest(APITestCase):
|
|||||||
def test_login_with_correct_info(self):
|
def test_login_with_correct_info(self):
|
||||||
response = self.client.post(self.login_url,
|
response = self.client.post(self.login_url,
|
||||||
data={"username": self.username, "password": self.password})
|
data={"username": self.username, "password": self.password})
|
||||||
self.assertDictEqual(response.data, {"error": None, "data": _("Succeeded")})
|
self.assertDictEqual(response.data, {"error": None, "data": "Succeeded"})
|
||||||
|
|
||||||
user = auth.get_user(self.client)
|
user = auth.get_user(self.client)
|
||||||
self.assertTrue(user.is_authenticated())
|
self.assertTrue(user.is_authenticated())
|
||||||
@@ -53,7 +52,7 @@ class UserLoginAPITest(APITestCase):
|
|||||||
def test_login_with_wrong_info(self):
|
def test_login_with_wrong_info(self):
|
||||||
response = self.client.post(self.login_url,
|
response = self.client.post(self.login_url,
|
||||||
data={"username": self.username, "password": "invalid_password"})
|
data={"username": self.username, "password": "invalid_password"})
|
||||||
self.assertDictEqual(response.data, {"error": "error", "data": _("Invalid username or password")})
|
self.assertDictEqual(response.data, {"error": "error", "data": "Invalid username or password"})
|
||||||
|
|
||||||
user = auth.get_user(self.client)
|
user = auth.get_user(self.client)
|
||||||
self.assertFalse(user.is_authenticated())
|
self.assertFalse(user.is_authenticated())
|
||||||
@@ -67,7 +66,7 @@ class UserLoginAPITest(APITestCase):
|
|||||||
data={"username": self.username,
|
data={"username": self.username,
|
||||||
"password": self.password,
|
"password": self.password,
|
||||||
"tfa_code": code})
|
"tfa_code": code})
|
||||||
self.assertDictEqual(response.data, {"error": None, "data": _("Succeeded")})
|
self.assertDictEqual(response.data, {"error": None, "data": "Succeeded"})
|
||||||
|
|
||||||
user = auth.get_user(self.client)
|
user = auth.get_user(self.client)
|
||||||
self.assertTrue(user.is_authenticated())
|
self.assertTrue(user.is_authenticated())
|
||||||
@@ -78,7 +77,7 @@ class UserLoginAPITest(APITestCase):
|
|||||||
data={"username": self.username,
|
data={"username": self.username,
|
||||||
"password": self.password,
|
"password": self.password,
|
||||||
"tfa_code": "qqqqqq"})
|
"tfa_code": "qqqqqq"})
|
||||||
self.assertDictEqual(response.data, {"error": "error", "data": _("Invalid two factor verification code")})
|
self.assertDictEqual(response.data, {"error": "error", "data": "Invalid two factor verification code"})
|
||||||
|
|
||||||
user = auth.get_user(self.client)
|
user = auth.get_user(self.client)
|
||||||
self.assertFalse(user.is_authenticated())
|
self.assertFalse(user.is_authenticated())
|
||||||
@@ -116,7 +115,7 @@ class UserRegisterAPITest(CaptchaTest):
|
|||||||
def test_invalid_captcha(self):
|
def test_invalid_captcha(self):
|
||||||
self.data["captcha"] = "****"
|
self.data["captcha"] = "****"
|
||||||
response = self.client.post(self.register_url, data=self.data)
|
response = self.client.post(self.register_url, data=self.data)
|
||||||
self.assertDictEqual(response.data, {"error": "error", "data": _("Invalid captcha")})
|
self.assertDictEqual(response.data, {"error": "error", "data": "Invalid captcha"})
|
||||||
|
|
||||||
self.data.pop("captcha")
|
self.data.pop("captcha")
|
||||||
response = self.client.post(self.register_url, data=self.data)
|
response = self.client.post(self.register_url, data=self.data)
|
||||||
@@ -124,7 +123,7 @@ class UserRegisterAPITest(CaptchaTest):
|
|||||||
|
|
||||||
def test_register_with_correct_info(self):
|
def test_register_with_correct_info(self):
|
||||||
response = self.client.post(self.register_url, data=self.data)
|
response = self.client.post(self.register_url, data=self.data)
|
||||||
self.assertDictEqual(response.data, {"error": None, "data": _("Succeeded")})
|
self.assertDictEqual(response.data, {"error": None, "data": "Succeeded"})
|
||||||
|
|
||||||
def test_username_already_exists(self):
|
def test_username_already_exists(self):
|
||||||
self.test_register_with_correct_info()
|
self.test_register_with_correct_info()
|
||||||
@@ -132,7 +131,7 @@ class UserRegisterAPITest(CaptchaTest):
|
|||||||
self.data["captcha"] = self._set_captcha(self.client.session)
|
self.data["captcha"] = self._set_captcha(self.client.session)
|
||||||
self.data["email"] = "test1@qduoj.com"
|
self.data["email"] = "test1@qduoj.com"
|
||||||
response = self.client.post(self.register_url, data=self.data)
|
response = self.client.post(self.register_url, data=self.data)
|
||||||
self.assertDictEqual(response.data, {"error": "error", "data": _("Username already exists")})
|
self.assertDictEqual(response.data, {"error": "error", "data": "Username already exists"})
|
||||||
|
|
||||||
def test_email_already_exists(self):
|
def test_email_already_exists(self):
|
||||||
self.test_register_with_correct_info()
|
self.test_register_with_correct_info()
|
||||||
@@ -140,7 +139,7 @@ class UserRegisterAPITest(CaptchaTest):
|
|||||||
self.data["captcha"] = self._set_captcha(self.client.session)
|
self.data["captcha"] = self._set_captcha(self.client.session)
|
||||||
self.data["username"] = "test_user1"
|
self.data["username"] = "test_user1"
|
||||||
response = self.client.post(self.register_url, data=self.data)
|
response = self.client.post(self.register_url, data=self.data)
|
||||||
self.assertDictEqual(response.data, {"error": "error", "data": _("Email already exists")})
|
self.assertDictEqual(response.data, {"error": "error", "data": "Email already exists"})
|
||||||
|
|
||||||
|
|
||||||
class UserChangePasswordAPITest(CaptchaTest):
|
class UserChangePasswordAPITest(CaptchaTest):
|
||||||
@@ -159,19 +158,19 @@ class UserChangePasswordAPITest(CaptchaTest):
|
|||||||
|
|
||||||
def test_login_required(self):
|
def test_login_required(self):
|
||||||
response = self.client.post(self.url, data=self.data)
|
response = self.client.post(self.url, data=self.data)
|
||||||
self.assertEqual(response.data, {"error": "permission-denied", "data": _("Please login in first")})
|
self.assertEqual(response.data, {"error": "permission-denied", "data": "Please login in first"})
|
||||||
|
|
||||||
def test_valid_ola_password(self):
|
def test_valid_ola_password(self):
|
||||||
self.assertTrue(self.client.login(username=self.username, password=self.old_password))
|
self.assertTrue(self.client.login(username=self.username, password=self.old_password))
|
||||||
response = self.client.post(self.url, data=self.data)
|
response = self.client.post(self.url, data=self.data)
|
||||||
self.assertEqual(response.data, {"error": None, "data": _("Succeeded")})
|
self.assertEqual(response.data, {"error": None, "data": "Succeeded"})
|
||||||
self.assertTrue(self.client.login(username=self.username, password=self.new_password))
|
self.assertTrue(self.client.login(username=self.username, password=self.new_password))
|
||||||
|
|
||||||
def test_invalid_old_password(self):
|
def test_invalid_old_password(self):
|
||||||
self.assertTrue(self.client.login(username=self.username, password=self.old_password))
|
self.assertTrue(self.client.login(username=self.username, password=self.old_password))
|
||||||
self.data["old_password"] = "invalid"
|
self.data["old_password"] = "invalid"
|
||||||
response = self.client.post(self.url, data=self.data)
|
response = self.client.post(self.url, data=self.data)
|
||||||
self.assertEqual(response.data, {"error": "error", "data": _("Invalid old password")})
|
self.assertEqual(response.data, {"error": "error", "data": "Invalid old password"})
|
||||||
|
|
||||||
|
|
||||||
class AdminUserTest(APITestCase):
|
class AdminUserTest(APITestCase):
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ from django.conf.urls import url
|
|||||||
from ..views.admin import UserAdminAPI
|
from ..views.admin import UserAdminAPI
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r"^user$", UserAdminAPI.as_view(), name="user_admin_api"),
|
url(r"^user/?$", UserAdminAPI.as_view(), name="user_admin_api"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
|
||||||
from ..views.oj import UserChangePasswordAPI, UserLoginAPI, UserRegisterAPI
|
from ..views.oj import (ApplyResetPasswordAPI, ResetPasswordAPI,
|
||||||
|
UserChangePasswordAPI, UserLoginAPI, UserRegisterAPI)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r"^login$", UserLoginAPI.as_view(), name="user_login_api"),
|
url(r"^login/?$", UserLoginAPI.as_view(), name="user_login_api"),
|
||||||
url(r"^register$", UserRegisterAPI.as_view(), name="user_register_api"),
|
url(r"^register/?$", UserRegisterAPI.as_view(), name="user_register_api"),
|
||||||
url(r"^change_password$", UserChangePasswordAPI.as_view(), name="user_change_password_api")
|
url(r"^change_password/?$", UserChangePasswordAPI.as_view(), name="user_change_password_api"),
|
||||||
|
url(r"^apply_reset_password/?$", ApplyResetPasswordAPI.as_view(), name="apply_reset_password_api"),
|
||||||
|
url(r"^reset_password/?$", ResetPasswordAPI.as_view(), name="apply_reset_password_api")
|
||||||
]
|
]
|
||||||
|
|||||||
12
account/urls/user.py
Normal file
12
account/urls/user.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
from ..views.user import (SSOAPI, AvatarUploadAPI, TwoFactorAuthAPI,
|
||||||
|
UserInfoAPI, UserProfileAPI)
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r"^user/?$", UserInfoAPI.as_view(), name="user_info_api"),
|
||||||
|
url(r"^profile/?$", UserProfileAPI.as_view(), name="user_profile_api"),
|
||||||
|
url(r"^avatar/upload/?$", AvatarUploadAPI.as_view(), name="avatar_upload_api"),
|
||||||
|
url(r"^sso/?$", SSOAPI.as_view(), name="sso_api"),
|
||||||
|
url(r"^two_factor_auth/?$", TwoFactorAuthAPI.as_view(), name="two_factor_auth_api")
|
||||||
|
]
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
from django.core.exceptions import MultipleObjectsReturned
|
from django.core.exceptions import MultipleObjectsReturned
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
|
|
||||||
from utils.api import APIView, validate_serializer
|
from utils.api import APIView, validate_serializer
|
||||||
from utils.shortcuts import rand_str
|
from utils.shortcuts import rand_str
|
||||||
@@ -21,21 +20,21 @@ class UserAdminAPI(APIView):
|
|||||||
try:
|
try:
|
||||||
user = User.objects.get(id=data["id"])
|
user = User.objects.get(id=data["id"])
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
return self.error(_("User does not exist"))
|
return self.error("User does not exist")
|
||||||
try:
|
try:
|
||||||
user = User.objects.get(username=data["username"])
|
user = User.objects.get(username=data["username"])
|
||||||
if user.id != data["id"]:
|
if user.id != data["id"]:
|
||||||
return self.error(_("Username already exists"))
|
return self.error("Username already exists")
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user = User.objects.get(email=data["email"])
|
user = User.objects.get(email=data["email"])
|
||||||
if user.id != data["id"]:
|
if user.id != data["id"]:
|
||||||
return self.error(_("Email already exists"))
|
return self.error("Email already exists")
|
||||||
# Some old data has duplicate email
|
# Some old data has duplicate email
|
||||||
except MultipleObjectsReturned:
|
except MultipleObjectsReturned:
|
||||||
return self.error(_("Email already exists"))
|
return self.error("Email already exists")
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -85,7 +84,7 @@ class UserAdminAPI(APIView):
|
|||||||
try:
|
try:
|
||||||
user = User.objects.get(id=user_id)
|
user = User.objects.get(id=user_id)
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
return self.error(_("User does not exist"))
|
return self.error("User does not exist")
|
||||||
return self.success(UserSerializer(user).data)
|
return self.success(UserSerializer(user).data)
|
||||||
|
|
||||||
user = User.objects.all().order_by("-create_time")
|
user = User.objects.all().order_by("-create_time")
|
||||||
|
|||||||
@@ -1,15 +1,23 @@
|
|||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib import auth
|
from django.contrib import auth
|
||||||
from django.core.exceptions import MultipleObjectsReturned
|
from django.core.exceptions import MultipleObjectsReturned
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.timezone import now
|
||||||
from otpauth import OtpAuth
|
from otpauth import OtpAuth
|
||||||
|
|
||||||
|
from conf.models import WebsiteConfig
|
||||||
from utils.api import APIView, validate_serializer
|
from utils.api import APIView, validate_serializer
|
||||||
from utils.captcha import Captcha
|
from utils.captcha import Captcha
|
||||||
|
from utils.shortcuts import rand_str
|
||||||
|
|
||||||
from ..decorators import login_required
|
from ..decorators import login_required
|
||||||
from ..models import User, UserProfile
|
from ..models import User, UserProfile
|
||||||
from ..serializers import (UserChangePasswordSerializer, UserLoginSerializer,
|
from ..serializers import (ApplyResetPasswordSerializer,
|
||||||
|
ResetPasswordSerializer,
|
||||||
|
UserChangePasswordSerializer, UserLoginSerializer,
|
||||||
UserRegisterSerializer)
|
UserRegisterSerializer)
|
||||||
|
from ..tasks import send_email_async
|
||||||
|
|
||||||
|
|
||||||
class UserLoginAPI(APIView):
|
class UserLoginAPI(APIView):
|
||||||
@@ -24,7 +32,7 @@ class UserLoginAPI(APIView):
|
|||||||
if user:
|
if user:
|
||||||
if not user.two_factor_auth:
|
if not user.two_factor_auth:
|
||||||
auth.login(request, user)
|
auth.login(request, user)
|
||||||
return self.success(_("Succeeded"))
|
return self.success("Succeeded")
|
||||||
|
|
||||||
# `tfa_code` not in post data
|
# `tfa_code` not in post data
|
||||||
if user.two_factor_auth and "tfa_code" not in data:
|
if user.two_factor_auth and "tfa_code" not in data:
|
||||||
@@ -32,11 +40,11 @@ class UserLoginAPI(APIView):
|
|||||||
|
|
||||||
if OtpAuth(user.tfa_token).valid_totp(data["tfa_code"]):
|
if OtpAuth(user.tfa_token).valid_totp(data["tfa_code"]):
|
||||||
auth.login(request, user)
|
auth.login(request, user)
|
||||||
return self.success(_("Succeeded"))
|
return self.success("Succeeded")
|
||||||
else:
|
else:
|
||||||
return self.error(_("Invalid two factor verification code"))
|
return self.error("Invalid two factor verification code")
|
||||||
else:
|
else:
|
||||||
return self.error(_("Invalid username or password"))
|
return self.error("Invalid username or password")
|
||||||
|
|
||||||
# todo remove this, only for debug use
|
# todo remove this, only for debug use
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
@@ -53,24 +61,24 @@ class UserRegisterAPI(APIView):
|
|||||||
data = request.data
|
data = request.data
|
||||||
captcha = Captcha(request)
|
captcha = Captcha(request)
|
||||||
if not captcha.check(data["captcha"]):
|
if not captcha.check(data["captcha"]):
|
||||||
return self.error(_("Invalid captcha"))
|
return self.error("Invalid captcha")
|
||||||
try:
|
try:
|
||||||
User.objects.get(username=data["username"])
|
User.objects.get(username=data["username"])
|
||||||
return self.error(_("Username already exists"))
|
return self.error("Username already exists")
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
User.objects.get(email=data["email"])
|
User.objects.get(email=data["email"])
|
||||||
return self.error(_("Email already exists"))
|
return self.error("Email already exists")
|
||||||
# Some old data has duplicate email
|
# Some old data has duplicate email
|
||||||
except MultipleObjectsReturned:
|
except MultipleObjectsReturned:
|
||||||
return self.error(_("Email already exists"))
|
return self.error("Email already exists")
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
user = User.objects.create(username=data["username"], email=data["email"])
|
user = User.objects.create(username=data["username"], email=data["email"])
|
||||||
user.set_password(data["password"])
|
user.set_password(data["password"])
|
||||||
user.save()
|
user.save()
|
||||||
UserProfile.objects.create(user=user)
|
UserProfile.objects.create(user=user)
|
||||||
return self.success(_("Succeeded"))
|
return self.success("Succeeded")
|
||||||
|
|
||||||
|
|
||||||
class UserChangePasswordAPI(APIView):
|
class UserChangePasswordAPI(APIView):
|
||||||
@@ -83,12 +91,64 @@ class UserChangePasswordAPI(APIView):
|
|||||||
data = request.data
|
data = request.data
|
||||||
captcha = Captcha(request)
|
captcha = Captcha(request)
|
||||||
if not captcha.check(data["captcha"]):
|
if not captcha.check(data["captcha"]):
|
||||||
return self.error(_("Invalid captcha"))
|
return self.error("Invalid captcha")
|
||||||
username = request.user.username
|
username = request.user.username
|
||||||
user = auth.authenticate(username=username, password=data["old_password"])
|
user = auth.authenticate(username=username, password=data["old_password"])
|
||||||
if user:
|
if user:
|
||||||
user.set_password(data["new_password"])
|
user.set_password(data["new_password"])
|
||||||
user.save()
|
user.save()
|
||||||
return self.success(_("Succeeded"))
|
return self.success("Succeeded")
|
||||||
else:
|
else:
|
||||||
return self.error(_("Invalid old password"))
|
return self.error("Invalid old password")
|
||||||
|
|
||||||
|
|
||||||
|
class ApplyResetPasswordAPI(APIView):
|
||||||
|
@validate_serializer(ApplyResetPasswordSerializer)
|
||||||
|
def post(self, request):
|
||||||
|
data = request.data
|
||||||
|
captcha = Captcha(request)
|
||||||
|
config = WebsiteConfig.objects.first()
|
||||||
|
if not captcha.check(data["captcha"]):
|
||||||
|
return self.error("Invalid captcha")
|
||||||
|
try:
|
||||||
|
user = User.objects.get(email=data["email"])
|
||||||
|
except User.DoesNotExist:
|
||||||
|
return self.error("User does not exist")
|
||||||
|
if user.reset_password_token_expire_time and 0 < (
|
||||||
|
user.reset_password_token_expire_time - now()).total_seconds() < 20 * 60:
|
||||||
|
return self.error("You can only reset password once per 20 minutes")
|
||||||
|
user.reset_password_token = rand_str()
|
||||||
|
|
||||||
|
user.reset_password_token_expire_time = now() + timedelta(minutes=20)
|
||||||
|
user.save()
|
||||||
|
email_template = open("reset_password_email.html", "w",
|
||||||
|
encoding="utf-8").read()
|
||||||
|
email_template = email_template.replace("{{ username }}", user.username). \
|
||||||
|
replace("{{ website_name }}", settings.WEBSITE_INFO["website_name"]). \
|
||||||
|
replace("{{ link }}", settings.WEBSITE_INFO["url"] + "/reset_password/t/" +
|
||||||
|
user.reset_password_token)
|
||||||
|
send_email_async.delay(config.name,
|
||||||
|
user.email,
|
||||||
|
user.username,
|
||||||
|
config.name + " 登录信息找回邮件",
|
||||||
|
email_template)
|
||||||
|
return self.success("Succeeded")
|
||||||
|
|
||||||
|
|
||||||
|
class ResetPasswordAPI(APIView):
|
||||||
|
@validate_serializer(ResetPasswordSerializer)
|
||||||
|
def post(self, request):
|
||||||
|
data = request.data
|
||||||
|
captcha = Captcha(request)
|
||||||
|
if not captcha.check(data["captcha"]):
|
||||||
|
return self.error("Invalid captcha")
|
||||||
|
try:
|
||||||
|
user = User.objects.get(reset_password_token=data["token"])
|
||||||
|
except User.DoesNotExist:
|
||||||
|
return self.error("Token dose not exist")
|
||||||
|
if 0 < (user.reset_password_token_expire_time - now()).total_seconds() < 30 * 60:
|
||||||
|
return self.error("Token expired")
|
||||||
|
user.reset_password_token = None
|
||||||
|
user.set_password(data["password"])
|
||||||
|
user.save()
|
||||||
|
return self.success("Succeeded")
|
||||||
|
|||||||
148
account/views/user.py
Normal file
148
account/views/user.py
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
import os
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
import qrcode
|
||||||
|
from django.conf import settings
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from otpauth import OtpAuth
|
||||||
|
|
||||||
|
from conf.models import WebsiteConfig
|
||||||
|
from utils.api import APIView, validate_serializer
|
||||||
|
from utils.shortcuts import rand_str
|
||||||
|
|
||||||
|
from ..decorators import login_required
|
||||||
|
from ..models import User
|
||||||
|
from ..serializers import (EditUserSerializer, SSOSerializer,
|
||||||
|
TwoFactorAuthCodeSerializer, UserSerializer)
|
||||||
|
|
||||||
|
|
||||||
|
class UserInfoAPI(APIView):
|
||||||
|
@login_required
|
||||||
|
def get(self, request):
|
||||||
|
"""
|
||||||
|
Return user info api
|
||||||
|
"""
|
||||||
|
return self.success(UserSerializer(request.user).data)
|
||||||
|
|
||||||
|
|
||||||
|
class UserProfileAPI(APIView):
|
||||||
|
@login_required
|
||||||
|
def get(self, request):
|
||||||
|
"""
|
||||||
|
Return user info api
|
||||||
|
"""
|
||||||
|
return self.success(UserSerializer(request.user).data)
|
||||||
|
|
||||||
|
@validate_serializer(EditUserSerializer)
|
||||||
|
@login_required
|
||||||
|
def put(self, request):
|
||||||
|
data = request.data
|
||||||
|
user_profile = request.user.userprofile
|
||||||
|
if data["avatar"]:
|
||||||
|
user_profile.avatar = data["avatar"]
|
||||||
|
else:
|
||||||
|
user_profile.mood = data["mood"]
|
||||||
|
user_profile.blog = data["blog"]
|
||||||
|
user_profile.school = data["school"]
|
||||||
|
user_profile.student_id = data["student_id"]
|
||||||
|
user_profile.phone_number = data["phone_number"]
|
||||||
|
user_profile.major = data["major"]
|
||||||
|
# Timezone & language 暂时不加
|
||||||
|
user_profile.save()
|
||||||
|
return self.success("Succeeded")
|
||||||
|
|
||||||
|
|
||||||
|
class AvatarUploadAPI(APIView):
|
||||||
|
def post(self, request):
|
||||||
|
if "file" not in request.FILES:
|
||||||
|
return self.error("Upload failed")
|
||||||
|
|
||||||
|
f = request.FILES["file"]
|
||||||
|
if f.size > 1024 * 1024:
|
||||||
|
return self.error("Picture too large")
|
||||||
|
if os.path.splitext(f.name)[-1].lower() not in [".gif", ".jpg", ".jpeg", ".bmp", ".png"]:
|
||||||
|
return self.error("Unsupported file format")
|
||||||
|
|
||||||
|
name = "avatar_" + rand_str(5) + os.path.splitext(f.name)[-1]
|
||||||
|
with open(os.path.join(settings.IMAGE_UPLOAD_DIR, name), "wb") as img:
|
||||||
|
for chunk in request.FILES["file"]:
|
||||||
|
img.write(chunk)
|
||||||
|
return self.success({"path": "/static/upload/" + name})
|
||||||
|
|
||||||
|
|
||||||
|
class SSOAPI(APIView):
|
||||||
|
@login_required
|
||||||
|
def get(self, request):
|
||||||
|
callback = request.GET.get("callback", None)
|
||||||
|
if not callback:
|
||||||
|
return self.error("Parameter Error")
|
||||||
|
token = rand_str()
|
||||||
|
request.user.auth_token = token
|
||||||
|
request.user.save()
|
||||||
|
return self.success({"redirect_url": callback + "?token=" + token,
|
||||||
|
"callback": callback})
|
||||||
|
|
||||||
|
@validate_serializer(SSOSerializer)
|
||||||
|
def post(self, request):
|
||||||
|
data = request.data
|
||||||
|
try:
|
||||||
|
User.objects.get(open_api_appkey=data["appkey"])
|
||||||
|
except User.DoesNotExist:
|
||||||
|
return self.error("Invalid appkey")
|
||||||
|
try:
|
||||||
|
user = User.objects.get(auth_token=data["token"])
|
||||||
|
user.auth_token = None
|
||||||
|
user.save()
|
||||||
|
return self.success({"username": user.username,
|
||||||
|
"id": user.id,
|
||||||
|
"admin_type": user.admin_type,
|
||||||
|
"avatar": user.userprofile.avatar})
|
||||||
|
except User.DoesNotExist:
|
||||||
|
return self.error("User does not exist")
|
||||||
|
|
||||||
|
|
||||||
|
class TwoFactorAuthAPI(APIView):
|
||||||
|
@login_required
|
||||||
|
def get(self, request):
|
||||||
|
"""
|
||||||
|
Get QR code
|
||||||
|
"""
|
||||||
|
user = request.user
|
||||||
|
if user.two_factor_auth:
|
||||||
|
return self.error("Already open 2FA")
|
||||||
|
token = rand_str()
|
||||||
|
user.tfa_token = token
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
config = WebsiteConfig.objects.first()
|
||||||
|
image = qrcode.make(OtpAuth(token).to_uri("totp", config.base_url, config.name))
|
||||||
|
buf = StringIO()
|
||||||
|
image.save(buf, "gif")
|
||||||
|
|
||||||
|
return HttpResponse(buf.getvalue(), "image/gif")
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@validate_serializer(TwoFactorAuthCodeSerializer)
|
||||||
|
def post(self, request):
|
||||||
|
"""
|
||||||
|
Open 2FA
|
||||||
|
"""
|
||||||
|
code = request.data["code"]
|
||||||
|
user = request.user
|
||||||
|
if OtpAuth(user.tfa_token).valid_totp(code):
|
||||||
|
user.two_factor_auth = True
|
||||||
|
user.save()
|
||||||
|
return self.success("Succeeded")
|
||||||
|
else:
|
||||||
|
return self.error("Invalid captcha")
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@validate_serializer(TwoFactorAuthCodeSerializer)
|
||||||
|
def put(self, request):
|
||||||
|
code = request.data["code"]
|
||||||
|
user = request.user
|
||||||
|
if OtpAuth(user.tfa_token).valid_totp(code):
|
||||||
|
user.two_factor_auth = False
|
||||||
|
user.save()
|
||||||
|
else:
|
||||||
|
return self.error("Invalid captcha")
|
||||||
@@ -3,5 +3,5 @@ from django.conf.urls import url
|
|||||||
from ..views import AnnouncementAdminAPI
|
from ..views import AnnouncementAdminAPI
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r"^announcement$", AnnouncementAdminAPI.as_view(), name="announcement_admin_api"),
|
url(r"^announcement/?$", AnnouncementAdminAPI.as_view(), name="announcement_admin_api"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
from django.utils.translation import ugettext as _
|
|
||||||
|
|
||||||
from account.decorators import super_admin_required
|
from account.decorators import super_admin_required
|
||||||
from utils.api import APIView, validate_serializer
|
from utils.api import APIView, validate_serializer
|
||||||
|
|
||||||
@@ -32,7 +30,7 @@ class AnnouncementAdminAPI(APIView):
|
|||||||
try:
|
try:
|
||||||
announcement = Announcement.objects.get(id=data["id"])
|
announcement = Announcement.objects.get(id=data["id"])
|
||||||
except Announcement.DoesNotExist:
|
except Announcement.DoesNotExist:
|
||||||
return self.error(_("Announcement does not exist"))
|
return self.error("Announcement does not exist")
|
||||||
|
|
||||||
announcement.title = data["title"]
|
announcement.title = data["title"]
|
||||||
announcement.content = data["content"]
|
announcement.content = data["content"]
|
||||||
@@ -52,7 +50,7 @@ class AnnouncementAdminAPI(APIView):
|
|||||||
announcement = Announcement.objects.get(id=announcement_id)
|
announcement = Announcement.objects.get(id=announcement_id)
|
||||||
return self.success(AnnouncementSerializer(announcement).data)
|
return self.success(AnnouncementSerializer(announcement).data)
|
||||||
except Announcement.DoesNotExist:
|
except Announcement.DoesNotExist:
|
||||||
return self.error(_("Announcement does not exist"))
|
return self.error("Announcement does not exist")
|
||||||
announcement = Announcement.objects.all().order_by("-create_time")
|
announcement = Announcement.objects.all().order_by("-create_time")
|
||||||
if request.GET.get("visible") == "true":
|
if request.GET.get("visible") == "true":
|
||||||
announcement = announcement.filter(visible=True)
|
announcement = announcement.filter(visible=True)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from django.conf.urls import url
|
|||||||
from ..views import SMTPAPI, JudgeServerAPI, WebsiteConfigAPI
|
from ..views import SMTPAPI, JudgeServerAPI, WebsiteConfigAPI
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r"^smtp$", SMTPAPI.as_view(), name="smtp_admin_api"),
|
url(r"^smtp/?$", SMTPAPI.as_view(), name="smtp_admin_api"),
|
||||||
url(r"^website$", WebsiteConfigAPI.as_view(), name="website_config_api"),
|
url(r"^website/?$", WebsiteConfigAPI.as_view(), name="website_config_api"),
|
||||||
url(r"^judge_server", JudgeServerAPI.as_view(), name="judge_server_api")
|
url(r"^judge_server/?$", JudgeServerAPI.as_view(), name="judge_server_api")
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from django.conf.urls import url
|
|||||||
from ..views import JudgeServerHeartbeatAPI, LanguagesAPI, WebsiteConfigAPI
|
from ..views import JudgeServerHeartbeatAPI, LanguagesAPI, WebsiteConfigAPI
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r"^website$", WebsiteConfigAPI.as_view(), name="website_info_api"),
|
url(r"^website/?$", WebsiteConfigAPI.as_view(), name="website_info_api"),
|
||||||
url(r"^judge_server_heartbeat$", JudgeServerHeartbeatAPI.as_view(), name="judge_server_heartbeat_api"),
|
url(r"^judge_server_heartbeat/?$", JudgeServerHeartbeatAPI.as_view(), name="judge_server_heartbeat_api"),
|
||||||
url(r"^languages$", LanguagesAPI.as_view(), name="language_list_api")
|
url(r"^languages/?$", LanguagesAPI.as_view(), name="language_list_api")
|
||||||
]
|
]
|
||||||
|
|||||||
107
contest/tests.py
107
contest/tests.py
@@ -0,0 +1,107 @@
|
|||||||
|
import copy
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from utils.api._serializers import DateTimeTZField
|
||||||
|
from utils.api.tests import APITestCase
|
||||||
|
|
||||||
|
from .models import ContestAnnouncement, ContestRuleType
|
||||||
|
|
||||||
|
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",
|
||||||
|
"visible": True, "real_time_rank": True}
|
||||||
|
|
||||||
|
|
||||||
|
class ContestAPITest(APITestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.create_super_admin()
|
||||||
|
self.url = self.reverse("contest_api")
|
||||||
|
self.data = DEFAULT_CONTEST_DATA
|
||||||
|
|
||||||
|
def test_create_contest(self):
|
||||||
|
response = self.client.post(self.url, data=self.data)
|
||||||
|
self.assertSuccess(response)
|
||||||
|
return response
|
||||||
|
|
||||||
|
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"]
|
||||||
|
datetime_tz_field = DateTimeTZField()
|
||||||
|
for k in data.keys():
|
||||||
|
if isinstance(data[k], datetime):
|
||||||
|
data[k] = datetime_tz_field.to_representation(data[k])
|
||||||
|
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 ContestAnnouncementAPITest(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}
|
||||||
|
|
||||||
|
def create_contest(self):
|
||||||
|
url = self.reverse("contest_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)
|
||||||
|
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_list_api")
|
||||||
|
|
||||||
|
def create_contest_announcements(self):
|
||||||
|
contest_id = self.client.post(self.reverse("contest_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)
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ from django.conf.urls import url
|
|||||||
from ..views.admin import ContestAnnouncementAPI, ContestAPI
|
from ..views.admin import ContestAnnouncementAPI, ContestAPI
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r"^contest$", ContestAPI.as_view(), name="contest_api"),
|
url(r"^contest/?$", ContestAPI.as_view(), name="contest_api"),
|
||||||
url(r"^contest/announcement$", ContestAnnouncementAPI.as_view(), name="contest_announcement_admin_api")
|
url(r"^contest/announcement/?$", ContestAnnouncementAPI.as_view(), name="contest_announcement_admin_api")
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ from django.conf.urls import url
|
|||||||
from ..views.oj import ContestAnnouncementListAPI
|
from ..views.oj import ContestAnnouncementListAPI
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r"^contest$", ContestAnnouncementListAPI.as_view(), name="contest_list_api"),
|
url(r"^contest/?$", ContestAnnouncementListAPI.as_view(), name="contest_list_api"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ class ContestAPI(APIView):
|
|||||||
return self.error("Start time must occur earlier than end time")
|
return self.error("Start time must occur earlier than end time")
|
||||||
if not data["password"]:
|
if not data["password"]:
|
||||||
data["password"] = None
|
data["password"] = None
|
||||||
Contest.objects.create(**data)
|
contest = Contest.objects.create(**data)
|
||||||
return self.success()
|
return self.success(ContestSerializer(contest).data)
|
||||||
|
|
||||||
@validate_serializer(EditConetestSeriaizer)
|
@validate_serializer(EditConetestSeriaizer)
|
||||||
def put(self, request):
|
def put(self, request):
|
||||||
@@ -90,7 +90,8 @@ class ContestAnnouncementAPI(APIView):
|
|||||||
contest_announcement_id = request.GET.get("id")
|
contest_announcement_id = request.GET.get("id")
|
||||||
if contest_announcement_id:
|
if contest_announcement_id:
|
||||||
if request.user.is_admin():
|
if request.user.is_admin():
|
||||||
ContestAnnouncement.objects.filter(id=contest_announcement_id, contest__created_by=request.user).delete()
|
ContestAnnouncement.objects.filter(id=contest_announcement_id,
|
||||||
|
contest__created_by=request.user).delete()
|
||||||
else:
|
else:
|
||||||
ContestAnnouncement.objects.filter(id=contest_announcement_id).delete()
|
ContestAnnouncement.objects.filter(id=contest_announcement_id).delete()
|
||||||
return self.success()
|
return self.success()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
Django<1.10
|
django==1.9.6
|
||||||
djangorestframework==3.3.3
|
djangorestframework==3.4.0
|
||||||
pillow
|
pillow
|
||||||
jsonfield
|
jsonfield
|
||||||
otpauth
|
otpauth
|
||||||
@@ -7,3 +7,7 @@ flake8-quotes
|
|||||||
pytz
|
pytz
|
||||||
coverage
|
coverage
|
||||||
python-dateutil
|
python-dateutil
|
||||||
|
celery
|
||||||
|
Envelopes
|
||||||
|
qrcode
|
||||||
|
flake8-coding
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import copy
|
|
||||||
import base64
|
import base64
|
||||||
|
import copy
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
@@ -63,7 +63,7 @@ class FPSParser(object):
|
|||||||
if not lang:
|
if not lang:
|
||||||
raise ValueError("Invalid " + tag + ", language name is missed")
|
raise ValueError("Invalid " + tag + ", language name is missed")
|
||||||
problem[tag].append({"language": lang, "code": item.text})
|
problem[tag].append({"language": lang, "code": item.text})
|
||||||
elif tag == 'spj':
|
elif tag == "spj":
|
||||||
lang = item.attrib.get("language")
|
lang = item.attrib.get("language")
|
||||||
if not lang:
|
if not lang:
|
||||||
raise ValueError("Invalid spj, language name if missed")
|
raise ValueError("Invalid spj, language name if missed")
|
||||||
|
|||||||
@@ -7,11 +7,6 @@ DATABASES = {
|
|||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||||
},
|
|
||||||
# submission 的 name 和 engine 请勿修改,其他代码会用到
|
|
||||||
'submission': {
|
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
|
||||||
'NAME': os.path.join(BASE_DIR, 'db1.sqlite3'),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,3 +28,4 @@ ALLOWED_HOSTS = ["*"]
|
|||||||
|
|
||||||
TEST_CASE_DIR = "/tmp"
|
TEST_CASE_DIR = "/tmp"
|
||||||
|
|
||||||
|
LOG_PATH = "log/"
|
||||||
|
|||||||
@@ -12,15 +12,6 @@ DATABASES = {
|
|||||||
'PORT': 3306,
|
'PORT': 3306,
|
||||||
'USER': os.environ["MYSQL_ENV_MYSQL_USER"],
|
'USER': os.environ["MYSQL_ENV_MYSQL_USER"],
|
||||||
'PASSWORD': os.environ["MYSQL_ENV_MYSQL_ROOT_PASSWORD"]
|
'PASSWORD': os.environ["MYSQL_ENV_MYSQL_ROOT_PASSWORD"]
|
||||||
},
|
|
||||||
'submission': {
|
|
||||||
'NAME': 'oj_submission',
|
|
||||||
'ENGINE': 'django.db.backends.mysql',
|
|
||||||
'CONN_MAX_AGE': 0.1,
|
|
||||||
'HOST': os.environ["MYSQL_PORT_3306_TCP_ADDR"],
|
|
||||||
'PORT': 3306,
|
|
||||||
'USER': os.environ["MYSQL_ENV_MYSQL_USER"],
|
|
||||||
'PASSWORD': os.environ["MYSQL_ENV_MYSQL_ROOT_PASSWORD"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,3 +34,4 @@ ALLOWED_HOSTS = ['*']
|
|||||||
|
|
||||||
TEST_CASE_DIR = "/test_case"
|
TEST_CASE_DIR = "/test_case"
|
||||||
|
|
||||||
|
LOG_PATH = "log/"
|
||||||
|
|||||||
@@ -104,8 +104,6 @@ STATIC_URL = '/static/'
|
|||||||
|
|
||||||
AUTH_USER_MODEL = 'account.User'
|
AUTH_USER_MODEL = 'account.User'
|
||||||
|
|
||||||
LOG_PATH = "log/"
|
|
||||||
|
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
'version': 1,
|
'version': 1,
|
||||||
'disable_existing_loggers': True,
|
'disable_existing_loggers': True,
|
||||||
@@ -118,13 +116,13 @@ LOGGING = {
|
|||||||
'django_error': {
|
'django_error': {
|
||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'class': 'logging.handlers.RotatingFileHandler',
|
'class': 'logging.handlers.RotatingFileHandler',
|
||||||
'filename': LOG_PATH + 'django.log',
|
'filename': os.path.join(LOG_PATH, 'django.log'),
|
||||||
'formatter': 'standard'
|
'formatter': 'standard'
|
||||||
},
|
},
|
||||||
'app_info': {
|
'app_info': {
|
||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'class': 'logging.handlers.RotatingFileHandler',
|
'class': 'logging.handlers.RotatingFileHandler',
|
||||||
'filename': LOG_PATH + 'app_info.log',
|
'filename': os.path.join(LOG_PATH, 'app_info.log'),
|
||||||
'formatter': 'standard'
|
'formatter': 'standard'
|
||||||
},
|
},
|
||||||
'console': {
|
'console': {
|
||||||
@@ -152,11 +150,7 @@ LOGGING = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if DEBUG:
|
|
||||||
REST_FRAMEWORK = {
|
|
||||||
'TEST_REQUEST_DEFAULT_FORMAT': 'json'
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
'TEST_REQUEST_DEFAULT_FORMAT': 'json',
|
'TEST_REQUEST_DEFAULT_FORMAT': 'json',
|
||||||
'DEFAULT_RENDERER_CLASSES': (
|
'DEFAULT_RENDERER_CLASSES': (
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from django.conf.urls import include, url
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r"^api/", include("account.urls.oj")),
|
url(r"^api/", include("account.urls.oj")),
|
||||||
url(r"^api/admin/", include("account.urls.admin")),
|
url(r"^api/admin/", include("account.urls.admin")),
|
||||||
|
url(r"^api/account/", include("account.urls.user")),
|
||||||
url(r"^api/admin/", include("announcement.urls.admin")),
|
url(r"^api/admin/", include("announcement.urls.admin")),
|
||||||
url(r"^api/", include("conf.urls.oj")),
|
url(r"^api/", include("conf.urls.oj")),
|
||||||
url(r"^api/admin/", include("conf.urls.admin")),
|
url(r"^api/admin/", include("conf.urls.admin")),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from zipfile import ZipFile
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from utils.api.tests import APITestCase
|
from utils.api.tests import APITestCase
|
||||||
|
|
||||||
from .models import ProblemTag
|
from .models import ProblemTag
|
||||||
from .views.admin import TestCaseUploadAPI
|
from .views.admin import TestCaseUploadAPI
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
|
||||||
from ..views.admin import ProblemAPI, TestCaseUploadAPI, ContestProblemAPI
|
from ..views.admin import ContestProblemAPI, ProblemAPI, TestCaseUploadAPI
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r"^test_case/upload$", TestCaseUploadAPI.as_view(), name="test_case_upload_api"),
|
url(r"^test_case/upload/?$", TestCaseUploadAPI.as_view(), name="test_case_upload_api"),
|
||||||
url(r"^problem$", ProblemAPI.as_view(), name="problem_api"),
|
url(r"^problem/?$", ProblemAPI.as_view(), name="problem_api"),
|
||||||
url(r'contest/problem$', ContestProblemAPI.as_view(), name="contest_problem_api")
|
url(r"^contest/problem/?$", ContestProblemAPI.as_view(), name="contest_problem_api")
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ from django.conf.urls import url
|
|||||||
from ..views.oj import ProblemTagAPI
|
from ..views.oj import ProblemTagAPI
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r"^problem/tags$", ProblemTagAPI.as_view(), name="problem_tag_list_api")
|
url(r"^problem/tags/?$", ProblemTagAPI.as_view(), name="problem_tag_list_api")
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ from contest.models import Contest
|
|||||||
from utils.api import APIView, CSRFExemptAPIView, validate_serializer
|
from utils.api import APIView, CSRFExemptAPIView, validate_serializer
|
||||||
from utils.shortcuts import rand_str
|
from utils.shortcuts import rand_str
|
||||||
|
|
||||||
from ..models import Problem, ProblemRuleType, ProblemTag, ContestProblem
|
from ..models import ContestProblem, Problem, ProblemRuleType, ProblemTag
|
||||||
from ..serializers import (CreateProblemSerializer, EditProblemSerializer,
|
from ..serializers import (CreateContestProblemSerializer,
|
||||||
ProblemSerializer, TestCaseUploadForm, CreateContestProblemSerializer)
|
CreateProblemSerializer, EditProblemSerializer,
|
||||||
|
ProblemSerializer, TestCaseUploadForm)
|
||||||
|
|
||||||
|
|
||||||
class TestCaseUploadAPI(CSRFExemptAPIView):
|
class TestCaseUploadAPI(CSRFExemptAPIView):
|
||||||
@@ -162,15 +163,15 @@ class ProblemAPI(APIView):
|
|||||||
if problem_id:
|
if problem_id:
|
||||||
try:
|
try:
|
||||||
problem = Problem.objects.get(id=problem_id)
|
problem = Problem.objects.get(id=problem_id)
|
||||||
if not user.can_mgmt_all_problem():
|
if not user.can_mgmt_all_problem() and problem.created_by != user:
|
||||||
problem = problem.get(created_by=request.user)
|
return self.error("Problem does not exist")
|
||||||
return self.success(ProblemSerializer(problem).data)
|
return self.success(ProblemSerializer(problem).data)
|
||||||
except Problem.DoesNotExist:
|
except Problem.DoesNotExist:
|
||||||
return self.error("Problem does not exist")
|
return self.error("Problem does not exist")
|
||||||
|
|
||||||
problems = Problem.objects.all().order_by("-create_time")
|
problems = Problem.objects.all().order_by("-create_time")
|
||||||
if not user.can_mgmt_all_problem():
|
if not user.can_mgmt_all_problem():
|
||||||
problems = problems.filter(created_by=request.user)
|
problems = problems.filter(created_by=user)
|
||||||
keyword = request.GET.get("keyword")
|
keyword = request.GET.get("keyword")
|
||||||
if keyword:
|
if keyword:
|
||||||
problems = problems.filter(title__contains=keyword)
|
problems = problems.filter(title__contains=keyword)
|
||||||
@@ -185,8 +186,8 @@ class ProblemAPI(APIView):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
problem = Problem.objects.get(id=problem_id)
|
problem = Problem.objects.get(id=problem_id)
|
||||||
if not user.can_mgmt_all_problem():
|
if not user.can_mgmt_all_problem() and problem.created_by != user:
|
||||||
problem = problem.get(created_by=request.user)
|
return self.error("Problem does not exist")
|
||||||
except Problem.DoesNotExist:
|
except Problem.DoesNotExist:
|
||||||
return self.error("Problem does not exist")
|
return self.error("Problem does not exist")
|
||||||
|
|
||||||
@@ -290,7 +291,7 @@ class ContestProblemAPI(APIView):
|
|||||||
if problem_id:
|
if problem_id:
|
||||||
try:
|
try:
|
||||||
problem = ContestProblem.objects.get(id=problem_id)
|
problem = ContestProblem.objects.get(id=problem_id)
|
||||||
if request.user.is_admin() and problem.contest.created_by != user:
|
if user.is_admin() and problem.contest.created_by != user:
|
||||||
return self.error("Problem does not exist")
|
return self.error("Problem does not exist")
|
||||||
except ContestProblem.DoesNotExist:
|
except ContestProblem.DoesNotExist:
|
||||||
return self.error("Problem does not exist")
|
return self.error("Problem does not exist")
|
||||||
|
|||||||
10
requirements.txt
Normal file
10
requirements.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
django==1.9.6
|
||||||
|
djangorestframework==3.4.0
|
||||||
|
otpauth
|
||||||
|
pillow
|
||||||
|
python-dateutil
|
||||||
|
celery
|
||||||
|
Envelopes
|
||||||
|
pytz
|
||||||
|
jsonfield
|
||||||
|
qrcode
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
import sys
|
|
||||||
import getopt
|
import getopt
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
opts, args = getopt.getopt(sys.argv[1:], "cm:", ["coverage=", "module="])
|
opts, args = getopt.getopt(sys.argv[1:], "cm:", ["coverage=", "module="])
|
||||||
|
|
||||||
is_coverage = False
|
is_coverage = False
|
||||||
test_module = ""
|
test_module = ""
|
||||||
waf_addr = "127.0.0.1:50001"
|
|
||||||
setting = "oj.settings"
|
setting = "oj.settings"
|
||||||
|
|
||||||
for opt, arg in opts:
|
for opt, arg in opts:
|
||||||
|
|||||||
@@ -2,10 +2,33 @@ import logging
|
|||||||
import random
|
import random
|
||||||
|
|
||||||
from django.utils.crypto import get_random_string
|
from django.utils.crypto import get_random_string
|
||||||
|
from envelopes import Envelope
|
||||||
|
|
||||||
|
from conf.models import SMTPConfig
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def send_email(from_name, to_email, to_name, subject, content):
|
||||||
|
smtp = SMTPConfig.objects.first()
|
||||||
|
if not smtp:
|
||||||
|
return
|
||||||
|
envlope = Envelope(from_addr=(smtp.email, from_name),
|
||||||
|
to_addr=(to_email, to_name),
|
||||||
|
subject=subject,
|
||||||
|
html_body=content)
|
||||||
|
try:
|
||||||
|
envlope.send(smtp.server,
|
||||||
|
login=smtp.email,
|
||||||
|
password=smtp.password,
|
||||||
|
port=smtp.port,
|
||||||
|
tls=smtp.tls)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def rand_str(length=32, type="lower_hex"):
|
def rand_str(length=32, type="lower_hex"):
|
||||||
"""
|
"""
|
||||||
生成指定长度的随机字符串或者数字, 可以用于密钥等安全场景
|
生成指定长度的随机字符串或者数字, 可以用于密钥等安全场景
|
||||||
|
|||||||
Reference in New Issue
Block a user