Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

유저 프로필 페이지 구현 #342

Draft
wants to merge 39 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
36a5671
feat: Add and link user profile page
jimin9038 Jan 8, 2022
d4ade56
feat: initialize user profile page
jimin9038 Jan 9, 2022
e9a5bd6
Initialize Group Page.
DailyPS Feb 1, 2022
7b0297c
Change CSS.
DailyPS Feb 1, 2022
438c453
change section title to common css
jimin9038 Feb 1, 2022
487affd
Add Profile Submission in Profile Page (#290)
st42597 Feb 1, 2022
fc86e17
Add profileSubmissionListAPI in Profile Page (#308)
st42597 Mar 1, 2022
290634c
refactor: Rebase
goo314 Jan 10, 2022
544d53d
fix: Delete routers of profile-contest
goo314 Jan 11, 2022
cad0069
feat: Add ProfileContest vue
goo314 Jan 11, 2022
2cea5ce
feat: Publish user contest page
goo314 Jan 14, 2022
e2d31c2
add: Add file in gitignore
goo314 Jan 14, 2022
256e3e5
feat: Add Pagination component
goo314 Jan 21, 2022
09a2576
feat: Add methods for each button in pagination vue
goo314 Jan 24, 2022
067df10
chore: Remove chart.js outside frontend folder
goo314 Jan 28, 2022
6680799
fix: Show at most limit pages
goo314 Jan 28, 2022
79c9576
style: Shorten style code
goo314 Jan 28, 2022
c7fcc3b
fix: Add component communication(props, event)
goo314 Jan 28, 2022
7b4ac7d
style: Apply Pagination vue
goo314 Jan 28, 2022
77eb69b
style: Edit code style
goo314 Feb 6, 2022
90e2509
feat: Create USerContestAPI
goo314 Feb 8, 2022
fb4d8bd
add: Calate and return user rank in UserContestAPI
goo314 Feb 9, 2022
1725bc7
style: Change code style
goo314 Feb 10, 2022
883bd9f
add: Add rank field in ACMContestRank & Shorten UserContestAPI
goo314 Feb 10, 2022
af494ed
add: Connect UserContestAPI to ProfileContest page
goo314 Feb 14, 2022
27298e4
feat: Add sorting contests function in ProfileContest
goo314 Feb 20, 2022
d0b5531
fix: Connect sorting api to ProfileContest
goo314 Feb 20, 2022
f3246cc
feat: Add test for user-contest-api
goo314 Feb 26, 2022
4d0313c
feat: add contestprize model
jimin9038 Feb 25, 2022
285db50
style: Change code style
goo314 Mar 1, 2022
4f7df70
migrate model change
jimin9038 Mar 2, 2022
08d23a9
resolve conflict: whiel rebase on master
jimin9038 Mar 2, 2022
22fe0ee
Fix UserContestAPITest
jimin9038 Mar 2, 2022
564e9ba
minor design change followed by tailwind css
jimin9038 Mar 2, 2022
d993ad9
resolve conflict
jimin9038 Apr 1, 2022
86c294b
move user setting
Kohminchae Mar 22, 2022
59b24dd
change details in user setting
Kohminchae Mar 23, 2022
e13fb1e
fix: add assertSuccess to testcase
jimin9038 Apr 12, 2022
bd82fcd
fix: Delete unnecessary file and edit gitignore
goo314 Apr 19, 2022
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: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,6 @@ typings/

# devcontainer
.devcontainer/data

# MacOS file
.DS_Store*
Binary file not shown.
14 changes: 14 additions & 0 deletions backend/contest/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@
from group.models import Group
from utils.models import RichTextField

from account.models import AdminType


class Contest(models.Model):
title = models.TextField()
description = RichTextField()
requirements = JSONField(default=list)
constraints = JSONField(default=list)
allowed_groups = models.ManyToManyField(Group, blank=True)
# allowed_groups = models.ManyToManyField(Group, blank=True)
scoring = models.TextField(default="ACM-ICPC style")
# show real time rank or cached rank
real_time_rank = models.BooleanField()
Expand Down Expand Up @@ -92,6 +95,17 @@ class ACMContestRank(AbstractContestRank):
# key is problem id
submission_info = JSONField(default=dict)

@property
def rank(self):
qs_contest = Contest.objects.get(id=self.contest.id)
if qs_contest.status == ContestStatus.CONTEST_ENDED:
qs_participants = ACMContestRank.objects.filter(contest=self.contest.id, user__admin_type=AdminType.REGULAR_USER, user__is_disabled=False).\
select_related("user").order_by("-accepted_number", "total_penalty", "total_time")
for i in range(qs_participants.count()):
if qs_participants[i].user.id == self.user.id:
return i+1
return -1

class Meta:
db_table = "acm_contest_rank"
unique_together = (("user", "contest"),)
Expand Down
8 changes: 8 additions & 0 deletions backend/contest/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,11 @@ class ACMContesHelperSerializer(serializers.Serializer):
problem_id = serializers.CharField()
rank_id = serializers.IntegerField()
checked = serializers.BooleanField()


class ProfileContestSerializer(serializers.Serializer):
id = serializers.IntegerField()
title = serializers.CharField(max_length=128)
start_time = serializers.DateTimeField()
rank = serializers.IntegerField()
percentage = serializers.FloatField()
67 changes: 64 additions & 3 deletions backend/contest/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

from utils.api.tests import APITestCase

from .models import ContestAnnouncement, ContestRuleType, Contest

from problem.models import ProblemIOMode
from .models import ContestAnnouncement, ContestRuleType, Contest, ACMContestRank
from submission.models import Submission
from problem.models import Problem, ProblemIOMode, ProblemTag

DEFAULT_PROBLEM_DATA = {"_id": "A-110", "title": "test", "description": "<p>test</p>", "input_description": "test",
"output_description": "test", "time_limit": 1000, "memory_limit": 256, "difficulty": "Level1",
Expand Down Expand Up @@ -39,6 +39,22 @@
"bank_filter": [],
"visible": True, "real_time_rank": True, "rank_penalty_visible": True}

DEFAULT_SUBMISSION_DATA = {
"problem_id": "1",
"user_id": 1,
"username": "test",
"code": "xxxxxxxxxxxxxx",
"result": -2,
"info": {},
"language": "C",
"statistic_info": {}
}


DEFAULT_ACMCONTESTRANK_DATA = {"submission_number": 1, "accepted_number": 1, "total_time": 123, "total_penalty": 123,
"submission_info": {"1": {"is_ac": True, "ac_time": 123, "penalty": 123, "problem_submission": 1}},
"contest": 1}


class ContestAdminAPITest(APITestCase):
def setUp(self):
Expand Down Expand Up @@ -252,3 +268,48 @@ def test_create_problem_bank(self):
url = self.reverse("contest_bank_api")
response = self.client.post(url, data={"contest_id": contest["id"]})
self.assertSuccess(response)


class UserContestAPITest(APITestCase):
def setUp(self):
# create contest
admin = self.create_admin()
data = copy.deepcopy(DEFAULT_CONTEST_DATA)
data.pop("allowed_groups")
data.pop("prizes")
self.contest = Contest.objects.create(created_by=admin, **data)

# create problem in contest
data = copy.deepcopy(DEFAULT_PROBLEM_DATA)
data["contest_id"] = self.contest.id
tags = data.pop("tags")
problem = Problem.objects.create(created_by=admin, **data)

for item in tags:
try:
tag = ProblemTag.objects.get(name=item)
except ProblemTag.DoesNotExist:
tag = ProblemTag.objects.create(name=item)
problem.tags.add(tag)

self.problem = problem
# user submit problem
user = self.create_user("test", "test123")
data = copy.deepcopy(DEFAULT_SUBMISSION_DATA)
data["contest_id"] = self.contest.id
data["problem_id"] = self.problem.id
data["user_id"] = user.id
self.submission = Submission.objects.create(**data)

# create ACMContestRank
data = copy.deepcopy(DEFAULT_ACMCONTESTRANK_DATA)
data["user"] = user
data["contest"] = self.contest
self.rank = ACMContestRank.objects.create(**data)

self.url = self.reverse("contest_user_api")

# test UserContestAPI : can user get contest info which he participated and rank?
def test_get_participated_contest_list(self):
response = self.client.get(self.url)
self.assertSuccess(response)
2 changes: 2 additions & 0 deletions backend/contest/urls/oj.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from ..views.oj import ContestAnnouncementListAPI
from ..views.oj import ContestPasswordVerifyAPI, ContestAccessAPI
from ..views.oj import ContestListAPI, ContestAPI, ContestRankAPI, ProblemBankAPI
from ..views.oj import UserContestAPI

urlpatterns = [
path("contests/", ContestListAPI.as_view(), name="contest_list_api"),
Expand All @@ -12,4 +13,5 @@
path("contest/access/", ContestAccessAPI.as_view(), name="contest_access_api"),
path("contest/rank/", ContestRankAPI.as_view(), name="contest_rank_api"),
path("contest/bank/", ProblemBankAPI.as_view(), name="contest_bank_api"),
path("contest/user/", UserContestAPI.as_view(), name="contest_user_api"),
]
27 changes: 27 additions & 0 deletions backend/contest/views/oj.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from ..serializers import ACMContestRankNoPenaltySerializer, ContestAnnouncementSerializer
from ..serializers import ContestSerializer, ContestPasswordVerifySerializer
from ..serializers import ACMContestRankSerializer
from ..serializers import ProfileContestSerializer

import random
import json
Expand Down Expand Up @@ -277,3 +278,29 @@ def get(self, request):
except ProblemBank.DoesNotExist:
return self.success(False)
return self.success(True)


class UserContestAPI(APIView):
def get(self, request):
user = request.user
# queryset for all problems information which user submitted
qs_problems = ACMContestRank.objects.filter(user=user.id, user__admin_type=AdminType.REGULAR_USER, user__is_disabled=False)
contests = []
for problem in qs_problems:
contest_id = problem.contest.id
try:
contest = Contest.objects.get(id=contest_id, visible=True)
contest = ContestSerializer(contest).data
total_participants = ACMContestRank.objects.filter(contest=contest_id, user__admin_type=AdminType.REGULAR_USER, user__is_disabled=False).count()
contest["rank"] = problem.rank
contest["percentage"] = round(contest["rank"]/total_participants*100, 2)
contests.append(contest)
except Contest.DoesNotExist:
return self.error("Contest does not exist")

# priority
priority = request.GET.get("priority")
if priority:
contests = sorted(contests, key=lambda c: c[priority])

return self.success(ProfileContestSerializer(contests, many=True).data)
23 changes: 23 additions & 0 deletions backend/submission/migrations/0017_auto_20220302_1508.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 3.2.12 on 2022-03-02 06:08

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('submission', '0016_auto_20211226_1458'),
]

operations = [
migrations.AddField(
model_name='submission',
name='code_length',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='submission',
name='title',
field=models.TextField(null=True),
),
]
2 changes: 2 additions & 0 deletions backend/submission/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ class Submission(models.Model):
contest = models.ForeignKey(Contest, null=True, on_delete=models.CASCADE)
problem = models.ForeignKey(Problem, on_delete=models.CASCADE)
assignment = models.ForeignKey(Assignment, null=True, on_delete=models.CASCADE, related_name="submissions")
title = models.TextField(null=True)
create_time = models.DateTimeField(auto_now_add=True)
user_id = models.IntegerField(db_index=True)
username = models.TextField()
code = models.TextField()
code_length = models.IntegerField(default=0)
result = models.IntegerField(db_index=True, default=JudgeStatus.PENDING)
# Judgment details returned from JudgeServer
info = JSONField(default=dict)
Expand Down
2 changes: 2 additions & 0 deletions backend/submission/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from .views import (SubmissionAPI, SubmissionListAPI, ContestSubmissionListAPI, AssignmentSubmissionListAPI,
AssignmentSubmissionListProfessorAPI, SubmissionExistsAPI, EditSubmissionScoreAPI)
from .views import ProfileSubmissionListAPI

urlpatterns = [
path("submission/", SubmissionAPI.as_view(), name="submission_api"),
Expand All @@ -11,4 +12,5 @@
path("assignment_submissions/", AssignmentSubmissionListAPI.as_view(), name="assignment_submission_list_api"),
path("assignment_submissions_professor/", AssignmentSubmissionListProfessorAPI.as_view(), name="assignment_submission_list_professor_api"),
path("edit_submission_score/", EditSubmissionScoreAPI.as_view(), name="edit_submission_score_api"),
path("profile_submissions/", ProfileSubmissionListAPI.as_view(), name="profile_submission_list_api"),
]
22 changes: 22 additions & 0 deletions backend/submission/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# from judge.dispatcher import JudgeDispatcher
from problem.models import Problem, ProblemRuleType
from account.models import User, AdminType
from django.db.models import Q
from utils.api import APIView, validate_serializer
from utils.constants import AssignmentStatus
from utils.cache import cache
Expand Down Expand Up @@ -86,7 +87,9 @@ def post(self, request):
username=request.user.username,
language=data["language"],
code=data["code"],
code_length=len(data["code"].encode("utf-8")),
problem_id=problem.id,
title=problem.title,
ip=request.session["ip"],
contest_id=data.get("contest_id"),
assignment_id=data.get("assignment_id"))
Expand Down Expand Up @@ -473,3 +476,22 @@ def get(self, request):
return self.success(request.user.is_authenticated and
Submission.objects.filter(problem_id=request.GET["problem_id"],
user_id=request.user.id).exists())


class ProfileSubmissionListAPI(APIView):
def get(self, request):
submissions = Submission.objects.filter(user_id=request.user.id)
# keyword search, 문제 번호로 검색 안됨
keyword = request.GET.get("keyword", "").strip()
if keyword:
submissions = submissions.filter(Q(title__icontains=keyword))
# result search
result = request.GET.get("result")
if result:
if result == "AC":
submissions = submissions.filter(result=0)
elif result == "NAC":
submissions = submissions.exclude(result=0)
data = self.paginate_data(request, submissions)
data["results"] = SubmissionListSerializer(data["results"], many=True, user=request.user).data
return self.success(data)
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"axios": "^0.24.0",
"bootstrap-vue": "^2.21.2",
"browser-detect": "^0.2.28",
"chart.js": "^3.7.0",
"core-js": "^3.19.3",
"highlight.js": "10.7.3",
"iview": "2",
Expand Down
1 change: 1 addition & 0 deletions frontend/skku-coding-platform
Submodule skku-coding-platform added at 62983c
14 changes: 14 additions & 0 deletions frontend/src/pages/oj/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,13 @@ export default {
}
})
},
getUserContestInfo (offset, limit, params) {
params.limit = limit
params.offset = offset
return ajax('contest/user/', 'get', {
params
})
},
submitCode (data) {
return ajax('submission/', 'post', {
data
Expand All @@ -227,6 +234,13 @@ export default {
params
})
},
getProfileSubmissionList (offset, limit, params) {
params.limit = limit
params.offset = offset
return ajax('profile_submissions/', 'get', {
params
})
},
getSubmission (id) {
return ajax('submission/', 'get', {
params: {
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/pages/oj/components/Header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
</template>
<template v-slot:content v-else>
<button v-if="isAdminRole" @click="goManagement()" class="flex w-full justify-between items-center rounded-lg px-3 py-1 my-1 text-base font-medium text-text-title hover:text-green hover:no-underline">Management</button>
<a class="flex w-full justify-between items-center rounded-lg px-3 py-1 my-1 text-base text-text-title font-medium hover:text-green hover:no-underline" href="/#">Settings</a>
<button v-if="!isAdminRole" class="flex w-full justify-between items-center rounded-lg px-3 py-1 my-1 text-base text-text-title font-medium hover:text-green hover:no-underline" @click="goProfile()">My Profile</button>
<a class="flex w-full justify-between items-center rounded-lg px-3 py-1 my-1 text-base text-text-title hover:text-green hover:no-underline" href="/logout">Sign Out</a>
</template>
</dropdown>
Expand Down Expand Up @@ -77,6 +77,11 @@ export default {
} else {
window.open('/admin/')
}
},
async goProfile () {
await this.$router.push({
name: 'profile'
})
}
},
computed: {
Expand Down
Loading