Skip to content
Merged

Dev #196

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
164 commits
Select commit Hold shift + click to select a range
6c3bc55
feat: I add User avatar picture.
noyapoyo Dec 24, 2025
158eb96
feat:api authentic方式改全域
chewinggum-master Dec 25, 2025
a527318
feat: add custom checker settings to Problems model and serializers
happylittle7 Dec 26, 2025
71d53b0
feat: update custom checker settings in Problems model and serializers
happylittle7 Dec 26, 2025
78c0ac5
feat: update checker_name logic to use custom checker conditionally
happylittle7 Dec 26, 2025
f028c26
Update problems/models.py help_text
happylittle7 Dec 26, 2025
d3d6509
Merge branch 'feat/problem-checker-field' of https://github.com/Spagh…
happylittle7 Dec 26, 2025
643d891
feat: add custom checker settings to ProblemStudentSerializer and API…
happylittle7 Dec 26, 2025
4f676db
feat: 移除 permission
ktyns71 Dec 26, 2025
bf9980f
Merge pull request #145 from Spaghetti-OJ/feat/problem-checker-field
RokuSennyou Dec 26, 2025
ffd1b8e
Merge pull request #146 from Spaghetti-OJ/problem_API_Permission
RokuSennyou Dec 26, 2025
567bc58
feat: add management commands and seed data script for database initi…
happylittle7 Dec 26, 2025
bef3afe
feat: simplify user creation in seed_minimal and seed_full methods
happylittle7 Dec 26, 2025
2c2574d
Merge pull request #148 from Spaghetti-OJ/feat/add-mock-data
happylittle7 Dec 26, 2025
e114162
feat: add submission results creation and output/error message genera…
happylittle7 Dec 26, 2025
4d49a69
Update user/management/commands/seed_data.py
happylittle7 Dec 26, 2025
6dd776f
Merge pull request #149 from Spaghetti-OJ/feat/add-mock-data
happylittle7 Dec 26, 2025
3ce0e42
feat: update submission result probabilities and error handling in te…
happylittle7 Dec 26, 2025
5af0677
Merge branch 'feat/add-mock-data' of https://github.com/Spaghetti-OJ/…
happylittle7 Dec 26, 2025
b6672bc
fix: remove per-view IsAuthenticated to enforce email verification
Yi-Ting-Wang Dec 26, 2025
4a719a3
feat: add email field to ForgotPasswordSerializer and update ForgotPa…
RokuSennyou Dec 26, 2025
db45949
docs: update forgot password API READKE to require both username and …
RokuSennyou Dec 26, 2025
00f7e2f
Merge pull request #147 from Spaghetti-OJ/feat/api_permmision
RokuSennyou Dec 26, 2025
ca2e459
Update auths/serializers/password.py
RokuSennyou Dec 26, 2025
1717c05
Merge pull request #152 from Spaghetti-OJ/feat/add-username-to-email
RokuSennyou Dec 26, 2025
67f0ade
Merge pull request #151 from Spaghetti-OJ/fix/email-verified-permissi…
RokuSennyou Dec 26, 2025
a3ef0b7
feat: Add some limit in POST submission API but haven't implement IP …
noyapoyo Dec 26, 2025
acb1763
fix: remove all permission_classes = [IsAuthenticated] and @permissio…
noyapoyo Dec 26, 2025
0b45cb0
feat:學生只能看到有加入的課程的problem或publec的problem
chewinggum-master Dec 26, 2025
ff326eb
Add test files to gitignore
chewinggum-master Dec 26, 2025
00ddbe4
Ignore test.api.py
chewinggum-master Dec 26, 2025
8acd516
feat: add submission testing script for Problem 1 diagnostics
happylittle7 Dec 26, 2025
4bf0a43
feat: enhance environment variable display in submission testing script
happylittle7 Dec 26, 2025
8b3f1ff
Update submissions/views.py
RokuSennyou Dec 27, 2025
731363c
Update submissions/views.py
RokuSennyou Dec 27, 2025
88372c2
Update submissions/views.py
RokuSennyou Dec 27, 2025
cc8d3eb
Merge pull request #153 from Spaghetti-OJ/feat/submission_API_feat_add
RokuSennyou Dec 27, 2025
c438468
Update problems/views/api.py
RokuSennyou Dec 27, 2025
8c52602
Update problems/views/api.py
RokuSennyou Dec 27, 2025
e2d9be7
Merge pull request #154 from Spaghetti-OJ/problem_API_Permission
RokuSennyou Dec 27, 2025
7f9fb6c
feat: enhance Copycat permissions and authentication checks for cours…
happylittle7 Dec 27, 2025
9889fd9
Merge pull request #150 from Spaghetti-OJ/feat/add-mock-data
happylittle7 Dec 27, 2025
81d1836
feat: add MOSS connection tests and setup for local execution
happylittle7 Dec 27, 2025
accfa9d
fix: improve permission checks and error handling in CopycatView
happylittle7 Dec 27, 2025
5c7c5be
Fix typo
happylittle7 Dec 27, 2025
3aa315c
Merge pull request #156 from Spaghetti-OJ/fix/copycat
happylittle7 Dec 27, 2025
df0433f
fix: I remove Editorial's title, difficulty, and official.
noyapoyo Dec 27, 2025
8a54b41
docs: I update editorials.MD.
noyapoyo Dec 27, 2025
fe9b187
fix: fix the bug of admin and makemigrations again.
noyapoyo Dec 27, 2025
06e2940
docs: Update editorials.MD.
noyapoyo Dec 27, 2025
2eb64a4
docs: Update editorials.MD.
noyapoyo Dec 27, 2025
fb3c438
Merge pull request #158 from Spaghetti-OJ/fix/editorials_bug_fix
happylittle7 Dec 27, 2025
2f98140
feat: Add many filter in GET /submission/ API.
noyapoyo Dec 27, 2025
d2ba90c
docs: I update document about GET /submission/ API.
noyapoyo Dec 27, 2025
7a8e79c
fix: I fix some problem that copilot mentioned before.
noyapoyo Dec 27, 2025
d1fce41
Merge pull request #159 from Spaghetti-OJ/feat/finish_all_submission_…
happylittle7 Dec 27, 2025
397209e
Merge pull request #155 from Spaghetti-OJ/test/submission-to-sandbox-…
happylittle7 Dec 27, 2025
eb04d69
feat: Add access_course field to MeSerializer for retrieving course m…
RokuSennyou Dec 27, 2025
56313c8
feat: Enhance course access retrieval in MeSerializer and add course …
RokuSennyou Dec 27, 2025
c0d67b5
docs: Add access_course field to GET /auth/me/ response at README
RokuSennyou Dec 27, 2025
fa7bb7d
Merge pull request #162 from Spaghetti-OJ/feat/access-course-at-auth-me
RokuSennyou Dec 27, 2025
48d2dfc
feat: Add static analysis rules and validation to Problems model
happylittle7 Dec 27, 2025
b309dec
feat: Add static analysis settings to ProblemManageSerializer and Pro…
happylittle7 Dec 27, 2025
86699fc
feat: Add static analysis fields and configuration to problem respons…
happylittle7 Dec 27, 2025
64857b7
feat;新增user name在stat
chewinggum-master Dec 27, 2025
f7175d8
Merge pull request #163 from Spaghetti-OJ/problem_API_Permission
RokuSennyou Dec 27, 2025
acfff8d
feat:get/problem新增submit_count、get/problem/{id}新增subtaskDescription
chewinggum-master Dec 27, 2025
b0e2703
Merge pull request #165 from Spaghetti-OJ/problem_API_Permission
RokuSennyou Dec 28, 2025
9c1ae6d
Initial plan
Copilot Dec 28, 2025
e1c866b
refactor: Address review comments - fix docs, remove dead code, impro…
Copilot Dec 28, 2025
7d23b08
feat: I add IP whitelist in GET /submission/ API and modify test code…
noyapoyo Dec 28, 2025
d24f9e1
docs: I update GET /submission/ API document.
noyapoyo Dec 28, 2025
913e635
test: Add comprehensive test coverage for static analysis functionality
Copilot Dec 28, 2025
12a8d46
refactor: Address code review feedback - improve validation and consi…
Copilot Dec 28, 2025
cb94e8c
Merge pull request #166 from Spaghetti-OJ/copilot/sub-pr-164
happylittle7 Dec 28, 2025
b1f9ff9
Merge branch 'dev' into feat/static-analysis
happylittle7 Dec 28, 2025
24d5e8f
Merge pull request #164 from Spaghetti-OJ/feat/static-analysis
happylittle7 Dec 28, 2025
683d152
feat: Update meta structure to include detailed testcases information
happylittle7 Dec 28, 2025
070229a
Initial plan
Copilot Dec 28, 2025
c478280
Refactor: Extract testcase building logic and enhance test coverage
Copilot Dec 28, 2025
6904a3e
Fix: Address code review comments and test isolation issue
Copilot Dec 28, 2025
bfc8f53
Merge pull request #172 from Spaghetti-OJ/copilot/sub-pr-171
happylittle7 Dec 28, 2025
36d73c5
fix: I try to fix the problem about student cannot submit.
noyapoyo Dec 28, 2025
4054e0a
Merge pull request #171 from Spaghetti-OJ/fix/adjust-meta-json
happylittle7 Dec 28, 2025
e31733c
docs: add PUT /homework/<id> update API doc
Yi-Ting-Wang Dec 28, 2025
341a333
fix: Correct callback URL in submit_to_sandbox function
happylittle7 Dec 28, 2025
b024efa
Merge branch 'dev' of https://github.com/Spaghetti-OJ/back_end into dev
happylittle7 Dec 28, 2025
48f3914
fix: Update callback URL to remove trailing slash
happylittle7 Dec 28, 2025
f3ce171
Merge pull request #174 from Spaghetti-OJ/fix/callback-path
happylittle7 Dec 28, 2025
e46730a
feat: add testcase hash to Problems model and change problem hash fro…
happylittle7 Dec 28, 2025
0755ef0
fix: I modify callback_url from {settings.BACKEND_BASE_URL}/submissio…
noyapoyo Dec 28, 2025
b34ef12
fix: update checksum endpoint and documentation to use SHA256 instead…
happylittle7 Dec 28, 2025
9a14295
fix: I fix the problem mentioned by copilot. It about IP filter check.
noyapoyo Dec 28, 2025
a0e2011
Merge branch 'dev' into feat/finish_all_submission_function
happylittle7 Dec 28, 2025
2b714ae
Merge pull request #170 from Spaghetti-OJ/feat/finish_all_submission_…
happylittle7 Dec 28, 2025
e998410
feat: add testcase_hash field to problems model for storing SHA256 ha…
happylittle7 Dec 28, 2025
240e310
Initial plan
Copilot Dec 28, 2025
60d021a
Address PR review comments: use stored hash, add migration, fix tests
Copilot Dec 28, 2025
832cb4b
fix: I add use_static_analysis.
noyapoyo Dec 28, 2025
1503912
Merge pull request #177 from Spaghetti-OJ/copilot/sub-pr-176
happylittle7 Dec 28, 2025
263f1e2
Merge pull request #176 from Spaghetti-OJ/fix/add-problem-hash
happylittle7 Dec 28, 2025
56d2009
Initial plan
Copilot Dec 28, 2025
690e83b
fix: address review comments for homework update API
Copilot Dec 28, 2025
a14206c
Merge pull request #178 from Spaghetti-OJ/copilot/sub-pr-173
happylittle7 Dec 28, 2025
e002bd1
fix: Fix subtask problem and CE problem.
noyapoyo Dec 28, 2025
f9f5b40
Update deploy setting
happylittle7 Dec 28, 2025
579f76d
Update branch name for deployment workflow
happylittle7 Dec 28, 2025
63572bc
fix: subtask again.
noyapoyo Dec 28, 2025
28dbfe2
Merge pull request #173 from Spaghetti-OJ/feat/homework-put-doc
happylittle7 Dec 28, 2025
d72c0a8
fix: Fix subtask again.
noyapoyo Dec 28, 2025
63dd778
fix: I add error messenge detail.
noyapoyo Dec 28, 2025
391ec53
fix: Fix subtask again.
noyapoyo Dec 28, 2025
88128d3
fix: I add more error messages.
noyapoyo Dec 28, 2025
c95644b
fix: I modify SubmissionResult PK.
noyapoyo Dec 28, 2025
c6ea49c
fix: I fix seed_data.
noyapoyo Dec 28, 2025
f4d7102
fix: Add more Error message.
noyapoyo Dec 28, 2025
b11bd78
fix: I fix AC or orther status cannot work problem.
noyapoyo Dec 28, 2025
a5416bd
fix: adjust homework stats api state structure
Yi-Ting-Wang Dec 28, 2025
de607d7
fix: Fix subtask problem.
noyapoyo Dec 28, 2025
7d35c33
Initial plan
Copilot Dec 28, 2025
6cc3389
fix: I add some log to debug.
noyapoyo Dec 28, 2025
7181c38
fix: apply review comments - use specific exceptions and remove redun…
Copilot Dec 28, 2025
9207e53
Merge pull request #180 from Spaghetti-OJ/copilot/sub-pr-179
happylittle7 Dec 28, 2025
ebb45dc
Merge pull request #179 from Spaghetti-OJ/fix/homework-state
happylittle7 Dec 28, 2025
c8860b9
fix: I try to fix some problem.
noyapoyo Dec 28, 2025
eae7e6e
Merge branch 'dev' into feat/finish_all_submission_function
happylittle7 Dec 28, 2025
1565709
Merge pull request #181 from Spaghetti-OJ/feat/finish_all_submission_…
happylittle7 Dec 28, 2025
957226a
fix: update quota check logic for user submissions
happylittle7 Dec 28, 2025
6d4190a
feat(assignments): add ip_whitelist field
Yi-Ting-Wang Dec 28, 2025
8adf521
Initial plan
Copilot Dec 28, 2025
4d2f9fc
Fix transaction safety, race conditions, add docs and comprehensive q…
Copilot Dec 28, 2025
d220474
feat(assignments): add ip_whitelist field
Yi-Ting-Wang Dec 28, 2025
69bd922
Fix test URL and remove duplicate migrations
Copilot Dec 28, 2025
bc7c91c
Address code review feedback: use IntegrityError and consistent query…
Copilot Dec 28, 2025
3d4c1c8
Use __isnull=True for get() queries and remove unused imports
Copilot Dec 28, 2025
faaab37
Use consistent assignment_id=None and simplify test assertions
Copilot Dec 28, 2025
17a1df4
Update assignments/models.py
RokuSennyou Dec 28, 2025
f1c8326
Merge pull request #183 from Spaghetti-OJ/feature/assignments-models-…
RokuSennyou Dec 28, 2025
e540ebe
Merge pull request #184 from Spaghetti-OJ/copilot/sub-pr-182
happylittle7 Dec 28, 2025
c31e5c4
Merge pull request #182 from Spaghetti-OJ/fix/quota
happylittle7 Dec 28, 2025
334dd73
feat:alloewdNetwork的欄位
chewinggum-master Dec 28, 2025
2122141
Merge branch 'dev' into problem_API_Permission
chewinggum-master Dec 28, 2025
f649b23
Merge pull request #185 from Spaghetti-OJ/problem_API_Permission
RokuSennyou Dec 28, 2025
4c51426
feat: I add some data for sending data to SandBox.
noyapoyo Dec 28, 2025
d52f512
feat:支援localhost
chewinggum-master Dec 28, 2025
7bfdfbc
Merge branch 'dev' into problem_API_Permission
chewinggum-master Dec 28, 2025
39eb00d
Merge pull request #188 from Spaghetti-OJ/problem_API_Permission
RokuSennyou Dec 28, 2025
0a370b2
feat: Add some test code.
noyapoyo Dec 28, 2025
7d0047a
Initial plan
Copilot Dec 28, 2025
6d209bf
feat:like會回傳course_name
chewinggum-master Dec 28, 2025
41bdde4
refactor: Extract duplicated static analysis and network config logic…
Copilot Dec 28, 2025
55a1139
Merge pull request #190 from Spaghetti-OJ/copilot/sub-pr-189
happylittle7 Dec 28, 2025
7de42dc
Merge pull request #189 from Spaghetti-OJ/feat/Add_some_data_for_submit
happylittle7 Dec 28, 2025
735f5ae
Merge branch 'dev' into problem_API_Permission
chewinggum-master Dec 28, 2025
2ee2d87
Merge pull request #191 from Spaghetti-OJ/problem_API_Permission
RokuSennyou Dec 28, 2025
bebe131
fix: align homework scoreboard response keys with frontend
Yi-Ting-Wang Dec 28, 2025
9223e23
Merge pull request #192 from Spaghetti-OJ/fix/assignments-scoreboard
RokuSennyou Dec 28, 2025
89895df
feat:權限bug
chewinggum-master Dec 28, 2025
f7d8d4b
fix(rbac): 修復 RBAC 測試 authentication 問題並實現管理員特權\n\n修改內容:\n1. 註解掉覆蓋全域配…
BlankTsai Dec 28, 2025
1ecb33e
fix: 修正 assignments serializer 類型和其他 bug\n\n- 修正 course_id 類型從 UUIDFi…
BlankTsai Dec 28, 2025
dbcab7e
test: 添加各應用程式的 API 測試文件
BlankTsai Dec 28, 2025
cb55c31
Merge pull request #193 from Spaghetti-OJ/problem_API_Permission
RokuSennyou Dec 28, 2025
1cb3a82
Merge branch 'dev' into fix/rbac-authentication-issues
RokuSennyou Dec 28, 2025
0b909df
Merge pull request #194 from Spaghetti-OJ/fix/rbac-authentication-issues
RokuSennyou Dec 28, 2025
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
3 changes: 1 addition & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ name: Deploy to dev
on:
push:
branches:
- deploy/add-email-sender
- dev
- feat/api-SandBox
- feat/finish_all_submission_function

concurrency: deploy-dev

Expand Down
Binary file modified .gitignore
Binary file not shown.
93 changes: 93 additions & 0 deletions announcements/tests/test_announcements_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@

from django.test import TestCase
from django.contrib.auth import get_user_model
from django.urls import reverse
from rest_framework.test import APIClient
from rest_framework import status
from courses.models import Courses, Announcements, Course_members
from user.models import UserProfile

User = get_user_model()

class AnnouncementApiTest(TestCase):
def setUp(self):
self.client = APIClient()
self.teacher = User.objects.create_user(username='teacher', email='teacher@example.com', password='password', identity=User.Identity.TEACHER)
UserProfile.objects.update_or_create(user=self.teacher, defaults={'email_verified': True})

self.student = User.objects.create_user(username='student', email='student@example.com', password='password', identity=User.Identity.STUDENT)
UserProfile.objects.update_or_create(user=self.student, defaults={'email_verified': True})

self.course = Courses.objects.create(name="Test Course", teacher_id=self.teacher)

# Add student
Course_members.objects.create(course_id=self.course, user_id=self.student, role=Course_members.Role.STUDENT)

self.announcement = Announcements.objects.create(
course_id=self.course,
title="Welcome",
content="Hello everyone",
creator_id=self.teacher
)

def test_list_announcements(self):
self.client.force_authenticate(user=self.student)
url = reverse('announcements:course', kwargs={'course_id': self.course.id})
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(len(response.data['data']) >= 1)

Copilot AI Dec 29, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertTrue(a >= b) cannot provide an informative message. Using assertGreaterEqual(a, b) instead will give more informative messages.

Suggested change
self.assertTrue(len(response.data['data']) >= 1)
self.assertGreaterEqual(len(response.data['data']), 1)

Copilot uses AI. Check for mistakes.

def test_retrieve_announcement(self):
self.client.force_authenticate(user=self.student)
url = reverse('announcements:announcement', kwargs={'course_id': self.course.id, 'ann_id': self.announcement.id})
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['data'][0]['title'], "Welcome")

def test_create_announcement_teacher(self):
self.client.force_authenticate(user=self.teacher)
url = reverse('announcements:create')
data = {
'course_id': self.course.id,
'title': 'New Announcement',
'content': 'Content here',
'is_pinned': False
}
response = self.client.post(url, data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)

def test_create_announcement_student_forbidden(self):
self.client.force_authenticate(user=self.student)
url = reverse('announcements:create')
data = {
'course_id': self.course.id,
'title': 'Hacker Announcement',
'content': 'Content here'
}
response = self.client.post(url, data)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

def test_update_announcement(self):
self.client.force_authenticate(user=self.teacher)
url = reverse('announcements:create') # PUT uses same endpoint name logic? No, let's check view.
# View class AnnouncementCreateView handles POST, PUT, DELETE at /ann/ (name='create')

data = {
'annId': self.announcement.id,
'title': 'Updated Title',
'content': 'Updated Content',
'is_pinned': True
}
response = self.client.put(url, data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.announcement.refresh_from_db()
self.assertEqual(self.announcement.title, 'Updated Title')

def test_delete_announcement(self):
self.client.force_authenticate(user=self.teacher)
url = reverse('announcements:create') # DELETE uses same endpoint

data = {'annId': self.announcement.id}
response = self.client.delete(url, data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertFalse(Announcements.objects.filter(id=self.announcement.id).exists())
2 changes: 0 additions & 2 deletions announcements/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

class CourseAnnouncementBaseView(generics.GenericAPIView):
serializer_class = SystemAnnouncementSerializer
permission_classes = [permissions.IsAuthenticated]

def get_permissions(self):
course_id = self.kwargs.get("course_id")
Expand Down Expand Up @@ -103,7 +102,6 @@ class AnnouncementCreateView(generics.GenericAPIView):
"""

serializer_class = AnnouncementCreateSerializer
permission_classes = [permissions.IsAuthenticated]

def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
Expand Down
4 changes: 2 additions & 2 deletions api_tokens/views/api_tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class ApiTokenListView(APIView):
- POST: 為當前使用者建立一個新的 API Token。
"""

authentication_classes = [ApiTokenAuthentication, JWTAuthentication, SessionAuthentication]
# authentication_classes = [ApiTokenAuthentication, JWTAuthentication, SessionAuthentication] # Commented to use global default
# permission_classes = [IsAuthenticated, TokenHasScope] # Removed to use global default

def get(self, request):
Expand Down Expand Up @@ -72,7 +72,7 @@ class ApiTokenDetailView(APIView):
- DELETE: 刪除指定的 Token
"""

authentication_classes = [ApiTokenAuthentication, JWTAuthentication, SessionAuthentication]
# authentication_classes = [ApiTokenAuthentication, JWTAuthentication, SessionAuthentication] # Commented to use global default
# permission_classes = [IsAuthenticated] # Removed to use global default

def get_object(self, request, tokenId):
Expand Down
19 changes: 19 additions & 0 deletions assignments/migrations/0004_assignments_ip_whitelist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 5.2.7 on 2025-12-28 17:21

import assignments.models
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('assignments', '0003_initial'),
]

operations = [
migrations.AddField(
model_name='assignments',
name='ip_whitelist',
field=models.TextField(blank=True, validators=[assignments.models.validate_cidr_whitelist]),
),
]
38 changes: 37 additions & 1 deletion assignments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,38 @@
from django.conf import settings
from django.db import models
from django.db.models import Q, F

import ipaddress
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _

def validate_cidr_whitelist(value: str):
"""
允許空字串(代表不限制)。
允許多筆 CIDR,用逗號或換行分隔,例如:
192.168.1.0/24
10.0.0.0/8, 140.112.0.0/16
2001:db8::/32
"""
if not value:
return

raw_parts = value.replace("\n", ",").split(",")
parts = [p.strip() for p in raw_parts if p.strip()]
if not parts:
return

errors = []
for p in parts:
try:
ipaddress.ip_network(p, strict=False)
except ValueError:
errors.append(p)

if errors:
raise ValidationError(
_("Invalid CIDR(s): %(items)s"),
params={"items": ", ".join(errors)},
)

class Assignments(models.Model):
"""作業本體"""
Expand Down Expand Up @@ -49,6 +80,11 @@ class Status(models.TextChoices):
)

ip_restriction = models.TextField(blank=True)
ip_whitelist = models.TextField(
blank=True,
default="",
validators=[validate_cidr_whitelist],
)

created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
Expand Down
58 changes: 52 additions & 6 deletions assignments/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def to_epoch_from_dt(dt):

class HomeworkCreateSerializer(serializers.Serializer):
name = serializers.CharField(required=True)
course_id = serializers.UUIDField()
course_id = serializers.IntegerField()
markdown = serializers.CharField(required=False, allow_blank=True, default="")
start = serializers.IntegerField(required=False, allow_null=True)
end = serializers.IntegerField(required=False, allow_null=True)
Expand Down Expand Up @@ -106,6 +106,7 @@ class HomeworkUpdateSerializer(serializers.Serializer):
)
scoreboard_status = serializers.IntegerField(required=False, allow_null=True)
penalty = serializers.CharField(required=False, allow_blank=True)
max_attempts = serializers.IntegerField(required=False, allow_null=True)

def validate(self, attrs):
# 時間檢查
Expand All @@ -126,6 +127,12 @@ def validate(self, attrs):
if not_found:
raise serializers.ValidationError({"problem_ids": f"problems not found: {not_found}"})

# 驗證 max_attempts
if "max_attempts" in attrs:
ma = attrs.get("max_attempts")
if ma is not None and ma != -1 and ma < 1:
raise serializers.ValidationError({"max_attempts": "max_attempts must be -1 or >=1"})

attrs["_start_dt"] = start_dt
attrs["_end_dt"] = end_dt
return attrs
Expand Down Expand Up @@ -185,7 +192,7 @@ class HomeworkDeadlineSerializer(serializers.Serializer):
id = serializers.IntegerField()
name = serializers.CharField()
markdown = serializers.CharField(allow_blank=True)
course_id = serializers.UUIDField()
course_id = serializers.IntegerField()
start = serializers.DateTimeField(allow_null=True)
end = serializers.DateTimeField(allow_null=True)
is_overdue = serializers.BooleanField()
Expand All @@ -211,7 +218,7 @@ class HomeworkProblemStatsSerializer(serializers.Serializer):

class HomeworkStatsSerializer(serializers.Serializer):
homework_id = serializers.IntegerField()
course_id = serializers.UUIDField()
course_id = serializers.IntegerField()

title = serializers.CharField()
description = serializers.CharField(allow_blank=True, allow_null=True)
Expand All @@ -232,7 +239,7 @@ class ScoreboardRowSerializer(serializers.Serializer):
rank = serializers.IntegerField()
user_id = serializers.UUIDField()
username = serializers.CharField()
real_name = serializers.CharField()
real_name = serializers.CharField(allow_null=True, required=False)

total_score = serializers.IntegerField()
max_total_score = serializers.IntegerField()
Expand All @@ -244,8 +251,8 @@ class ScoreboardRowSerializer(serializers.Serializer):
problems = ScoreboardProblemSerializer(many=True)

class HomeworkScoreboardSerializer(serializers.Serializer):
assignment_id = serializers.IntegerField()
title = serializers.CharField()
homework_id = serializers.IntegerField()
homework_title = serializers.CharField()
course_id = serializers.UUIDField()
items = ScoreboardRowSerializer(many=True)

Expand Down Expand Up @@ -287,3 +294,42 @@ class Meta:
"status_display",
]
read_only_fields = fields
class AcSubmissionRatioSerializer(serializers.Serializer):
ac = serializers.IntegerField()
tried = serializers.IntegerField()


class HomeworkProblemStateSerializer(serializers.Serializer):
"""
單一題目的 state(不含 top10)
"""
problemId = serializers.IntegerField()
numUsersTried = serializers.IntegerField()
numAcUsers = serializers.IntegerField()
acSubmissionRatio = AcSubmissionRatioSerializer()
averageScore = serializers.FloatField()
standardDeviation = serializers.FloatField()


class HomeworkStatsPageSerializer(serializers.Serializer):
"""
GET /homework/{id}/stats 用
對應你前端 stats 頁面需要的結構
"""
name = serializers.CharField()
markdown = serializers.CharField(allow_blank=True)
start = serializers.IntegerField(allow_null=True)
end = serializers.IntegerField(allow_null=True)
penalty = serializers.CharField(allow_blank=True)

problemIds = serializers.ListField(
child=serializers.IntegerField()
)

# stats 頁目前不一定用到,但欄位要存在
studentStatus = serializers.DictField()

# key 是 problemId(字串),value 是 HomeworkProblemState
state = serializers.DictField(
child=HomeworkProblemStateSerializer()
)
74 changes: 74 additions & 0 deletions assignments/tests/test_assignments_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@

from django.test import TestCase
from django.contrib.auth import get_user_model
from django.urls import reverse
from rest_framework.test import APIClient
from rest_framework import status
from django.utils import timezone
from datetime import timedelta
from courses.models import Courses, Course_members

Copilot AI Dec 29, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import of 'Course_members' is not used.

Suggested change
from courses.models import Courses, Course_members
from courses.models import Courses

Copilot uses AI. Check for mistakes.
from assignments.models import Assignments
from user.models import UserProfile

User = get_user_model()

class AssignmentApiTest(TestCase):
def setUp(self):
self.client = APIClient()
self.teacher = User.objects.create_user(username='teacher', email='teacher@example.com', password='password', identity=User.Identity.TEACHER)
UserProfile.objects.update_or_create(user=self.teacher, defaults={'email_verified': True})

self.course = Courses.objects.create(name="CS101", teacher_id=self.teacher)

self.assignment = Assignments.objects.create(
title="HW1",
course=self.course,
creator=self.teacher,
status=Assignments.Status.ACTIVE
)
self.assignment_url_base = reverse('assignments:homework-create')

def test_create_assignment(self):
self.client.force_authenticate(user=self.teacher)
data = {
'name': 'New HW',
'course_id': self.course.id,
'markdown': 'Desc',
'_start_dt': timezone.now(),
'_end_dt': timezone.now() + timedelta(days=7)
}
# The serializer expects specific fields, checking views.py HWCreateSerializer usage
# views.py: ser = HomeworkCreateSerializer(data=request.data)
# title=ser.validated_data["name"] => input field 'name'.
# start_time=ser.validated_data.get("_start_dt") => input field '_start_dt' ?
# Actually I should verify serializer fields.
# But assuming names from view code: name, markdown, _start_dt, _end_dt, problem_ids.

response = self.client.post(self.assignment_url_base, data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(Assignments.objects.filter(title='New HW').exists())

def test_get_assignment_detail(self):
self.client.force_authenticate(user=self.teacher)
url = reverse('assignments:homework-detail', kwargs={'homework_id': self.assignment.id})
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# api_response
self.assertEqual(response.data['data']['name'], 'HW1')

def test_update_assignment_deadline(self):
self.client.force_authenticate(user=self.teacher)
url = reverse('assignments:homework-deadline-update', kwargs={'homework_id': self.assignment.id})

new_due = timezone.now() + timedelta(days=7)
data = {
'end': new_due.strftime('%Y-%m-%dT%H:%M:%SZ') # View expects 'end'
}
response = self.client.put(url, data)
self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_scoreboard(self):
self.client.force_authenticate(user=self.teacher)
url = reverse('assignments:homework-scoreboard', kwargs={'homework_id': self.assignment.id})
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
Loading