From e8b06f0487c523ea92bc7392850288ff2a8cbf8b Mon Sep 17 00:00:00 2001 From: zema1 Date: Sat, 18 Nov 2017 08:07:03 +0800 Subject: [PATCH] add generate user api --- account/models.py | 2 +- account/serializers.py | 9 +++++ account/urls/admin.py | 3 +- account/views/admin.py | 87 ++++++++++++++++++++++++++++++++++++++++- deploy/requirements.txt | 1 + judge/dispatcher.py | 4 +- problem/serializers.py | 4 +- problem/tests.py | 2 +- problem/views/admin.py | 33 ++++++++++------ 9 files changed, 123 insertions(+), 22 deletions(-) diff --git a/account/models.py b/account/models.py index eb92f750..ceac9aff 100644 --- a/account/models.py +++ b/account/models.py @@ -67,7 +67,7 @@ class User(AbstractBaseUser): class UserProfile(models.Model): - user = models.OneToOneField(User) + user = models.OneToOneField(User, on_delete=models.CASCADE) # acm_problems_status examples: # { # "problems": { diff --git a/account/serializers.py b/account/serializers.py index c07e6ca2..93f0498a 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -35,6 +35,15 @@ class UserChangeEmailSerializer(serializers.Serializer): tfa_code = serializers.CharField(required=False, allow_blank=True) +class GenerateUserSerializer(serializers.Serializer): + prefix = serializers.CharField(max_length=16, allow_blank=True) + suffix = serializers.CharField(max_length=16, allow_blank=True) + number_from = serializers.IntegerField() + number_to = serializers.IntegerField() + default_email = serializers.CharField(max_length=64) + password_length = serializers.IntegerField(required=False, max_value=16) + + class UserSerializer(serializers.ModelSerializer): create_time = DateTimeTZField() last_login = DateTimeTZField() diff --git a/account/urls/admin.py b/account/urls/admin.py index b10741eb..5826ae2e 100644 --- a/account/urls/admin.py +++ b/account/urls/admin.py @@ -1,7 +1,8 @@ from django.conf.urls import url -from ..views.admin import UserAdminAPI +from ..views.admin import UserAdminAPI, GenerateUserAPI urlpatterns = [ url(r"^user/?$", UserAdminAPI.as_view(), name="user_admin_api"), + url(r"^generate_user/?$", GenerateUserAPI.as_view(), name="generate_user_api"), ] diff --git a/account/views/admin.py b/account/views/admin.py index 7b9259e6..4ae11adc 100644 --- a/account/views/admin.py +++ b/account/views/admin.py @@ -1,12 +1,16 @@ +import os +import re +import xlsxwriter from django.db.models import Q +from django.http import HttpResponse from submission.models import Submission from utils.api import APIView, validate_serializer from utils.shortcuts import rand_str from ..decorators import super_admin_required -from ..models import AdminType, ProblemPermission, User -from ..serializers import EditUserSerializer, UserSerializer +from ..models import AdminType, ProblemPermission, User, UserProfile +from ..serializers import EditUserSerializer, UserSerializer, GenerateUserSerializer class UserAdminAPI(APIView): @@ -85,3 +89,82 @@ class UserAdminAPI(APIView): Q(userprofile__real_name__icontains=keyword) | Q(email__icontains=keyword)) return self.success(self.paginate_data(request, user, UserSerializer)) + + def delete_one(self, user_id): + try: + user = User.objects.get(id=user_id) + except User.DoesNotExist: + return f"User {user_id} does not exist" + profile = user.userprofile + if profile.submission_number: + return f"Can't delete the user {user_id} as he/she has submissions" + user.delete() + + @super_admin_required + def delete(self, request): + id = request.GET.get("id") + if not id: + return self.error("Invalid Parameter, user_id is required") + for user_id in id.split(","): + if user_id: + error = self.delete_one(user_id) + if error: + return self.error(error) + return self.success() + + +class GenerateUserAPI(APIView): + @super_admin_required + def get(self, request): + """ + download users excel + """ + file_id = request.GET.get("file_id") + if not file_id: + return self.error("Invalid Parameter, file_id is required") + if not re.match(r"[a-zA-Z0-9]+", file_id): + return self.error("Illegal file_id") + file_path = f"/tmp/{file_id}.xlsx" + if not os.path.isfile(file_path): + return self.error("File does not exist") + with open(file_path, "rb") as f: + raw_data = f.read() + os.remove(file_path) + response = HttpResponse(raw_data) + response["Content-Disposition"] = f"attachment; filename=users.xlsx" + response["Content-Type"] = "application/xlsx" + return response + + @validate_serializer(GenerateUserSerializer) + @super_admin_required + def post(self, request): + data = request.data + number_max_length = max(len(str(data["number_from"])), len(str(data["number_to"]))) + if number_max_length + len(data["prefix"]) + len(data["suffix"]) > 32: + return self.error("Username should not more than 32 characters") + if data["number_from"] >= data["number_to"]: + return self.error("Start number must be lower than end number") + + password_length = data.get("password_length", 8) + default_email = data.get("default_email") + + file_id = rand_str(8) + filename = f"/tmp/{file_id}.xlsx" + workbook = xlsxwriter.Workbook(filename) + worksheet = workbook.add_worksheet() + worksheet.set_column("A:B", 20) + worksheet.write("A1", "Username") + worksheet.write("B1", "Password") + i = 1 + for number in range(data["number_from"], data["number_to"] + 1): + username = f"{data['prefix']}{number}{data['suffix']}" + password = rand_str(password_length) + user = User.objects.create(username=username, email=default_email) + user.set_password(password) + user.save() + UserProfile.objects.create(user=user) + worksheet.write_string(i, 0, username) + worksheet.write_string(i, 1, password) + i += 1 + workbook.close() + return self.success(file_id) diff --git a/deploy/requirements.txt b/deploy/requirements.txt index 559cce86..3d83ebfd 100644 --- a/deploy/requirements.txt +++ b/deploy/requirements.txt @@ -15,3 +15,4 @@ django-redis psycopg2 gunicorn jsonfield +XlsxWriter diff --git a/judge/dispatcher.py b/judge/dispatcher.py index f108f1fb..7c040297 100644 --- a/judge/dispatcher.py +++ b/judge/dispatcher.py @@ -221,10 +221,10 @@ class JudgeDispatcher(DispatcherBase): user_profile.accepted_number += 1 else: if oi_problems_status[problem_id]["status"] == JudgeStatus.ACCEPTED and \ - self.submission.result != JudgeStatus.ACCEPTED: + self.submission.result != JudgeStatus.ACCEPTED: user_profile.accepted_number -= 1 elif oi_problems_status[problem_id]["status"] != JudgeStatus.ACCEPTED and \ - self.submission.result == JudgeStatus: + self.submission.result == JudgeStatus: user_profile.accepted_number += 1 # minus last time score, add this time score diff --git a/problem/serializers.py b/problem/serializers.py index d035e755..57a034ce 100644 --- a/problem/serializers.py +++ b/problem/serializers.py @@ -49,6 +49,7 @@ class CreateOrEditProblemSerializer(serializers.Serializer): spj = serializers.BooleanField() spj_language = serializers.ChoiceField(choices=spj_language_names, allow_blank=True, allow_null=True) spj_code = serializers.CharField(allow_blank=True, allow_null=True) + spj_compile_ok = serializers.BooleanField(default=False) visible = serializers.BooleanField() difficulty = serializers.ChoiceField(choices=[Difficulty.LOW, Difficulty.MID, Difficulty.HIGH]) tags = serializers.ListField(child=serializers.CharField(max_length=32), allow_empty=False) @@ -80,7 +81,6 @@ class TagSerializer(serializers.ModelSerializer): class CompileSPJSerializer(serializers.Serializer): - id = serializers.IntegerField() spj_language = serializers.ChoiceField(choices=spj_language_names) spj_code = serializers.CharField() @@ -131,5 +131,3 @@ class ContestProblemSafeSerializer(BaseProblemSerializer): class ContestProblemMakePublicSerializer(serializers.Serializer): id = serializers.IntegerField() display_id = serializers.CharField(max_length=32) - - diff --git a/problem/tests.py b/problem/tests.py index 7595260e..dbd01b7f 100644 --- a/problem/tests.py +++ b/problem/tests.py @@ -19,7 +19,7 @@ DEFAULT_PROBLEM_DATA = {"_id": "A-110", "title": "test", "description": "

test "output_description": "test", "time_limit": 1000, "memory_limit": 256, "difficulty": "Low", "visible": True, "tags": ["test"], "languages": ["C", "C++", "Java", "Python2"], "template": {}, "samples": [{"input": "test", "output": "test"}], "spj": False, "spj_language": "C", - "spj_code": "", "test_case_id": "499b26290cc7994e0b497212e842ea85", + "spj_code": "", "spj_compile_ok": True, "test_case_id": "499b26290cc7994e0b497212e842ea85", "test_case_score": [{"output_name": "1.out", "input_name": "1.in", "output_size": 0, "stripped_output_md5": "d41d8cd98f00b204e9800998ecf8427e", "input_size": 0, "score": 0}], diff --git a/problem/views/admin.py b/problem/views/admin.py index 6fc54513..36fe6550 100644 --- a/problem/views/admin.py +++ b/problem/views/admin.py @@ -1,6 +1,7 @@ import hashlib import json import os +import shutil import zipfile from wsgiref.util import FileWrapper @@ -10,6 +11,7 @@ from django.http import StreamingHttpResponse from account.decorators import problem_permission_required from judge.dispatcher import SPJCompiler from contest.models import Contest +from submission.models import Submission from utils.api import APIView, CSRFExemptAPIView, validate_serializer from utils.shortcuts import rand_str, natural_sort_key @@ -140,23 +142,11 @@ class CompileSPJAPI(APIView): @problem_permission_required def post(self, request): data = request.data - try: - problem = Problem.objects.get(pk=data["id"]) - except Problem.DoesNotExist: - return self.error("Problem does not exist") spj_version = rand_str(8) - problem.spj = True - problem.spj_version = spj_version - problem.spj_language = data["spj_language"] - problem.spj_code = data["spj_code"] error = SPJCompiler(data["spj_code"], spj_version, data["spj_language"]).compile_spj() if error: - problem.spj_compile_ok = False - problem.save() return self.error(error) else: - problem.spj_compile_ok = True - problem.save() return self.success() @@ -166,6 +156,8 @@ class ProblemBase(APIView): if data["spj"]: if not data["spj_language"] or not data["spj_code"]: return "Invalid spj" + if not data["spj_compile_ok"]: + return "SPJ code must be compiled successfully" data["spj_version"] = hashlib.md5( (data["spj_language"] + ":" + data["spj_code"]).encode("utf-8")).hexdigest() else: @@ -182,6 +174,23 @@ class ProblemBase(APIView): data["created_by"] = request.user data["languages"] = list(data["languages"]) + @problem_permission_required + def delete(self, request): + id = request.GET.get("id") + if not id: + return self.error("Invalid parameter, id is requred") + try: + problem = Problem.objects.get(id=id) + except Problem.DoesNotExist: + return self.error("Problem does not exists") + if Submission.objects.filter(problem=problem).exists(): + return self.error("Can't delete the problem as it has submissions") + d = os.path.join(settings.TEST_CASE_DIR, problem.test_case_id) + if os.path.isdir(d): + shutil.rmtree(d, ignore_errors=True) + problem.delete() + return self.success() + class ProblemAPI(ProblemBase): @validate_serializer(CreateProblemSerializer)