diff --git a/account/models.py b/account/models.py index afd644e..4716d26 100644 --- a/account/models.py +++ b/account/models.py @@ -60,7 +60,9 @@ class User(AbstractBaseUser): return self.problem_permission == ProblemPermission.ALL def is_contest_admin(self, contest): - return self.is_authenticated and (contest.created_by == self or self.admin_type == AdminType.SUPER_ADMIN) + return self.is_authenticated and ( + contest.created_by == self or self.admin_type == AdminType.SUPER_ADMIN + ) class Meta: db_table = "user" diff --git a/account/serializers.py b/account/serializers.py index 403b137..0f88ec7 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -43,9 +43,10 @@ class GenerateUserSerializer(serializers.Serializer): password_length = serializers.IntegerField(max_value=16, default=8) -class ImportUserSeralizer(serializers.Serializer): +class ImportUserSerializer(serializers.Serializer): users = serializers.ListField( - child=serializers.ListField(child=serializers.CharField(max_length=64))) + child=serializers.ListField(child=serializers.CharField(max_length=64)) + ) class UserAdminSerializer(serializers.ModelSerializer): @@ -53,8 +54,19 @@ class UserAdminSerializer(serializers.ModelSerializer): class Meta: model = User - fields = ["id", "username", "email", "admin_type", "problem_permission", "real_name", - "create_time", "last_login", "two_factor_auth", "open_api", "is_disabled"] + fields = [ + "id", + "username", + "email", + "admin_type", + "problem_permission", + "real_name", + "create_time", + "last_login", + "two_factor_auth", + "open_api", + "is_disabled", + ] def get_real_name(self, obj): return obj.userprofile.real_name @@ -63,8 +75,18 @@ class UserAdminSerializer(serializers.ModelSerializer): class UserSerializer(serializers.ModelSerializer): class Meta: model = User - fields = ["id", "username", "email", "admin_type", "problem_permission", - "create_time", "last_login", "two_factor_auth", "open_api", "is_disabled"] + fields = [ + "id", + "username", + "email", + "admin_type", + "problem_permission", + "create_time", + "last_login", + "two_factor_auth", + "open_api", + "is_disabled", + ] class UserProfileSerializer(serializers.ModelSerializer): @@ -87,11 +109,16 @@ class EditUserSerializer(serializers.Serializer): id = serializers.IntegerField() username = serializers.CharField(max_length=32) real_name = serializers.CharField(max_length=32, allow_blank=True, allow_null=True) - password = serializers.CharField(min_length=6, allow_blank=True, required=False, default=None) + password = serializers.CharField( + min_length=6, allow_blank=True, required=False, default=None + ) email = serializers.EmailField(max_length=64) - admin_type = serializers.ChoiceField(choices=(AdminType.REGULAR_USER, AdminType.ADMIN, AdminType.SUPER_ADMIN)) - problem_permission = serializers.ChoiceField(choices=(ProblemPermission.NONE, ProblemPermission.OWN, - ProblemPermission.ALL)) + admin_type = serializers.ChoiceField( + choices=(AdminType.REGULAR_USER, AdminType.ADMIN, AdminType.SUPER_ADMIN) + ) + problem_permission = serializers.ChoiceField( + choices=(ProblemPermission.NONE, ProblemPermission.OWN, ProblemPermission.ALL) + ) open_api = serializers.BooleanField() two_factor_auth = serializers.BooleanField() is_disabled = serializers.BooleanField() diff --git a/account/views/admin.py b/account/views/admin.py index b207db9..b17c457 100644 --- a/account/views/admin.py +++ b/account/views/admin.py @@ -13,12 +13,16 @@ from utils.shortcuts import rand_str from ..decorators import super_admin_required from ..models import AdminType, ProblemPermission, User, UserProfile -from ..serializers import EditUserSerializer, UserAdminSerializer, GenerateUserSerializer -from ..serializers import ImportUserSeralizer +from ..serializers import ( + EditUserSerializer, + UserAdminSerializer, + GenerateUserSerializer, +) +from ..serializers import ImportUserSerializer class UserAdminAPI(APIView): - @validate_serializer(ImportUserSeralizer) + @validate_serializer(ImportUserSerializer) @super_admin_required def post(self, request): """ @@ -30,12 +34,23 @@ class UserAdminAPI(APIView): for user_data in data: if len(user_data) != 4 or len(user_data[0]) > 32: return self.error(f"Error occurred while processing data '{user_data}'") - user_list.append(User(username=user_data[0], password=make_password(user_data[1]), email=user_data[2])) + user_list.append( + User( + username=user_data[0], + password=make_password(user_data[1]), + email=user_data[2], + ) + ) try: with transaction.atomic(): ret = User.objects.bulk_create(user_list) - UserProfile.objects.bulk_create([UserProfile(user=ret[i], real_name=data[i][3]) for i in range(len(ret))]) + UserProfile.objects.bulk_create( + [ + UserProfile(user=ret[i], real_name=data[i][3]) + for i in range(len(ret)) + ] + ) return self.success() except IntegrityError as e: # Extract detail from exception message @@ -54,9 +69,17 @@ class UserAdminAPI(APIView): user = User.objects.get(id=data["id"]) except User.DoesNotExist: return self.error("User does not exist") - if User.objects.filter(username=data["username"].lower()).exclude(id=user.id).exists(): + if ( + User.objects.filter(username=data["username"].lower()) + .exclude(id=user.id) + .exists() + ): return self.error("Username already exists") - if User.objects.filter(email=data["email"].lower()).exclude(id=user.id).exists(): + if ( + User.objects.filter(email=data["email"].lower()) + .exclude(id=user.id) + .exists() + ): return self.error("Email already exists") pre_username = user.username @@ -94,7 +117,9 @@ class UserAdminAPI(APIView): user.save() if pre_username != user.username: - Submission.objects.filter(username=pre_username).update(username=user.username) + Submission.objects.filter(username=pre_username).update( + username=user.username + ) UserProfile.objects.filter(user=user).update(real_name=data["real_name"]) return self.success(UserAdminSerializer(user).data) @@ -114,11 +139,18 @@ class UserAdminAPI(APIView): user = User.objects.all().order_by("-create_time") + is_admin = request.GET.get("admin", False) + + if is_admin: + user = user.exclude(admin_type=AdminType.REGULAR_USER) + keyword = request.GET.get("keyword", None) if keyword: - user = user.filter(Q(username__icontains=keyword) | - Q(userprofile__real_name__icontains=keyword) | - Q(email__icontains=keyword)) + user = user.filter( + Q(username__icontains=keyword) + | Q(userprofile__real_name__icontains=keyword) + | Q(email__icontains=keyword) + ) return self.success(self.paginate_data(request, user, UserAdminSerializer)) @super_admin_required @@ -162,7 +194,9 @@ class GenerateUserAPI(APIView): Generate User """ data = request.data - number_max_length = max(len(str(data["number_from"])), len(str(data["number_to"]))) + 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"]: @@ -180,15 +214,19 @@ class GenerateUserAPI(APIView): user_list = [] for number in range(data["number_from"], data["number_to"] + 1): raw_password = rand_str(data["password_length"]) - user = User(username=f"{data['prefix']}{number}{data['suffix']}", password=make_password(raw_password)) + user = User( + username=f"{data['prefix']}{number}{data['suffix']}", + password=make_password(raw_password), + ) user.raw_password = raw_password user_list.append(user) try: with transaction.atomic(): - ret = User.objects.bulk_create(user_list) - UserProfile.objects.bulk_create([UserProfile(user=user) for user in ret]) + UserProfile.objects.bulk_create( + [UserProfile(user=user) for user in ret] + ) for item in user_list: worksheet.write_string(i, 0, item.username) worksheet.write_string(i, 1, item.raw_password) diff --git a/problem/migrations/0016_alter_problem_last_update_time.py b/problem/migrations/0016_alter_problem_last_update_time.py new file mode 100644 index 0000000..34eaba2 --- /dev/null +++ b/problem/migrations/0016_alter_problem_last_update_time.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2025-04-30 01:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('problem', '0015_alter_problem_io_mode_alter_problem_languages_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='problem', + name='last_update_time', + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/problem/models.py b/problem/models.py index e3c7c4c..bd8fec7 100644 --- a/problem/models.py +++ b/problem/models.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.db import models from utils.models import JSONField @@ -59,7 +60,7 @@ class Problem(models.Model): template = JSONField() create_time = models.DateTimeField(auto_now_add=True) # we can not use auto_now here - last_update_time = models.DateTimeField(null=True) + last_update_time = models.DateTimeField(auto_now=True) created_by = models.ForeignKey(User, on_delete=models.CASCADE) # ms time_limit = models.IntegerField() @@ -82,7 +83,7 @@ class Problem(models.Model): total_score = models.IntegerField(default=0) submission_number = models.BigIntegerField(default=0) accepted_number = models.BigIntegerField(default=0) - # {JudgeStatus.ACCEPTED: 3, JudgeStaus.WRONG_ANSWER: 11}, the number means count + # {JudgeStatus.ACCEPTED: 3, JudgeStatus.WRONG_ANSWER: 11}, the number means count statistic_info = JSONField(default=dict) share_submission = models.BooleanField(default=False) @@ -98,3 +99,13 @@ class Problem(models.Model): def add_ac_number(self): self.accepted_number = models.F("accepted_number") + 1 self.save(update_fields=["accepted_number"]) + + +# class ProblemSet(models): +# title = models.CharField(max_length=20) +# subtitle = models.CharField(max_length=50) +# problems = models.ManyToManyField(Problem) +# badge = models.FilePathField(path=settings.UPLOAD_DIR) +# overviews = models.JSONField() +# create_time = models.DateTimeField(auto_now_add=True) +# update_time = models.DateTimeField(auto_now=True)