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

feat(FE, BE): Contest list design change / use group permission #305

Merged
merged 47 commits into from
Mar 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
881f34e
design change: contest list title
jimin9038 Feb 6, 2022
72430f2
feat: create neon box
jimin9038 Feb 6, 2022
a9f901e
feat: neonbox css props
jimin9038 Feb 7, 2022
4d04c3b
feat: neonbox add overlay
jimin9038 Feb 8, 2022
96c8949
feat: publishing contestInformation
jimin9038 Feb 9, 2022
a7b3fb4
feat: contestInformation- detail
jimin9038 Feb 10, 2022
971e401
feat(api): add contest summary field
jimin9038 Feb 10, 2022
7bcf867
add contest group manytomanyfield
jimin9038 Feb 11, 2022
fb15baf
migrate contest summary model change again
jimin9038 Feb 11, 2022
e3760dc
fix allowed_groups associated problems
jimin9038 Feb 11, 2022
2a76c4c
decorators
jimin9038 Feb 11, 2022
a71c191
test: contest group permission
jimin9038 Feb 11, 2022
f047af6
Add contest summary field in contest admin page
jimin9038 Feb 12, 2022
d98d850
feat: add admin create group api
jimin9038 Feb 13, 2022
efc9538
feat: add admin get group api
jimin9038 Feb 13, 2022
4380104
feat: add vuex group module
jimin9038 Feb 13, 2022
cbc8f0d
feat: connect contest design change api
jimin9038 Feb 13, 2022
e754435
feat: neonbox can get contents as props
jimin9038 Feb 13, 2022
c6bc9d7
feat: contestlist api return participants count
jimin9038 Feb 13, 2022
50ab26b
feat: contest list api connection
jimin9038 Feb 13, 2022
9e2161c
fix: check_contest_perm. inspect group permission
jimin9038 Feb 13, 2022
cc7872a
fix: allowed_group not fetched issue
jimin9038 Feb 13, 2022
aaa60b7
feat: temp button to go contest
jimin9038 Feb 13, 2022
b0b2d91
fix: merge conflict resolve
jimin9038 Feb 14, 2022
5b93a9c
fix: apply tailwindcss default header size
jimin9038 Feb 14, 2022
13be155
use b-modal temporarily
jimin9038 Feb 14, 2022
12866c7
feat: contestList neonbox righttop shows contest
jimin9038 Feb 15, 2022
992be70
fix: make header bg-white
jimin9038 Feb 15, 2022
0dcf339
Neonbox hover design change
jimin9038 Feb 15, 2022
2f2ffc1
show contest status in contestlist
jimin9038 Feb 15, 2022
1c520a3
Comment todo (enter now)
jimin9038 Feb 15, 2022
79b49bb
fix: contests list structure
jimin9038 Feb 17, 2022
a5e8206
fix: loadmorecontests call with status
jimin9038 Feb 17, 2022
8ecff81
Add Pagination to finishedcontests + adjust api call
jimin9038 Feb 17, 2022
ac86e26
fix: assign key for contest list neonboxes
jimin9038 Feb 17, 2022
5531749
fix: goContest click event
jimin9038 Feb 17, 2022
2738bee
fix: contest finished neonbox click event
jimin9038 Feb 17, 2022
0e204ab
chore: remove bad quotes
jimin9038 Feb 17, 2022
136c7e3
feat: contestList shows time difference
jimin9038 Feb 17, 2022
fe3c205
fix: calculateTimeDiff on pagination change
jimin9038 Feb 19, 2022
bc841c3
feat: add contestprize model
jimin9038 Feb 25, 2022
0fe8254
feat: change contest prize related serializer, api
jimin9038 Feb 25, 2022
9539696
fix: contest prize model, test, serializer, api
jimin9038 Feb 25, 2022
f6fb13a
fix: contest prize put api
jimin9038 Feb 25, 2022
c3963e2
fix: contestprize put api
jimin9038 Feb 25, 2022
1ea7796
feat: contestrank put api for contest prize field
jimin9038 Feb 25, 2022
33914a0
chore(lint): lint
jimin9038 Feb 25, 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
29 changes: 29 additions & 0 deletions backend/contest/migrations/0014_auto_20220211_1432.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 3.2.12 on 2022-02-11 05:32

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('group', '0004_rename_is_admin_groupmember_is_group_admin'),
('contest', '0013_contestannouncement_problem'),
]

operations = [
migrations.AddField(
model_name='contest',
name='allowed_groups',
field=models.ManyToManyField(to='group.Group'),
),
migrations.AddField(
model_name='contest',
name='prize',
field=models.JSONField(default=dict),
),
migrations.AddField(
model_name='contest',
name='scoring',
field=models.TextField(default='ACM-ICPC style'),
),
]
23 changes: 23 additions & 0 deletions backend/contest/migrations/0015_auto_20220211_1433.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 3.2.12 on 2022-02-11 05:33

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('contest', '0014_auto_20220211_1432'),
]

operations = [
migrations.AddField(
model_name='contest',
name='constraints',
field=models.JSONField(default=list),
),
migrations.AddField(
model_name='contest',
name='requirements',
field=models.JSONField(default=list),
),
]
18 changes: 18 additions & 0 deletions backend/contest/migrations/0016_rename_prize_contest_prizes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.12 on 2022-02-11 05:52

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('contest', '0015_auto_20220211_1433'),
]

operations = [
migrations.RenameField(
model_name='contest',
old_name='prize',
new_name='prizes',
),
]
19 changes: 19 additions & 0 deletions backend/contest/migrations/0017_alter_contest_allowed_groups.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 3.2.12 on 2022-02-11 05:56

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('group', '0004_rename_is_admin_groupmember_is_group_admin'),
('contest', '0016_rename_prize_contest_prizes'),
]

operations = [
migrations.AlterField(
model_name='contest',
name='allowed_groups',
field=models.ManyToManyField(blank=True, to='group.Group'),
),
]
38 changes: 38 additions & 0 deletions backend/contest/migrations/0018_auto_20220225_1422.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Generated by Django 3.2.12 on 2022-02-25 05:22

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


class Migration(migrations.Migration):

dependencies = [
('contest', '0017_alter_contest_allowed_groups'),
]

operations = [
migrations.RemoveField(
model_name='contest',
name='prizes',
),
migrations.CreateModel(
name='ContestPrize',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('color', models.TextField()),
('name', models.TextField()),
('reward', models.TextField()),
('contest', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contest.contest')),
],
),
migrations.AddField(
model_name='acmcontestrank',
name='prize',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='contest.contestprize'),
),
migrations.AddField(
model_name='oicontestrank',
name='prize',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='contest.contestprize'),
),
]
19 changes: 19 additions & 0 deletions backend/contest/migrations/0019_alter_contestprize_contest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 3.2.12 on 2022-02-25 05:46

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


class Migration(migrations.Migration):

dependencies = [
('contest', '0018_auto_20220225_1422'),
]

operations = [
migrations.AlterField(
model_name='contestprize',
name='contest',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='prizes', to='contest.contest'),
),
]
13 changes: 13 additions & 0 deletions backend/contest/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@

from utils.constants import ContestStatus, ContestType
from account.models import User
from group.models import Group
from utils.models import RichTextField


class Contest(models.Model):
title = models.TextField()
description = RichTextField()
requirements = JSONField(default=list)
constraints = JSONField(default=list)
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()
password = models.TextField(null=True)
Expand Down Expand Up @@ -55,10 +60,18 @@ class Meta:
ordering = ("-start_time",)


class ContestPrize(models.Model):
contest = models.ForeignKey(Contest, on_delete=models.CASCADE, related_name="prizes")
color = models.TextField()
name = models.TextField()
reward = models.TextField()


class AbstractContestRank(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
contest = models.ForeignKey(Contest, on_delete=models.CASCADE)
submission_number = models.IntegerField(default=0)
prize = models.ForeignKey(ContestPrize, on_delete=models.SET_NULL, blank=True, null=True)

class Meta:
abstract = True
Expand Down
34 changes: 32 additions & 2 deletions backend/contest/serializers.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
from utils.api import UsernameSerializer, serializers

from .models import Contest, ContestAnnouncement, ContestRuleType
from .models import Contest, ContestAnnouncement, ContestPrize, ContestRuleType
from .models import ACMContestRank, OIContestRank
from group.serializers import GroupSummarySerializer


class CreateOrEditContestPrizeSerializer(serializers.Serializer):
id = serializers.IntegerField(required=False)
color = serializers.CharField(max_length=20)
name = serializers.CharField(max_length=20)
reward = serializers.CharField(max_length=20)


class ContestPrizeSerializer(serializers.ModelSerializer):
class Meta:
model = ContestPrize
fields = "__all__"


class CreateConetestSeriaizer(serializers.Serializer):
title = serializers.CharField(max_length=128)
description = serializers.CharField()
requirements = serializers.ListField(child=serializers.CharField(max_length=128))
constraints = serializers.ListField(child=serializers.CharField(max_length=128), allow_empty=True)
allowed_groups = serializers.ListField(child=serializers.IntegerField(), allow_empty=True)
scoring = serializers.CharField()
prizes = serializers.ListField(child=CreateOrEditContestPrizeSerializer())
start_time = serializers.DateTimeField()
end_time = serializers.DateTimeField()
rule_type = serializers.ChoiceField(choices=[ContestRuleType.ACM, ContestRuleType.OI])
Expand All @@ -20,6 +39,11 @@ class EditConetestSeriaizer(serializers.Serializer):
id = serializers.IntegerField()
title = serializers.CharField(max_length=128)
description = serializers.CharField()
requirements = serializers.ListField(child=serializers.CharField(max_length=128))
constraints = serializers.ListField(child=serializers.CharField(max_length=128), allow_empty=True)
allowed_groups = serializers.ListField(child=serializers.IntegerField(), allow_empty=True)
scoring = serializers.CharField()
prizes = serializers.ListField(child=CreateOrEditContestPrizeSerializer())
start_time = serializers.DateTimeField()
end_time = serializers.DateTimeField()
password = serializers.CharField(allow_blank=True, allow_null=True, max_length=32)
Expand All @@ -32,13 +56,18 @@ class ContestAdminSerializer(serializers.ModelSerializer):
created_by = UsernameSerializer()
status = serializers.CharField()
contest_type = serializers.CharField()
allowed_groups = GroupSummarySerializer(many=True)
prizes = ContestPrizeSerializer(many=True)

class Meta:
model = Contest
fields = "__all__"


class ContestSerializer(ContestAdminSerializer):
participants_count = serializers.IntegerField(required=False)
prizes = ContestPrizeSerializer(many=True)

class Meta:
model = Contest
exclude = ("password", "visible", "allowed_ip_ranges")
Expand Down Expand Up @@ -75,10 +104,11 @@ class ContestPasswordVerifySerializer(serializers.Serializer):

class ACMContestRankSerializer(serializers.ModelSerializer):
username = serializers.SerializerMethodField()
prize = ContestPrizeSerializer()

class Meta:
model = ACMContestRank
fields = ["accepted_number", "total_time", "total_penalty", "submission_info", "username"]
fields = ["id", "accepted_number", "total_time", "total_penalty", "submission_info", "username", "prize"]

def __init__(self, *args, **kwargs):
self.is_contest_admin = kwargs.pop("is_contest_admin", False)
Expand Down
56 changes: 53 additions & 3 deletions backend/contest/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from datetime import datetime, timedelta

from django.utils import timezone
from group.models import Group

from utils.api.tests import APITestCase

Expand All @@ -22,6 +23,14 @@
"rule_type": "ACM", "hint": "<p>test</p>", "source": "test"}

DEFAULT_CONTEST_DATA = {"title": "test title", "description": "test description",
"requirements": ["SKKU undergrate enrolled 2021 Spring"],
"constraints": ["Not Awarded in same past competition"],
"scoring": "ACM-ICPC style",
"prizes": [{"color": "#FF6663", "name": "Top 2", "reward": "1,000,000 Won"},
{"color": "#FFD700", "name": "3(3~5)", "reward": "500,000 Won"},
{"color": "#C0C0C0", "name": "5(6~10)", "reward": "100,000 Won"},
{"color": "#CD7F32", "name": "10(11~20)", "reward": "50,000 Won"}],
"allowed_groups": [],
"start_time": timezone.localtime(timezone.now()),
"end_time": timezone.localtime(timezone.now()) + timedelta(days=1),
"rule_type": ContestRuleType.ACM,
Expand All @@ -32,7 +41,7 @@

class ContestAdminAPITest(APITestCase):
def setUp(self):
self.create_super_admin()
self.super_admin = self.create_super_admin()
self.url = self.reverse("contest_admin_api")
self.data = copy.deepcopy(DEFAULT_CONTEST_DATA)

Expand All @@ -46,17 +55,38 @@ def test_create_contest_with_invalid_cidr(self):
resp = self.client.post(self.url, data=self.data)
self.assertTrue(resp.data["data"].endswith("is not a valid cidr network"))

def test_create_contest_with_group_not_existing(self):
self.data["allowed_groups"] = [1]
response = self.client.post(self.url, data=self.data)
self.assertFailed(response)

def test_create_contest_with_group_existing(self):
group = Group.objects.create(
created_by=self.super_admin,
name="SKKUding",
short_description="post group registration request",
description="post group registration request",
is_official=True
)
group.members.add(self.super_admin, through_defaults={"is_group_admin": False})
self.data["allowed_groups"] = [group.id]
response = self.client.post(self.url, data=self.data)
self.assertSuccess(response)

def test_update_contest(self):
id = self.test_create_contest().data["data"]["id"]
update_data = {"id": id, "title": "update title",
"description": "update description",
"password": "12345",
"visible": False, "real_time_rank": False}
"visible": False, "real_time_rank": False
}
data = copy.deepcopy(self.data)
data.update(update_data)
response = self.client.put(self.url, data=data)
self.assertSuccess(response)
response_data = response.data["data"]
response_data.pop("prizes")
data.pop("prizes")
for k in data.keys():
if isinstance(data[k], datetime):
continue
Expand All @@ -76,7 +106,14 @@ def test_get_one_contest(self):
class ContestAPITest(APITestCase):
def setUp(self):
user = self.create_admin()
self.contest = Contest.objects.create(created_by=user, **DEFAULT_CONTEST_DATA)
group = self.create_group(created_by=user)
data = copy.deepcopy(DEFAULT_CONTEST_DATA)
data.pop("allowed_groups")
data.pop("prizes")
allowed_groups = [group.id]
allowed_groups_qs = Group.objects.filter(id__in=allowed_groups)
self.contest = Contest.objects.create(created_by=user, **data)
self.contest.allowed_groups.set(allowed_groups_qs)
self.url = self.reverse("contest_api") + "?id=" + str(self.contest.id)

def test_get_contest_list(self):
Expand Down Expand Up @@ -111,6 +148,19 @@ def test_regular_user_access_contest(self):
resp = self.client.get(self.url)
self.assertSuccess(resp)

def test_not_group_member_access_contest(self):
self.create_user("test2", "test1234")
url = self.reverse("contest_access_api")
resp = self.client.get(url + "?contest_id=" + str(self.contest.id))
self.assertFalse(resp.data["data"]["access"])

password_url = self.reverse("contest_password_api")
resp = self.client.post(password_url,
{"contest_id": self.contest.id, "password": DEFAULT_CONTEST_DATA["password"]})
self.assertSuccess(resp)
resp = self.client.get(self.url)
self.assertSuccess(resp)


class ContestAnnouncementAdminAPITest(APITestCase):
def setUp(self):
Expand Down
3 changes: 2 additions & 1 deletion backend/contest/urls/admin.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from django.urls import path

from ..views.admin import ContestAnnouncementAPI, ContestAPI, DownloadContestSubmissions
from ..views.admin import ContestAnnouncementAPI, ContestAPI, ContestRankAPI, DownloadContestSubmissions

urlpatterns = [
path("contest/", ContestAPI.as_view(), name="contest_admin_api"),
path("contest/announcement/", ContestAnnouncementAPI.as_view(), name="contest_announcement_admin_api"),
path("download_submissions/", DownloadContestSubmissions.as_view(), name="acm_contest_helper"),
path("contest/rank/", ContestRankAPI.as_view(), name="contest_rank_api")
]
Loading