From eb5874ed9d8ffe7b7b8f18ca852ed240ce8e3133 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Mon, 15 May 2023 15:19:03 +0700 Subject: [PATCH 01/16] Accessiable account for each Topic --- ModelGrader.sqlite3 | Bin 1650688 -> 1662976 bytes api/migrations/0022_topicaccountaccess.py | 22 +++++++++++++ api/models.py | 6 +++- api/serializers.py | 12 ++++++- api/urls.py | 1 + api/views/topic.py | 38 +++++++++++++++++++--- 6 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 api/migrations/0022_topicaccountaccess.py diff --git a/ModelGrader.sqlite3 b/ModelGrader.sqlite3 index 7a178fd6743f2bf0b155ec23ce2fd85d53fdd040..918a70d940649fa9bad0dc33948911ee6e57ea7a 100644 GIT binary patch delta 1610 zcmah}Uu@Gx9QN7gIC0WDcP&fT7Gc8JS}hdEj+b1cwWLV|65D_YhJ-+s6Pu9<3R2VT zPqn7rhR`%=V#**7dtnk!ys=EGcml0Rd)N?_m?klaZZDg7st~FK+S7h^D|LXweERO} z`}zCsySv}r{Gayx4d?to_W_PW7rBk+=dY87cz_$39K32@qCyhOO-VV-W4WDDuLwDo z6uF*U-a1p&G+m#pojy~oSe44@nW-8y&h)gd>BbJt+>tU>Ei-DEqk3v2oyum?>2|62 zdQ?!zTvJk2GzWU`lCAwC0z;q{pcLtURmkwI2Y2{Q%^ya7L&fHoJNF=^`RmTt29h#3 zR>)0`uF&1&5tZpIJxt%HjNEMZX;;WVKki9+y3ZO^+JB5~M?w+To6;^fEb90n{vOxC z&fh{~v?y{Yw_B-Koeu&m6|5=M3#q=Wx;}BQg_(~PfIGfdv7M7n&FR2i3kLHi#D*_W zIblt`*GXV47$}gGQBl=25l>cRZP zo25?6(#*%q*5dEH(^BW~(a`WGE-%Gf$L{j8=ZrcAh=3440)zn(fDDKNVt_6H0Z@Pf z=mxw1=mGQsHUVA)#21Wu->qxCUGxFR)B7BKOz%Tk@(N_?b(+@@iZQtCh7gK+t%{J$ zLJ`cZ_lLdykmP;KOs1i^@zvX5rgH?Q!9Ta3Y-?vj`WOiW9nau9c2(;%3%jO){ z8C=W`T&$KkG;uM-ELgQ0>rF9 z%m&2lK+FNeoIuP4#N0s41H`;Q%m>8$Kr8^nfq*~EC$5l+l|X4&h7*N D9+r`E diff --git a/api/migrations/0022_topicaccountaccess.py b/api/migrations/0022_topicaccountaccess.py new file mode 100644 index 0000000..3571b5c --- /dev/null +++ b/api/migrations/0022_topicaccountaccess.py @@ -0,0 +1,22 @@ +# Generated by Django 4.1.2 on 2023-05-15 07:35 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0021_collectionproblem_order_topiccollection_order_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='TopicAccountAccess', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('account', models.ForeignKey(db_column='account_id', on_delete=django.db.models.deletion.CASCADE, to='api.account')), + ('topic', models.ForeignKey(db_column='topic_id', on_delete=django.db.models.deletion.CASCADE, to='api.topic')), + ], + ), + ] diff --git a/api/models.py b/api/models.py index fd87de1..e25ce75 100644 --- a/api/models.py +++ b/api/models.py @@ -81,4 +81,8 @@ class CollectionProblem(models.Model): # Doesn't use anymore class TopicProblem(models.Model): topic_id = models.ForeignKey(Topic,on_delete=models.CASCADE,db_column="topic_id") - problem_id = models.ForeignKey(Problem,on_delete=models.CASCADE,db_column="problem_id") \ No newline at end of file + problem_id = models.ForeignKey(Problem,on_delete=models.CASCADE,db_column="problem_id") + +class TopicAccountAccess(models.Model): + topic = models.ForeignKey(Topic,on_delete=models.CASCADE,db_column="topic_id") + account = models.ForeignKey(Account,on_delete=models.CASCADE,db_column="account_id") \ No newline at end of file diff --git a/api/serializers.py b/api/serializers.py index 683baa9..192a1e3 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -1,6 +1,11 @@ from rest_framework import serializers from .models import * +class AccountSerializer(serializers.ModelSerializer): + class Meta: + model = Account + fields = "__all__" + class TopicSerializer(serializers.ModelSerializer): class Meta: model = Topic @@ -58,4 +63,9 @@ def update(self, instance, validated_data): instance.is_private = validated_data.get('is_private',instance.is_private) instance.save() - return instance \ No newline at end of file + return instance + +class TopicAccountAccessSerialize(serializers.ModelSerializer): + class Meta: + model = TopicAccountAccess + fields = "__all__" \ No newline at end of file diff --git a/api/urls.py b/api/urls.py index 30373b7..5e20b42 100644 --- a/api/urls.py +++ b/api/urls.py @@ -22,6 +22,7 @@ path('accounts//topics',topic.create_topic), path('topics',topic.all_topic), path('topics/',topic.one_topic), + path('topics//access',topic.account_access), path('topics//collections/',topic.topic_collection), path('accounts//collections',collection.create_collections), diff --git a/api/views/topic.py b/api/views/topic.py index b59713b..cc3eedf 100644 --- a/api/views/topic.py +++ b/api/views/topic.py @@ -3,7 +3,7 @@ from rest_framework.decorators import api_view,parser_classes from rest_framework.parsers import MultiPartParser, FormParser from ..constant import GET,POST,PUT,DELETE -from ..models import Account, Problem, Submission,Testcase, Topic, TopicProblem, Collection +from ..models import * from rest_framework import status from django.forms.models import model_to_dict from ..serializers import * @@ -38,9 +38,8 @@ def all_topic(request): @api_view([GET,PUT,DELETE]) def one_topic(request,topic_id:int): topic = Topic.objects.get(topic_id=topic_id) - topicProblem = Problem.objects.filter(topicproblem__topic_id=topic_id) - collections = Collection.objects.filter(topiccollection__topic_id=topic_id) topicCollections = TopicCollection.objects.filter(topic_id=topic_id) + accessedAccounts = Account.objects.filter(topicaccountaccess__topic_id=topic_id) if request.method == GET: topic_ser = TopicSerializer(topic) @@ -61,9 +60,12 @@ def one_topic(request,topic_id:int): top_col_serialize = TopicCollectionSerializer(top_col) populate_collections.append({**top_col_serialize.data,**collection_data}) + accessedAccountsSerialize = AccountSerializer(accessedAccounts,many=True) + return Response({ "topic": topic_ser.data, - "collections": sorted(populate_collections,key=lambda collection: collection['order']) + "collections": sorted(populate_collections,key=lambda collection: collection['order']), + "accessed_accounts": accessedAccountsSerialize.data },status=status.HTTP_200_OK) elif request.method == PUT: topic_ser = TopicSerializer(topic,data=request.data,partial=True) @@ -110,4 +112,32 @@ def topic_collection(request,topic_id:int,method:str): # collections = Collection.objects.filter(collection_id__in=request.data['collection_ids']) # problems = Problem.objects.filter(problem_id__in=request.data['problems_id']) # TopicProblem.objects.filter(topic_id=topic,problem_id__in=problems).delete() + return Response(status=status.HTTP_204_NO_CONTENT) + +@api_view([POST,PUT]) +def account_access(request,topic_id:int): + topic = Topic.objects.get(topic_id=topic_id) + target_accounts = Account.objects.filter(account_id__in=request.data['account_ids']) + + if request.method == POST: + accessedAccounts = [] + for account in target_accounts: + topic_account = TopicAccountAccess( + topic = topic, + account = account + ) + # print(topic_account) + topic_account.save() + accessedAccounts.append(topic_account) + # ta_serialize = TopicAccountAccessSerialize(topic_account) + + serialize = TopicAccountAccessSerialize(accessedAccounts,many=True) + + return Response({ + "accounts": serialize.data + },status=status.HTTP_201_CREATED) + + elif request.method == PUT: + topicAccountAccesses = TopicAccountAccess.objects.filter(account_id__in=request.data['account_ids']) + topicAccountAccesses.delete() return Response(status=status.HTTP_204_NO_CONTENT) \ No newline at end of file From 03259c6c2f951510118e8f50bf0fe0ab0aac228a Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sun, 4 Jun 2023 02:08:46 +0700 Subject: [PATCH 02/16] Get all account method --- ModelGrader.sqlite3 | Bin 1662976 -> 1662976 bytes api/urls.py | 2 +- api/views/account.py | 26 +++++++++++++++++--------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/ModelGrader.sqlite3 b/ModelGrader.sqlite3 index 918a70d940649fa9bad0dc33948911ee6e57ea7a..83852cddd854be223e1bfa086277cd74789485e9 100644 GIT binary patch delta 568 zcmZvYy-LJD6oqFp*(Am!H~VMQwYM^CBXT=Mtk*}dvG)y35V5iFceixGClJL|5ER>O z1hKFX7POERMMM-UJ(Jiemv7D`Irq$El8eRUVsWD;S2xFIKDEyGR3!^$9n5-|^I*<{IS=MMm=WAo zvYiKa1$F6zdf54fPU#YLLhA@#qSkuDjOXZHl!cUz-nyh4J1rHm5A38vx|0E$PFl%Y zj}0fSX06Z4PRqI-7+m{qn4kbGU;_sX02k>09`Hc`2!x;rN}voXpbCaS)cI}<+da1@ z=n<=0KD~DCh-)s7^RNa!cb1GgQE71^Nk1 WAE%|fzwUXsBltLTbLQsE&E5~hIg^M0 delta 568 zcmZvZPiqrV6vgkGm-(BS+*A|CG%nl~gbdw?+?_?F%b0KArhCPGGKq_9vS@LUWdnW! zu_P6Q1nJ&FR|ym<*@QrYi%P{!J#W%o%i%Y3-<`vGmpkcXFP-e|S5@QS$t%_P@>UYZ z-`vtKtp4vatNiIGd9FGimq~Y-C}542iH>)wydaxr#Q*q;fAJ;HczUbruN&!r``=qS zt`X6*-~%~YSq18tS4b(Ec4X>^+O}3I-!jjjIZU-G-V;3M@mdJ}%>D79F4f49_xvXd zmUXbKgT(~PI#{w`X$}Wk4XT*d<`d#;Uhz+!^9g_BAJ8%P5d4n&y?QQrO|+|&7YLEp zwro;L6(r}iDJw&8Q9}Ipu8(^AwvC$Is#35JQuPeTw6nb07g< tk;N7e diff --git a/api/urls.py b/api/urls.py index 5e20b42..347bf00 100644 --- a/api/urls.py +++ b/api/urls.py @@ -7,7 +7,7 @@ path("logout",auth.logout), path('token',auth.get_authorization), - path("accounts",account.create_account), + path("accounts",account.account_collection), path("accounts/",account.get_account), path("accounts//daily-submissions",account.get_daily_submission), path("accounts//password",account.change_password), diff --git a/api/views/account.py b/api/views/account.py index b69df3b..558e981 100644 --- a/api/views/account.py +++ b/api/views/account.py @@ -6,16 +6,24 @@ from ..models import * from rest_framework import status from django.forms.models import model_to_dict -from ..serializers import SubmissionSerializer +from ..serializers import * -@api_view([POST]) -def create_account(request): - request.data['password'] = passwordEncryption(request.data['password']) - try: - account = Account.objects.create(**request.data) - except Exception as e: - return Response({'message':str(e)},status=status.HTTP_400_BAD_REQUEST) - return Response({'message':'Registration Completed','account':model_to_dict(account)},status=status.HTTP_201_CREATED) +@api_view([GET,POST]) +def account_collection(request): + if request.method == GET: + accounts = Account.objects.all() + serialize = AccountSerializer(accounts,many=True) + return Response({ + "accounts": serialize.data + },status=status.HTTP_200_OK) + + elif request.method == POST: + request.data['password'] = passwordEncryption(request.data['password']) + try: + account = Account.objects.create(**request.data) + except Exception as e: + return Response({'message':str(e)},status=status.HTTP_400_BAD_REQUEST) + return Response({'message':'Registration Completed','account':model_to_dict(account)},status=status.HTTP_201_CREATED) @api_view([GET]) def get_account(request,account_id): From 304a7085e0850ee4a5a7e8ca201a8728e564dc17 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sat, 17 Jun 2023 18:41:38 +0700 Subject: [PATCH 03/16] Remodel --- ...ame_account_id_problem_account_and_more.py | 48 ++++++++++++++ api/models.py | 14 ++--- api/sandbox/grader.py | 3 +- api/serializers.py | 6 ++ api/views/problem.py | 18 +++--- api/views/submission.py | 62 ++++++++++++------- api/views/topic.py | 2 +- 7 files changed, 114 insertions(+), 39 deletions(-) create mode 100644 api/migrations/0023_rename_account_id_problem_account_and_more.py diff --git a/api/migrations/0023_rename_account_id_problem_account_and_more.py b/api/migrations/0023_rename_account_id_problem_account_and_more.py new file mode 100644 index 0000000..7f711ad --- /dev/null +++ b/api/migrations/0023_rename_account_id_problem_account_and_more.py @@ -0,0 +1,48 @@ +# Generated by Django 4.1.2 on 2023-06-17 07:25 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0022_topicaccountaccess'), + ] + + operations = [ + migrations.RenameField( + model_name='problem', + old_name='account_id', + new_name='account', + ), + migrations.RenameField( + model_name='submission', + old_name='account_id', + new_name='account', + ), + migrations.RenameField( + model_name='submission', + old_name='problem_id', + new_name='problem', + ), + migrations.RenameField( + model_name='testcase', + old_name='problem_id', + new_name='problem', + ), + migrations.RenameField( + model_name='topic', + old_name='account_id', + new_name='account', + ), + migrations.RenameField( + model_name='topicproblem', + old_name='problem_id', + new_name='problem', + ), + migrations.RenameField( + model_name='topicproblem', + old_name='topic_id', + new_name='topic', + ), + ] diff --git a/api/models.py b/api/models.py index e25ce75..31a61a7 100644 --- a/api/models.py +++ b/api/models.py @@ -27,7 +27,7 @@ class Account(models.Model): class Problem(models.Model): problem_id = models.AutoField(primary_key=True) - account_id = models.ForeignKey(Account,on_delete=models.CASCADE,db_column="account_id") + account = models.ForeignKey(Account,on_delete=models.CASCADE,db_column="account_id") language = models.CharField(max_length=10,choices=ProgrammingLanguage.choices,default=ProgrammingLanguage.PYTHON) title = models.CharField(max_length=50) description = models.CharField(max_length=10000) @@ -38,14 +38,14 @@ class Problem(models.Model): class Testcase(models.Model): testcase_id = models.AutoField(primary_key=True) - problem_id = models.ForeignKey(Problem,on_delete=models.CASCADE,db_column="problem_id") + problem = models.ForeignKey(Problem,on_delete=models.CASCADE,db_column="problem_id") input = models.CharField(max_length=100000) output = models.CharField(max_length=100000) class Submission(models.Model): submission_id = models.AutoField(primary_key=True) - problem_id = models.ForeignKey(Problem,on_delete=models.CASCADE,db_column="problem_id") - account_id = models.ForeignKey(Account,on_delete=models.CASCADE,db_column="account_id") + problem = models.ForeignKey(Problem,on_delete=models.CASCADE,db_column="problem_id") + account = models.ForeignKey(Account,on_delete=models.CASCADE,db_column="account_id") submission_code = models.CharField(max_length=20000) result = models.CharField(max_length=100) is_passed = models.BooleanField() @@ -61,7 +61,7 @@ class Collection(models.Model): class Topic(models.Model): topic_id = models.AutoField(primary_key=True) - account_id = models.ForeignKey(Account,on_delete=models.CASCADE,db_column="account_id") + account = models.ForeignKey(Account,on_delete=models.CASCADE,db_column="account_id") name = models.CharField(max_length=100) description = models.CharField(max_length=1000,null=True,blank=True,default=None) image_url = models.ImageField(upload_to='topic/',null=True,blank=True,default=None) @@ -80,8 +80,8 @@ class CollectionProblem(models.Model): # Doesn't use anymore class TopicProblem(models.Model): - topic_id = models.ForeignKey(Topic,on_delete=models.CASCADE,db_column="topic_id") - problem_id = models.ForeignKey(Problem,on_delete=models.CASCADE,db_column="problem_id") + topic = models.ForeignKey(Topic,on_delete=models.CASCADE,db_column="topic_id") + problem = models.ForeignKey(Problem,on_delete=models.CASCADE,db_column="problem_id") class TopicAccountAccess(models.Model): topic = models.ForeignKey(Topic,on_delete=models.CASCADE,db_column="topic_id") diff --git a/api/sandbox/grader.py b/api/sandbox/grader.py index 3ad976d..ff0c5e0 100644 --- a/api/sandbox/grader.py +++ b/api/sandbox/grader.py @@ -2,6 +2,7 @@ def forgiveableFormat(string:str)->str: return string.replace('\r','') + # return string def checker(section:int,code:str,testcases:list,timeout=1.5)->dict: result = [] @@ -38,7 +39,7 @@ def grading(section:int,code:str,input:list,output:list,timeout=1.5)->str: for i in range(len(output)): if graded_result[i]['runtime_status'] == 'OK': - if graded_result[i]['output'] == forgiveableFormat(output[i]): + if forgiveableFormat(graded_result[i]['output']) == forgiveableFormat(output[i]): score += 'P' else: score += '-' diff --git a/api/serializers.py b/api/serializers.py index 192a1e3..52f3c51 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -68,4 +68,10 @@ def update(self, instance, validated_data): class TopicAccountAccessSerialize(serializers.ModelSerializer): class Meta: model = TopicAccountAccess + fields = "__all__" + +class SubmissionPoplulateProblemSerializer(serializers.ModelSerializer): + problem = ProblemSerializer() + class Meta: + model = Submission fields = "__all__" \ No newline at end of file diff --git a/api/views/problem.py b/api/views/problem.py index 8690787..0cc9b65 100644 --- a/api/views/problem.py +++ b/api/views/problem.py @@ -22,7 +22,7 @@ def create_problem(request,account_id): problem = Problem( language = request.data['language'], - account_id = account, + account = account, title = request.data['title'], description = request.data['description'], solution = request.data['solution'], @@ -33,7 +33,7 @@ def create_problem(request,account_id): testcase_result = [] for unit in checked['result']: testcase = Testcase( - problem_id = problem, + problem = problem, input = unit['input'], output = unit['output'] ) @@ -63,7 +63,7 @@ def all_problem(request): result = [model_to_dict(i) for i in problem] for i in result: - i['creator'] = model_to_dict(Account.objects.get(account_id=i['account_id'])) + i['creator'] = model_to_dict(Account.objects.get(account_id=i['account'])) return Response({'result':result},status=status.HTTP_200_OK) elif request.method == DELETE: @@ -75,14 +75,16 @@ def all_problem(request): @api_view([GET,PUT,DELETE]) def one_problem(request,problem_id: int): - problem = Problem.objects.get(problem_id=problem_id) + try: + problem = Problem.objects.get(problem_id=problem_id) + except Problem.DoesNotExist: + return Response({'detail': "Problem doesn't exist!"},status=status.HTTP_404_NOT_FOUND) testcases = Testcase.objects.filter(problem_id=problem_id) if request.method == GET: - result = model_to_dict(problem) - account = Account.objects.get(account_id=result['account_id']) - return Response({**result,'testcases':[model_to_dict(i) for i in testcases],'creator': model_to_dict(account)},status=status.HTTP_200_OK) - + result = model_to_dict(problem) + account = Account.objects.get(account_id=result['account']) + return Response({**result,'testcases':[model_to_dict(i) for i in testcases],'creator': model_to_dict(account)},status=status.HTTP_200_OK) elif request.method == PUT: problem.title = request.data.get("title",problem.title) diff --git a/api/views/submission.py b/api/views/submission.py index 209a3c4..cbfbc35 100644 --- a/api/views/submission.py +++ b/api/views/submission.py @@ -1,6 +1,8 @@ from statistics import mode from rest_framework.response import Response from rest_framework.decorators import api_view + +from api.serializers import SubmissionPoplulateProblemSerializer from ..constant import GET,POST,PUT,DELETE from ..models import Account, Problem, Submission,Testcase from rest_framework import status @@ -21,7 +23,7 @@ def avaliableQueue(): def submit_problem(request,problem_id,account_id): global QUEUE problem = Problem.objects.get(problem_id=problem_id) - testcases = Testcase.objects.filter(problem_id=problem_id) + testcases = Testcase.objects.filter(problem=problem) submission_code = request.data['submission_code'] solution_input = [model_to_dict(i)['input'] for i in testcases] @@ -41,8 +43,8 @@ def submit_problem(request,problem_id,account_id): is_passed = True submission = Submission( - problem_id = problem, - account_id = Account.objects.get(account_id=account_id), + problem = problem, + account = Account.objects.get(account_id=account_id), submission_code = request.data['submission_code'], result = grading_result, is_passed = is_passed @@ -61,33 +63,49 @@ def view_all_submission(request): sort_score = int(request.query_params.get("sort_score", 0)) sort_date = int(request.query_params.get("sort_date", 0)) - if problem_id: + if problem_id != 0: submission = submission.filter(problem_id=problem_id) - if account_id: + if account_id != 0: submission = submission.filter(account_id=account_id) + + if passed == 0: + submission = submission.filter(is_passed=False) + elif passed == 1: + submission = submission.filter(is_passed=True) + + # if sort_score == -1: + # submission = submission.order_by('result') + # elif sort_score == 1: + # submission = submission.order_by('-result') + if sort_date == -1: submission = submission.order_by('date') elif sort_date == 1: submission = submission.order_by('-date') - result = [model_to_dict(i) for i in submission] + # result = [model_to_dict(i) for i in submission] + serialize = SubmissionPoplulateProblemSerializer(submission,many=True) + return Response({"result": serialize.data},status=status.HTTP_200_OK) + # result = serialize.data - for row in result: - count = 0 - for j in row['result']: - if j == 'P': - count += 1 - row['score'] = count + # print(result[0]) - if passed == 0: - result = [i for i in result if not i['is_passed']] - elif passed == 1: - result = [i for i in result if i['is_passed']] + # for row in result: + # count = 0 + # for j in row.get('result'): + # if j == 'P': + # count += 1 + # row['score'] = count + + # if passed == 0: + # result = [i for i in result if not i['is_passed']] + # elif passed == 1: + # result = [i for i in result if i['is_passed']] - if sort_score == -1: - result.sort(key=lambda value: value['score']) - if sort_score == 1: - result.sort(key=lambda value: value['score'],reverse=True) + # if sort_score == -1: + # result.sort(key=lambda value: value['score']) + # if sort_score == 1: + # result.sort(key=lambda value: value['score'],reverse=True) - result = [{'problem': model_to_dict(Problem.objects.get(problem_id=i['problem_id'])),**i} for i in result] - return Response({'count':len(result),'result':result},status=status.HTTP_200_OK) \ No newline at end of file + # result = [{'problem': model_to_dict(Problem.objects.get(problem_id=i['problem'])),**i} for i in result] + # return Response({'count':len(result),'result':result},status=status.HTTP_200_OK) \ No newline at end of file diff --git a/api/views/topic.py b/api/views/topic.py index 7c7c34f..8e59039 100644 --- a/api/views/topic.py +++ b/api/views/topic.py @@ -12,7 +12,7 @@ @parser_classes([MultiPartParser,FormParser]) def create_topic(request,account_id :int): request.data._mutable=True - request.data['account_id'] = account_id + request.data['account'] = account_id serializer = TopicSerializer(data=request.data) if serializer.is_valid(): From df7a8c4103fb21cb6674b6654a4dcfced10650ff Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sun, 18 Jun 2023 20:14:08 +0700 Subject: [PATCH 04/16] Get all account only needed field --- api/serializers.py | 5 +++++ api/views/account.py | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index 52f3c51..305af23 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -6,6 +6,11 @@ class Meta: model = Account fields = "__all__" +class AccountDetailSerializer(serializers.ModelSerializer): + class Meta: + model = Account + fields = ['account_id','username'] + class TopicSerializer(serializers.ModelSerializer): class Meta: model = Topic diff --git a/api/views/account.py b/api/views/account.py index 511ca75..aa9e29e 100644 --- a/api/views/account.py +++ b/api/views/account.py @@ -12,7 +12,7 @@ def account_collection(request): if request.method == GET: accounts = Account.objects.all() - serialize = AccountSerializer(accounts,many=True) + serialize = AccountDetailSerializer(accounts,many=True) return Response({ "accounts": serialize.data },status=status.HTTP_200_OK) @@ -29,7 +29,8 @@ def account_collection(request): def get_account(request,account_id): try: account = Account.objects.get(account_id=account_id) - return Response(model_to_dict(account),status=status.HTTP_200_OK) + serialize = AccountSerializer(account) + return Response(serialize.data,status=status.HTTP_200_OK) except: return Response({'message':'Account not found!'},status=status.HTTP_404_NOT_FOUND) From 9e82049889227b0058105caf5f9bbf0cda53ec13 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Mon, 19 Jun 2023 02:00:02 +0700 Subject: [PATCH 05/16] MOD-17,18: Add score field for submission and recalculate score sorting --- ..._score_submission_passed_ratio_and_more.py | 28 +++++++++++++++++++ api/models.py | 3 ++ api/urls.py | 4 ++- api/views/script.py | 24 ++++++++++++++++ api/views/submission.py | 13 +++++---- 5 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 api/migrations/0024_submission_max_score_submission_passed_ratio_and_more.py create mode 100644 api/views/script.py diff --git a/api/migrations/0024_submission_max_score_submission_passed_ratio_and_more.py b/api/migrations/0024_submission_max_score_submission_passed_ratio_and_more.py new file mode 100644 index 0000000..417d4bb --- /dev/null +++ b/api/migrations/0024_submission_max_score_submission_passed_ratio_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.1.2 on 2023-06-18 18:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0023_rename_account_id_problem_account_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='submission', + name='max_score', + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name='submission', + name='passed_ratio', + field=models.FloatField(default=0), + ), + migrations.AddField( + model_name='submission', + name='score', + field=models.IntegerField(default=0), + ), + ] diff --git a/api/models.py b/api/models.py index 31a61a7..7c02131 100644 --- a/api/models.py +++ b/api/models.py @@ -50,6 +50,9 @@ class Submission(models.Model): result = models.CharField(max_length=100) is_passed = models.BooleanField() date = models.DateTimeField(default=timezone.now) + score = models.IntegerField(default=0) + max_score = models.IntegerField(default=0) + passed_ratio = models.FloatField(default=0) class Collection(models.Model): collection_id = models.AutoField(primary_key=True) diff --git a/api/urls.py b/api/urls.py index 347bf00..286bf15 100644 --- a/api/urls.py +++ b/api/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from .views import account,auth,problem,submission,topic,collection +from .views import account,auth,problem, script,submission,topic,collection urlpatterns = [ @@ -29,4 +29,6 @@ path('collections',collection.all_collections), path('collections/',collection.one_collection), path('collections//problems/',collection.collection_problems), + + path('script',script.run_script), ] \ No newline at end of file diff --git a/api/views/script.py b/api/views/script.py new file mode 100644 index 0000000..707b694 --- /dev/null +++ b/api/views/script.py @@ -0,0 +1,24 @@ +from api.utility import passwordEncryption +from rest_framework.response import Response +from rest_framework.decorators import api_view +from api.sandbox.grader import grading, checker +from ..constant import GET,POST,PUT,DELETE +from ..models import * +from rest_framework import status +from django.forms.models import model_to_dict +from ..serializers import * + + +@api_view([POST]) +def run_script(request): + submissions = Submission.objects.all() + total = len(submissions) + count = 0 + for submission in submissions: + submission.score = submission.result.count('P') + submission.max_score = len(submission.result) + submission.passed_ratio = submission.score/submission.max_score + submission.save() + count += 1 + print(f"({count}/{total})") + return Response({'message': 'Success!'},status=status.HTTP_201_CREATED) \ No newline at end of file diff --git a/api/views/submission.py b/api/views/submission.py index cbfbc35..c4097d0 100644 --- a/api/views/submission.py +++ b/api/views/submission.py @@ -47,7 +47,10 @@ def submit_problem(request,problem_id,account_id): account = Account.objects.get(account_id=account_id), submission_code = request.data['submission_code'], result = grading_result, - is_passed = is_passed + is_passed = is_passed, + score = grading_result.count('P'), + max_score = len(grading_result), + passed_ratio = grading_result.count('P')/len(grading_result) ) submission.save() @@ -73,10 +76,10 @@ def view_all_submission(request): elif passed == 1: submission = submission.filter(is_passed=True) - # if sort_score == -1: - # submission = submission.order_by('result') - # elif sort_score == 1: - # submission = submission.order_by('-result') + if sort_score == -1: + submission = submission.order_by('passed_ratio') + elif sort_score == 1: + submission = submission.order_by('-passed_ratio') if sort_date == -1: submission = submission.order_by('date') From 5ca4d835a3e11470fbd65218d68196b29b4609ef Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sun, 6 Aug 2023 20:10:27 +0700 Subject: [PATCH 06/16] Ignore media --- .gitignore | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ab0564a..cc669d7 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,10 @@ env/ [0-9]*.txt +media/testfile +media/topic +media/resource + *png *jpg *jpeg @@ -16,4 +20,5 @@ env/ tempCodeRunnerFile.py *.sqlite3 -*.exe \ No newline at end of file +*.exe + From 3487fdf14c31fc8c10dd1a5f04ea137100175bc0 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sat, 30 Sep 2023 19:19:48 +0700 Subject: [PATCH 07/16] support topic submission query --- api/views/submission.py | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/api/views/submission.py b/api/views/submission.py index c4097d0..d86e58f 100644 --- a/api/views/submission.py +++ b/api/views/submission.py @@ -60,8 +60,10 @@ def submit_problem(request,problem_id,account_id): def view_all_submission(request): submission = Submission.objects.all() + # Query params problem_id = int(request.query_params.get("problem_id", 0)) account_id = int(request.query_params.get("account_id", 0)) + topic_id = int(request.query_params.get("topic_id", 0)) passed = int(request.query_params.get("passed", -1)) sort_score = int(request.query_params.get("sort_score", 0)) sort_date = int(request.query_params.get("sort_date", 0)) @@ -70,6 +72,8 @@ def view_all_submission(request): submission = submission.filter(problem_id=problem_id) if account_id != 0: submission = submission.filter(account_id=account_id) + if topic_id != 0: + submission = submission.filter(problem__topic_id=topic_id) if passed == 0: submission = submission.filter(is_passed=False) @@ -86,29 +90,5 @@ def view_all_submission(request): elif sort_date == 1: submission = submission.order_by('-date') - # result = [model_to_dict(i) for i in submission] serialize = SubmissionPoplulateProblemSerializer(submission,many=True) - return Response({"result": serialize.data},status=status.HTTP_200_OK) - # result = serialize.data - - # print(result[0]) - - # for row in result: - # count = 0 - # for j in row.get('result'): - # if j == 'P': - # count += 1 - # row['score'] = count - - # if passed == 0: - # result = [i for i in result if not i['is_passed']] - # elif passed == 1: - # result = [i for i in result if i['is_passed']] - - # if sort_score == -1: - # result.sort(key=lambda value: value['score']) - # if sort_score == 1: - # result.sort(key=lambda value: value['score'],reverse=True) - - # result = [{'problem': model_to_dict(Problem.objects.get(problem_id=i['problem'])),**i} for i in result] - # return Response({'count':len(result),'result':result},status=status.HTTP_200_OK) \ No newline at end of file + return Response({"result": serialize.data},status=status.HTTP_200_OK) \ No newline at end of file From 8ead88ea69737d82c06ed37f601335f63a0d136a Mon Sep 17 00:00:00 2001 From: KanonKC Date: Fri, 10 Nov 2023 16:10:56 +0700 Subject: [PATCH 08/16] MOD-49 Multiple grading system --- api/sandbox/grader.py | 198 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 187 insertions(+), 11 deletions(-) diff --git a/api/sandbox/grader.py b/api/sandbox/grader.py index ff0c5e0..aef7a87 100644 --- a/api/sandbox/grader.py +++ b/api/sandbox/grader.py @@ -28,15 +28,11 @@ def checker(section:int,code:str,testcases:list,timeout=1.5)->dict: return {'result':result,'has_error':hasError,'has_timeout':hasTimeout} - def grading(section:int,code:str,input:list,output:list,timeout=1.5)->str: score = '' graded = checker(section,code,input,timeout) graded_result = graded['result'] - # print(graded) - # print(graded_result) - for i in range(len(output)): if graded_result[i]['runtime_status'] == 'OK': if forgiveableFormat(graded_result[i]['output']) == forgiveableFormat(output[i]): @@ -51,15 +47,190 @@ def grading(section:int,code:str,input:list,output:list,timeout=1.5)->str: return score +class RuntimeResult: + + RUNTIME_STATUS = [ + 'OK', + 'ERROR', + 'TIMEOUT' + ] + + def __init__(self,input:str,output:str,runtime_status:RUNTIME_STATUS) -> None: + self.input = input + self.output = output + self.runtime_status = runtime_status + +class ProgramGrader: + def __init__(self,code:str,testcases:list[str],section:int,timeout:float) -> None: + self.code = code + self.testcases = testcases + self.section = section + self.timeout = timeout + + def import_testcases(self) -> None: + for i in range(len(self.testcases)): + with open(f'./api/sandbox/section{self.section}/testcases/{i}.txt','w') as f: + f.write(self.testcases[i]) + + def import_source_code(self) -> None: + pass + + def setup(self) -> None: + self.import_testcases() + self.import_source_code() + + def compile(self) -> None: + pass + + def runtime(self) -> list[RuntimeResult]: + pass + + def generate_output(self) -> list[RuntimeResult]: + self.setup() + self.compile() + return self.runtime() + + def grading(self,expected_output:list[str]) -> list[dict]: + self.setup() + self.compile() + runtime_result = self.runtime() + + if len(runtime_result) != len(expected_output): + raise Exception("Length of expected output and runtime result is not equal") + + grading_result = [] + for i in range(len(runtime_result)): + + is_passed = False + output = None + + if runtime_result[i].runtime_status == "OK": + + output = runtime_result[i].output + if forgiveableFormat(runtime_result[i].output) == forgiveableFormat(expected_output[i]): + is_passed = True + + grading_result.append({ + "input": runtime_result[i].input, + "output": output, + "runtime_status": runtime_result[i].runtime_status, + "expected_output": expected_output[i], + "is_passed": is_passed + }) + + return grading_result + +class PythonGrader(ProgramGrader): + + def import_source_code(self) -> None: + with open(f'./api/sandbox/section{self.section}/runner.py','w') as f: + f.write(self.code) + + def runtime(self) -> list[RuntimeResult]: + + result = [] + + for i in range(len(self.testcases)): + try: + runner = subprocess.check_output([ + 'python',f'./api/sandbox/section{self.section}/runner.py'], + stdin=open(f'./api/sandbox/section{self.section}/testcases/{i}.txt', + 'r' + ),stderr=subprocess.DEVNULL,timeout=float(self.timeout)) + result.append(RuntimeResult(self.testcases[i],runner.decode(),"OK")) + except subprocess.CalledProcessError: + result.append(RuntimeResult(self.testcases[i],None,"ERROR")) + except subprocess.TimeoutExpired: + result.append(RuntimeResult(self.testcases[i],None,"TIMEOUT")) + + return result + +class CGrader(ProgramGrader): + + def import_source_code(self) -> None: + with open(f'./api/sandbox/section{self.section}/runner.c','w') as f: + f.write(self.code) + + def compile(self) -> None: + subprocess.check_output(['gcc',f'./api/sandbox/section{self.section}/runner.c','-o',f'./api/sandbox/section{self.section}/runner.exe'],stderr=subprocess.DEVNULL) + + def runtime(self) -> list[RuntimeResult]: + + result = [] + + for i in range(len(self.testcases)): + try: + runner = subprocess.check_output([ + f'./api/sandbox/section{self.section}/runner.exe'], + stdin=open(f'./api/sandbox/section{self.section}/testcases/{i}.txt', + 'r' + ),stderr=subprocess.DEVNULL,timeout=float(self.timeout)) + result.append(RuntimeResult(self.testcases[i],runner.decode(),"OK")) + except subprocess.CalledProcessError: + result.append(RuntimeResult(self.testcases[i],None,"ERROR")) + except subprocess.TimeoutExpired: + result.append(RuntimeResult(self.testcases[i],None,"TIMEOUT")) + + return result + +class CppGrader(ProgramGrader): + def import_source_code(self) -> None: + with open(f'./api/sandbox/section{self.section}/runner.cpp','w') as f: + f.write(self.code) + + def compile(self) -> None: + subprocess.check_output(['g++',f'./api/sandbox/section{self.section}/runner.cpp','-o',f'./api/sandbox/section{self.section}/runner.exe'],stderr=subprocess.DEVNULL) + + def runtime(self) -> list[RuntimeResult]: + + result = [] + + for i in range(len(self.testcases)): + try: + runner = subprocess.check_output([ + f'./api/sandbox/section{self.section}/runner.exe'], + stdin=open(f'./api/sandbox/section{self.section}/testcases/{i}.txt', + 'r' + ),stderr=subprocess.DEVNULL,timeout=float(self.timeout)) + result.append(RuntimeResult(self.testcases[i],runner.decode(),"OK")) + except subprocess.CalledProcessError: + result.append(RuntimeResult(self.testcases[i],None,"ERROR")) + except subprocess.TimeoutExpired: + result.append(RuntimeResult(self.testcases[i],None,"TIMEOUT")) + + return result + + adder = ''' x = int(input("x: ")) y = int(input("y: ")) -print(x-y) -if x == 52: - raise Exception -if x == 9: - while True: - pass +while True: + pass +''' + +adderC = r''' +#include + +int main() { + int a,b; + scanf("%d",&a); + scanf("%d",&b); + printf("%d\n",a-b); + return 0; +} +''' + +adderCpp = r''' +#include +using namespace std; + +int main() { + int a,b; + cin >> a; + cin >> b; + + cout << a - b << "\n"; +} ''' test = [ @@ -74,4 +245,9 @@ def grading(section:int,code:str,input:list,output:list,timeout=1.5)->str: ''' ] -# print(grading(adder,test,['x: y: 3\r\n','x: y: 70\r\n','x: y: 4\r\n'])) \ No newline at end of file +pyresult = ["x: y: -1\r\n","x: y: 34\r\n","x: y: 14\r\n"] +cresult = ["-1\r\n","34\r\n","14\r\n"] + +program1 = CppGrader(adderCpp,test,1,1.5) +for i in program1.grading(cresult): + print(i) \ No newline at end of file From b3ecc7187698c9205eb8973b4b21c3bde30627d5 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Fri, 10 Nov 2023 19:23:18 +0700 Subject: [PATCH 09/16] MOD-50, 51 SubmissionTestcase model, new grader model --- ..._score_submission_passed_ratio_and_more.py | 5 + api/migrations/0025_merge_20231110_1723.py | 14 +++ api/migrations/0026_submissiontestcase.py | 25 ++++ .../0027_alter_submissiontestcase_output.py | 18 +++ api/migrations/0028_alter_problem_language.py | 18 +++ api/models.py | 13 ++- api/sandbox/grader.py | 107 ++++++++++-------- api/serializers.py | 10 ++ api/views/account.py | 2 +- api/views/auth.py | 1 - api/views/problem.py | 41 +++---- api/views/script.py | 1 - api/views/submission.py | 48 +++++--- tests/problem/create_problem.py | 22 ++++ tests/problem/submit_problem.py | 13 +++ 15 files changed, 254 insertions(+), 84 deletions(-) create mode 100644 api/migrations/0025_merge_20231110_1723.py create mode 100644 api/migrations/0026_submissiontestcase.py create mode 100644 api/migrations/0027_alter_submissiontestcase_output.py create mode 100644 api/migrations/0028_alter_problem_language.py create mode 100644 tests/problem/create_problem.py create mode 100644 tests/problem/submit_problem.py diff --git a/api/migrations/0024_submission_max_score_submission_passed_ratio_and_more.py b/api/migrations/0024_submission_max_score_submission_passed_ratio_and_more.py index 417d4bb..e97d6ef 100644 --- a/api/migrations/0024_submission_max_score_submission_passed_ratio_and_more.py +++ b/api/migrations/0024_submission_max_score_submission_passed_ratio_and_more.py @@ -25,4 +25,9 @@ class Migration(migrations.Migration): name='score', field=models.IntegerField(default=0), ), + migrations.RemoveField( + model_name='submission', + name='result' + ), + ] diff --git a/api/migrations/0025_merge_20231110_1723.py b/api/migrations/0025_merge_20231110_1723.py new file mode 100644 index 0000000..1823df8 --- /dev/null +++ b/api/migrations/0025_merge_20231110_1723.py @@ -0,0 +1,14 @@ +# Generated by Django 4.1.2 on 2023-11-10 10:23 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0022_problem_submission_regex'), + ('api', '0024_submission_max_score_submission_passed_ratio_and_more'), + ] + + operations = [ + ] diff --git a/api/migrations/0026_submissiontestcase.py b/api/migrations/0026_submissiontestcase.py new file mode 100644 index 0000000..beaf94e --- /dev/null +++ b/api/migrations/0026_submissiontestcase.py @@ -0,0 +1,25 @@ +# Generated by Django 4.1.2 on 2023-11-10 10:51 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0025_merge_20231110_1723'), + ] + + operations = [ + migrations.CreateModel( + name='SubmissionTestcase', + fields=[ + ('submission_testcase_id', models.AutoField(primary_key=True, serialize=False)), + ('output', models.CharField(max_length=100000)), + ('is_passed', models.BooleanField(blank=True, default=False)), + ('runtime_status', models.CharField(max_length=10)), + ('submission', models.ForeignKey(db_column='submission_id', on_delete=django.db.models.deletion.CASCADE, to='api.submission')), + ('testcase', models.ForeignKey(db_column='testcase_id', on_delete=django.db.models.deletion.CASCADE, to='api.testcase')), + ], + ), + ] diff --git a/api/migrations/0027_alter_submissiontestcase_output.py b/api/migrations/0027_alter_submissiontestcase_output.py new file mode 100644 index 0000000..20499ae --- /dev/null +++ b/api/migrations/0027_alter_submissiontestcase_output.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.2 on 2023-11-10 12:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0026_submissiontestcase'), + ] + + operations = [ + migrations.AlterField( + model_name='submissiontestcase', + name='output', + field=models.CharField(blank=True, max_length=100000, null=True), + ), + ] diff --git a/api/migrations/0028_alter_problem_language.py b/api/migrations/0028_alter_problem_language.py new file mode 100644 index 0000000..40fac62 --- /dev/null +++ b/api/migrations/0028_alter_problem_language.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.2 on 2023-11-10 12:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0027_alter_submissiontestcase_output'), + ] + + operations = [ + migrations.AlterField( + model_name='problem', + name='language', + field=models.CharField(max_length=15), + ), + ] diff --git a/api/models.py b/api/models.py index 8d8d220..7baa5b4 100644 --- a/api/models.py +++ b/api/models.py @@ -28,7 +28,7 @@ class Account(models.Model): class Problem(models.Model): problem_id = models.AutoField(primary_key=True) account = models.ForeignKey(Account,on_delete=models.CASCADE,db_column="account_id") - language = models.CharField(max_length=10,choices=ProgrammingLanguage.choices,default=ProgrammingLanguage.PYTHON) + language = models.CharField(max_length=15) # ,choices=ProgrammingLanguage.choices,default=ProgrammingLanguage.PYTHON) title = models.CharField(max_length=50) description = models.CharField(max_length=10000) solution = models.CharField(max_length=20000) @@ -48,7 +48,6 @@ class Submission(models.Model): problem = models.ForeignKey(Problem,on_delete=models.CASCADE,db_column="problem_id") account = models.ForeignKey(Account,on_delete=models.CASCADE,db_column="account_id") submission_code = models.CharField(max_length=20000) - result = models.CharField(max_length=100) is_passed = models.BooleanField() date = models.DateTimeField(default=timezone.now) score = models.IntegerField(default=0) @@ -89,4 +88,12 @@ class TopicProblem(models.Model): class TopicAccountAccess(models.Model): topic = models.ForeignKey(Topic,on_delete=models.CASCADE,db_column="topic_id") - account = models.ForeignKey(Account,on_delete=models.CASCADE,db_column="account_id") \ No newline at end of file + account = models.ForeignKey(Account,on_delete=models.CASCADE,db_column="account_id") + +class SubmissionTestcase(models.Model): + submission_testcase_id = models.AutoField(primary_key=True) + submission = models.ForeignKey(Submission,on_delete=models.CASCADE,db_column="submission_id") + testcase = models.ForeignKey(Testcase,on_delete=models.CASCADE,db_column="testcase_id") + output = models.CharField(max_length=100000,blank=True,null=True) + is_passed = models.BooleanField(default=False,blank=True) + runtime_status = models.CharField(max_length=10) \ No newline at end of file diff --git a/api/sandbox/grader.py b/api/sandbox/grader.py index aef7a87..53eb1d8 100644 --- a/api/sandbox/grader.py +++ b/api/sandbox/grader.py @@ -4,47 +4,47 @@ def forgiveableFormat(string:str)->str: return string.replace('\r','') # return string -def checker(section:int,code:str,testcases:list,timeout=1.5)->dict: - result = [] - hasError = False - hasTimeout = False - for i in range(len(testcases)): - with open(f'./api/sandbox/section{section}/testcases/{i}.txt','w') as f: - f.write(testcases[i]) +# def checker(section:int,code:str,testcases:list,timeout=1.5)->dict: +# result = [] +# hasError = False +# hasTimeout = False +# for i in range(len(testcases)): +# with open(f'./api/sandbox/section{section}/testcases/{i}.txt','w') as f: +# f.write(testcases[i]) - with open(f'./api/sandbox/section{section}/runner.py','w') as f: - f.write(code) - - for i in range(len(testcases)): - try: - runner = subprocess.check_output(['python',f'./api/sandbox/section{section}/runner.py'],stdin=open(f'./api/sandbox/section{section}/testcases/{i}.txt','r'),stderr=subprocess.DEVNULL,timeout=float(timeout)) - result.append({'input':testcases[i],'output':runner.decode(),'runtime_status':'OK'}) - except subprocess.CalledProcessError: - hasError = True - result.append({'input':testcases[i],'output':None,'runtime_status':'ERROR'}) - except subprocess.TimeoutExpired: - hasTimeout = True - result.append({'input':testcases[i],'output':None,'runtime_status':'TIMEOUT'}) - - return {'result':result,'has_error':hasError,'has_timeout':hasTimeout} +# with open(f'./api/sandbox/section{section}/runner.py','w') as f: +# f.write(code) + +# for i in range(len(testcases)): +# try: +# runner = subprocess.check_output(['python',f'./api/sandbox/section{section}/runner.py'],stdin=open(f'./api/sandbox/section{section}/testcases/{i}.txt','r'),stderr=subprocess.DEVNULL,timeout=float(timeout)) +# result.append({'input':testcases[i],'output':runner.decode(),'runtime_status':'OK'}) +# except subprocess.CalledProcessError: +# hasError = True +# result.append({'input':testcases[i],'output':None,'runtime_status':'ERROR'}) +# except subprocess.TimeoutExpired: +# hasTimeout = True +# result.append({'input':testcases[i],'output':None,'runtime_status':'TIMEOUT'}) + +# return {'result':result,'has_error':hasError,'has_timeout':hasTimeout} -def grading(section:int,code:str,input:list,output:list,timeout=1.5)->str: - score = '' - graded = checker(section,code,input,timeout) - graded_result = graded['result'] - - for i in range(len(output)): - if graded_result[i]['runtime_status'] == 'OK': - if forgiveableFormat(graded_result[i]['output']) == forgiveableFormat(output[i]): - score += 'P' - else: - score += '-' - elif graded_result[i]['runtime_status'] == 'TIMEOUT': - score += 'T' - else: - score += 'E' +# def grading(section:int,code:str,input:list,output:list,timeout=1.5)->str: +# score = '' +# graded = checker(section,code,input,timeout) +# graded_result = graded['result'] + +# for i in range(len(output)): +# if graded_result[i]['runtime_status'] == 'OK': +# if forgiveableFormat(graded_result[i]['output']) == forgiveableFormat(output[i]): +# score += 'P' +# else: +# score += '-' +# elif graded_result[i]['runtime_status'] == 'TIMEOUT': +# score += 'T' +# else: +# score += 'E' - return score +# return score class RuntimeResult: @@ -60,6 +60,20 @@ def __init__(self,input:str,output:str,runtime_status:RUNTIME_STATUS) -> None: self.output = output self.runtime_status = runtime_status + def __iter__(self): + yield 'input',self.input + yield 'output',self.output + yield 'runtime_status',self.runtime_status + +class GradingResult: + def __init__(self,input:str,output:str,runtime_status:RuntimeResult.RUNTIME_STATUS,expected_output:str,is_passed:bool) -> None: + self.input = input + self.output = output + self.runtime_status = runtime_status + self.expected_output = expected_output + self.is_passed = is_passed + + class ProgramGrader: def __init__(self,code:str,testcases:list[str],section:int,timeout:float) -> None: self.code = code @@ -90,7 +104,7 @@ def generate_output(self) -> list[RuntimeResult]: self.compile() return self.runtime() - def grading(self,expected_output:list[str]) -> list[dict]: + def grading(self,expected_output:list[str]) -> list[GradingResult]: self.setup() self.compile() runtime_result = self.runtime() @@ -110,16 +124,17 @@ def grading(self,expected_output:list[str]) -> list[dict]: if forgiveableFormat(runtime_result[i].output) == forgiveableFormat(expected_output[i]): is_passed = True - grading_result.append({ - "input": runtime_result[i].input, - "output": output, - "runtime_status": runtime_result[i].runtime_status, - "expected_output": expected_output[i], - "is_passed": is_passed - }) + grading_result.append(GradingResult( + runtime_result[i].input, + output, + runtime_result[i].runtime_status, + expected_output[i], + is_passed + )) return grading_result + class PythonGrader(ProgramGrader): def import_source_code(self) -> None: diff --git a/api/serializers.py b/api/serializers.py index 305af23..bd73b80 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -79,4 +79,14 @@ class SubmissionPoplulateProblemSerializer(serializers.ModelSerializer): problem = ProblemSerializer() class Meta: model = Submission + fields = "__all__" + +class SubmissionTestcaseSerializer(serializers.ModelSerializer): + class Meta: + model = SubmissionTestcase + fields = "__all__" + +class TestcaseSerializer(serializers.ModelSerializer): + class Meta: + model = Testcase fields = "__all__" \ No newline at end of file diff --git a/api/views/account.py b/api/views/account.py index aa9e29e..39f7329 100644 --- a/api/views/account.py +++ b/api/views/account.py @@ -1,7 +1,7 @@ from api.utility import passwordEncryption from rest_framework.response import Response from rest_framework.decorators import api_view -from api.sandbox.grader import grading, checker +from api.sandbox.grader import PythonGrader from ..constant import GET,POST,PUT,DELETE from ..models import * from rest_framework import status diff --git a/api/views/auth.py b/api/views/auth.py index 11df9d1..150631d 100644 --- a/api/views/auth.py +++ b/api/views/auth.py @@ -2,7 +2,6 @@ from api.utility import passwordEncryption from rest_framework.response import Response from rest_framework.decorators import api_view -from api.sandbox.grader import grading, checker from ..constant import GET,POST,PUT,DELETE from ..models import Account, Problem,Testcase from rest_framework import status diff --git a/api/views/problem.py b/api/views/problem.py index 0cc9b65..67fd3da 100644 --- a/api/views/problem.py +++ b/api/views/problem.py @@ -1,24 +1,22 @@ # from ..utility import JSONParser, JSONParserOne, passwordEncryption from rest_framework.response import Response from rest_framework.decorators import api_view -from api.sandbox.grader import grading, checker +from api.sandbox.grader import PythonGrader from ..constant import GET,POST,PUT,DELETE from ..models import Account, Problem,Testcase from rest_framework import status from django.forms.models import model_to_dict - +from ..serializers import * # Create your views here. @api_view([POST]) def create_problem(request,account_id): - print(request.data) - request._mutable = True account = Account.objects.get(account_id=account_id) - request.data['account_id'] = account - checked = checker(1,request.data['solution'],request.data['testcases'],request.data.get('time_limit',1.5)) - - if checked['has_error'] or checked['has_timeout']: - return Response({'detail': 'Error during creating. Your code may has an error/timeout!','result': checked},status=status.HTTP_406_NOT_ACCEPTABLE) + + program_output = PythonGrader(request.data['solution'],request.data['testcases'],1,1.5).generate_output() + for result in program_output: + if result.runtime_status != "OK": + return Response({'detail': 'Error during creating. Your code may has an error/timeout!','output': [dict(i) for i in program_output]},status=status.HTTP_406_NOT_ACCEPTABLE) problem = Problem( language = request.data['language'], @@ -30,16 +28,21 @@ def create_problem(request,account_id): ) problem.save() - testcase_result = [] - for unit in checked['result']: - testcase = Testcase( - problem = problem, - input = unit['input'], - output = unit['output'] - ) - testcase.save() - testcase_result.append(model_to_dict(testcase)) - return Response({'detail': 'Problem has been created!','problem': model_to_dict(problem),'testcase': testcase_result},status=status.HTTP_201_CREATED) + testcases_result = [] + for unit in program_output: + testcases_result.append( + Testcase( + problem = problem, + input = unit.input, + output = unit.output + )) + + Testcase.objects.bulk_create(testcases_result) + + problem_serialize = ProblemSerializer(problem) + testcases_serialize = TestcaseSerializer(testcases_result,many=True) + + return Response({**problem_serialize.data,'testcases': testcases_serialize.data},status=status.HTTP_201_CREATED) @api_view([GET,DELETE]) def all_problem(request): diff --git a/api/views/script.py b/api/views/script.py index 707b694..50c338b 100644 --- a/api/views/script.py +++ b/api/views/script.py @@ -1,7 +1,6 @@ from api.utility import passwordEncryption from rest_framework.response import Response from rest_framework.decorators import api_view -from api.sandbox.grader import grading, checker from ..constant import GET,POST,PUT,DELETE from ..models import * from rest_framework import status diff --git a/api/views/submission.py b/api/views/submission.py index f12dd63..509bc3e 100644 --- a/api/views/submission.py +++ b/api/views/submission.py @@ -2,12 +2,12 @@ from rest_framework.response import Response from rest_framework.decorators import api_view -from api.serializers import SubmissionPoplulateProblemSerializer +from api.serializers import * from ..constant import GET,POST,PUT,DELETE -from ..models import Account, Problem, Submission,Testcase +from ..models import * from rest_framework import status from django.forms.models import model_to_dict -from ..sandbox import grader +from ..sandbox.grader import PythonGrader from time import sleep from ..utility import regexMatching @@ -39,27 +39,49 @@ def submit_problem(request,problem_id,account_id): sleep(5) QUEUE[empty_queue] = 1 - grading_result = grader.grading(empty_queue+1,submission_code,solution_input,solution_output) + # grading_result = grader.grading(empty_queue+1,submission_code,solution_input,solution_output) + grading_result = PythonGrader(submission_code,solution_input,empty_queue+1,1.5).grading(solution_output) QUEUE[empty_queue] = 0 - if '-' in grading_result or 'E' in grading_result or 'T' in grading_result: - is_passed = False - else: - is_passed = True + is_passed = True + for result in grading_result: + if not result.is_passed: + is_passed = False + break + + total_score = sum([i.is_passed for i in grading_result if i.is_passed]) + max_score = len(grading_result) submission = Submission( problem = problem, account = Account.objects.get(account_id=account_id), submission_code = request.data['submission_code'], - result = grading_result, is_passed = is_passed, - score = grading_result.count('P'), - max_score = len(grading_result), - passed_ratio = grading_result.count('P')/len(grading_result) + score = total_score, + max_score = max_score, + passed_ratio = total_score/max_score ) submission.save() - return Response(model_to_dict(submission),status=status.HTTP_201_CREATED) + submission_testcases = [] + for i in range(len(grading_result)): + submission_testcases.append(SubmissionTestcase( + submission = submission, + testcase = testcases[i], + output = grading_result[i].output, + is_passed = grading_result[i].is_passed, + runtime_status = grading_result[i].runtime_status + )) + + SubmissionTestcase.objects.bulk_create(submission_testcases) + + submission_serialize = SubmissionPoplulateProblemSerializer(submission) + testcases_serialize = SubmissionTestcaseSerializer(submission_testcases,many=True) + + return Response({ + **submission_serialize.data, + "runtime_output": testcases_serialize.data + },status=status.HTTP_201_CREATED) @api_view([GET]) def view_all_submission(request): diff --git a/tests/problem/create_problem.py b/tests/problem/create_problem.py new file mode 100644 index 0000000..9846362 --- /dev/null +++ b/tests/problem/create_problem.py @@ -0,0 +1,22 @@ +import requests + +code = ''' +n = int(input("N: ")) +print(n+1) +''' + +testcases = [ + "1","2","3" +] + +body = { + "title": "Plus One", + "language": "Python", + "description": "แสดงคำว่า 'Hello World' ออกมา", + "solution": code, + "testcases": testcases, + "time_limit": 1.5 +} + +result = requests.post("http://localhost:8000/api/accounts/1/problems",data=body) +print(result.text) \ No newline at end of file diff --git a/tests/problem/submit_problem.py b/tests/problem/submit_problem.py new file mode 100644 index 0000000..49f4592 --- /dev/null +++ b/tests/problem/submit_problem.py @@ -0,0 +1,13 @@ +import requests + +code = ''' +n = int(input("N: ")) +print(n+1) +''' + +body = { + "submission_code": code +} + +result = requests.post("http://localhost:8000/api/problems/4/1",data=body) +print(result.text) \ No newline at end of file From e5be1224a00996f62e5995ab15235d2cdf879ce7 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Fri, 10 Nov 2023 19:31:48 +0700 Subject: [PATCH 10/16] GradingResult dict transformation --- api/sandbox/grader.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/api/sandbox/grader.py b/api/sandbox/grader.py index 53eb1d8..ddcb561 100644 --- a/api/sandbox/grader.py +++ b/api/sandbox/grader.py @@ -73,6 +73,13 @@ def __init__(self,input:str,output:str,runtime_status:RuntimeResult.RUNTIME_STAT self.expected_output = expected_output self.is_passed = is_passed + def __iter__(self): + yield 'input',self.input + yield 'output',self.output + yield 'runtime_status',self.runtime_status + yield 'expected_output',self.expected_output + yield 'is_passed',self.is_passed + class ProgramGrader: def __init__(self,code:str,testcases:list[str],section:int,timeout:float) -> None: From b7c31e47dd5f4b171908d05eea5c0e7b6972b2ad Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sat, 11 Nov 2023 18:47:59 +0700 Subject: [PATCH 11/16] Update dependencies --- requirements.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 9d956ad..94a9df4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ django-cors-headers==3.13.0 django-environ==0.9.0 django-filter==22.1 djangorestframework==3.13.1 -djangorestframework-jwt==1.11.0 +djangorestframework-jwt==1.11.0 djangorestframework-simplejwt==5.2.2 environ==1.0 idna==3.4 @@ -16,9 +16,10 @@ Pillow==9.4.0 psycopg2==2.9.5 pycparser==2.21 PyJWT==1.7.1 +python-decouple==3.8 pytz==2022.6 +regex==2023.8.8 requests==2.28.1 sqlparse==0.4.3 tzdata==2022.6 -urllib3==1.26.12 -python-decouple==3.8 \ No newline at end of file +urllib3==1.26.12 \ No newline at end of file From dfd6dcd699b05cf5e02b4fbffbc6aadbbb476a38 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sat, 11 Nov 2023 19:27:08 +0700 Subject: [PATCH 12/16] RuntimeResult and GradingResult can print --- api/sandbox/grader.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/api/sandbox/grader.py b/api/sandbox/grader.py index ddcb561..c46c5f3 100644 --- a/api/sandbox/grader.py +++ b/api/sandbox/grader.py @@ -65,6 +65,9 @@ def __iter__(self): yield 'output',self.output yield 'runtime_status',self.runtime_status + def __str__(self) -> str: + return str(dict(self)) + class GradingResult: def __init__(self,input:str,output:str,runtime_status:RuntimeResult.RUNTIME_STATUS,expected_output:str,is_passed:bool) -> None: self.input = input @@ -80,6 +83,9 @@ def __iter__(self): yield 'expected_output',self.expected_output yield 'is_passed',self.is_passed + def __str__(self) -> str: + return str(dict(self)) + class ProgramGrader: def __init__(self,code:str,testcases:list[str],section:int,timeout:float) -> None: @@ -223,11 +229,16 @@ def runtime(self) -> list[RuntimeResult]: return result +Grader:list[ProgramGrader] = { + "python": PythonGrader, + "c": CGrader, + "cpp": CppGrader +} + adder = ''' x = int(input("x: ")) y = int(input("y: ")) -while True: - pass +print(x-y) ''' adderC = r''' @@ -270,6 +281,6 @@ def runtime(self) -> list[RuntimeResult]: pyresult = ["x: y: -1\r\n","x: y: 34\r\n","x: y: 14\r\n"] cresult = ["-1\r\n","34\r\n","14\r\n"] -program1 = CppGrader(adderCpp,test,1,1.5) -for i in program1.grading(cresult): - print(i) \ No newline at end of file +grader = Grader['python'] +result = grader(adder,test,1,1.5).grading(pyresult) +print(result) \ No newline at end of file From 3d3e797bd95d9933755493e9c135d9b248db347e Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sat, 11 Nov 2023 19:33:20 +0700 Subject: [PATCH 13/16] Clear all print --- api/utility.py | 1 - api/views/collection.py | 2 -- api/views/topic.py | 2 -- 3 files changed, 5 deletions(-) diff --git a/api/utility.py b/api/utility.py index 4c99eb2..0c11760 100644 --- a/api/utility.py +++ b/api/utility.py @@ -14,5 +14,4 @@ def uploadTopic(instance,filename): def regexMatching(regex:str,code:str)->bool: code = ";".join([i.strip() for i in code.split("\n") if i != ""]) - print(bool(re.search(regex, code))) return re.search(regex, code) \ No newline at end of file diff --git a/api/views/collection.py b/api/views/collection.py index 9e7ec8e..0df57b5 100644 --- a/api/views/collection.py +++ b/api/views/collection.py @@ -42,7 +42,6 @@ def all_collections(request): serialize = CollectionSerializer(collection) collection_data = serialize.data collection_data['problems'] = populated_cp - # print() populated_collections.append(collection_data) @@ -103,7 +102,6 @@ def collection_problems(request,collection_id:int,method:str): collection=collection, order=index ) - print(problem,index) collection_problem.save() index += 1 cp_serialize = CollectionProblemSerializer(collection_problem) diff --git a/api/views/topic.py b/api/views/topic.py index 8e59039..19232cb 100644 --- a/api/views/topic.py +++ b/api/views/topic.py @@ -126,10 +126,8 @@ def account_access(request,topic_id:int): topic = topic, account = account ) - # print(topic_account) topic_account.save() accessedAccounts.append(topic_account) - # ta_serialize = TopicAccountAccessSerialize(topic_account) serialize = TopicAccountAccessSerialize(accessedAccounts,many=True) From f0ba451c8480dcbe010036692892a374ea879516 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sun, 12 Nov 2023 00:52:37 +0700 Subject: [PATCH 14/16] MOD-13: Secure sensitive data for account --- api/serializers.py | 2 +- api/views/account.py | 2 +- api/views/topic.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index bd73b80..4eb25fc 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -6,7 +6,7 @@ class Meta: model = Account fields = "__all__" -class AccountDetailSerializer(serializers.ModelSerializer): +class AccountSecureSerializer(serializers.ModelSerializer): class Meta: model = Account fields = ['account_id','username'] diff --git a/api/views/account.py b/api/views/account.py index 39f7329..63160a6 100644 --- a/api/views/account.py +++ b/api/views/account.py @@ -12,7 +12,7 @@ def account_collection(request): if request.method == GET: accounts = Account.objects.all() - serialize = AccountDetailSerializer(accounts,many=True) + serialize = AccountSecureSerializer(accounts,many=True) return Response({ "accounts": serialize.data },status=status.HTTP_200_OK) diff --git a/api/views/topic.py b/api/views/topic.py index 19232cb..24c7e03 100644 --- a/api/views/topic.py +++ b/api/views/topic.py @@ -60,7 +60,7 @@ def one_topic(request,topic_id:int): top_col_serialize = TopicCollectionSerializer(top_col) populate_collections.append({**top_col_serialize.data,**collection_data}) - accessedAccountsSerialize = AccountSerializer(accessedAccounts,many=True) + accessedAccountsSerialize = AccountSecureSerializer(accessedAccounts,many=True) return Response({ "topic": topic_ser.data, From 7ff64abc6cd26d3605a2019a6c35abdea92a3194 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sun, 12 Nov 2023 01:20:03 +0700 Subject: [PATCH 15/16] Not finished. Topic & Collection remain --- api/serializers.py | 6 ++++++ api/views/account.py | 3 ++- api/views/problem.py | 33 ++++++++++++++++++++------------- api/views/submission.py | 2 +- 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index 4eb25fc..4f847b0 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -43,6 +43,12 @@ class Meta: model = Problem fields = "__all__" +class ProblemPopulateAccountSerializer(serializers.ModelSerializer): + account = AccountSecureSerializer() + class Meta: + model = Problem + fields = "__all__" + class TopicCollectionSerializer(serializers.ModelSerializer): class Meta: model = TopicCollection diff --git a/api/views/account.py b/api/views/account.py index 63160a6..85b688c 100644 --- a/api/views/account.py +++ b/api/views/account.py @@ -23,7 +23,8 @@ def account_collection(request): account = Account.objects.create(**request.data) except Exception as e: return Response({'message':str(e)},status=status.HTTP_400_BAD_REQUEST) - return Response({'message':'Registration Completed','account':model_to_dict(account)},status=status.HTTP_201_CREATED) + serialize = AccountSerializer(account) + return Response(serialize.data,status=status.HTTP_201_CREATED) @api_view([GET]) def get_account(request,account_id): diff --git a/api/views/problem.py b/api/views/problem.py index 67fd3da..382ca88 100644 --- a/api/views/problem.py +++ b/api/views/problem.py @@ -63,12 +63,14 @@ def all_problem(request): problem = problem.order_by('-problem_id') + serialize = ProblemPopulateAccountSerializer(problem,many=True) + result = [model_to_dict(i) for i in problem] for i in result: i['creator'] = model_to_dict(Account.objects.get(account_id=i['account'])) - return Response({'result':result},status=status.HTTP_200_OK) + return Response({'problems':serialize.data},status=status.HTTP_200_OK) elif request.method == DELETE: target = request.data.get("problem",[]) problems = Problem.objects.filter(problem_id__in=target) @@ -85,9 +87,9 @@ def one_problem(request,problem_id: int): testcases = Testcase.objects.filter(problem_id=problem_id) if request.method == GET: - result = model_to_dict(problem) - account = Account.objects.get(account_id=result['account']) - return Response({**result,'testcases':[model_to_dict(i) for i in testcases],'creator': model_to_dict(account)},status=status.HTTP_200_OK) + problem_serialize = ProblemPopulateAccountSerializer(problem) + testcases_serialize = TestcaseSerializer(testcases,many=True) + return Response({**problem_serialize.data,'testcases': testcases_serialize.data},status=status.HTTP_200_OK) elif request.method == PUT: problem.title = request.data.get("title",problem.title) @@ -98,26 +100,31 @@ def one_problem(request,problem_id: int): problem.is_private = request.data.get("is_private",problem.is_private) if 'testcases' in request.data: - checked = checker(1,problem.solution,request.data['testcases'],request.data.get('time_limit',1.5)) - if checked['has_error'] or checked['has_timeout']: + program_output = PythonGrader(problem.solution,request.data['testcases'],1,1.5).generate_output() + + if sum([1 for i in program_output if i.runtime_status != "OK"]) > 0: return Response({'detail': 'Error during editing. Your code may has an error/timeout!'},status=status.HTTP_406_NOT_ACCEPTABLE) testcases.delete() testcase_result = [] - for unit in checked['result']: + for unit in program_output: testcase = Testcase( - problem_id = problem, - input = unit['input'], - output = unit['output'] + problem = problem, + input = unit.input, + output = unit.output ) testcase.save() - testcase_result.append(model_to_dict(testcase)) + testcase_result.append(testcase) problem.save() - return Response({'detail': 'Problem has been edited!','problem': model_to_dict(problem),'testcase': testcase_result},status=status.HTTP_201_CREATED) + problem_serialize = ProblemSerializer(problem) + testcases_serialize = TestcaseSerializer(testcase_result,many=True) + + return Response({**problem_serialize.data,'testcases': testcases_serialize.data},status=status.HTTP_201_CREATED) problem.save() - return Response({'detail': 'Problem has been edited!','problem': model_to_dict(problem)},status=status.HTTP_201_CREATED) + problem_serialize = ProblemSerializer(problem) + return Response(problem_serialize.data,status=status.HTTP_201_CREATED) elif request.method == DELETE: problem.delete() diff --git a/api/views/submission.py b/api/views/submission.py index 509bc3e..576b745 100644 --- a/api/views/submission.py +++ b/api/views/submission.py @@ -118,4 +118,4 @@ def view_all_submission(request): submission = submission.order_by('-date') serialize = SubmissionPoplulateProblemSerializer(submission,many=True) - return Response({"result": serialize.data},status=status.HTTP_200_OK) \ No newline at end of file + return Response({"submissions": serialize.data},status=status.HTTP_200_OK) \ No newline at end of file From ff8952d05ad6d8b4a05e78ab69bc1baedc696b1d Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sun, 12 Nov 2023 20:18:31 +0700 Subject: [PATCH 16/16] MOD-48,53: Fixed response format --- api/views/collection.py | 4 ++-- api/views/topic.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/views/collection.py b/api/views/collection.py index 0df57b5..bac5146 100644 --- a/api/views/collection.py +++ b/api/views/collection.py @@ -65,7 +65,7 @@ def one_collection(request,collection_id:int): populated_problems.append({**col_prob_serialize.data,**prob_serialize.data}) return Response({ - 'collection': collection_ser.data, + **collection_ser.data, 'problems': sorted(populated_problems,key=lambda problem: problem['order']) } ,status=status.HTTP_200_OK) @@ -110,7 +110,7 @@ def collection_problems(request,collection_id:int,method:str): collection_serialize = CollectionSerializer(collection) return Response({ - 'collection': collection_serialize.data, + **collection_serialize.data, 'problems': populated_problems },status=status.HTTP_201_CREATED) diff --git a/api/views/topic.py b/api/views/topic.py index 24c7e03..1d25201 100644 --- a/api/views/topic.py +++ b/api/views/topic.py @@ -63,7 +63,7 @@ def one_topic(request,topic_id:int): accessedAccountsSerialize = AccountSecureSerializer(accessedAccounts,many=True) return Response({ - "topic": topic_ser.data, + **topic_ser.data, "collections": sorted(populate_collections,key=lambda collection: collection['order']), "accessed_accounts": accessedAccountsSerialize.data },status=status.HTTP_200_OK) @@ -103,7 +103,7 @@ def topic_collection(request,topic_id:int,method:str): populated_collections.append(tc_serialize.data) return Response({ - "topic": TopicSerializer(topic).data, + **TopicSerializer(topic).data, "collections": populated_collections },status=status.HTTP_201_CREATED)