diff --git a/account/decorators.py b/account/decorators.py index 2857c004..d2ed1acd 100644 --- a/account/decorators.py +++ b/account/decorators.py @@ -1,12 +1,13 @@ # coding=utf-8 +from __future__ import unicode_literals import urllib import functools -from functools import wraps from django.http import HttpResponseRedirect +from django.utils.translation import ugettext as _ from utils.shortcuts import error_response, error_page -from .models import SUPER_ADMIN, ADMIN +from .models import AdminType class BasePermissionDecorator(object): @@ -23,15 +24,15 @@ class BasePermissionDecorator(object): self.request = args[0] if self.check_permission(): - if self.request.user.is_forbidden is True: + if self.request.user.is_disabled: if self.request.is_ajax(): - return error_response(u"您已被禁用,请联系管理员") + return error_response(_("Your account is disabled")) else: - return error_page(self.request, u"您已被禁用,请联系管理员") + return error_page(self.request, _("Your account is disabled")) return self.func(*args, **kwargs) else: if self.request.is_ajax(): - return error_response(u"请先登录") + return error_response(_("Please login in first")) else: return HttpResponseRedirect("/login/?__from=" + urllib.quote(self.request.path)) @@ -46,9 +47,9 @@ class login_required(BasePermissionDecorator): class super_admin_required(BasePermissionDecorator): def check_permission(self): - return self.request.user.is_authenticated() and self.request.user.admin_type == SUPER_ADMIN + return self.request.user.is_authenticated() and self.request.user.admin_type == AdminType.SUPER_ADMIN class admin_required(BasePermissionDecorator): def check_permission(self): - return self.request.user.is_authenticated() and self.request.user.admin_type in [SUPER_ADMIN, ADMIN] + return self.request.user.is_authenticated() and self.request.user.admin_type in [AdminType.SUPER_ADMIN, AdminType.ADMIN] diff --git a/account/locale/zh_CN/LC_MESSAGES/django.po b/account/locale/zh_CN/LC_MESSAGES/django.po new file mode 100644 index 00000000..6b650075 --- /dev/null +++ b/account/locale/zh_CN/LC_MESSAGES/django.po @@ -0,0 +1,56 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-06-13 21:24+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: account/decorators.py:29 account/decorators.py:31 +msgid "Your account is disabled" +msgstr "您的账号已被禁用" + +#: account/decorators.py:35 account/middleware.py:21 +msgid "Please login in first" +msgstr "请先登录" + +#: account/tests.py:36 account/tests.py:59 account/tests.py:114 +#: account/views.py:51 account/views.py:59 account/views.py:96 +#: account/views.py:118 +msgid "Succeeded" +msgstr "" + +#: account/tests.py:45 account/views.py:63 +msgid "Invalid username or password" +msgstr "" + +#: account/tests.py:70 account/views.py:61 +msgid "Invalid two factor verification code" +msgstr "" + +#: account/tests.py:106 account/views.py:78 account/views.py:112 +msgid "Invalid captcha" +msgstr "" + +#: account/tests.py:122 account/views.py:81 +msgid "Username already exists" +msgstr "" + +#: account/tests.py:130 account/views.py:86 account/views.py:89 +msgid "Email already exists" +msgstr "" + +#: account/views.py:120 +msgid "Invalid old password" +msgstr "" diff --git a/account/middleware.py b/account/middleware.py index ec6d022d..a0e64f3b 100644 --- a/account/middleware.py +++ b/account/middleware.py @@ -2,20 +2,23 @@ import time import json import urllib + from django.http import HttpResponseRedirect, HttpResponse +from django.utils.translation import ugettext as _ from django.contrib import auth -from .models import ADMIN + +from .models import AdminType class SessionSecurityMiddleware(object): def process_request(self, request): - if request.user.is_authenticated() and request.user.admin_type >= ADMIN: + if request.user.is_authenticated() and request.user.admin_type in [AdminType.ADMIN, AdminType.SUPER_ADMIN]: if "last_activity" in request.session: - # 24个小时没有活动 + # 24 hours passwd since last visit if time.time() - request.session["last_activity"] >= 24 * 60 * 60: auth.logout(request) if request.is_ajax(): - return HttpResponse(json.dumps({"code": 1, "data": u"请先登录"}), + return HttpResponse(json.dumps({"code": 1, "data": _("Please login in first")}), content_type="application/json") else: return HttpResponseRedirect("/login/?__from=" + urllib.quote(request.path)) diff --git a/account/migrations/0022_auto_20160613_1606.py b/account/migrations/0022_auto_20160613_1606.py new file mode 100644 index 00000000..e6c307d5 --- /dev/null +++ b/account/migrations/0022_auto_20160613_1606.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.5 on 2016-06-13 08:06 +from __future__ import unicode_literals + +from django.db import migrations, models +import jsonfield.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0021_auto_20160424_1243'), + ] + + operations = [ + migrations.RenameField( + model_name='user', + old_name='is_forbidden', + new_name='is_disabled', + ), + migrations.RenameField( + model_name='user', + old_name='openapi_appkey', + new_name='open_api_appkey', + ), + migrations.RemoveField( + model_name='userprofile', + name='problems_status', + ), + migrations.AddField( + model_name='user', + name='admin_extra_permission', + field=jsonfield.fields.JSONField(default=[]), + ), + migrations.AddField( + model_name='user', + name='open_api', + field=models.BooleanField(default=False), + ), + ] diff --git a/account/models.py b/account/models.py index e91ea3fd..cfa0ff8e 100644 --- a/account/models.py +++ b/account/models.py @@ -4,6 +4,31 @@ from django.contrib.auth.models import AbstractBaseUser from jsonfield import JSONField +# TODO remove these +REGULAR_USER = 0 +ADMIN = 1 +SUPER_ADMIN = 2 + + +class AdminType(object): + REGULAR_USER = 0 + ADMIN = 1 + SUPER_ADMIN = 2 + + +class ProblemSolutionStatus(object): + ACCEPTED = 1 + PENDING = 2 + + +class AdminExtraPermission(object): + CREATE_PUBLIC_CONTEST = 1 + MANAGE_ALL_CONTEST = 2 + # 3 and 4 are mutually exclusive + MANAGE_ALL_PROBLEM = 3 + # Manage public problem user created + MANAGE_OWN_PROBLEM = 4 + class UserManager(models.Manager): use_in_migrations = True @@ -12,37 +37,28 @@ class UserManager(models.Manager): return self.get(**{self.model.USERNAME_FIELD: username}) -REGULAR_USER = 0 -ADMIN = 1 -SUPER_ADMIN = 2 - - class User(AbstractBaseUser): - # 用户名 username = models.CharField(max_length=30, unique=True) - # 真实姓名 real_name = models.CharField(max_length=30, blank=True, null=True) - # 用户邮箱 email = models.EmailField(max_length=254, blank=True, null=True) - # 用户注册时间 create_time = models.DateTimeField(auto_now_add=True, null=True) - # 0代表不是管理员 1是普通管理员 2是超级管理员 + # One of UserType admin_type = models.IntegerField(default=0) - # JSON字典用来表示该用户的问题的解决状态 1为ac,2为正在进行 + # List of items in AdminExtraPermission + admin_extra_permission = JSONField(default=[]) + # Store user problem solution status with json string format + # {"problems": {1: ProblemSolutionStatus.ACCEPTED}, "contest_problems": {20: ProblemSolutionStatus.PENDING)} problems_status = JSONField(default={}) - # 找回密码用的token reset_password_token = models.CharField(max_length=40, blank=True, null=True) - # token 生成时间 reset_password_token_create_time = models.DateTimeField(blank=True, null=True) - # SSO授权token + # SSO auth token auth_token = models.CharField(max_length=40, blank=True, null=True) - # 是否开启两步验证 two_factor_auth = models.BooleanField(default=False) tfa_token = models.CharField(max_length=40, blank=True, null=True) # open api key - openapi_appkey = models.CharField(max_length=35, blank=True, null=True) - # 是否禁用用户 - is_forbidden = models.BooleanField(default=False) + open_api = models.BooleanField(default=False) + open_api_appkey = models.CharField(max_length=35, blank=True, null=True) + is_disabled = models.BooleanField(default=False) USERNAME_FIELD = 'username' REQUIRED_FIELDS = [] @@ -68,23 +84,21 @@ class UserProfile(models.Model): codeforces_username = models.CharField(max_length=30, blank=True, null=True) accepted_problem_number = models.IntegerField(default=0) submission_number = models.IntegerField(default=0) - # JSON字典用来表示该用户的问题的解决状态 1为ac,2为正在进行 - problems_status = JSONField(default={}) phone_number = models.CharField(max_length=15, blank=True, null=True) school = models.CharField(max_length=200, blank=True, null=True) student_id = models.CharField(max_length=15, blank=True, null=True) def add_accepted_problem_number(self): - self.accepted_problem_number += 1 - self.save(update_fields=["accepted_problem_number"]) + self.accepted_problem_number = models.F("accepted_problem_number") + 1 + self.save() def add_submission_number(self): - self.submission_number += 1 - self.save(update_fields=["submission_number"]) + self.submission_number = models.F("submission_number") + 1 + self.save() def minus_accepted_problem_number(self): - self.accepted_problem_number -= 1 - self.save(update_fields=["accepted_problem_number"]) + self.accepted_problem_number = models.F("accepted_problem_number") - 1 + self.save() class Meta: db_table = "user_profile" diff --git a/account/serializers.py b/account/serializers.py index d535d3f2..c4077e05 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -1,6 +1,7 @@ # coding=utf-8 from rest_framework import serializers +from utils.serializers import DateTimeTZField, JSONField from .models import User, UserProfile @@ -21,10 +22,10 @@ class EmailCheckSerializer(serializers.Serializer): class UserRegisterSerializer(serializers.Serializer): username = serializers.CharField(max_length=30) real_name = serializers.CharField(max_length=30) - school = serializers.CharField(max_length=200, required=False, default=None) password = serializers.CharField(max_length=30, min_length=6) email = serializers.EmailField(max_length=254) captcha = serializers.CharField(max_length=4, min_length=4) + school = serializers.CharField(max_length=200, required=False, default=None) student_id = serializers.CharField(max_length=15, required=False, default=None) @@ -35,11 +36,14 @@ class UserChangePasswordSerializer(serializers.Serializer): class UserSerializer(serializers.ModelSerializer): + create_time = DateTimeTZField() + last_login = DateTimeTZField() + admin_extra_permission = serializers.ListField() class Meta: model = User - fields = ["id", "username", "real_name", "email", "admin_type", - "create_time", "last_login", "two_factor_auth", "openapi_appkey", "is_forbidden"] + fields = ["id", "username", "real_name", "email", "admin_type", "admin_extra_permission", + "create_time", "last_login", "two_factor_auth", "open_api", "is_disabled"] class EditUserSerializer(serializers.Serializer): @@ -49,9 +53,11 @@ class EditUserSerializer(serializers.Serializer): password = serializers.CharField(max_length=30, min_length=6, required=False, default=None) email = serializers.EmailField(max_length=254) admin_type = serializers.IntegerField(default=0) - openapi = serializers.BooleanField() - tfa_auth = serializers.BooleanField() - is_forbidden = serializers.BooleanField() + open_api = serializers.BooleanField() + two_factor_auth = serializers.BooleanField() + is_disabled = serializers.BooleanField() + admin_extra_permission = serializers.ListField(required=False, default=[], + child=serializers.IntegerField()) class ApplyResetPasswordSerializer(serializers.Serializer): diff --git a/account/tests.py b/account/tests.py index e69de29b..d2512aa6 100644 --- a/account/tests.py +++ b/account/tests.py @@ -0,0 +1,204 @@ +# coding=utf-8 +from __future__ import unicode_literals +import time +import mock + +from django.contrib import auth +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext as _ + +from rest_framework.test import APIClient, APITestCase + +from utils.shortcuts import rand_str +from utils.otp_auth import OtpAuth +from .models import User + + +class PermissionDecoratorTest(APITestCase): + def setUp(self): + self.regular_user = User.objects.create(username="regular_user") + self.admin = User.objects.create(username="admin") + self.super_admin = User.objects.create(username="super_admin") + self.request = mock.MagicMock() + self.request.user.is_authenticated = mock.MagicMock() + + def test_login_required(self): + self.request.user.is_authenticated.return_value = False + + def test_admin_required(self): + pass + + def test_super_admin_required(self): + pass + + +class UserLoginAPITest(APITestCase): + def setUp(self): + self.username = "testuser" + self.password = "testuserpassword" + self.user = User.objects.create(username=self.username) + self.user.set_password(self.password) + self.user.save() + + self.login_url = reverse("user_login_api") + + def _set_tfa(self): + self.user.two_factor_auth = True + tfa_token = rand_str(32) + self.user.tfa_token = tfa_token + self.user.save() + return tfa_token + + def test_login_with_correct_info(self): + response = self.client.post(self.login_url, + data={"username": self.username, "password": self.password}) + self.assertDictEqual(response.data, {"code": 0, "data": _("Succeeded")}) + + user = auth.get_user(self.client) + self.assertTrue(user.is_authenticated()) + + def test_login_with_wrong_info(self): + response = self.client.post(self.login_url, + data={"username": self.username, "password": "invalid_password"}) + + self.assertDictEqual(response.data, {"code": 1, "data": _("Invalid username or password")}) + + user = auth.get_user(self.client) + self.assertFalse(user.is_authenticated()) + + def test_tfa_login(self): + token = self._set_tfa() + code = OtpAuth(token).totp() + if len(str(code)) < 6: + code = (6 - len(str(code))) * "0" + str(code) + response = self.client.post(self.login_url, + data={"username": self.username, + "password": self.password, + "tfa_code": code}) + self.assertDictEqual(response.data, {"code": 0, "data": _("Succeeded")}) + + user = auth.get_user(self.client) + self.assertTrue(user.is_authenticated()) + + def test_tfa_login_wrong_code(self): + self._set_tfa() + response = self.client.post(self.login_url, + data={"username": self.username, + "password": self.password, + "tfa_code": "qqqqqq"}) + self.assertDictEqual(response.data, {"code": 1, "data": _("Invalid two factor verification code")}) + + user = auth.get_user(self.client) + self.assertFalse(user.is_authenticated()) + + def test_tfa_login_without_code(self): + self._set_tfa() + response = self.client.post(self.login_url, + data={"username": self.username, + "password": self.password}) + self.assertDictEqual(response.data, {"code": 0, "data": "tfa_required"}) + + user = auth.get_user(self.client) + self.assertFalse(user.is_authenticated()) + + +class CaptchaTest(APITestCase): + def _set_captcha(self, session): + captcha = rand_str(4) + session["_django_captcha_key"] = captcha + session["_django_captcha_expires_time"] = int(time.time()) + 30 + session.save() + return captcha + + +class UserRegisterAPITest(CaptchaTest): + def setUp(self): + self.client = APIClient() + self.register_url = reverse("user_register_api") + self.captcha = rand_str(4) + + self.data = {"username": "test_user", "password": "testuserpassword", + "real_name": "real_name", "email": "test@qduoj.com", + "captcha": self._set_captcha(self.client.session)} + + def test_invalid_captcha(self): + self.data["captcha"] = "****" + response = self.client.post(self.register_url, data=self.data) + self.assertDictEqual(response.data, {"code": 1, "data": _("Invalid captcha")}) + + self.data.pop("captcha") + response = self.client.post(self.register_url, data=self.data) + self.assertEqual(response.data["code"], 1) + + def test_register_with_correct_info(self): + response = self.client.post(self.register_url, data=self.data) + self.assertDictEqual(response.data, {"code": 0, "data": _("Succeeded")}) + + def test_username_already_exists(self): + self.test_register_with_correct_info() + + self.data["captcha"] = self._set_captcha(self.client.session) + self.data["email"] = "test1@qduoj.com" + response = self.client.post(self.register_url, data=self.data) + self.assertDictEqual(response.data, {"code": 1, "data": _("Username already exists")}) + + def test_email_already_exists(self): + self.test_register_with_correct_info() + + self.data["captcha"] = self._set_captcha(self.client.session) + self.data["username"] = "test_user1" + response = self.client.post(self.register_url, data=self.data) + self.assertDictEqual(response.data, {"code": 1, "data": _("Email already exists")}) + + +class UserChangePasswordAPITest(CaptchaTest): + def setUp(self): + self.client = APIClient() + self.url = reverse("user_change_password_api") + + # Create user at first + self.username = "test_user" + self.old_password = "testuserpassword" + self.new_password = "new_password" + register_data = {"username": self.username, "password": self.old_password, + "real_name": "real_name", "email": "test@qduoj.com", + "captcha": self._set_captcha(self.client.session)} + + response = self.client.post(reverse("user_register_api"), data=register_data) + self.assertDictEqual(response.data, {"code": 0, "data": _("Succeeded")}) + + self.data = {"old_password": self.old_password, "new_password": self.new_password, + "captcha": self._set_captcha(self.client.session)} + + def test_login_required(self): + response = self.client.post(self.url, data=self.data, HTTP_X_REQUESTED_WITH="XMLHttpRequest") + self.assertEqual(response.data, {"code": 1, "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, HTTP_X_REQUESTED_WITH="XMLHttpRequest") + self.assertEqual(response.data, {"code": 0, "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, HTTP_X_REQUESTED_WITH="XMLHttpRequest") + self.assertEqual(response.data, {"code": 1, "data": _("Invalid old password")}) + + +class AdminEditUserTest(APITestCase): + def setUp(self): + pass + + def test_edit_user_successfully(self): + pass + + def test_change_user_admin_type(self): + pass + + def test_change_user_permission(self): + pass + + def test_change_user_password(self): + pass diff --git a/account/views.py b/account/views.py index 74e740fe..2500de54 100644 --- a/account/views.py +++ b/account/views.py @@ -3,6 +3,7 @@ import os import codecs import qrcode import StringIO + from django import http from django.contrib import auth from django.shortcuts import render @@ -12,62 +13,209 @@ from django.conf import settings from django.http import HttpResponse from django.core.exceptions import MultipleObjectsReturned from django.utils.timezone import now +from django.utils.translation import ugettext as _ from rest_framework.views import APIView from rest_framework.response import Response + from utils.shortcuts import (serializer_invalid_response, error_response, success_response, error_page, paginate, rand_str) from utils.captcha import Captcha from utils.otp_auth import OtpAuth from .tasks import _send_email - from .decorators import login_required -from .models import User, UserProfile - +from .models import User, UserProfile, AdminExtraPermission, AdminType from .serializers import (UserLoginSerializer, UserRegisterSerializer, UserChangePasswordSerializer, UserSerializer, EditUserSerializer, ApplyResetPasswordSerializer, ResetPasswordSerializer, SSOSerializer, EditUserProfileSerializer, - UserProfileSerializer, TwoFactorAuthCodeSerializer) - + TwoFactorAuthCodeSerializer) from .decorators import super_admin_required class UserLoginAPIView(APIView): def post(self, request): """ - 用户登录json api接口 - --- - request_serializer: UserLoginSerializer + User login api """ serializer = UserLoginSerializer(data=request.data) if serializer.is_valid(): data = serializer.data user = auth.authenticate(username=data["username"], password=data["password"]) - # 用户名或密码错误的话 返回None + # None is returned if username or password is wrong if user: if not user.two_factor_auth: auth.login(request, user) - return success_response(u"登录成功") + return success_response(_("Succeeded")) - # 没有输入两步验证的验证码 + # `tfa_code` not in post data if user.two_factor_auth and "tfa_code" not in data: return success_response("tfa_required") if OtpAuth(user.tfa_token).valid_totp(data["tfa_code"]): auth.login(request, user) - return success_response(u"登录成功") + return success_response(_("Succeeded")) else: - return error_response(u"验证码错误") + return error_response(_("Invalid two factor verification code")) else: - return error_response(u"用户名或密码错误") + return error_response(_("Invalid username or password")) else: return serializer_invalid_response(serializer) -#@login_required +class UserRegisterAPIView(APIView): + def post(self, request): + """ + User register api + """ + serializer = UserRegisterSerializer(data=request.data) + if serializer.is_valid(): + data = serializer.data + captcha = Captcha(request) + if not captcha.check(data["captcha"]): + return error_response(_("Invalid captcha")) + try: + User.objects.get(username=data["username"]) + return error_response(_("Username already exists")) + except User.DoesNotExist: + pass + try: + User.objects.get(email=data["email"]) + return error_response(_("Email already exists")) + # Some old data has duplicate email + except MultipleObjectsReturned: + return error_response(_("Email already exists")) + except User.DoesNotExist: + user = User.objects.create(username=data["username"], real_name=data["real_name"], + email=data["email"]) + user.set_password(data["password"]) + user.save() + UserProfile.objects.create(user=user, school=data["school"], student_id=data["student_id"]) + return success_response(_("Succeeded")) + else: + return serializer_invalid_response(serializer) + + +class UserChangePasswordAPIView(APIView): + @login_required + def post(self, request): + """ + User change password api + """ + serializer = UserChangePasswordSerializer(data=request.data) + if serializer.is_valid(): + data = serializer.data + captcha = Captcha(request) + if not captcha.check(data["captcha"]): + return error_response(_("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 success_response(_("Succeeded")) + else: + return error_response(_("Invalid old password")) + else: + return serializer_invalid_response(serializer) + + +class UserAdminAPIView(APIView): + @super_admin_required + def put(self, request): + """ + Edit user api + """ + serializer = EditUserSerializer(data=request.data) + if serializer.is_valid(): + data = serializer.data + try: + user = User.objects.get(id=data["id"]) + except User.DoesNotExist: + return error_response(_("User does not exist")) + try: + user = User.objects.get(username=data["username"]) + if user.id != data["id"]: + return error_response(_("Username already exists")) + except User.DoesNotExist: + pass + + try: + user = User.objects.get(email=data["email"]) + if user.id != data["id"]: + return error_response(_("Email already exists")) + # Some old data has duplicate email + except MultipleObjectsReturned: + return error_response(_("Email already exists")) + except User.DoesNotExist: + pass + + user.username = data["username"] + user.real_name = data["real_name"] + user.email = data["email"] + user.admin_type = data["admin_type"] + user.is_disabled = data["is_disabled"] + + if data["password"]: + user.set_password(data["password"]) + + if data["open_api"]: + # Avoid reset user appkey after saving changes + if not user.open_api: + user.open_api_appkey = rand_str() + else: + user.open_api_appkey = None + user.open_api = data["open_api"] + + if data["two_factor_auth"]: + # Avoid reset user tfa_token after saving changes + if not user.two_factor_auth: + user.tfa_token = rand_str() + else: + user.tfa_token = None + user.two_factor_auth = data["two_factor_auth"] + + if data["admin_type"] == AdminType.ADMIN: + user.admin_extra_permission = list(set(data["admin_extra_permission"])) + else: + user.admin_extra_permission = [] + + user.save() + return success_response(UserSerializer(user).data) + else: + return serializer_invalid_response(serializer) + + @super_admin_required + def get(self, request): + """ + User list api / Get user by id + """ + user_id = request.GET.get("user_id") + if user_id: + try: + user = User.objects.get(id=user_id) + except User.DoesNotExist: + return error_response(_("User does not exist")) + return success_response(UserSerializer(user).data) + + user = User.objects.all().order_by("-create_time") + + admin_type = request.GET.get("admin_type", None) + if admin_type: + try: + user = user.filter(admin_type__gte=int(admin_type)) + except ValueError: + return error_response(_("Invalid parameter")) + keyword = request.GET.get("keyword", None) + if keyword: + user = user.filter(Q(username__contains=keyword) | + Q(real_name__contains=keyword) | + Q(email__contains=keyword)) + return paginate(request, user, UserSerializer) + + def logout(request): auth.logout(request) return http.HttpResponseRedirect("/") @@ -83,67 +231,6 @@ def index_page(request): return http.HttpResponseRedirect('/problems/') -class UserRegisterAPIView(APIView): - def post(self, request): - """ - 用户注册json api接口 - --- - request_serializer: UserRegisterSerializer - """ - serializer = UserRegisterSerializer(data=request.data) - if serializer.is_valid(): - data = serializer.data - captcha = Captcha(request) - if not captcha.check(data["captcha"]): - return error_response(u"验证码错误") - try: - User.objects.get(username=data["username"]) - return error_response(u"用户名已存在") - except User.DoesNotExist: - pass - try: - User.objects.get(email=data["email"]) - return error_response(u"该邮箱已被注册,请换其他邮箱进行注册") - # 兼容部分老数据,有邮箱重复的 - except MultipleObjectsReturned: - return error_response(u"该邮箱已被注册,请换其他邮箱进行注册") - except User.DoesNotExist: - user = User.objects.create(username=data["username"], real_name=data["real_name"], - email=data["email"]) - user.set_password(data["password"]) - user.save() - UserProfile.objects.create(user=user, school=data["school"], student_id=data["student_id"]) - return success_response(u"注册成功!") - else: - return serializer_invalid_response(serializer) - - -class UserChangePasswordAPIView(APIView): - @login_required - def post(self, request): - """ - 用户修改密码json api接口 - --- - request_serializer: UserChangePasswordSerializer - """ - serializer = UserChangePasswordSerializer(data=request.data) - if serializer.is_valid(): - data = serializer.data - captcha = Captcha(request) - if not captcha.check(data["captcha"]): - return error_response(u"验证码错误") - username = request.user.username - user = auth.authenticate(username=username, password=data["old_password"]) - if user: - user.set_password(data["new_password"]) - user.save() - return success_response(u"用户密码修改成功!") - else: - return error_response(u"密码不正确,请重新修改!") - else: - return serializer_invalid_response(serializer) - - class UsernameCheckAPIView(APIView): def get(self, request): """ @@ -186,80 +273,6 @@ class EmailCheckAPIView(APIView): return Response(status=does_not_existed) -class UserAdminAPIView(APIView): - @super_admin_required - def put(self, request): - """ - 用户编辑json api接口 - --- - request_serializer: EditUserSerializer - response_serializer: UserSerializer - """ - serializer = EditUserSerializer(data=request.data) - if serializer.is_valid(): - data = serializer.data - try: - user = User.objects.get(id=data["id"]) - except User.DoesNotExist: - return error_response(u"该用户不存在!") - try: - user = User.objects.get(username=data["username"]) - if user.id != data["id"]: - return error_response(u"昵称已经存在") - except User.DoesNotExist: - pass - user.username = data["username"] - user.real_name = data["real_name"] - user.email = data["email"] - user.admin_type = data["admin_type"] - - if data["password"]: - user.set_password(data["password"]) - - # 后台控制用户是否可以使用openapi - if data["openapi"] is False: - user.openapi_appkey = None - elif data["openapi"] and user.openapi_appkey is None: - user.openapi_appkey = rand_str() - - # 后台控制用户是否使用两步验证 - # 注意:用户没开启,后台开启的话,用户没有绑定过两步验证token,会造成无法登陆的! - if data["tfa_auth"] is False: - user.two_factor_auth = False - elif data["tfa_auth"] and user.two_factor_auth is False: - user.two_factor_auth = True - user.tfa_token = rand_str() - - # 后台控制用户是否被禁用 - user.is_forbidden = data["is_forbidden"] - - user.save() - return success_response(UserSerializer(user).data) - else: - return serializer_invalid_response(serializer) - - @super_admin_required - def get(self, request): - """ - 用户分页json api接口 - --- - response_serializer: UserSerializer - """ - user = User.objects.all().order_by("-create_time") - admin_type = request.GET.get("admin_type", None) - if admin_type: - try: - user = user.filter(admin_type__gte=int(admin_type)) - except ValueError: - return error_response(u"参数错误") - keyword = request.GET.get("keyword", None) - if keyword: - user = user.filter(Q(username__contains=keyword) | - Q(real_name__contains=keyword) | - Q(email__contains=keyword)) - return paginate(request, user, UserSerializer) - - class UserInfoAPIView(APIView): @login_required def get(self, request): diff --git a/oj/settings.py b/oj/settings.py index 847c19e5..0786077d 100644 --- a/oj/settings.py +++ b/oj/settings.py @@ -95,7 +95,7 @@ WSGI_APPLICATION = 'oj.wsgi.application' # Internationalization # https://docs.djangoproject.com/en/1.8/topics/i18n/ -LANGUAGE_CODE = 'zh-hans' +LANGUAGE_CODE = 'en-us' TIME_ZONE = 'Asia/Shanghai' diff --git a/oj/urls.py b/oj/urls.py index 2a802e0a..747c0990 100644 --- a/oj/urls.py +++ b/oj/urls.py @@ -35,6 +35,7 @@ urlpatterns = [ url(r'^admin/template/(?P\w+)/(?P\w+).html$', AdminTemplateView.as_view(), name="admin_template"), + # account app url(r'^login/$', TemplateView.as_view(template_name="oj/account/login.html"), name="user_login_page"), url(r'^logout/$', "account.views.logout", name="user_logout_api"), url(r'^register/$', TemplateView.as_view(template_name="oj/account/register.html"), @@ -43,13 +44,14 @@ urlpatterns = [ name="user_change_password_page"), url(r'^announcement/(?P\d+)/$', "announcement.views.announcement_page", name="announcement_page"), - url(r'^api/user/$', UserInfoAPIView.as_view(), name="user_info_api"), url(r'^api/login/$', UserLoginAPIView.as_view(), name="user_login_api"), url(r'^api/register/$', UserRegisterAPIView.as_view(), name="user_register_api"), url(r'^api/change_password/$', UserChangePasswordAPIView.as_view(), name="user_change_password_api"), url(r'^api/username_check/$', UsernameCheckAPIView.as_view(), name="username_check_api"), url(r'^api/email_check/$', EmailCheckAPIView.as_view(), name="email_check_api"), + + url(r'^api/contest/password/$', ContestPasswordVerifyAPIView.as_view(), name="contest_password_verify_api"), url(r'^api/contest/submission/$', ContestSubmissionAPIView.as_view(), name="contest_submission_api"), url(r'^api/submission/$', SubmissionAPIView.as_view(), name="submission_api"), diff --git a/problem/serizalizers.py b/problem/serizalizers.py index 95f3ccc7..6cdd515d 100644 --- a/problem/serizalizers.py +++ b/problem/serizalizers.py @@ -1,9 +1,8 @@ # coding=utf-8 -import json - from rest_framework import serializers from account.models import User +from utils.serializers import JSONField from .models import Problem, ProblemTag @@ -12,11 +11,6 @@ class ProblemSampleSerializer(serializers.ListField): output = serializers.CharField(max_length=3000) -class JSONField(serializers.Field): - def to_representation(self, value): - return json.loads(value) - - class CreateProblemSerializer(serializers.Serializer): title = serializers.CharField(max_length=50) description = serializers.CharField(max_length=10000) diff --git a/utils/serializers.py b/utils/serializers.py new file mode 100644 index 00000000..154ce810 --- /dev/null +++ b/utils/serializers.py @@ -0,0 +1,18 @@ +# coding=utf-8 +import json + +from django.utils import timezone + +from rest_framework import serializers + + +class JSONField(serializers.Field): + def to_representation(self, value): + return json.loads(value) + + +class DateTimeTZField(serializers.DateTimeField): + def to_representation(self, value): + self.format = "%Y-%-m-%d %-H:%-M:%-S" + value = timezone.localtime(value) + return super(DateTimeTZField, self).to_representation(value) \ No newline at end of file diff --git a/utils/shortcuts.py b/utils/shortcuts.py index 2f5906bb..9055db81 100644 --- a/utils/shortcuts.py +++ b/utils/shortcuts.py @@ -81,7 +81,7 @@ def paginate_data(request, query_set, object_serializer): "previous_page": None, "next_page": None, "page_size": page_size, - "current_page": page, + "current_page": int(page), "count": paginator.count, "total_page": paginator.num_pages}