From 60ebece8537a47932b7ca439f906d5d2625f3862 Mon Sep 17 00:00:00 2001 From: zhanghedr Date: Sun, 26 Feb 2017 23:18:22 -0500 Subject: [PATCH 01/16] add contest tests --- contest/tests.py | 107 +++++++++++++++++++++++++++++++++++++++++ contest/views/admin.py | 8 +-- 2 files changed, 111 insertions(+), 4 deletions(-) diff --git a/contest/tests.py b/contest/tests.py index e69de29..11d29b2 100644 --- a/contest/tests.py +++ b/contest/tests.py @@ -0,0 +1,107 @@ +from datetime import datetime, timedelta + +import copy +from django.utils import timezone + +from utils.api._serializers import DateTimeTZField +from utils.api.tests import APITestCase +from .models import ContestAnnouncement +from .models import 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) diff --git a/contest/views/admin.py b/contest/views/admin.py index 15007ad..4f479cc 100644 --- a/contest/views/admin.py +++ b/contest/views/admin.py @@ -1,7 +1,6 @@ import dateutil.parser from utils.api import APIView, validate_serializer - from ..models import Contest, ContestAnnouncement from ..serializers import (ContestAnnouncementSerializer, ContestSerializer, CreateConetestSeriaizer, @@ -20,8 +19,8 @@ class ContestAPI(APIView): return self.error("Start time must occur earlier than end time") if not data["password"]: data["password"] = None - Contest.objects.create(**data) - return self.success() + contest = Contest.objects.create(**data) + return self.success(ContestSerializer(contest).data) @validate_serializer(EditConetestSeriaizer) def put(self, request): @@ -90,7 +89,8 @@ class ContestAnnouncementAPI(APIView): contest_announcement_id = request.GET.get("id") if contest_announcement_id: 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: ContestAnnouncement.objects.filter(id=contest_announcement_id).delete() return self.success() From 808b92780f87b3a83fb39b4652f3b4a6d90dda6e Mon Sep 17 00:00:00 2001 From: zhanghedr Date: Sun, 26 Feb 2017 23:35:46 -0500 Subject: [PATCH 02/16] fix problem admin filter bug --- problem/urls/admin.py | 2 +- problem/views/admin.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/problem/urls/admin.py b/problem/urls/admin.py index a0e0b66..bfd66c2 100644 --- a/problem/urls/admin.py +++ b/problem/urls/admin.py @@ -5,5 +5,5 @@ from ..views.admin import ProblemAPI, TestCaseUploadAPI, ContestProblemAPI urlpatterns = [ url(r"^test_case/upload$", TestCaseUploadAPI.as_view(), name="test_case_upload_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") ] diff --git a/problem/views/admin.py b/problem/views/admin.py index 2fc8cf7..9e306d6 100644 --- a/problem/views/admin.py +++ b/problem/views/admin.py @@ -162,15 +162,15 @@ class ProblemAPI(APIView): if problem_id: try: problem = Problem.objects.get(id=problem_id) - if not user.can_mgmt_all_problem(): - problem = problem.get(created_by=request.user) + if not user.can_mgmt_all_problem() and problem.created_by != user: + return self.error("Problem does not exist") return self.success(ProblemSerializer(problem).data) except Problem.DoesNotExist: return self.error("Problem does not exist") problems = Problem.objects.all().order_by("-create_time") 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") if keyword: problems = problems.filter(title__contains=keyword) @@ -185,8 +185,8 @@ class ProblemAPI(APIView): try: problem = Problem.objects.get(id=problem_id) - if not user.can_mgmt_all_problem(): - problem = problem.get(created_by=request.user) + if not user.can_mgmt_all_problem() and problem.created_by != user: + return self.error("Problem does not exist") except Problem.DoesNotExist: return self.error("Problem does not exist") @@ -290,7 +290,7 @@ class ContestProblemAPI(APIView): if problem_id: try: 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") except ContestProblem.DoesNotExist: return self.error("Problem does not exist") From 4256f088cb750410454855eeef77ff0fceb630c8 Mon Sep 17 00:00:00 2001 From: zhanghedr Date: Sun, 26 Feb 2017 23:54:25 -0500 Subject: [PATCH 03/16] fix flake8 --- fps/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fps/parser.py b/fps/parser.py index 44b37da..73bca86 100644 --- a/fps/parser.py +++ b/fps/parser.py @@ -63,7 +63,7 @@ class FPSParser(object): if not lang: raise ValueError("Invalid " + tag + ", language name is missed") problem[tag].append({"language": lang, "code": item.text}) - elif tag == 'spj': + elif tag == "spj": lang = item.attrib.get("language") if not lang: raise ValueError("Invalid spj, language name if missed") From df8781b9b36e6d946d9fa00f6e01f1a0261464ca Mon Sep 17 00:00:00 2001 From: Chiaki Date: Sat, 15 Apr 2017 14:22:37 +0800 Subject: [PATCH 04/16] Add requirements.txt --- requirements.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c837631 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +django==1.9.6 +djangorestframework==3.4.0 +dateutil +otpauth +pillow +python-dateutil \ No newline at end of file From 8a68ad7ac99913ca81ac4cb285f264f5e39a606c Mon Sep 17 00:00:00 2001 From: Chiaki Date: Sun, 16 Apr 2017 10:15:26 +0800 Subject: [PATCH 05/16] add account view for preview --- account/urls/user.py | 10 +++++++++ account/views/user.py | 47 +++++++++++++++++++++++++++++++++++++++++++ oj/urls.py | 1 + 3 files changed, 58 insertions(+) create mode 100644 account/urls/user.py create mode 100644 account/views/user.py diff --git a/account/urls/user.py b/account/urls/user.py new file mode 100644 index 0000000..7ddca03 --- /dev/null +++ b/account/urls/user.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from django.conf.urls import url + +from ..views.user import UserProfileAPI + +urlpatterns = [ + url(r"^profile$", UserProfileAPI.as_view(), name="user_profile_api"), +] diff --git a/account/views/user.py b/account/views/user.py new file mode 100644 index 0000000..f20a77b --- /dev/null +++ b/account/views/user.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from django.utils.translation import ugettext as _ + +from utils.api import APIView, validate_serializer + +from ..decorators import login_required +from ..serializers import EditUserSerializer, 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")) + + diff --git a/oj/urls.py b/oj/urls.py index b8cc62d..79e6b03 100644 --- a/oj/urls.py +++ b/oj/urls.py @@ -3,6 +3,7 @@ from django.conf.urls import include, url urlpatterns = [ url(r"^api/", include("account.urls.oj")), 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/", include("conf.urls.oj")), url(r"^api/admin/", include("conf.urls.admin")), From fd1685c26d2ddaa02fa85f602bdc30d6dd789d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E6=89=AC?= Date: Sun, 16 Apr 2017 13:08:11 +0800 Subject: [PATCH 06/16] add slack ci result push --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 51d41d6..f51b0b5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,3 +12,5 @@ script: - flake8 . - coverage run --include="$PWD/*" manage.py test - coverage report +notifications: + slack: onlinejudgeteam:BzBz8UFgmS5crpiblof17K2W From e3692c232916c5ce6c456e83143847629ef7a300 Mon Sep 17 00:00:00 2001 From: Chiaki Date: Tue, 18 Apr 2017 11:57:57 +0800 Subject: [PATCH 07/16] Add reset password api --- account/serializers.py | 6 +++++ account/urls/oj.py | 10 ++++++-- account/urls/user.py | 3 ++- account/views/oj.py | 53 +++++++++++++++++++++++++++++++++++++++++- account/views/user.py | 16 ++++++------- 5 files changed, 75 insertions(+), 13 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index fe128f6..a16caa8 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -44,3 +44,9 @@ class EditUserSerializer(serializers.Serializer): open_api = serializers.BooleanField() two_factor_auth = serializers.BooleanField() is_disabled = serializers.BooleanField() + + +class ApplyResetPasswordSerializer(serializers.Serializer): + email = serializers.EmailField() + captcha = serializers.CharField(max_length=4, min_length=4) + diff --git a/account/urls/oj.py b/account/urls/oj.py index 6a1ef6c..899f26a 100644 --- a/account/urls/oj.py +++ b/account/urls/oj.py @@ -1,9 +1,15 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + from django.conf.urls import url -from ..views.oj import UserChangePasswordAPI, UserLoginAPI, UserRegisterAPI +from ..views.oj import (UserChangePasswordAPI, UserLoginAPI, UserRegisterAPI, + ApplyResetPasswordAPI, ResetPasswordAPI) urlpatterns = [ url(r"^login$", UserLoginAPI.as_view(), name="user_login_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") ] diff --git a/account/urls/user.py b/account/urls/user.py index 7ddca03..518b952 100644 --- a/account/urls/user.py +++ b/account/urls/user.py @@ -3,8 +3,9 @@ from django.conf.urls import url -from ..views.user import UserProfileAPI +from ..views.user import UserInfoAPI ,UserProfileAPI urlpatterns = [ + url(r"^user", UserInfoAPI.as_view(), name="user_info_api"), url(r"^profile$", UserProfileAPI.as_view(), name="user_profile_api"), ] diff --git a/account/views/oj.py b/account/views/oj.py index 3e48a12..6d47dcb 100644 --- a/account/views/oj.py +++ b/account/views/oj.py @@ -1,15 +1,25 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import codecs +from datetime import timedelta + from django.contrib import auth +from django.conf import settings from django.core.exceptions import MultipleObjectsReturned from django.utils.translation import ugettext as _ +from django.utils.timezone import now from otpauth import OtpAuth from utils.api import APIView, validate_serializer from utils.captcha import Captcha +from utils.shortcuts import rand_str from ..decorators import login_required from ..models import User, UserProfile from ..serializers import (UserChangePasswordSerializer, UserLoginSerializer, - UserRegisterSerializer) + UserRegisterSerializer, + ApplyResetPasswordSerializer) class UserLoginAPI(APIView): @@ -92,3 +102,44 @@ class UserChangePasswordAPI(APIView): return self.success(_("Succeeded")) else: return self.error(_("Invalid old password")) + + +class ApplyResetPasswordAPI(APIView): + @validate_serializer(ApplyResetPasswordSerializer) + 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(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() + # TODO:email template + # TODO:send email + return self.success(_("Succeeded")) + + +class ResetPasswordAPI(APIView): + 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")) diff --git a/account/views/user.py b/account/views/user.py index f20a77b..190c39c 100644 --- a/account/views/user.py +++ b/account/views/user.py @@ -9,13 +9,13 @@ from ..decorators import login_required from ..serializers import EditUserSerializer, UserSerializer -# class UserInfoAPI(APIView): -# @login_required -# def get(self, request): -# """ -# Return user info api -# """ -# return self.success(UserSerializer(request.user).data) +class UserInfoAPI(APIView): + @login_required + def get(self, request): + """ + Return user info api + """ + return self.success(UserSerializer(request.user).data) class UserProfileAPI(APIView): @@ -43,5 +43,3 @@ class UserProfileAPI(APIView): # Timezone & language 暂时不加 user_profile.save() return self.success(_("Succeeded")) - - From a9b25b872a2e71b3173191b2bcdabb76699e231c Mon Sep 17 00:00:00 2001 From: Chiaki Date: Tue, 18 Apr 2017 12:05:07 +0800 Subject: [PATCH 08/16] for pass ci... --- account/serializers.py | 1 - account/urls/oj.py | 2 +- account/urls/user.py | 4 ++-- account/views/oj.py | 4 +--- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index a16caa8..8061d5d 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -49,4 +49,3 @@ class EditUserSerializer(serializers.Serializer): class ApplyResetPasswordSerializer(serializers.Serializer): email = serializers.EmailField() captcha = serializers.CharField(max_length=4, min_length=4) - diff --git a/account/urls/oj.py b/account/urls/oj.py index 899f26a..1a66339 100644 --- a/account/urls/oj.py +++ b/account/urls/oj.py @@ -11,5 +11,5 @@ urlpatterns = [ url(r"^register$", UserRegisterAPI.as_view(), name="user_register_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") + url(r"^reset_password$", ResetPasswordAPI.as_view(), name="apply_reset_password_api") ] diff --git a/account/urls/user.py b/account/urls/user.py index 518b952..deba25b 100644 --- a/account/urls/user.py +++ b/account/urls/user.py @@ -3,9 +3,9 @@ from django.conf.urls import url -from ..views.user import UserInfoAPI ,UserProfileAPI +from ..views.user import 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"^profile$", UserProfileAPI.as_view(), name="user_profile_api") ] diff --git a/account/views/oj.py b/account/views/oj.py index 6d47dcb..1d63f0b 100644 --- a/account/views/oj.py +++ b/account/views/oj.py @@ -1,11 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import codecs from datetime import timedelta from django.contrib import auth -from django.conf import settings from django.core.exceptions import MultipleObjectsReturned from django.utils.translation import ugettext as _ from django.utils.timezone import now @@ -116,7 +114,7 @@ class ApplyResetPasswordAPI(APIView): 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: + 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() From c6f49c1fe7477c1bcc5ac75c4b8d5e7627182c65 Mon Sep 17 00:00:00 2001 From: Chiaki Date: Tue, 18 Apr 2017 14:34:23 +0800 Subject: [PATCH 09/16] Add mail module and fix reset password api --- account/tasks.py | 10 +++ account/templates/reset_password_email.html | 78 +++++++++++++++++++++ account/views/oj.py | 21 +++++- requirements.txt | 4 +- reset_password_email.html | 0 utils/mail.py | 21 ++++++ 6 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 account/tasks.py create mode 100644 account/templates/reset_password_email.html create mode 100644 reset_password_email.html create mode 100644 utils/mail.py diff --git a/account/tasks.py b/account/tasks.py new file mode 100644 index 0000000..6470b70 --- /dev/null +++ b/account/tasks.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from celery import shared_task +from utils.mail import send_email + + +@shared_task +def _send_email(from_name, to_email, to_name, subject, content): + send_email(from_name, to_email, to_name, subject, content) diff --git a/account/templates/reset_password_email.html b/account/templates/reset_password_email.html new file mode 100644 index 0000000..5a0b591 --- /dev/null +++ b/account/templates/reset_password_email.html @@ -0,0 +1,78 @@ + + + + + + +
+ + + + + + +
+ {{ website_name }} 登录信息找回 +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Hello, {{ username }}: +
+ 您刚刚在 {{ website_name }} 申请了找回登录信息服务。 +
+ 请在30分钟内点击下面链接设置您的新密码: +
+ 重置密码 +
+ 如果上面的链接点击无效,请复制以下链接至浏览器的地址栏直接打开。 +
+ + {{ link }} + +
+ 如果您没有提出过该申请,请忽略此邮件。有可能是其他用户误填了您的邮件地址,我们不会对你的帐户进行任何修改。 + 请不要向他人透露本邮件的内容,否则可能会导致您的账号被盗。 +
+
\ No newline at end of file diff --git a/account/views/oj.py b/account/views/oj.py index 1d63f0b..86f82d6 100644 --- a/account/views/oj.py +++ b/account/views/oj.py @@ -1,14 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import os + from datetime import timedelta from django.contrib import auth +from django.conf import settings from django.core.exceptions import MultipleObjectsReturned from django.utils.translation import ugettext as _ from django.utils.timezone import now from otpauth import OtpAuth +from conf.models import WebsiteConfig from utils.api import APIView, validate_serializer from utils.captcha import Captcha from utils.shortcuts import rand_str @@ -18,6 +22,7 @@ from ..models import User, UserProfile from ..serializers import (UserChangePasswordSerializer, UserLoginSerializer, UserRegisterSerializer, ApplyResetPasswordSerializer) +from ..tasks import _send_email class UserLoginAPI(APIView): @@ -114,14 +119,24 @@ class ApplyResetPasswordAPI(APIView): 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: + 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() - # TODO:email template - # TODO:send email + 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) + config = WebsiteConfig.objects.first() + _send_email.delay(config.name, + user.email, + user.username, + config.name + " 登录信息找回邮件", + email_template) return self.success(_("Succeeded")) diff --git a/requirements.txt b/requirements.txt index c837631..f17f7a4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,6 @@ djangorestframework==3.4.0 dateutil otpauth pillow -python-dateutil \ No newline at end of file +python-dateutil +celery +Envelopes \ No newline at end of file diff --git a/reset_password_email.html b/reset_password_email.html new file mode 100644 index 0000000..e69de29 diff --git a/utils/mail.py b/utils/mail.py new file mode 100644 index 0000000..6ff7e7d --- /dev/null +++ b/utils/mail.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from envelopes import Envelope + +from conf.models import SMTPConfig + + +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) + envlope.send(smtp.server, + login=smtp.email, + password=smtp.password, + port=smtp.port, + tls=smtp.tls) From 2c4518e80303bc3227969aa99828d888148e195b Mon Sep 17 00:00:00 2001 From: Chiaki Date: Tue, 18 Apr 2017 14:40:36 +0800 Subject: [PATCH 10/16] Fix deploy requirements.txt --- deploy/requirements.txt | 5 +++-- requirements.txt | 5 +++-- reset_password_email.html | 0 3 files changed, 6 insertions(+), 4 deletions(-) delete mode 100644 reset_password_email.html diff --git a/deploy/requirements.txt b/deploy/requirements.txt index ae2a33c..54b4aa3 100644 --- a/deploy/requirements.txt +++ b/deploy/requirements.txt @@ -1,5 +1,5 @@ -Django<1.10 -djangorestframework==3.3.3 +django==1.9.6 +djangorestframework==3.4.0 pillow jsonfield otpauth @@ -7,3 +7,4 @@ flake8-quotes pytz coverage python-dateutil +celery \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index f17f7a4..a972bb2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,9 @@ django==1.9.6 djangorestframework==3.4.0 -dateutil otpauth pillow python-dateutil celery -Envelopes \ No newline at end of file +Envelopes +pytz +jsonfield \ No newline at end of file diff --git a/reset_password_email.html b/reset_password_email.html deleted file mode 100644 index e69de29..0000000 From 1a4cb9332ec8fb02b5b75b7c12b3cd543a0b5bff Mon Sep 17 00:00:00 2001 From: Chiaki Date: Tue, 18 Apr 2017 14:42:28 +0800 Subject: [PATCH 11/16] Fix deploy requirements.txt --- deploy/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deploy/requirements.txt b/deploy/requirements.txt index 54b4aa3..98ef6ed 100644 --- a/deploy/requirements.txt +++ b/deploy/requirements.txt @@ -7,4 +7,5 @@ flake8-quotes pytz coverage python-dateutil -celery \ No newline at end of file +celery +Envelopes \ No newline at end of file From ee05af8e5a3ee8daa5ee9e0be1652438f79e2503 Mon Sep 17 00:00:00 2001 From: Chiaki Date: Tue, 18 Apr 2017 15:19:26 +0800 Subject: [PATCH 12/16] Add sso and 2fa api --- account/serializers.py | 15 +++++++ account/urls/user.py | 9 ++-- account/views/oj.py | 9 ++-- account/views/user.py | 91 ++++++++++++++++++++++++++++++++++++++++- deploy/requirements.txt | 3 +- requirements.txt | 3 +- 6 files changed, 119 insertions(+), 11 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index 8061d5d..112d59d 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -49,3 +49,18 @@ class EditUserSerializer(serializers.Serializer): 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() diff --git a/account/urls/user.py b/account/urls/user.py index deba25b..307b94e 100644 --- a/account/urls/user.py +++ b/account/urls/user.py @@ -3,9 +3,12 @@ from django.conf.urls import url -from ..views.user import UserInfoAPI, UserProfileAPI +from ..views.user import (UserInfoAPI, UserProfileAPI, + SSOAPI, TwoFactorAuthAPI) urlpatterns = [ - url(r"^user", UserInfoAPI.as_view(), name="user_info_api"), - url(r"^profile$", UserProfileAPI.as_view(), name="user_profile_api") + url(r"^user$", UserInfoAPI.as_view(), name="user_info_api"), + url(r"^profile$", UserProfileAPI.as_view(), name="user_profile_api"), + url(r"^sso$", SSOAPI.as_view(), name="sso_api"), + url(r"^two_factor_auth$", TwoFactorAuthAPI.as_view(), name="two_factor_auth_api") ] diff --git a/account/views/oj.py b/account/views/oj.py index 86f82d6..125fc69 100644 --- a/account/views/oj.py +++ b/account/views/oj.py @@ -1,16 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import os - from datetime import timedelta +from otpauth import OtpAuth from django.contrib import auth from django.conf import settings from django.core.exceptions import MultipleObjectsReturned from django.utils.translation import ugettext as _ from django.utils.timezone import now -from otpauth import OtpAuth from conf.models import WebsiteConfig from utils.api import APIView, validate_serializer @@ -21,7 +19,7 @@ from ..decorators import login_required from ..models import User, UserProfile from ..serializers import (UserChangePasswordSerializer, UserLoginSerializer, UserRegisterSerializer, - ApplyResetPasswordSerializer) + ApplyResetPasswordSerializer, ResetPasswordSerializer) from ..tasks import _send_email @@ -112,6 +110,7 @@ class ApplyResetPasswordAPI(APIView): 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: @@ -131,7 +130,6 @@ class ApplyResetPasswordAPI(APIView): replace("{{ website_name }}", settings.WEBSITE_INFO["website_name"]). \ replace("{{ link }}", settings.WEBSITE_INFO["url"] + "/reset_password/t/" + user.reset_password_token) - config = WebsiteConfig.objects.first() _send_email.delay(config.name, user.email, user.username, @@ -141,6 +139,7 @@ class ApplyResetPasswordAPI(APIView): class ResetPasswordAPI(APIView): + @validate_serializer(ResetPasswordSerializer) def post(self, request): data = request.data captcha = Captcha(request) diff --git a/account/views/user.py b/account/views/user.py index 190c39c..2388c59 100644 --- a/account/views/user.py +++ b/account/views/user.py @@ -1,12 +1,23 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import qrcode + +from io import StringIO +from otpauth import OtpAuth + +from django.conf import settings +from django.http import HttpResponse from django.utils.translation import ugettext as _ +from conf.models import WebsiteConfig from utils.api import APIView, validate_serializer +from utils.shortcuts import rand_str from ..decorators import login_required -from ..serializers import EditUserSerializer, UserSerializer +from ..models import User +from ..serializers import (EditUserSerializer, UserSerializer, + SSOSerializer, TwoFactorAuthCodeSerializer) class UserInfoAPI(APIView): @@ -43,3 +54,81 @@ class UserProfileAPI(APIView): # Timezone & language 暂时不加 user_profile.save() return self.success(_("Succeeded")) + + +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")) diff --git a/deploy/requirements.txt b/deploy/requirements.txt index 98ef6ed..308698b 100644 --- a/deploy/requirements.txt +++ b/deploy/requirements.txt @@ -8,4 +8,5 @@ pytz coverage python-dateutil celery -Envelopes \ No newline at end of file +Envelopes +qrcode \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index a972bb2..9b0d805 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ python-dateutil celery Envelopes pytz -jsonfield \ No newline at end of file +jsonfield +qrcode \ No newline at end of file From 1fcd13b5e12359a150e7bc28bac43e17cd201f48 Mon Sep 17 00:00:00 2001 From: Chiaki Date: Tue, 18 Apr 2017 15:35:08 +0800 Subject: [PATCH 13/16] Add avatar upload api --- account/urls/user.py | 3 ++- account/views/user.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/account/urls/user.py b/account/urls/user.py index 307b94e..3eb9252 100644 --- a/account/urls/user.py +++ b/account/urls/user.py @@ -3,12 +3,13 @@ from django.conf.urls import url -from ..views.user import (UserInfoAPI, UserProfileAPI, +from ..views.user import (UserInfoAPI, UserProfileAPI, AvatarUploadAPI, SSOAPI, TwoFactorAuthAPI) 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") ] diff --git a/account/views/user.py b/account/views/user.py index 2388c59..7cbd791 100644 --- a/account/views/user.py +++ b/account/views/user.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import os import qrcode from io import StringIO @@ -56,6 +57,24 @@ class UserProfileAPI(APIView): 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): From 3caf2d9d2c1fbdc71d6b4d3bee98e3004f998a15 Mon Sep 17 00:00:00 2001 From: Chiaki Date: Tue, 18 Apr 2017 15:39:49 +0800 Subject: [PATCH 14/16] Allow multiple ways to access url and fix ci --- account/urls/admin.py | 2 +- account/urls/oj.py | 10 +++++----- account/urls/user.py | 10 +++++----- account/views/user.py | 4 ++-- announcement/urls/admin.py | 2 +- conf/urls/admin.py | 6 +++--- conf/urls/oj.py | 6 +++--- contest/urls/admin.py | 4 ++-- contest/urls/oj.py | 2 +- problem/urls/admin.py | 6 +++--- problem/urls/oj.py | 2 +- 11 files changed, 27 insertions(+), 27 deletions(-) diff --git a/account/urls/admin.py b/account/urls/admin.py index 372ba24..b10741e 100644 --- a/account/urls/admin.py +++ b/account/urls/admin.py @@ -3,5 +3,5 @@ from django.conf.urls import url from ..views.admin import UserAdminAPI urlpatterns = [ - url(r"^user$", UserAdminAPI.as_view(), name="user_admin_api"), + url(r"^user/?$", UserAdminAPI.as_view(), name="user_admin_api"), ] diff --git a/account/urls/oj.py b/account/urls/oj.py index 1a66339..e73311f 100644 --- a/account/urls/oj.py +++ b/account/urls/oj.py @@ -7,9 +7,9 @@ from ..views.oj import (UserChangePasswordAPI, UserLoginAPI, UserRegisterAPI, ApplyResetPasswordAPI, ResetPasswordAPI) urlpatterns = [ - url(r"^login$", UserLoginAPI.as_view(), name="user_login_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"^apply_reset_password$", ApplyResetPasswordAPI.as_view(), name="apply_reset_password_api"), - url(r"^reset_password$", ResetPasswordAPI.as_view(), name="apply_reset_password_api") + url(r"^login/?$", UserLoginAPI.as_view(), name="user_login_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"^apply_reset_password/?$", ApplyResetPasswordAPI.as_view(), name="apply_reset_password_api"), + url(r"^reset_password/?$", ResetPasswordAPI.as_view(), name="apply_reset_password_api") ] diff --git a/account/urls/user.py b/account/urls/user.py index 3eb9252..3ec765a 100644 --- a/account/urls/user.py +++ b/account/urls/user.py @@ -7,9 +7,9 @@ from ..views.user import (UserInfoAPI, UserProfileAPI, AvatarUploadAPI, SSOAPI, TwoFactorAuthAPI) 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") + 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") ] diff --git a/account/views/user.py b/account/views/user.py index 7cbd791..d3bcacf 100644 --- a/account/views/user.py +++ b/account/views/user.py @@ -122,9 +122,9 @@ class TwoFactorAuthAPI(APIView): config = WebsiteConfig.objects.first() image = qrcode.make(OtpAuth(token).to_uri("totp", config.base_url, config.name)) buf = StringIO() - image.save(buf, 'gif') + image.save(buf, "gif") - return HttpResponse(buf.getvalue(), 'image/gif') + return HttpResponse(buf.getvalue(), "image/gif") @login_required @validate_serializer(TwoFactorAuthCodeSerializer) diff --git a/announcement/urls/admin.py b/announcement/urls/admin.py index fefc160..6b9ce0f 100644 --- a/announcement/urls/admin.py +++ b/announcement/urls/admin.py @@ -3,5 +3,5 @@ from django.conf.urls import url from ..views import AnnouncementAdminAPI urlpatterns = [ - url(r"^announcement$", AnnouncementAdminAPI.as_view(), name="announcement_admin_api"), + url(r"^announcement/?$", AnnouncementAdminAPI.as_view(), name="announcement_admin_api"), ] diff --git a/conf/urls/admin.py b/conf/urls/admin.py index dcb5c6f..be24272 100644 --- a/conf/urls/admin.py +++ b/conf/urls/admin.py @@ -3,7 +3,7 @@ from django.conf.urls import url from ..views import SMTPAPI, JudgeServerAPI, WebsiteConfigAPI urlpatterns = [ - url(r"^smtp$", SMTPAPI.as_view(), name="smtp_admin_api"), - url(r"^website$", WebsiteConfigAPI.as_view(), name="website_config_api"), - url(r"^judge_server", JudgeServerAPI.as_view(), name="judge_server_api") + url(r"^smtp/?$", SMTPAPI.as_view(), name="smtp_admin_api"), + url(r"^website/?$", WebsiteConfigAPI.as_view(), name="website_config_api"), + url(r"^judge_server/?$", JudgeServerAPI.as_view(), name="judge_server_api") ] diff --git a/conf/urls/oj.py b/conf/urls/oj.py index b5a06e5..3e6d757 100644 --- a/conf/urls/oj.py +++ b/conf/urls/oj.py @@ -3,7 +3,7 @@ from django.conf.urls import url from ..views import JudgeServerHeartbeatAPI, LanguagesAPI, WebsiteConfigAPI urlpatterns = [ - 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"^languages$", LanguagesAPI.as_view(), name="language_list_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"^languages/?$", LanguagesAPI.as_view(), name="language_list_api") ] diff --git a/contest/urls/admin.py b/contest/urls/admin.py index d9ea9c9..2a7705a 100644 --- a/contest/urls/admin.py +++ b/contest/urls/admin.py @@ -3,6 +3,6 @@ from django.conf.urls import url from ..views.admin import ContestAnnouncementAPI, ContestAPI urlpatterns = [ - url(r"^contest$", ContestAPI.as_view(), name="contest_api"), - url(r"^contest/announcement$", ContestAnnouncementAPI.as_view(), name="contest_announcement_admin_api") + url(r"^contest/?$", ContestAPI.as_view(), name="contest_api"), + url(r"^contest/announcement/?$", ContestAnnouncementAPI.as_view(), name="contest_announcement_admin_api") ] diff --git a/contest/urls/oj.py b/contest/urls/oj.py index e2e48ca..bfc80b8 100644 --- a/contest/urls/oj.py +++ b/contest/urls/oj.py @@ -3,5 +3,5 @@ from django.conf.urls import url from ..views.oj import ContestAnnouncementListAPI urlpatterns = [ - url(r"^contest$", ContestAnnouncementListAPI.as_view(), name="contest_list_api"), + url(r"^contest/?$", ContestAnnouncementListAPI.as_view(), name="contest_list_api"), ] diff --git a/problem/urls/admin.py b/problem/urls/admin.py index bfd66c2..7ea423e 100644 --- a/problem/urls/admin.py +++ b/problem/urls/admin.py @@ -3,7 +3,7 @@ from django.conf.urls import url from ..views.admin import ProblemAPI, TestCaseUploadAPI, ContestProblemAPI urlpatterns = [ - url(r"^test_case/upload$", TestCaseUploadAPI.as_view(), name="test_case_upload_api"), - url(r"^problem$", ProblemAPI.as_view(), name="problem_api"), - url(r"^contest/problem$", ContestProblemAPI.as_view(), name="contest_problem_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"^contest/problem/?$", ContestProblemAPI.as_view(), name="contest_problem_api") ] diff --git a/problem/urls/oj.py b/problem/urls/oj.py index d99155a..a7613f1 100644 --- a/problem/urls/oj.py +++ b/problem/urls/oj.py @@ -3,5 +3,5 @@ from django.conf.urls import url from ..views.oj import ProblemTagAPI 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") ] From 8128e076f2169e492f3caed38a9b75d38b6b41a6 Mon Sep 17 00:00:00 2001 From: virusdefender Date: Wed, 19 Apr 2017 01:37:10 +0800 Subject: [PATCH 15/16] format code --- account/tasks.py | 4 +--- account/urls/oj.py | 7 ++----- account/urls/user.py | 7 ++----- account/views/oj.py | 16 +++++++--------- account/views/user.py | 12 ++++-------- contest/tests.py | 6 +++--- contest/views/admin.py | 1 + deploy/requirements.txt | 3 ++- fps/parser.py | 2 +- oj/local_settings.py | 6 +----- oj/server_settings.py | 10 +--------- oj/settings.py | 24 +++++++++--------------- problem/tests.py | 1 + problem/urls/admin.py | 2 +- problem/views/admin.py | 7 ++++--- run_test.py | 3 +-- utils/mail.py | 3 --- 17 files changed, 41 insertions(+), 73 deletions(-) diff --git a/account/tasks.py b/account/tasks.py index 6470b70..00248ba 100644 --- a/account/tasks.py +++ b/account/tasks.py @@ -1,7 +1,5 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - from celery import shared_task + from utils.mail import send_email diff --git a/account/urls/oj.py b/account/urls/oj.py index e73311f..fa47e33 100644 --- a/account/urls/oj.py +++ b/account/urls/oj.py @@ -1,10 +1,7 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - from django.conf.urls import url -from ..views.oj import (UserChangePasswordAPI, UserLoginAPI, UserRegisterAPI, - ApplyResetPasswordAPI, ResetPasswordAPI) +from ..views.oj import (ApplyResetPasswordAPI, ResetPasswordAPI, + UserChangePasswordAPI, UserLoginAPI, UserRegisterAPI) urlpatterns = [ url(r"^login/?$", UserLoginAPI.as_view(), name="user_login_api"), diff --git a/account/urls/user.py b/account/urls/user.py index 3ec765a..1676ddc 100644 --- a/account/urls/user.py +++ b/account/urls/user.py @@ -1,10 +1,7 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - from django.conf.urls import url -from ..views.user import (UserInfoAPI, UserProfileAPI, AvatarUploadAPI, - SSOAPI, TwoFactorAuthAPI) +from ..views.user import (SSOAPI, AvatarUploadAPI, TwoFactorAuthAPI, + UserInfoAPI, UserProfileAPI) urlpatterns = [ url(r"^user/?$", UserInfoAPI.as_view(), name="user_info_api"), diff --git a/account/views/oj.py b/account/views/oj.py index 125fc69..48a35c3 100644 --- a/account/views/oj.py +++ b/account/views/oj.py @@ -1,14 +1,11 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - from datetime import timedelta -from otpauth import OtpAuth -from django.contrib import auth from django.conf import settings +from django.contrib import auth from django.core.exceptions import MultipleObjectsReturned -from django.utils.translation import ugettext as _ from django.utils.timezone import now +from django.utils.translation import ugettext as _ +from otpauth import OtpAuth from conf.models import WebsiteConfig from utils.api import APIView, validate_serializer @@ -17,9 +14,10 @@ from utils.shortcuts import rand_str from ..decorators import login_required from ..models import User, UserProfile -from ..serializers import (UserChangePasswordSerializer, UserLoginSerializer, - UserRegisterSerializer, - ApplyResetPasswordSerializer, ResetPasswordSerializer) +from ..serializers import (ApplyResetPasswordSerializer, + ResetPasswordSerializer, + UserChangePasswordSerializer, UserLoginSerializer, + UserRegisterSerializer) from ..tasks import _send_email diff --git a/account/views/user.py b/account/views/user.py index d3bcacf..94b8ccb 100644 --- a/account/views/user.py +++ b/account/views/user.py @@ -1,15 +1,11 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - import os -import qrcode - from io import StringIO -from otpauth import OtpAuth +import qrcode from django.conf import settings from django.http import HttpResponse from django.utils.translation import ugettext as _ +from otpauth import OtpAuth from conf.models import WebsiteConfig from utils.api import APIView, validate_serializer @@ -17,8 +13,8 @@ from utils.shortcuts import rand_str from ..decorators import login_required from ..models import User -from ..serializers import (EditUserSerializer, UserSerializer, - SSOSerializer, TwoFactorAuthCodeSerializer) +from ..serializers import (EditUserSerializer, SSOSerializer, + TwoFactorAuthCodeSerializer, UserSerializer) class UserInfoAPI(APIView): diff --git a/contest/tests.py b/contest/tests.py index 11d29b2..8a8134f 100644 --- a/contest/tests.py +++ b/contest/tests.py @@ -1,12 +1,12 @@ +import copy from datetime import datetime, timedelta -import copy from django.utils import timezone from utils.api._serializers import DateTimeTZField from utils.api.tests import APITestCase -from .models import ContestAnnouncement -from .models import ContestRuleType + +from .models import ContestAnnouncement, ContestRuleType DEFAULT_CONTEST_DATA = {"title": "test title", "description": "test description", "start_time": timezone.localtime(timezone.now()), diff --git a/contest/views/admin.py b/contest/views/admin.py index 4f479cc..60bb161 100644 --- a/contest/views/admin.py +++ b/contest/views/admin.py @@ -1,6 +1,7 @@ import dateutil.parser from utils.api import APIView, validate_serializer + from ..models import Contest, ContestAnnouncement from ..serializers import (ContestAnnouncementSerializer, ContestSerializer, CreateConetestSeriaizer, diff --git a/deploy/requirements.txt b/deploy/requirements.txt index 308698b..3d8bf5a 100644 --- a/deploy/requirements.txt +++ b/deploy/requirements.txt @@ -9,4 +9,5 @@ coverage python-dateutil celery Envelopes -qrcode \ No newline at end of file +qrcode +flake8-coding diff --git a/fps/parser.py b/fps/parser.py index 73bca86..bca71b7 100644 --- a/fps/parser.py +++ b/fps/parser.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -import copy import base64 +import copy import random import string import xml.etree.ElementTree as ET diff --git a/oj/local_settings.py b/oj/local_settings.py index 902ca25..79dc44e 100644 --- a/oj/local_settings.py +++ b/oj/local_settings.py @@ -7,11 +7,6 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.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" +LOG_PATH = "log/" diff --git a/oj/server_settings.py b/oj/server_settings.py index e26eefa..7434ee7 100644 --- a/oj/server_settings.py +++ b/oj/server_settings.py @@ -12,15 +12,6 @@ DATABASES = { 'PORT': 3306, 'USER': os.environ["MYSQL_ENV_MYSQL_USER"], '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" +LOG_PATH = "log/" diff --git a/oj/settings.py b/oj/settings.py index 9e23a7b..e7199d9 100644 --- a/oj/settings.py +++ b/oj/settings.py @@ -104,8 +104,6 @@ STATIC_URL = '/static/' AUTH_USER_MODEL = 'account.User' -LOG_PATH = "log/" - LOGGING = { 'version': 1, 'disable_existing_loggers': True, @@ -118,13 +116,13 @@ LOGGING = { 'django_error': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', - 'filename': LOG_PATH + 'django.log', + 'filename': os.path.join(LOG_PATH, 'django.log'), 'formatter': 'standard' }, 'app_info': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', - 'filename': LOG_PATH + 'app_info.log', + 'filename': os.path.join(LOG_PATH, 'app_info.log'), 'formatter': 'standard' }, 'console': { @@ -152,17 +150,13 @@ LOGGING = { }, } -if DEBUG: - REST_FRAMEWORK = { - 'TEST_REQUEST_DEFAULT_FORMAT': 'json' - } -else: - REST_FRAMEWORK = { - 'TEST_REQUEST_DEFAULT_FORMAT': 'json', - 'DEFAULT_RENDERER_CLASSES': ( - 'rest_framework.renderers.JSONRenderer', - ) - } + +REST_FRAMEWORK = { + 'TEST_REQUEST_DEFAULT_FORMAT': 'json', + 'DEFAULT_RENDERER_CLASSES': ( + 'rest_framework.renderers.JSONRenderer', + ) +} # for celery BROKER_URL = 'redis://%s:%s/%s' % (REDIS_QUEUE["host"], str(REDIS_QUEUE["port"]), str(REDIS_QUEUE["db"])) diff --git a/problem/tests.py b/problem/tests.py index aa70af9..d80262a 100644 --- a/problem/tests.py +++ b/problem/tests.py @@ -6,6 +6,7 @@ from zipfile import ZipFile from django.conf import settings from utils.api.tests import APITestCase + from .models import ProblemTag from .views.admin import TestCaseUploadAPI diff --git a/problem/urls/admin.py b/problem/urls/admin.py index 7ea423e..b4813c5 100644 --- a/problem/urls/admin.py +++ b/problem/urls/admin.py @@ -1,6 +1,6 @@ from django.conf.urls import url -from ..views.admin import ProblemAPI, TestCaseUploadAPI, ContestProblemAPI +from ..views.admin import ContestProblemAPI, ProblemAPI, TestCaseUploadAPI urlpatterns = [ url(r"^test_case/upload/?$", TestCaseUploadAPI.as_view(), name="test_case_upload_api"), diff --git a/problem/views/admin.py b/problem/views/admin.py index 9e306d6..ad818ad 100644 --- a/problem/views/admin.py +++ b/problem/views/admin.py @@ -10,9 +10,10 @@ from contest.models import Contest from utils.api import APIView, CSRFExemptAPIView, validate_serializer from utils.shortcuts import rand_str -from ..models import Problem, ProblemRuleType, ProblemTag, ContestProblem -from ..serializers import (CreateProblemSerializer, EditProblemSerializer, - ProblemSerializer, TestCaseUploadForm, CreateContestProblemSerializer) +from ..models import ContestProblem, Problem, ProblemRuleType, ProblemTag +from ..serializers import (CreateContestProblemSerializer, + CreateProblemSerializer, EditProblemSerializer, + ProblemSerializer, TestCaseUploadForm) class TestCaseUploadAPI(CSRFExemptAPIView): diff --git a/run_test.py b/run_test.py index 17e23d6..8358f12 100644 --- a/run_test.py +++ b/run_test.py @@ -1,12 +1,11 @@ -import sys import getopt import os +import sys opts, args = getopt.getopt(sys.argv[1:], "cm:", ["coverage=", "module="]) is_coverage = False test_module = "" -waf_addr = "127.0.0.1:50001" setting = "oj.settings" for opt, arg in opts: diff --git a/utils/mail.py b/utils/mail.py index 6ff7e7d..6efcc33 100644 --- a/utils/mail.py +++ b/utils/mail.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - from envelopes import Envelope from conf.models import SMTPConfig From ef9cdd9f753c9b9b584f350fb36a91c4ed182c9a Mon Sep 17 00:00:00 2001 From: virusdefender Date: Wed, 19 Apr 2017 02:03:48 +0800 Subject: [PATCH 16/16] remove i18n --- account/decorators.py | 6 ++--- account/tasks.py | 4 ++-- account/tests.py | 23 +++++++++--------- account/views/admin.py | 11 ++++----- account/views/oj.py | 53 +++++++++++++++++++++--------------------- account/views/user.py | 19 +++++++-------- announcement/views.py | 6 ++--- utils/mail.py | 18 -------------- utils/shortcuts.py | 23 ++++++++++++++++++ 9 files changed, 80 insertions(+), 83 deletions(-) delete mode 100644 utils/mail.py diff --git a/account/decorators.py b/account/decorators.py index c11560d..9df97e5 100644 --- a/account/decorators.py +++ b/account/decorators.py @@ -1,7 +1,5 @@ import functools -from django.utils.translation import ugettext as _ - from utils.api import JSONResponse from .models import ProblemPermission @@ -22,10 +20,10 @@ class BasePermissionDecorator(object): if self.check_permission(): 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) else: - return self.error(_("Please login in first")) + return self.error("Please login in first") def check_permission(self): raise NotImplementedError() diff --git a/account/tasks.py b/account/tasks.py index 00248ba..0aacec9 100644 --- a/account/tasks.py +++ b/account/tasks.py @@ -1,8 +1,8 @@ from celery import shared_task -from utils.mail import send_email +from utils.shortcuts import send_email @shared_task -def _send_email(from_name, to_email, to_name, subject, content): +def send_email_async(from_name, to_email, to_name, subject, content): send_email(from_name, to_email, to_name, subject, content) diff --git a/account/tests.py b/account/tests.py index 877b317..d4addd5 100644 --- a/account/tests.py +++ b/account/tests.py @@ -2,7 +2,6 @@ import time from unittest import mock from django.contrib import auth -from django.utils.translation import ugettext as _ from otpauth import OtpAuth from utils.api.tests import APIClient, APITestCase @@ -45,7 +44,7 @@ class UserLoginAPITest(APITestCase): 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")}) + self.assertDictEqual(response.data, {"error": None, "data": "Succeeded"}) user = auth.get_user(self.client) self.assertTrue(user.is_authenticated()) @@ -53,7 +52,7 @@ class UserLoginAPITest(APITestCase): 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")}) + self.assertDictEqual(response.data, {"error": "error", "data": "Invalid username or password"}) user = auth.get_user(self.client) self.assertFalse(user.is_authenticated()) @@ -67,7 +66,7 @@ class UserLoginAPITest(APITestCase): data={"username": self.username, "password": self.password, "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) self.assertTrue(user.is_authenticated()) @@ -78,7 +77,7 @@ class UserLoginAPITest(APITestCase): data={"username": self.username, "password": self.password, "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) self.assertFalse(user.is_authenticated()) @@ -116,7 +115,7 @@ class UserRegisterAPITest(CaptchaTest): 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.assertDictEqual(response.data, {"error": "error", "data": "Invalid captcha"}) self.data.pop("captcha") response = self.client.post(self.register_url, data=self.data) @@ -124,7 +123,7 @@ class UserRegisterAPITest(CaptchaTest): 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")}) + self.assertDictEqual(response.data, {"error": None, "data": "Succeeded"}) def test_username_already_exists(self): self.test_register_with_correct_info() @@ -132,7 +131,7 @@ class UserRegisterAPITest(CaptchaTest): 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")}) + self.assertDictEqual(response.data, {"error": "error", "data": "Username already exists"}) def test_email_already_exists(self): self.test_register_with_correct_info() @@ -140,7 +139,7 @@ class UserRegisterAPITest(CaptchaTest): 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")}) + self.assertDictEqual(response.data, {"error": "error", "data": "Email already exists"}) class UserChangePasswordAPITest(CaptchaTest): @@ -159,19 +158,19 @@ class UserChangePasswordAPITest(CaptchaTest): def test_login_required(self): 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): 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.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")}) + self.assertEqual(response.data, {"error": "error", "data": "Invalid old password"}) class AdminUserTest(APITestCase): diff --git a/account/views/admin.py b/account/views/admin.py index 49e8093..62c5115 100644 --- a/account/views/admin.py +++ b/account/views/admin.py @@ -1,6 +1,5 @@ from django.core.exceptions import MultipleObjectsReturned from django.db.models import Q -from django.utils.translation import ugettext as _ from utils.api import APIView, validate_serializer from utils.shortcuts import rand_str @@ -21,21 +20,21 @@ class UserAdminAPI(APIView): try: user = User.objects.get(id=data["id"]) except User.DoesNotExist: - return self.error(_("User does not exist")) + return self.error("User does not exist") try: user = User.objects.get(username=data["username"]) if user.id != data["id"]: - return self.error(_("Username already exists")) + return self.error("Username already exists") except User.DoesNotExist: pass try: user = User.objects.get(email=data["email"]) if user.id != data["id"]: - return self.error(_("Email already exists")) + return self.error("Email already exists") # Some old data has duplicate email except MultipleObjectsReturned: - return self.error(_("Email already exists")) + return self.error("Email already exists") except User.DoesNotExist: pass @@ -85,7 +84,7 @@ class UserAdminAPI(APIView): try: user = User.objects.get(id=user_id) except User.DoesNotExist: - return self.error(_("User does not exist")) + return self.error("User does not exist") return self.success(UserSerializer(user).data) user = User.objects.all().order_by("-create_time") diff --git a/account/views/oj.py b/account/views/oj.py index 48a35c3..7db3bbb 100644 --- a/account/views/oj.py +++ b/account/views/oj.py @@ -4,7 +4,6 @@ from django.conf import settings from django.contrib import auth from django.core.exceptions import MultipleObjectsReturned from django.utils.timezone import now -from django.utils.translation import ugettext as _ from otpauth import OtpAuth from conf.models import WebsiteConfig @@ -18,7 +17,7 @@ from ..serializers import (ApplyResetPasswordSerializer, ResetPasswordSerializer, UserChangePasswordSerializer, UserLoginSerializer, UserRegisterSerializer) -from ..tasks import _send_email +from ..tasks import send_email_async class UserLoginAPI(APIView): @@ -33,7 +32,7 @@ class UserLoginAPI(APIView): if user: if not user.two_factor_auth: auth.login(request, user) - return self.success(_("Succeeded")) + return self.success("Succeeded") # `tfa_code` not in post data if user.two_factor_auth and "tfa_code" not in data: @@ -41,11 +40,11 @@ class UserLoginAPI(APIView): if OtpAuth(user.tfa_token).valid_totp(data["tfa_code"]): auth.login(request, user) - return self.success(_("Succeeded")) + return self.success("Succeeded") else: - return self.error(_("Invalid two factor verification code")) + return self.error("Invalid two factor verification code") else: - return self.error(_("Invalid username or password")) + return self.error("Invalid username or password") # todo remove this, only for debug use def get(self, request): @@ -62,24 +61,24 @@ class UserRegisterAPI(APIView): data = request.data captcha = Captcha(request) if not captcha.check(data["captcha"]): - return self.error(_("Invalid captcha")) + return self.error("Invalid captcha") try: User.objects.get(username=data["username"]) - return self.error(_("Username already exists")) + return self.error("Username already exists") except User.DoesNotExist: pass try: User.objects.get(email=data["email"]) - return self.error(_("Email already exists")) + return self.error("Email already exists") # Some old data has duplicate email except MultipleObjectsReturned: - return self.error(_("Email already exists")) + return self.error("Email already exists") except User.DoesNotExist: user = User.objects.create(username=data["username"], email=data["email"]) user.set_password(data["password"]) user.save() UserProfile.objects.create(user=user) - return self.success(_("Succeeded")) + return self.success("Succeeded") class UserChangePasswordAPI(APIView): @@ -92,15 +91,15 @@ class UserChangePasswordAPI(APIView): data = request.data captcha = Captcha(request) if not captcha.check(data["captcha"]): - return self.error(_("Invalid captcha")) + return self.error("Invalid captcha") username = request.user.username user = auth.authenticate(username=username, password=data["old_password"]) if user: user.set_password(data["new_password"]) user.save() - return self.success(_("Succeeded")) + return self.success("Succeeded") else: - return self.error(_("Invalid old password")) + return self.error("Invalid old password") class ApplyResetPasswordAPI(APIView): @@ -110,14 +109,14 @@ class ApplyResetPasswordAPI(APIView): captcha = Captcha(request) config = WebsiteConfig.objects.first() if not captcha.check(data["captcha"]): - return self.error(_("Invalid captcha")) + return self.error("Invalid captcha") try: user = User.objects.get(email=data["email"]) except User.DoesNotExist: - return self.error(_("User does not exist")) + 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")) + 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) @@ -128,12 +127,12 @@ class ApplyResetPasswordAPI(APIView): replace("{{ website_name }}", settings.WEBSITE_INFO["website_name"]). \ replace("{{ link }}", settings.WEBSITE_INFO["url"] + "/reset_password/t/" + user.reset_password_token) - _send_email.delay(config.name, - user.email, - user.username, - config.name + " 登录信息找回邮件", - email_template) - return self.success(_("Succeeded")) + send_email_async.delay(config.name, + user.email, + user.username, + config.name + " 登录信息找回邮件", + email_template) + return self.success("Succeeded") class ResetPasswordAPI(APIView): @@ -142,14 +141,14 @@ class ResetPasswordAPI(APIView): data = request.data captcha = Captcha(request) if not captcha.check(data["captcha"]): - return self.error(_("Invalid 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")) + 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")) + return self.error("Token expired") user.reset_password_token = None user.set_password(data["password"]) user.save() - return self.success(_("Succeeded")) + return self.success("Succeeded") diff --git a/account/views/user.py b/account/views/user.py index 94b8ccb..19eb893 100644 --- a/account/views/user.py +++ b/account/views/user.py @@ -4,7 +4,6 @@ from io import StringIO import qrcode from django.conf import settings from django.http import HttpResponse -from django.utils.translation import ugettext as _ from otpauth import OtpAuth from conf.models import WebsiteConfig @@ -50,19 +49,19 @@ class UserProfileAPI(APIView): user_profile.major = data["major"] # Timezone & language 暂时不加 user_profile.save() - return self.success(_("Succeeded")) + return self.success("Succeeded") class AvatarUploadAPI(APIView): def post(self, request): if "file" not in request.FILES: - return self.error(_("Upload failed")) + return self.error("Upload failed") f = request.FILES["file"] if f.size > 1024 * 1024: - return self.error(_("Picture too large")) + 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")) + 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: @@ -76,7 +75,7 @@ class SSOAPI(APIView): def get(self, request): callback = request.GET.get("callback", None) if not callback: - return self.error(_("Parameter Error")) + return self.error("Parameter Error") token = rand_str() request.user.auth_token = token request.user.save() @@ -89,7 +88,7 @@ class SSOAPI(APIView): try: User.objects.get(open_api_appkey=data["appkey"]) except User.DoesNotExist: - return self.error(_("Invalid appkey")) + return self.error("Invalid appkey") try: user = User.objects.get(auth_token=data["token"]) user.auth_token = None @@ -133,9 +132,9 @@ class TwoFactorAuthAPI(APIView): if OtpAuth(user.tfa_token).valid_totp(code): user.two_factor_auth = True user.save() - return self.success(_("Succeeded")) + return self.success("Succeeded") else: - return self.error(_("Invalid captcha")) + return self.error("Invalid captcha") @login_required @validate_serializer(TwoFactorAuthCodeSerializer) @@ -146,4 +145,4 @@ class TwoFactorAuthAPI(APIView): user.two_factor_auth = False user.save() else: - return self.error(_("Invalid captcha")) + return self.error("Invalid captcha") diff --git a/announcement/views.py b/announcement/views.py index 8cbe78e..f607a7d 100644 --- a/announcement/views.py +++ b/announcement/views.py @@ -1,5 +1,3 @@ -from django.utils.translation import ugettext as _ - from account.decorators import super_admin_required from utils.api import APIView, validate_serializer @@ -32,7 +30,7 @@ class AnnouncementAdminAPI(APIView): try: announcement = Announcement.objects.get(id=data["id"]) except Announcement.DoesNotExist: - return self.error(_("Announcement does not exist")) + return self.error("Announcement does not exist") announcement.title = data["title"] announcement.content = data["content"] @@ -52,7 +50,7 @@ class AnnouncementAdminAPI(APIView): announcement = Announcement.objects.get(id=announcement_id) return self.success(AnnouncementSerializer(announcement).data) 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") if request.GET.get("visible") == "true": announcement = announcement.filter(visible=True) diff --git a/utils/mail.py b/utils/mail.py deleted file mode 100644 index 6efcc33..0000000 --- a/utils/mail.py +++ /dev/null @@ -1,18 +0,0 @@ -from envelopes import Envelope - -from conf.models import SMTPConfig - - -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) - envlope.send(smtp.server, - login=smtp.email, - password=smtp.password, - port=smtp.port, - tls=smtp.tls) diff --git a/utils/shortcuts.py b/utils/shortcuts.py index 1dd8834..9962ade 100644 --- a/utils/shortcuts.py +++ b/utils/shortcuts.py @@ -2,10 +2,33 @@ import logging import random from django.utils.crypto import get_random_string +from envelopes import Envelope + +from conf.models import SMTPConfig 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"): """ 生成指定长度的随机字符串或者数字, 可以用于密钥等安全场景