diff --git a/backend/course/views/professor.py b/backend/course/views/professor.py
index 1f6fd3e16..c54fd0684 100644
--- a/backend/course/views/professor.py
+++ b/backend/course/views/professor.py
@@ -32,6 +32,12 @@ class CourseAPI(APIView):
type=openapi.TYPE_STRING,
default=0,
),
+ openapi.Parameter(
+ name="bookmark",
+ in_=openapi.IN_QUERY,
+ description="bookmark state of course",
+ type=openapi.TYPE_BOOLEAN,
+ ),
],
operation_description="Get course list or specific course generated by requesting admin",
responses={200: CourseRegistrationSerializer},
@@ -49,10 +55,10 @@ def get(self, request):
return self.error("Course does not exist")
if request.GET.get("bookmark") == "true":
- courses = Registration.objects.filter(course__created_by=request.user, bookmark=True)
+ courses = Registration.objects.filter(course__created_by=request.user, bookmark=True, user=request.user)
return self.success(self.paginate_data(request, courses, CourseRegistrationSerializer))
- courses = Registration.objects.filter(course__created_by=request.user)
+ courses = Registration.objects.filter(course__created_by=request.user, user=request.user)
return self.success(self.paginate_data(request, courses, CourseRegistrationSerializer))
@swagger_auto_schema(
diff --git a/backend/problem/migrations/0019_problem_course.py b/backend/problem/migrations/0019_problem_course.py
new file mode 100644
index 000000000..25927c15e
--- /dev/null
+++ b/backend/problem/migrations/0019_problem_course.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.2.11 on 2022-02-10 09:21
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('course', '0004_registration_bookmark'),
+ ('problem', '0018_auto_20211226_1458'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='problem',
+ name='course',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='problems', to='course.course'),
+ ),
+ ]
diff --git a/backend/problem/models.py b/backend/problem/models.py
index 9f2d2a40a..1521b74b9 100644
--- a/backend/problem/models.py
+++ b/backend/problem/models.py
@@ -7,6 +7,7 @@
from utils.constants import Choices
from assignment.models import Assignment
+from course.models import Course
class ProblemTag(models.Model):
@@ -44,6 +45,8 @@ def _default_io_mode():
class Problem(models.Model):
# display ID
_id = models.TextField(db_index=True)
+ # course ID
+ course = models.ForeignKey(Course, null=True, on_delete=models.CASCADE, related_name="problems")
# assignment ID
assignment = models.ForeignKey(Assignment, null=True, on_delete=models.CASCADE, related_name="problems")
contest = models.ForeignKey(Contest, null=True, on_delete=models.CASCADE)
diff --git a/backend/problem/serializers.py b/backend/problem/serializers.py
index b5d0d77bc..49076d718 100644
--- a/backend/problem/serializers.py
+++ b/backend/problem/serializers.py
@@ -96,12 +96,21 @@ class EditContestProblemSerializer(CreateOrEditProblemSerializer):
class CreateAssignmentProblemSerializer(CreateOrEditProblemSerializer):
assignment_id = serializers.IntegerField()
+ course_id = serializers.IntegerField()
class EditAssignmentProblemSerializer(CreateOrEditProblemSerializer):
id = serializers.IntegerField()
+class CreateCourseProblemSerializer(CreateOrEditProblemSerializer):
+ course_id = serializers.IntegerField()
+
+
+class EditCourseProblemSerializer(CreateOrEditProblemSerializer):
+ id = serializers.IntegerField()
+
+
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = ProblemTag
@@ -117,11 +126,16 @@ class BaseProblemSerializer(serializers.ModelSerializer):
tags = serializers.SlugRelatedField(many=True, slug_field="name", read_only=True)
created_by = UsernameSerializer()
contest_name = serializers.SerializerMethodField()
+ assignment_name = serializers.SerializerMethodField()
def get_contest_name(self, obj):
if obj.contest:
return obj.contest.title
+ def get_assignment_name(self, obj):
+ if obj.assignment:
+ return obj.assignment.title
+
def get_public_template(self, obj):
ret = {}
for lang, code in obj.template.items():
@@ -135,7 +149,7 @@ class Meta:
fields = "__all__"
-class ProblemProfessorSerializer(BaseProblemSerializer):
+class ProblemAssignmentProfessorSerializer(BaseProblemSerializer):
total_submission_assignment = serializers.SerializerMethodField()
def get_total_submission_assignment(self, obj):
@@ -146,6 +160,17 @@ class Meta:
fields = "__all__"
+class ProblemCourseProfessorSerializer(BaseProblemSerializer):
+ total_submission_course = serializers.SerializerMethodField()
+
+ def get_total_submission_course(self, obj):
+ return Submission.objects.filter(problem=obj, course=obj.course).values("user_id").distinct().count()
+
+ class Meta:
+ model = Problem
+ fields = "__all__"
+
+
class ProblemSerializer(BaseProblemSerializer):
class Meta:
@@ -176,11 +201,18 @@ class AddContestProblemSerializer(serializers.Serializer):
class AddAssignmentProblemSerializer(serializers.Serializer):
+ course_id = serializers.IntegerField()
assignment_id = serializers.IntegerField()
problem_id = serializers.IntegerField()
display_id = serializers.CharField()
+class AddCourseProblemSerializer(serializers.Serializer):
+ course_id = serializers.IntegerField()
+ problem_id = serializers.IntegerField()
+ display_id = serializers.CharField()
+
+
class ExportProblemRequestSerialzier(serializers.Serializer):
problem_id = serializers.ListField(child=serializers.IntegerField(), allow_empty=False)
diff --git a/backend/problem/tests.py b/backend/problem/tests.py
index 381ef8204..dc53ee69e 100644
--- a/backend/problem/tests.py
+++ b/backend/problem/tests.py
@@ -270,6 +270,7 @@ def setUp(self):
admin = self.create_admin()
course = Course.objects.create(created_by=admin, **DEFAULT_COURSE_DATA)
Registration.objects.create(user_id=student.id, course_id=course.id)
+ self.course_id = course.id
self.assignment_id = Assignment.objects.create(created_by=admin, course=course, **DEFAULT_ASSIGNMENT_DATA).id
self.url = self.reverse("assignment_problem_professor_api")
@@ -286,6 +287,7 @@ def test_get_assignment_problem(self):
def test_create_assignment_problem(self):
data = copy.deepcopy(DEFAULT_PROBLEM_DATA)
data["assignment_id"] = self.assignment_id
+ data["course_id"] = self.course_id
res = self.client.post(self.url, data=data)
self.assertSuccess(res)
return res.data["data"]
@@ -295,6 +297,7 @@ def test_edit_assignment_problem(self):
data = copy.deepcopy(DEFAULT_PROBLEM_DATA)
data["id"] = problem_id
data["title"] = "edit test"
+ data["course"] = self.course_id
data["assignment"] = self.assignment_id
res = self.client.put(self.url, data=data)
self.assertSuccess(res)
@@ -357,12 +360,14 @@ def setUp(self):
admin = self.create_admin()
course = Course.objects.create(created_by=admin, **DEFAULT_COURSE_DATA)
self.assignment_id = Assignment.objects.create(created_by=admin, course=course, **DEFAULT_ASSIGNMENT_DATA).id
+ self.course_id = course.id
self.problem = self.add_problem(DEFAULT_PROBLEM_DATA, admin)
self.url = self.reverse("add_assignment_problem_from_public_api")
self.data = {
"display_id": "1000",
"assignment_id": self.assignment_id,
- "problem_id": self.problem.id
+ "problem_id": self.problem.id,
+ "course_id": self.course_id
}
def test_add_assignment_problem(self):
diff --git a/backend/problem/urls/professor.py b/backend/problem/urls/professor.py
index ca8d6a361..4919d2120 100644
--- a/backend/problem/urls/professor.py
+++ b/backend/problem/urls/professor.py
@@ -1,8 +1,10 @@
from django.urls import path
-from ..views.professor import AssignmentProblemAPI, AddAssignmentProblemAPI
+from ..views.professor import AssignmentProblemAPI, AddAssignmentProblemAPI, CourseProblemAPI, AddCourseProblemAPI
urlpatterns = [
path("course/assignment/problem/", AssignmentProblemAPI.as_view(), name="assignment_problem_professor_api"),
path("course/assignment/add_problem_from_public/", AddAssignmentProblemAPI.as_view(), name="add_assignment_problem_from_public_api"),
+ path("course/problem/", CourseProblemAPI.as_view(), name="course_problem_professor_api"),
+ path("course/add_problem_from_public/", AddCourseProblemAPI.as_view(), name="add_course_problem_from_public_api"),
]
diff --git a/backend/problem/views/admin.py b/backend/problem/views/admin.py
index ef48887fa..3f02af3ef 100644
--- a/backend/problem/views/admin.py
+++ b/backend/problem/views/admin.py
@@ -245,6 +245,8 @@ def get(self, request):
ensure_created_by(problem.contest, request.user)
elif problem.assignment:
ensure_created_by(problem.assignment, request.user)
+ elif problem.course:
+ ensure_created_by(problem.course, request.user)
else:
ensure_created_by(problem, request.user)
diff --git a/backend/problem/views/oj.py b/backend/problem/views/oj.py
index e87ebcc49..004c1b0a6 100644
--- a/backend/problem/views/oj.py
+++ b/backend/problem/views/oj.py
@@ -78,7 +78,7 @@ def get(self, request):
if problem_id:
try:
problem = Problem.objects.select_related("created_by") \
- .get(_id=problem_id, contest_id__isnull=True, assignment_id__isnull=True, visible=True)
+ .get(_id=problem_id, contest_id__isnull=True, assignment_id__isnull=True, course_id__isnull=True, visible=True)
problem_data = ProblemSerializer(problem).data
self._add_problem_status(request, problem_data)
return self.success(problem_data)
@@ -89,7 +89,7 @@ def get(self, request):
if not limit:
return self.error("Limit is needed")
- problems = Problem.objects.select_related("created_by").filter(contest_id__isnull=True, assignment_id__isnull=True, visible=True)
+ problems = Problem.objects.select_related("created_by").filter(contest_id__isnull=True, assignment_id__isnull=True, course_id__isnull=True, visible=True)
# filter by label
tag_text = request.GET.get("tag")
if tag_text:
diff --git a/backend/problem/views/professor.py b/backend/problem/views/professor.py
index 1f24f5971..18a65665d 100644
--- a/backend/problem/views/professor.py
+++ b/backend/problem/views/professor.py
@@ -5,11 +5,14 @@
from submission.models import Submission
from assignment.models import Assignment
+from course.models import Course
from .admin import ProblemBase
from ..models import Problem, ProblemRuleType, ProblemTag
from ..serializers import (CreateAssignmentProblemSerializer, ProblemAdminSerializer, AddAssignmentProblemSerializer,
- ProblemProfessorSerializer, EditAssignmentProblemSerializer)
+ ProblemAssignmentProfessorSerializer, EditAssignmentProblemSerializer,
+ CreateCourseProblemSerializer, AddCourseProblemSerializer,
+ ProblemCourseProfessorSerializer, EditCourseProblemSerializer)
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
@@ -32,7 +35,7 @@ class AssignmentProblemAPI(ProblemBase):
),
],
operation_description="Get problems of certain assignment. If problem_id is set, certain problem would be returned.",
- responses={200: ProblemProfessorSerializer},
+ responses={200: ProblemAssignmentProfessorSerializer},
)
@admin_role_required
def get(self, request):
@@ -45,7 +48,7 @@ def get(self, request):
ensure_created_by(problem.assignment, user)
except Problem.DoesNotExist:
return self.error("Problem does not exist")
- return self.success(ProblemProfessorSerializer(problem).data)
+ return self.success(ProblemAssignmentProfessorSerializer(problem).data)
if not assignment_id:
return self.error("Invalid parameter, assignment id is required")
@@ -56,7 +59,7 @@ def get(self, request):
return self.error("Assignment does not exist")
problems = Problem.objects.filter(assignment=assignment).order_by("create_time")
- return self.success(self.paginate_data(request, problems, ProblemProfessorSerializer))
+ return self.success(self.paginate_data(request, problems, ProblemAssignmentProfessorSerializer))
@swagger_auto_schema(
request_body=(CreateAssignmentProblemSerializer),
@@ -75,6 +78,12 @@ def post(self, request):
if assignment.status == AssignmentStatus.ASSIGNMENT_ENDED:
return self.error("Assignment deadline has expired")
+ try:
+ course = Course.objects.get(id=data.pop("course_id"))
+ ensure_created_by(course, request.user)
+ except Course.DoesNotExist:
+ return self.error("Course does not exist")
+
_id = data["_id"]
if Problem.objects.filter(_id=_id, assignment=assignment).exists():
@@ -85,6 +94,7 @@ def post(self, request):
return self.error(error_info)
data["assignment"] = assignment
+ data["course"] = course
tags = data.pop("tags")
data["created_by"] = request.user
problem = Problem.objects.create(**data)
@@ -164,7 +174,7 @@ def delete(self, request):
if not id:
return self.error("Invalid parameter, id is required")
try:
- problem = Problem.objects.get(id=id, assignment_id__isnull=False)
+ problem = Problem.objects.get(id=id, assignment_id__isnull=False, course_id__isnull=False)
ensure_created_by(problem.assignment, request.user)
except Problem.DoesNotExist:
return self.error("Problem does not exists")
@@ -185,11 +195,14 @@ class AddAssignmentProblemAPI(APIView):
def post(self, request):
data = request.data
try:
+ course = Course.objects.get(id=data["course_id"])
assignment = Assignment.objects.get(id=data["assignment_id"])
ensure_created_by(assignment, request.user)
problem = Problem.objects.get(id=data["problem_id"])
except Assignment.DoesNotExist:
return self.error("Assignment does not exist")
+ except Course.DoesNotExist:
+ return self.error("Course does not exist")
except Problem.DoesNotExist:
return self.error("Problem does not exist")
@@ -205,6 +218,207 @@ def post(self, request):
tags = problem.tags.all()
problem.pk = None
problem.assignment = assignment
+ problem.course = course
+ problem.rule_type = ProblemRuleType.ASSIGNMENT
+ problem.is_public = True
+ problem.visible = True
+ problem._id = request.data["display_id"]
+ problem.submission_number = problem.accepted_number = 0
+ problem.statistic_info = {}
+ problem.save()
+ problem.tags.set(tags)
+ return self.success()
+
+
+class CourseProblemAPI(ProblemBase):
+ @swagger_auto_schema(
+ manual_parameters=[
+ openapi.Parameter(
+ name="problem_id",
+ in_=openapi.IN_QUERY,
+ type=openapi.TYPE_INTEGER,
+ description="Unique id of problem.",
+ ),
+ openapi.Parameter(
+ name="course_id",
+ in_=openapi.IN_QUERY,
+ type=openapi.TYPE_INTEGER,
+ required=True,
+ description="Unique id of course.",
+ ),
+ ],
+ operation_description="Get problems of certain course. If problem_id is set, certain problem would be returned.",
+ responses={200: ProblemCourseProfessorSerializer},
+ )
+ @admin_role_required
+ def get(self, request):
+ problem_id = request.GET.get("problem_id")
+ course_id = request.GET.get("course_id")
+ user = request.user
+ if problem_id:
+ try:
+ problem = Problem.objects.get(id=problem_id)
+ ensure_created_by(problem.course, user)
+ except Problem.DoesNotExist:
+ return self.error("Problem does not exist")
+ return self.success(ProblemCourseProfessorSerializer(problem).data)
+
+ if not course_id:
+ return self.error("Invalid parameter, course id is required")
+ try:
+ course = Course.objects.get(id=course_id)
+ ensure_created_by(course, user)
+ except Course.DoesNotExist:
+ return self.error("Course does not exist")
+
+ problems = Problem.objects.filter(course=course).order_by("-create_time")
+ return self.success(self.paginate_data(request, problems, ProblemCourseProfessorSerializer))
+
+ @swagger_auto_schema(
+ request_body=(CreateCourseProblemSerializer),
+ operation_description="Create a problem of course.",
+ responses={200: ProblemAdminSerializer},
+ )
+ @validate_serializer(CreateCourseProblemSerializer)
+ @admin_role_required
+ def post(self, request):
+ data = request.data
+
+ try:
+ course = Course.objects.get(id=data.pop("course_id"))
+ ensure_created_by(course, request.user)
+ except Course.DoesNotExist:
+ return self.error("Course does not exist")
+
+ _id = data["_id"]
+
+ if Problem.objects.filter(_id=_id, course=course).exists():
+ return self.error("Duplicate Display id")
+
+ error_info = self.common_checks(request)
+ if error_info:
+ return self.error(error_info)
+
+ data["course"] = course
+ tags = data.pop("tags")
+ data["created_by"] = request.user
+ problem = Problem.objects.create(**data)
+
+ if not _id:
+ problem._id = problem.id
+ problem.save()
+
+ for item in tags:
+ try:
+ tag = ProblemTag.objects.get(name=item)
+ except ProblemTag.DoesNotExist:
+ tag = ProblemTag.objects.create(name=item)
+ problem.tags.add(tag)
+ return self.success(ProblemAdminSerializer(problem).data)
+
+ @validate_serializer(EditCourseProblemSerializer)
+ @admin_role_required
+ def put(self, request):
+ data = request.data
+ user = request.user
+
+ problem_id = data.pop("id")
+ course_id = Problem.objects.get(id=problem_id).course_id
+ try:
+ course = Course.objects.get(id=course_id)
+ ensure_created_by(course, user)
+ except Course.DoesNotExist:
+ return self.error("Course does not exist")
+
+ try:
+ problem = Problem.objects.get(id=problem_id, course=course)
+ except Problem.DoesNotExist:
+ return self.error("Problem does not exist")
+
+ _id = data["_id"]
+ if not _id:
+ return self.error("Display ID is required")
+ if Problem.objects.exclude(id=problem_id).filter(_id=_id, course=course).exists():
+ return self.error("Display ID already exists")
+
+ error_info = self.common_checks(request)
+ if error_info:
+ return self.error(error_info)
+
+ tags = data.pop("tags")
+ data["languages"] = list(data["languages"])
+
+ for k, v in data.items():
+ setattr(problem, k, v)
+ problem.save()
+
+ problem.tags.remove(*problem.tags.all())
+ for tag in tags:
+ try:
+ tag = ProblemTag.objects.get(name=tag)
+ except ProblemTag.DoesNotExist:
+ tag = ProblemTag.objects.create(name=tag)
+ problem.tags.add(tag)
+ return self.success(ProblemAdminSerializer(problem).data)
+
+ @swagger_auto_schema(
+ manual_parameters=[
+ openapi.Parameter(
+ name="id",
+ in_=openapi.IN_QUERY,
+ type=openapi.TYPE_INTEGER,
+ description="Unique id of problem.",
+ required=True
+ ),
+ ],
+ operation_description="Delete certain problem of course."
+ )
+ @admin_role_required
+ def delete(self, request):
+ id = request.GET.get("id")
+ if not id:
+ return self.error("Invalid parameter, id is required")
+ try:
+ problem = Problem.objects.get(id=id, course_id__isnull=False)
+ ensure_created_by(problem.course, request.user)
+ 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")
+
+ problem.delete()
+ return self.success()
+
+
+class AddCourseProblemAPI(APIView):
+ @validate_serializer(AddCourseProblemSerializer)
+ @swagger_auto_schema(
+ request_body=AddCourseProblemSerializer,
+ operation_description="Add problems from public problems into the course."
+ )
+ def post(self, request):
+ data = request.data
+ try:
+ course = Course.objects.get(id=data["course_id"])
+ ensure_created_by(course, request.user)
+ problem = Problem.objects.get(id=data["problem_id"])
+ except Course.DoesNotExist:
+ return self.error("Course does not exist")
+ except Problem.DoesNotExist:
+ return self.error("Problem does not exist")
+
+ if Problem.objects.filter(course=course, _id=data["display_id"]).exists():
+ return self.error("Duplicate display id in this course")
+
+ total_score = 0
+ for item in problem.test_case_score:
+ total_score += item["score"]
+ problem.total_score = total_score
+ tags = problem.tags.all()
+ problem.pk = None
+ problem.assignment = None
+ problem.course = course
problem.rule_type = ProblemRuleType.ASSIGNMENT
problem.is_public = True
problem.visible = True
diff --git a/backend/submission/migrations/0017_submission_course.py b/backend/submission/migrations/0017_submission_course.py
new file mode 100644
index 000000000..8ba117e09
--- /dev/null
+++ b/backend/submission/migrations/0017_submission_course.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.2.12 on 2022-02-11 06:48
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('course', '0004_registration_bookmark'),
+ ('submission', '0016_auto_20211226_1458'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='submission',
+ name='course',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='submissions', to='course.course'),
+ ),
+ ]
diff --git a/backend/submission/models.py b/backend/submission/models.py
index 809b0d7f3..c1d5e4f0e 100644
--- a/backend/submission/models.py
+++ b/backend/submission/models.py
@@ -8,6 +8,7 @@
from utils.shortcuts import rand_str
from assignment.models import Assignment
+from course.models import Course
class JudgeStatus:
@@ -28,6 +29,7 @@ class Submission(models.Model):
id = models.TextField(default=rand_str, primary_key=True, db_index=True)
contest = models.ForeignKey(Contest, null=True, on_delete=models.CASCADE)
problem = models.ForeignKey(Problem, on_delete=models.CASCADE)
+ course = models.ForeignKey(Course, null=True, on_delete=models.CASCADE, related_name="submissions")
assignment = models.ForeignKey(Assignment, null=True, on_delete=models.CASCADE, related_name="submissions")
create_time = models.DateTimeField(auto_now_add=True)
user_id = models.IntegerField(db_index=True)
diff --git a/backend/submission/serializers.py b/backend/submission/serializers.py
index 5ae336673..be296db58 100644
--- a/backend/submission/serializers.py
+++ b/backend/submission/serializers.py
@@ -11,6 +11,7 @@ class CreateSubmissionSerializer(serializers.Serializer):
code = serializers.CharField(max_length=1024 * 1024)
contest_id = serializers.IntegerField(required=False)
assignment_id = serializers.IntegerField(required=False)
+ course_id = serializers.IntegerField(required=False)
captcha = serializers.CharField(required=False)
@@ -72,4 +73,4 @@ def get_created_by(self, obj):
class Meta:
model = Submission
- exclude = ("contest", "assignment", "shared", "ip")
+ exclude = ("contest", "assignment", "course", "shared", "ip")
diff --git a/backend/submission/tests.py b/backend/submission/tests.py
index 099ff2593..9e55c2f77 100644
--- a/backend/submission/tests.py
+++ b/backend/submission/tests.py
@@ -105,7 +105,7 @@ def setUp(self):
def test_get_assignment_submission_list(self):
problem_id = self.problem["_id"]
- resp = self.client.get(f"{self.url}?assignment_id={self.assignment_id}&problem_id={problem_id}")
+ resp = self.client.get(f"{self.url}?course_id={self.course_id}&assignment_id={self.assignment_id}&problem_id={problem_id}")
self.assertSuccess(resp)
def test_get_student_assignment_submission_list(self):
@@ -115,7 +115,7 @@ def test_get_student_assignment_submission_list(self):
self.submission_data["username"] = student.username
Submission.objects.create(**self.submission_data)
problem_id = self.problem["_id"]
- resp = self.client.get(f"{self.url}?assignment_id={self.assignment_id}&problem_id={problem_id}")
+ resp = self.client.get(f"{self.url}?course_id={self.course_id}&assignment_id={self.assignment_id}&problem_id={problem_id}")
self.assertSuccess(resp)
@@ -127,7 +127,7 @@ def setUp(self):
def test_get_assignment_submission_list_professor(self):
assignment_id = self.assignment_id
problem_id = self.problem["id"]
- resp = self.client.get(f"{self.url}?assignment_id={assignment_id}&problem_id={problem_id}")
+ resp = self.client.get(f"{self.url}?course_id={self.course_id}&assignment_id={assignment_id}&problem_id={problem_id}")
self.assertSuccess(resp)
diff --git a/backend/submission/views.py b/backend/submission/views.py
index 3d2b5186f..75e36abaf 100644
--- a/backend/submission/views.py
+++ b/backend/submission/views.py
@@ -77,7 +77,11 @@ def post(self, request):
return self.error(error)
try:
- problem = Problem.objects.get(id=data["problem_id"], contest_id=data.get("contest_id"), assignment_id=data.get("assignment_id"), visible=True)
+ problem = Problem.objects.get(id=data["problem_id"],
+ contest_id=data.get("contest_id"),
+ assignment_id=data.get("assignment_id"),
+ course_id=data.get("course_id"),
+ visible=True)
except Problem.DoesNotExist:
return self.error("Problem not exist")
if data["language"] not in problem.languages:
@@ -89,7 +93,8 @@ def post(self, request):
problem_id=problem.id,
ip=request.session["ip"],
contest_id=data.get("contest_id"),
- assignment_id=data.get("assignment_id"))
+ assignment_id=data.get("assignment_id"),
+ course_id=data.get("course_id"))
# use this for debug
# JudgeDispatcher(submission.id, problem.id).judge()
judge_task.send(submission.id, problem.id)
@@ -199,14 +204,14 @@ def get(self, request):
if request.GET.get("contest_id"):
return self.error("Parameter error")
- submissions = Submission.objects.filter(contest_id__isnull=True, assignment_id__isnull=True).select_related("problem__created_by")
+ submissions = Submission.objects.filter(contest_id__isnull=True, assignment_id__isnull=True, course_id__isnull=True).select_related("problem__created_by")
problem_id = request.GET.get("problem_id")
myself = request.GET.get("myself")
result = request.GET.get("result")
username = request.GET.get("username")
if problem_id:
try:
- problem = Problem.objects.get(_id=problem_id, contest_id__isnull=True, assignment_id__isnull=True, visible=True)
+ problem = Problem.objects.get(_id=problem_id, contest_id__isnull=True, assignment_id__isnull=True, course_id__isnull=True, visible=True)
except Problem.DoesNotExist:
return self.error("Problem doesn't exist")
submissions = submissions.filter(problem=problem)
diff --git a/frontend/src/pages/oj/views/problem/Problem.vue b/frontend/src/pages/oj/views/problem/Problem.vue
index 0f24a9e5b..7d6ed959e 100644
--- a/frontend/src/pages/oj/views/problem/Problem.vue
+++ b/frontend/src/pages/oj/views/problem/Problem.vue
@@ -574,7 +574,8 @@ export default {
language: this.language,
code: this.code,
contest_id: this.contestID,
- assignment_id: this.assignmentID
+ assignment_id: this.assignmentID,
+ course_id: this.courseID
}
if (this.captchaRequired) {
data.captcha = this.captchaCode
diff --git a/frontend/src/pages/prof/api.js b/frontend/src/pages/prof/api.js
index a25f6bae3..dee7ca4bf 100644
--- a/frontend/src/pages/prof/api.js
+++ b/frontend/src/pages/prof/api.js
@@ -148,6 +148,36 @@ export default {
params: params
})
},
+ getCourseProblem (courseId, problemId, limit, offset) {
+ return ajax('lecture/professor/course/problem/', 'get', {
+ params: {
+ course_id: courseId,
+ problem_id: problemId,
+ limit: limit,
+ offset: offset
+ }
+ })
+ },
+ createCourseProblem (data) {
+ return ajax('lecture/professor/course/problem/', 'post', {
+ data
+ })
+ },
+ editCourseProblem (data) {
+ return ajax('lecture/professor/course/problem/', 'put', {
+ data
+ })
+ },
+ deleteCourseProblem (id) {
+ return ajax('lecture/professor/course/problem/', 'delete', {
+ params: { id: id }
+ })
+ },
+ addCourseProblemFromPublic (data) {
+ return ajax('lecture/professor/course/add_problem_from_public/', 'post', {
+ data
+ })
+ },
// only assignmentId param => return problemList instead
getAssignmentProblem (assignmentId, problemId) {
const params = { problem_id: problemId, assignment_id: assignmentId }
diff --git a/frontend/src/pages/prof/components/SideMenu.vue b/frontend/src/pages/prof/components/SideMenu.vue
index 38d912922..d1a5bd0ea 100644
--- a/frontend/src/pages/prof/components/SideMenu.vue
+++ b/frontend/src/pages/prof/components/SideMenu.vue
@@ -53,6 +53,17 @@
/>
Dashboard
+
+
+ Problems
+
@@ -212,7 +214,9 @@ export default {
'Operation'
],
selectedAssignmentId: null,
- studentTotal: 0
+ selectedCourseId: null,
+ studentTotal: 0,
+ courseId: ''
}
},
async mounted () {
@@ -283,8 +287,9 @@ export default {
createAssignmentProblem (assignment) {
if (this.routeName === 'course-assignment-list') {
this.$router.push({
- name: 'create-course-problem',
+ name: 'create-assignment-problem',
params: {
+ courseId: this.courseId,
assignmentId: assignment.id,
courseInfo: this.pageLocations[0].text,
assignmentInfo: assignment.title
@@ -295,7 +300,7 @@ export default {
editAssignmentProblem (assignment, problemId) {
if (this.routeName === 'course-assignment-list') {
this.$router.push({
- name: 'edit-course-problem',
+ name: 'edit-assignment-problem',
params: {
courseId: this.courseId,
assignmentId: assignment.id,
@@ -357,6 +362,7 @@ export default {
},
showImportPublicProblemModal (assignmentId) {
this.selectedAssignmentId = assignmentId
+ this.selectedCourseId = this.courseId
this.$bvModal.show('import-public-problem-modal')
},
async getUserTotal () {
diff --git a/frontend/src/pages/prof/views/assignment/ImportPublicProblem.vue b/frontend/src/pages/prof/views/assignment/ImportPublicProblem.vue
index 6fabe23b1..d523e88d3 100644
--- a/frontend/src/pages/prof/views/assignment/ImportPublicProblem.vue
+++ b/frontend/src/pages/prof/views/assignment/ImportPublicProblem.vue
@@ -94,7 +94,7 @@ import { DIFFICULTY_COLOR } from '@/utils/constants'
export default {
name: 'ImportPublicProblem',
mixins: [ProblemMixin],
- props: ['assignmentId'],
+ props: ['assignmentId', 'courseId', 'mode'],
data () {
return {
form: {
@@ -197,12 +197,17 @@ export default {
return
}
const data = {
+ course_id: this.courseId,
assignment_id: this.assignmentId,
problem_id: this.form.selectedProblemId,
display_id: this.form.displayId
}
try {
- await profApi.addProblemFromPublic(data)
+ if (this.mode === 'assignment') {
+ await profApi.addProblemFromPublic(data)
+ } else {
+ await profApi.addCourseProblemFromPublic(data)
+ }
} catch (err) {
}
this.$set(this.form, 'selectedProblemId', null)
diff --git a/frontend/src/pages/prof/views/general/CourseBookmark.vue b/frontend/src/pages/prof/views/general/CourseBookmark.vue
index 72c88acda..8708be342 100644
--- a/frontend/src/pages/prof/views/general/CourseBookmark.vue
+++ b/frontend/src/pages/prof/views/general/CourseBookmark.vue
@@ -95,7 +95,6 @@ export default {
try {
const resp = await api.getCourseList()
this.courseList = resp.data.data.results
- console.log(this.courseList)
} catch (err) {
}
},
diff --git a/frontend/src/pages/prof/views/general/CourseDashboard.vue b/frontend/src/pages/prof/views/general/CourseDashboard.vue
index cb5274b1c..4f1b399cd 100644
--- a/frontend/src/pages/prof/views/general/CourseDashboard.vue
+++ b/frontend/src/pages/prof/views/general/CourseDashboard.vue
@@ -15,10 +15,8 @@
cols = "1"
class="course-dashboard"
>
-
-
+
+
@@ -47,9 +45,41 @@
+
+
+
+
+
+
+
+
+
+ {{ data.value }}
+ -
+
+
+
+
@@ -63,6 +93,8 @@
import api from '../../api.js'
import UserList from '../users/UserList.vue'
+import moment from 'moment'
+
export default {
name: 'CourseDashboard',
components: {
@@ -77,7 +109,11 @@ export default {
'-1': 'Ended'
},
pageSize: 5,
- currentPage: 1,
+ mode: '',
+ assignmentCurrentPage: 1,
+ problemCurrentPage: 1,
+ assignmentTotal: 0,
+ problemTotal: 0,
courseId: null,
createdBy: {},
title: '',
@@ -109,7 +145,22 @@ export default {
}
],
assignmentList: [
- ]
+ ],
+ problemListField: [
+ 'title',
+ {
+ key: 'assignment_name',
+ label: 'Assignment'
+ },
+ {
+ key: 'create_time',
+ label: 'Create Time',
+ formatter: (value) => {
+ return moment(value).format('YYYY-M-D HH:mm')
+ }
+ }
+ ],
+ problemList: []
}
},
async mounted () {
@@ -125,25 +176,41 @@ export default {
} catch (err) {
this.$error(err)
}
- this.getAssignmentList(1)
+ await this.getCourseProblem(1)
+ await this.getAssignmentList(1)
this.pageLocations[0].text = this.title + '_' + this.courseCode + '-' + this.classNumber
},
methods: {
- async currentChange (page) {
- this.currentPage = page
+ async currentAssignmentChange (page) {
+ this.assignmentCurrentPage = page
await this.getAssignmentList(page)
},
+ async currentProblemChange (page) {
+ this.problemCurrentPage = page
+ await this.getCourseProblem(page)
+ },
async getAssignmentList (page) {
this.loading = true
try {
const res = await api.getAssignmentList(this.courseId, null, this.pageSize, (page - 1) * this.pageSize)
- this.total = res.data.data.total
+ this.assignmentTotal = res.data.data.total
this.assignmentList = res.data.data.results
} catch (err) {
} finally {
this.loading = false
}
},
+ async getCourseProblem (page) {
+ this.loading = true
+ try {
+ const res = await api.getCourseProblem(this.$route.params.courseId, null, this.pageSize, (page - 1) * this.pageSize)
+ this.problemList = res.data.data.results
+ this.problemTotal = res.data.data.total
+ } catch (err) {
+ } finally {
+ this.loading = false
+ }
+ },
async updateCourseList () {
try {
const res = await api.getCourseList()
@@ -156,8 +223,11 @@ export default {
}
},
computed: {
- updateCurrentPage () {
- return this.currentChange(this.currentPage)
+ updateAssignmentCurrentPage () {
+ return this.currentAssignmentChange(this.assignmentCurrentPage)
+ },
+ updateProblemCurrentPage () {
+ return this.currentProblemChange(this.problemCurrentPage)
}
}
}
diff --git a/frontend/src/pages/prof/views/index.js b/frontend/src/pages/prof/views/index.js
index 22b4c7e0c..72fe701f1 100644
--- a/frontend/src/pages/prof/views/index.js
+++ b/frontend/src/pages/prof/views/index.js
@@ -7,7 +7,8 @@ import Home from './Home.vue'
import QnA from './qna/QnA.vue'
import ProblemGrade from './problem/ProblemGrade.vue'
import CourseBookmark from './general/CourseBookmark.vue'
+import CourseProblem from './problem/CourseProblem.vue'
export {
- Dashboard, CourseDashboard, Problem, AssignmentList, Login, Home, QnA, ProblemGrade, CourseBookmark
+ Dashboard, CourseDashboard, Problem, CourseProblem, AssignmentList, Login, Home, QnA, ProblemGrade, CourseBookmark
}
diff --git a/frontend/src/pages/prof/views/problem/CourseProblem.vue b/frontend/src/pages/prof/views/problem/CourseProblem.vue
new file mode 100644
index 000000000..b9f589754
--- /dev/null
+++ b/frontend/src/pages/prof/views/problem/CourseProblem.vue
@@ -0,0 +1,221 @@
+
+
+
+
+
+
+
+
+ {{ data.value }}
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/pages/prof/views/problem/Problem.vue b/frontend/src/pages/prof/views/problem/Problem.vue
index c0c51b91e..a48076d31 100644
--- a/frontend/src/pages/prof/views/problem/Problem.vue
+++ b/frontend/src/pages/prof/views/problem/Problem.vue
@@ -549,21 +549,18 @@ export default {
],
search: '',
assignmentId: '',
+ courseId: '',
pageLocations: [
{
text: this.$route.params.courseInfo,
to: '/course/' + this.$route.params.courseId + '/dashboard'
- },
- {
- text: this.$route.params.assignmentInfo,
- to: '/course/' + this.$route.params.courseId + '/assignment'
}
]
}
},
async mounted () {
this.routeName = this.$route.name
- if (this.routeName === 'edit-course-problem') {
+ if (this.routeName === 'edit-assignment-problem' || this.routeName === 'edit-course-problem') {
this.mode = 'edit'
} else {
this.mode = 'add'
@@ -571,6 +568,8 @@ export default {
this.problem = this.reProblem = {
_id: '',
title: '',
+ course_id: '',
+ assignment_id: '',
description: '',
input_description: '',
output_description: '',
@@ -596,6 +595,8 @@ export default {
io_mode: { io_mode: 'Standard IO', input: 'input.txt', output: 'output.txt' }
}
this.assignmentId = this.$route.params.assignmentId
+ this.courseId = this.$route.params.courseId
+ this.problem.course_id = this.reProblem.course_id = this.courseId
if (this.assignmentId) {
this.problem.assignment_id = this.reProblem.assignment_id = this.assignmentId
const res = await api.getAssignmentList(null, this.assignmentId)
@@ -609,7 +610,9 @@ export default {
// get problem after getting languages list to avoid find undefined value in `watch problem.languages`
if (this.mode === 'edit') {
this.title = 'Edit Problem'
- const problemRes = await api.getAssignmentProblem(this.assignemntId, this.$route.params.problemId)
+ const problemRes = (this.routeName === 'edit-assignment-problem')
+ ? await api.getAssignmentProblem(this.assignmentId, this.$route.params.problemId)
+ : await api.getCourseProblem(this.courseId, this.$route.params.problemId)
const data = problemRes.data.data
if (!data.spj_code) {
data.spj_code = ''
@@ -623,20 +626,13 @@ export default {
this.problem.testcases = this.problem.testcases.concat(testcaseData.testcases)
if (testcaseData.spj === 'True') this.problem.spj = true
else this.problem.spj = testcaseData.spj === 'True'
- this.pageLocations.push({
- text: this.$route.params.problemId + ' - ' + this.problem.title
- }, {
- text: 'Edit problem'
- })
} else {
this.title = 'Add Problem'
for (const item of allLanguage.languages) {
this.problem.languages.push(item.name)
}
- this.pageLocations.push({
- text: 'Create Problem'
- })
}
+ this.setPageLocation()
this.getProblemTagList()
},
watch: {
@@ -676,6 +672,25 @@ export default {
}
},
methods: {
+ setPageLocation () {
+ if (this.assignmentId) {
+ this.pageLocations.push({
+ text: this.$route.params.assignmentInfo,
+ to: '/course/' + this.$route.params.courseId + '/assignment'
+ })
+ }
+ if (this.mode === 'edit') {
+ this.pageLocations.push({
+ text: this.$route.params.problemId + ' - ' + this.problem.title
+ }, {
+ text: 'Edit problem'
+ })
+ } else {
+ this.pageLocations.push({
+ text: 'Create Problem'
+ })
+ }
+ },
onTagClick ({ item, addTag }) {
addTag(item)
this.search = ''
@@ -854,8 +869,10 @@ export default {
}
}
const funcName = {
- 'create-course-problem': 'createAssignmentProblem',
- 'edit-course-problem': 'editAssignmentProblem'
+ 'create-assignment-problem': 'createAssignmentProblem',
+ 'edit-assignment-problem': 'editAssignmentProblem',
+ 'create-course-problem': 'createCourseProblem',
+ 'edit-course-problem': 'editCourseProblem'
}[this.routeName]
if (funcName === 'editAssignmentProblem') {
this.problem.assignment_id = this.assignment.id
@@ -904,16 +921,35 @@ export default {
})
})
await api[funcName](this.problem)
- this.$router.push({ name: 'course-assignment-list', params: { assignmentId: this.assignmentId } })
+ if (funcName === 'createCourseProblem' || funcName === 'editCourseProblem') {
+ this.$router.push({
+ name: 'course-problem',
+ params: { courseId: this.courseId }
+ })
+ } else {
+ this.$router.push({
+ name: 'course-assignment-list',
+ params: { assignmentId: this.assignmentId }
+ })
+ }
} catch (err) {
console.error(err)
}
} else {
try {
await api[funcName](this.problem)
- this.$router.push({ name: 'course-assignment-list', params: { assignmentId: this.assignmentId } })
- } catch (err) {
- }
+ if (funcName === 'createCourseProblem' || funcName === 'editCourseProblem') {
+ this.$router.push({
+ name: 'course-problem',
+ params: { courseId: this.courseId }
+ })
+ } else {
+ this.$router.push({
+ name: 'course-assignment-list',
+ params: { assignmentId: this.assignmentId }
+ })
+ }
+ } catch (err) {}
}
}
},