Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions problems/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,18 @@ def reset_user_quotas_on_change(sender, instance, created, **kwargs):
return

# 延遲導入避免循環依賴
from django.db import transaction
from submissions.models import UserProblemQuota

# 重置所有該題目的配額記錄
updated_count = UserProblemQuota.objects.filter(
problem_id=instance.id,
assignment_id__isnull=True # 只重置全域配額,不影響作業配額
).update(
total_quota=new_quota,
remaining_attempts=new_quota
)
with transaction.atomic():
updated_count = UserProblemQuota.objects.filter(
problem_id=instance.id,
assignment_id=None # 只重置全域配額,不影響作業配額
).update(
total_quota=new_quota,
remaining_attempts=new_quota
)

logger.info(
f"Problem {instance.id} total_quota changed from {old_quota} to {new_quota}. "
Expand Down
56 changes: 38 additions & 18 deletions submissions/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -886,15 +886,32 @@ def post(self, request, *args, **kwargs):
if problem_quota >= 0:
# 題目有配額限制,需要檢查/建立該用戶的 UserProblemQuota 記錄
with transaction.atomic():
quota, created = UserProblemQuota.objects.select_for_update().get_or_create(
user=user,
problem_id=problem_id,
assignment_id=None, # 全域配額(非作業)
defaults={
'total_quota': problem_quota,
'remaining_attempts': problem_quota
}
)
try:
# 先嘗試在行鎖下取得現有配額紀錄
quota = UserProblemQuota.objects.select_for_update().get(
user=user,
problem_id=problem_id,
assignment_id=None, # 全域配額(非作業)
)
created = False
except UserProblemQuota.DoesNotExist:
# 如不存在則嘗試建立,若發生競態導致 IntegrityError,再回頭取得
try:
quota = UserProblemQuota.objects.create(
user=user,
problem_id=problem_id,
assignment_id=None, # 全域配額(非作業)
total_quota=problem_quota,
remaining_attempts=problem_quota,
)
created = True
except IntegrityError:
quota = UserProblemQuota.objects.select_for_update().get(
user=user,
problem_id=problem_id,
assignment_id=None, # 全域配額(非作業)
)
created = False

if quota.remaining_attempts == 0:
return api_response(
Expand All @@ -904,9 +921,11 @@ def post(self, request, *args, **kwargs):
)

# 減少配額
if quota.remaining_attempts > 0:
quota.remaining_attempts -= 1
quota.save()
quota.remaining_attempts -= 1
quota.save()

# 儲存提交記錄,確保配額扣減與提交建立的原子性
submission = serializer.save()
else:
# 題目無配額限制 (total_quota == -1)
# 但仍檢查是否有管理員手動設定的 UserProblemQuota(針對特定用戶的限制)
Expand All @@ -915,7 +934,7 @@ def post(self, request, *args, **kwargs):
quota = UserProblemQuota.objects.select_for_update().get(
user=user,
problem_id=problem_id,
assignment_id__isnull=True
assignment_id=None # 全域配額(非作業),與上方 get_or_create 邏輯一致
)
# 只有當 total_quota >= 0 時才檢查(-1 表示此用戶也無限制)
if quota.total_quota >= 0:
Expand All @@ -925,14 +944,15 @@ def post(self, request, *args, **kwargs):
message="you have used all your quotas",
status_code=status.HTTP_403_FORBIDDEN
)
if quota.remaining_attempts > 0:
quota.remaining_attempts -= 1
quota.save()
quota.remaining_attempts -= 1
quota.save()

# 儲存提交記錄,確保配額扣減與提交建立的原子性
submission = serializer.save()
except UserProblemQuota.DoesNotExist:
# 沒有任何配額限制,允許提交
pass
submission = serializer.save()

submission = serializer.save()

# NOJ 格式響應
return api_response(
Expand Down