diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..c31941d --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +FRONTEND_URL="" +TOKEN_LIFETIME_HOURS="" \ No newline at end of file diff --git a/.gitignore b/.gitignore index befe1a2..235601b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,10 @@ runner.py env/ .env +[0-9]*.txt + +# *png +# *jpg +# *jpeg +# *webp tempCodeRunnerFile.py \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 9e26505..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "nuxt.isNuxtApp": false, - "python.analysis.extraPaths": [ - "./env/Lib/site-packages" - ] -} \ No newline at end of file diff --git a/Backend/settings.py b/Backend/settings.py index 2871d24..e9cf8a6 100644 --- a/Backend/settings.py +++ b/Backend/settings.py @@ -9,28 +9,28 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/4.1/ref/settings/ """ -from datetime import timedelta import os +from datetime import timedelta from pathlib import Path -import environ +from decouple import config -env = environ.Env( - # set casting, default value - DEBUG=(bool, False), - ALLOWED_HOSTS=(list, []), -) +# env = environ.Env( +# # set casting, default value +# DEBUG=(bool, False), +# ALLOWED_HOSTS=(list, []), +# ) # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent -environ.Env.read_env(os.path.join(BASE_DIR, ".env")) +# environ.Env.read_env(os.path.join(BASE_DIR, ".env")) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = env("SECRET_KEY") - +SECRET_KEY = 'django-insecure-7fk!^ke_z%97-at2-fff=%v+mk!7)&0(!bvku5)v#&*olpu#gp' +FRONEND_URL = config('FRONTEND_URL') # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -41,6 +41,10 @@ CORS_ALLOWED_ORIGINS = [ "http://localhost:3000", "http://localhost:3001", + "http://192.168.216.250:3000", + "http://192.168.0.2:3000", + "http://192.168.0.5:3000", + FRONEND_URL ] CORS_ALLOW_METHODS = [ @@ -165,4 +169,4 @@ 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), 'REFRESH_TOKEN_LIFETIME': timedelta(days=1), 'AUTH_HEADER_TYPES': ('Bearer',), -} \ No newline at end of file +} diff --git a/Backend/urls.py b/Backend/urls.py index 09c0b46..3947330 100644 --- a/Backend/urls.py +++ b/Backend/urls.py @@ -19,6 +19,9 @@ TokenObtainPairView, TokenRefreshView, ) +from django.conf import settings +from django.conf.urls.static import static + urlpatterns = [ path('admin/', admin.site.urls), @@ -26,3 +29,5 @@ path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), ] + +urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/ModelGrader copy.sqlite3 b/ModelGrader copy.sqlite3 new file mode 100644 index 0000000..f6b012f Binary files /dev/null and b/ModelGrader copy.sqlite3 differ diff --git a/ModelGrader.sqlite3 b/ModelGrader.sqlite3 index 939efce..04be82e 100644 Binary files a/ModelGrader.sqlite3 and b/ModelGrader.sqlite3 differ diff --git a/api/migrations/0016_alter_topic_description_alter_topic_image_url.py b/api/migrations/0016_alter_topic_description_alter_topic_image_url.py new file mode 100644 index 0000000..8dd1dd9 --- /dev/null +++ b/api/migrations/0016_alter_topic_description_alter_topic_image_url.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1.2 on 2023-02-23 18:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0015_remove_topic_image_topic_image_url'), + ] + + operations = [ + migrations.AlterField( + model_name='topic', + name='description', + field=models.CharField(blank=True, max_length=1000, null=True), + ), + migrations.AlterField( + model_name='topic', + name='image_url', + field=models.ImageField(blank=True, null=True, upload_to='topic/'), + ), + ] diff --git a/api/migrations/0017_alter_topic_is_active_alter_topic_is_private.py b/api/migrations/0017_alter_topic_is_active_alter_topic_is_private.py new file mode 100644 index 0000000..d9d3886 --- /dev/null +++ b/api/migrations/0017_alter_topic_is_active_alter_topic_is_private.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1.2 on 2023-02-24 12:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0016_alter_topic_description_alter_topic_image_url'), + ] + + operations = [ + migrations.AlterField( + model_name='topic', + name='is_active', + field=models.BooleanField(blank=True, default=False), + ), + migrations.AlterField( + model_name='topic', + name='is_private', + field=models.BooleanField(blank=True, default=True), + ), + ] diff --git a/api/migrations/0018_alter_topic_description_alter_topic_image_url.py b/api/migrations/0018_alter_topic_description_alter_topic_image_url.py new file mode 100644 index 0000000..45b2772 --- /dev/null +++ b/api/migrations/0018_alter_topic_description_alter_topic_image_url.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1.2 on 2023-02-24 12:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0017_alter_topic_is_active_alter_topic_is_private'), + ] + + operations = [ + migrations.AlterField( + model_name='topic', + name='description', + field=models.CharField(blank=True, default=None, max_length=1000, null=True), + ), + migrations.AlterField( + model_name='topic', + name='image_url', + field=models.ImageField(blank=True, default=None, null=True, upload_to='topic/'), + ), + ] diff --git a/api/migrations/0019_collection_topiccollection_collectionproblem.py b/api/migrations/0019_collection_topiccollection_collectionproblem.py new file mode 100644 index 0000000..44bf938 --- /dev/null +++ b/api/migrations/0019_collection_topiccollection_collectionproblem.py @@ -0,0 +1,40 @@ +# Generated by Django 4.1.2 on 2023-04-22 14:12 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0018_alter_topic_description_alter_topic_image_url'), + ] + + operations = [ + migrations.CreateModel( + name='Collection', + fields=[ + ('collection_id', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100)), + ('description', models.CharField(blank=True, default=None, max_length=1000, null=True)), + ('is_active', models.BooleanField(blank=True, default=False)), + ('is_private', models.BooleanField(blank=True, default=True)), + ], + ), + migrations.CreateModel( + name='TopicCollection', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('collection', models.ForeignKey(db_column='collection_id', on_delete=django.db.models.deletion.CASCADE, to='api.collection')), + ('topic', models.ForeignKey(db_column='topic_id', on_delete=django.db.models.deletion.CASCADE, to='api.topic')), + ], + ), + migrations.CreateModel( + name='CollectionProblem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('collection', models.ForeignKey(db_column='collection_id', on_delete=django.db.models.deletion.CASCADE, to='api.collection')), + ('problem', models.ForeignKey(db_column='problem_id', on_delete=django.db.models.deletion.CASCADE, to='api.problem')), + ], + ), + ] diff --git a/api/migrations/0020_collection_owner.py b/api/migrations/0020_collection_owner.py new file mode 100644 index 0000000..e8c7474 --- /dev/null +++ b/api/migrations/0020_collection_owner.py @@ -0,0 +1,20 @@ +# Generated by Django 4.1.2 on 2023-04-22 14:32 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0019_collection_topiccollection_collectionproblem'), + ] + + operations = [ + migrations.AddField( + model_name='collection', + name='owner', + field=models.ForeignKey(db_column='owner_id', default=4, on_delete=django.db.models.deletion.CASCADE, to='api.account'), + preserve_default=False, + ), + ] diff --git a/api/migrations/0021_collectionproblem_order_topiccollection_order_and_more.py b/api/migrations/0021_collectionproblem_order_topiccollection_order_and_more.py new file mode 100644 index 0000000..8caa423 --- /dev/null +++ b/api/migrations/0021_collectionproblem_order_topiccollection_order_and_more.py @@ -0,0 +1,53 @@ +# Generated by Django 4.1.2 on 2023-04-23 11:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0020_collection_owner'), + ] + + operations = [ + migrations.AddField( + model_name='collectionproblem', + name='order', + field=models.IntegerField(blank=True, default=0), + ), + migrations.AddField( + model_name='topiccollection', + name='order', + field=models.IntegerField(blank=True, default=0), + ), + migrations.AlterField( + model_name='collection', + name='is_active', + field=models.BooleanField(blank=True, default=True), + ), + migrations.AlterField( + model_name='collection', + name='is_private', + field=models.BooleanField(blank=True, default=False), + ), + migrations.AlterField( + model_name='problem', + name='is_active', + field=models.BooleanField(blank=True, default=True), + ), + migrations.AlterField( + model_name='problem', + name='is_private', + field=models.BooleanField(blank=True, default=False), + ), + migrations.AlterField( + model_name='topic', + name='is_active', + field=models.BooleanField(blank=True, default=True), + ), + migrations.AlterField( + model_name='topic', + name='is_private', + field=models.BooleanField(blank=True, default=False), + ), + ] diff --git a/api/models.py b/api/models.py index 896ef3c..fd87de1 100644 --- a/api/models.py +++ b/api/models.py @@ -33,8 +33,8 @@ class Problem(models.Model): description = models.CharField(max_length=10000) solution = models.CharField(max_length=20000) time_limit = models.FloatField(default=1.5,blank=True) - is_active = models.BooleanField(default=True) - is_private = models.BooleanField(default=True) + is_active = models.BooleanField(default=True,blank=True) + is_private = models.BooleanField(default=False,blank=True) class Testcase(models.Model): testcase_id = models.AutoField(primary_key=True) @@ -51,15 +51,34 @@ class Submission(models.Model): is_passed = models.BooleanField() date = models.DateTimeField(default=timezone.now) +class Collection(models.Model): + collection_id = models.AutoField(primary_key=True) + owner = models.ForeignKey(Account,on_delete=models.CASCADE,db_column="owner_id") + name = models.CharField(max_length=100) + description = models.CharField(max_length=1000,null=True,blank=True,default=None) + is_active = models.BooleanField(default=True,blank=True) + is_private = models.BooleanField(default=False,blank=True) + class Topic(models.Model): topic_id = models.AutoField(primary_key=True) account_id = models.ForeignKey(Account,on_delete=models.CASCADE,db_column="account_id") name = models.CharField(max_length=100) - description = models.CharField(max_length=1000) - image_url = models.CharField(max_length=1000,default="",blank=True) - is_active = models.BooleanField(default=False) - is_private = models.BooleanField(default=True) + 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) + is_active = models.BooleanField(default=True,blank=True) + is_private = models.BooleanField(default=False,blank=True) + +class TopicCollection(models.Model): + topic = models.ForeignKey(Topic,on_delete=models.CASCADE,db_column="topic_id") + collection = models.ForeignKey(Collection,on_delete=models.CASCADE,db_column="collection_id") + order = models.IntegerField(blank=True,default=0) + +class CollectionProblem(models.Model): + collection = models.ForeignKey(Collection,on_delete=models.CASCADE,db_column="collection_id") + problem = models.ForeignKey(Problem,on_delete=models.CASCADE,db_column="problem_id") + order = models.IntegerField(blank=True,default=0) +# 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 diff --git a/api/sandbox/grader.py b/api/sandbox/grader.py index 8115b9a..a0f82cc 100644 --- a/api/sandbox/grader.py +++ b/api/sandbox/grader.py @@ -1,19 +1,19 @@ import subprocess -def checker(code:str,testcases:list,timeout=1.5)->dict: +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/testcases/{i}.txt','w') as f: + with open(f'./api/sandbox/section{section}/testcases/{i}.txt','w') as f: f.write(testcases[i]) - with open('./api/sandbox/runner.py','w') as f: + 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','./api/sandbox/runner.py'],stdin=open(f'./api/sandbox/testcases/{i}.txt','r'),stderr=subprocess.DEVNULL,timeout=float(timeout)) + 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 @@ -25,9 +25,9 @@ def checker(code:str,testcases:list,timeout=1.5)->dict: return {'result':result,'has_error':hasError,'has_timeout':hasTimeout} -def grading(code:str,input:list,output:list,timeout=1.5)->str: +def grading(section:int,code:str,input:list,output:list,timeout=1.5)->str: score = '' - graded = checker(code,input,timeout) + graded = checker(section,code,input,timeout) graded_result = graded['result'] for i in range(len(output)): diff --git a/api/sandbox/queue.py b/api/sandbox/queue.py new file mode 100644 index 0000000..62caf81 --- /dev/null +++ b/api/sandbox/queue.py @@ -0,0 +1,16 @@ +class Queue(): + def __init__(self,SIZE) -> None: + self.memory = [0 for i in range(SIZE)] + self.size = SIZE + + def isAvaliable(self) -> int: + for i in range(self.size): + if self.memory[i] == 0: + return i + return -1 + + def reserve(self,index:int) -> None: + self.memory[index] = 1 + + def free(self,index:int) -> None: + self.memory[index] = 0 \ No newline at end of file diff --git a/api/sandbox/testcases/.gitkeep b/api/sandbox/section1/testcases/.gitkeep similarity index 100% rename from api/sandbox/testcases/.gitkeep rename to api/sandbox/section1/testcases/.gitkeep diff --git a/media/topics/.gitkeep b/api/sandbox/section2/testcases/.gitkeep similarity index 100% rename from media/topics/.gitkeep rename to api/sandbox/section2/testcases/.gitkeep diff --git a/api/sandbox/section3/testcases/.gitkeep b/api/sandbox/section3/testcases/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/api/serializers.py b/api/serializers.py index 4d490a1..683baa9 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -1,11 +1,61 @@ -from rest_framework_simplejwt.serializers import TokenObtainPairSerializer +from rest_framework import serializers +from .models import * -class CustomTokenObtainPairSerializer(TokenObtainPairSerializer): - """Customizes JWT default Serializer to add more information about user""" - @classmethod - def get_token(cls, user): - token = super().get_token(user) - token['username'] = user.name - token['password'] = user.email +class TopicSerializer(serializers.ModelSerializer): + class Meta: + model = Topic + fields = "__all__" + + def create(self,validate_data): + return Topic.objects.create(**validate_data) - return token \ No newline at end of file + def update(self,instance,validate_data): + instance.name = validate_data.get('name',instance.name) + instance.description = validate_data.get('description',instance.description) + instance.image_url = validate_data.get('image_url',instance.image_url) + instance.is_active = validate_data.get('is_active',instance.is_active) + instance.is_private = validate_data.get('is_private',instance.is_private) + instance.save() + return instance + +class SubmissionSerializer(serializers.ModelSerializer): + class Meta: + model = Submission + fields = "__all__" + +class TopicProblemSerializer(serializers.ModelSerializer): + class Meta: + model = TopicProblem + fields = "__all__" + +class ProblemSerializer(serializers.ModelSerializer): + class Meta: + model = Problem + fields = "__all__" + +class TopicCollectionSerializer(serializers.ModelSerializer): + class Meta: + model = TopicCollection + fields = "__all__" + +class CollectionProblemSerializer(serializers.ModelSerializer): + class Meta: + model = CollectionProblem + fields = "__all__" + +class CollectionSerializer(serializers.ModelSerializer): + class Meta: + model = Collection + fields = "__all__" + + def create(self,validate_data): + return Collection.objects.create(**validate_data) + + def update(self, instance, validated_data): + instance.name = validated_data.get('name',instance.name) + instance.description = validated_data.get('description',instance.description) + instance.is_active = validated_data.get('is_active',instance.is_active) + instance.is_private = validated_data.get('is_private',instance.is_private) + + instance.save() + return instance \ No newline at end of file diff --git a/api/urls.py b/api/urls.py index 15632ed..e5a2b4a 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 +from .views import account,auth,problem,submission,topic,collection urlpatterns = [ @@ -9,6 +9,7 @@ path("accounts",account.create_account), path("accounts/",account.get_account), + path("accounts//daily-submissions",account.get_daily_submission), path('accounts//problems',problem.create_problem), path('problems',problem.all_problem), @@ -20,5 +21,10 @@ path('accounts//topics',topic.create_topic), path('topics',topic.all_topic), path('topics/',topic.one_topic), - path('topics//problems',topic.topic_problem), + path('topics//collections/',topic.topic_collection), + + path('accounts//collections',collection.create_collections), + path('collections',collection.all_collections), + path('collections/',collection.one_collection), + path('collections//problems/',collection.collection_problems), ] \ No newline at end of file diff --git a/api/views/account.py b/api/views/account.py index 8c76834..0c1d610 100644 --- a/api/views/account.py +++ b/api/views/account.py @@ -3,9 +3,10 @@ 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 ..models import * from rest_framework import status from django.forms.models import model_to_dict +from ..serializers import SubmissionSerializer @api_view([POST]) def create_account(request): @@ -23,3 +24,22 @@ def get_account(request,account_id): return Response(model_to_dict(account),status=status.HTTP_200_OK) except: return Response({'message':'Account not found!'},status=status.HTTP_404_NOT_FOUND) + +@api_view([GET]) +def get_daily_submission(request,account_id:int): + submissions = Submission.objects.filter(account_id=account_id) + serializes = SubmissionSerializer(submissions,many=True) + + submission_by_date = {} + + for submission in serializes.data: + [date,time] = submission['date'].split("T") + if date in submission_by_date: + submission_by_date[date]["submissions"].append(submission) + submission_by_date[date]["count"] += 1 + else: + submission_by_date[date] = {"count":1, "submissions": [ submission ]} + + print(submission_by_date) + + return Response({"submissions_by_date": submission_by_date}) \ No newline at end of file diff --git a/api/views/auth.py b/api/views/auth.py index 3304167..110c8dd 100644 --- a/api/views/auth.py +++ b/api/views/auth.py @@ -9,8 +9,10 @@ from django.forms.models import model_to_dict from uuid import uuid4 from time import time +from decouple import config -TOKEN_LIFETIME = 2*60*60 # (Second) +TOKEN_LIFETIME_HOURS = int(config('TOKEN_LIFETIME_HOURS')) +TOKEN_LIFETIME = TOKEN_LIFETIME_HOURS * 60 * 60 # (Second) @api_view([POST]) def login(request): @@ -47,10 +49,10 @@ def get_authorization(request): account = Account.objects.get(account_id=request.data['account_id']) account_dict = model_to_dict(account) if account_dict['token_expire'] >= time() and account_dict['token'] == request.data['token']: - return Response({'result':True},status=status.HTTP_200_OK) - return Response({'result':False},status=status.HTTP_200_OK) + return Response({'result':True,'is_admin':account.is_admin},status=status.HTTP_200_OK) + return Response({'result':False,'is_admin':False},status=status.HTTP_200_OK) except Account.DoesNotExist: - return Response({'result':False},status=status.HTTP_200_OK) + return Response({'result':False,'is_admin':False},status=status.HTTP_200_OK) # return Response({'detail':"User doesn't exists!"},status=status.HTTP_404_NOT_FOUND) # @api_view([GET]) diff --git a/api/views/collection.py b/api/views/collection.py new file mode 100644 index 0000000..9e7ec8e --- /dev/null +++ b/api/views/collection.py @@ -0,0 +1,121 @@ +from statistics import mode +from rest_framework.response import Response +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 * +from rest_framework import status +from django.forms.models import model_to_dict +from ..serializers import * + +@api_view([POST]) +def create_collections(request,account_id:int): + request.data['owner'] = account_id + + serialize = CollectionSerializer(data=request.data) + + if serialize.is_valid(): + serialize.save() + return Response(serialize.data,status=status.HTTP_201_CREATED) + else: + return Response(serialize.errors,status=status.HTTP_400_BAD_REQUEST) + +@api_view([GET]) +def all_collections(request): + collections = Collection.objects.all() + + account_id = request.query_params.get('account_id',0) + + if account_id: + collections = collections.filter(owner_id=account_id) + + populated_collections = [] + for collection in collections: + con_probs = CollectionProblem.objects.filter(collection=collection) + + populated_cp = [] + for cp in con_probs: + prob_serialize = ProblemSerializer(cp.problem) + cp_serialize = CollectionProblemSerializer(cp) + populated_cp.append({**cp_serialize.data,**prob_serialize.data}) + + serialize = CollectionSerializer(collection) + collection_data = serialize.data + collection_data['problems'] = populated_cp + # print() + + populated_collections.append(collection_data) + + return Response({ + 'collections': populated_collections + },status=status.HTTP_200_OK) + +@api_view([GET,PUT,DELETE]) +def one_collection(request,collection_id:int): + collection = Collection.objects.get(collection_id=collection_id) + problems = Problem.objects.filter(collectionproblem__collection_id=collection_id) + collectionProblems = CollectionProblem.objects.filter(collection=collection) + + if request.method == GET: + collection_ser = CollectionSerializer(collection) + + populated_problems = [] + for col_prob in collectionProblems: + col_prob_serialize = CollectionProblemSerializer(col_prob) + prob_serialize = ProblemSerializer(col_prob.problem) + populated_problems.append({**col_prob_serialize.data,**prob_serialize.data}) + + return Response({ + 'collection': collection_ser.data, + 'problems': sorted(populated_problems,key=lambda problem: problem['order']) + } ,status=status.HTTP_200_OK) + + if request.method == PUT: + collection_ser = CollectionSerializer(collection,data=request.data,partial=True) + if collection_ser.is_valid(): + collection_ser.save() + return Response(collection_ser.data,status=status.HTTP_200_OK) + else: + return Response(collection_ser.errors,status=status.HTTP_400_BAD_REQUEST) + + if request.method == DELETE: + collection.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + +@api_view([PUT]) +def collection_problems(request,collection_id:int,method:str): + collection = Collection.objects.get(collection_id=collection_id) + + if method == "add": + populated_problems = [] + + index = 0 + for problem_id in request.data['problem_ids']: + + problem = Problem.objects.get(problem_id=problem_id) + + alreadyExist = CollectionProblem.objects.filter(problem=problem,collection=collection) + if alreadyExist: + alreadyExist.delete() + + collection_problem = CollectionProblem( + problem=problem, + collection=collection, + order=index + ) + print(problem,index) + collection_problem.save() + index += 1 + cp_serialize = CollectionProblemSerializer(collection_problem) + populated_problems.append(cp_serialize.data) + + collection_serialize = CollectionSerializer(collection) + + return Response({ + 'collection': collection_serialize.data, + 'problems': populated_problems + },status=status.HTTP_201_CREATED) + + if method == "remove": + CollectionProblem.objects.filter(collection=collection,problem_id__in=request.data['problem_ids']).delete() + return Response(status=status.HTTP_204_NO_CONTENT) \ No newline at end of file diff --git a/api/views/problem.py b/api/views/problem.py index 99f1093..28d2fd9 100644 --- a/api/views/problem.py +++ b/api/views/problem.py @@ -12,9 +12,10 @@ @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(request.data['solution'],request.data['testcases'],request.data.get('time_limit',1.5)) + 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!'},status=status.HTTP_406_NOT_ACCEPTABLE) @@ -49,6 +50,7 @@ def all_problem(request): for i in result: i['creator'] = model_to_dict(Account.objects.get(account_id=i['account_id'])) + result.reverse() return Response({'result':result},status=status.HTTP_200_OK) elif request.method == DELETE: target = request.data.get("problem",[]) @@ -74,24 +76,29 @@ def one_problem(request,problem_id: int): problem.description = request.data.get("description",problem.description) problem.solution = request.data.get("solution",problem.solution) problem.time_limit = request.data.get("time_limit",problem.time_limit) + problem.is_private = request.data.get("is_private",problem.is_private) - checked = checker(problem.solution,request.data['testcases'],request.data.get('time_limit',1.5)) - if checked['has_error'] or checked['has_timeout']: - return Response({'detail': 'Error during editing. Your code may has an error/timeout!'},status=status.HTTP_406_NOT_ACCEPTABLE) + 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']: + 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']: - testcase = Testcase( - problem_id = problem, - input = unit['input'], - output = unit['output'] - ) - testcase.save() - testcase_result.append(model_to_dict(testcase)) - problem.save() + testcases.delete() + testcase_result = [] + for unit in checked['result']: + testcase = Testcase( + problem_id = problem, + input = unit['input'], + output = unit['output'] + ) + testcase.save() + testcase_result.append(model_to_dict(testcase)) + problem.save() - return Response({'detail': 'Problem has been edited!','problem': model_to_dict(problem),'testcase': testcase_result},status=status.HTTP_201_CREATED) + return Response({'detail': 'Problem has been edited!','problem': model_to_dict(problem),'testcase': testcase_result},status=status.HTTP_201_CREATED) + + problem.save() + return Response({'detail': 'Problem has been edited!','problem': model_to_dict(problem)},status=status.HTTP_201_CREATED) elif request.method == DELETE: problem.delete() diff --git a/api/views/submission.py b/api/views/submission.py index 2e4fd68..6b6ea78 100644 --- a/api/views/submission.py +++ b/api/views/submission.py @@ -6,9 +6,20 @@ from rest_framework import status from django.forms.models import model_to_dict from ..sandbox import grader +from time import sleep + +QUEUE = [0,0,0] + +def avaliableQueue(): + global QUEUE + for i in range(len(QUEUE)): + if QUEUE[i] == 0: + return i + return -1 @api_view([POST]) 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) @@ -16,9 +27,14 @@ def submit_problem(request,problem_id,account_id): solution_input = [model_to_dict(i)['input'] for i in testcases] solution_output = [model_to_dict(i)['output'] for i in testcases] - grading_result = grader.grading(submission_code,solution_input,solution_output) + empty_queue = avaliableQueue() + while empty_queue == -1: + empty_queue = avaliableQueue() + sleep(5) + QUEUE[empty_queue] = 1 + grading_result = grader.grading(empty_queue+1,submission_code,solution_input,solution_output) + QUEUE[empty_queue] = 0 - if '-' in grading_result or 'E' in grading_result or 'T' in grading_result: is_passed = False else: diff --git a/api/views/topic.py b/api/views/topic.py index 45a433d..b59713b 100644 --- a/api/views/topic.py +++ b/api/views/topic.py @@ -3,71 +3,111 @@ 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 +from ..models import Account, Problem, Submission,Testcase, Topic, TopicProblem, Collection from rest_framework import status from django.forms.models import model_to_dict +from ..serializers import * @api_view([POST]) +@parser_classes([MultiPartParser,FormParser]) def create_topic(request,account_id :int): - account = Account.objects.get(account_id=account_id) - topic = Topic(**request.data,account_id=account) - topic.save() - return Response({'topic':model_to_dict(topic)},status=status.HTTP_201_CREATED) + request.data._mutable=True + request.data['account_id'] = account_id + serializer = TopicSerializer(data=request.data) + + if serializer.is_valid(): + serializer.save() + return Response(serializer.data,status=status.HTTP_201_CREATED) + return Response(serializer.errors,status=status.HTTP_400_BAD_REQUEST) @api_view([GET]) def all_topic(request): - topic = Topic.objects.all() + topics = Topic.objects.all() account_id = request.query_params.get('account_id',0) if account_id: - topic = topic.filter(account_id=account_id) + topics = topics.filter(account_id=account_id) + + serializer = TopicSerializer(topics,many=True) return Response({ - 'topics': [model_to_dict(i) for i in topic] + 'topics': serializer.data },status=status.HTTP_200_OK) -@api_view([GET,PUT]) +@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) + if request.method == GET: + topic_ser = TopicSerializer(topic) + populate_collections = [] + + for top_col in topicCollections: + collection_serialize = CollectionSerializer(top_col.collection) + collection_data = collection_serialize.data + + populate_problems = [] + collection_problems = CollectionProblem.objects.filter(collection=top_col.collection) + for col_prob in collection_problems: + prob_serialize = ProblemSerializer(col_prob.problem) + col_prob_serialize = CollectionProblemSerializer(col_prob) + populate_problems.append({**col_prob_serialize.data,**prob_serialize.data}) + + collection_data['problems'] = populate_problems + top_col_serialize = TopicCollectionSerializer(top_col) + populate_collections.append({**top_col_serialize.data,**collection_data}) + return Response({ - "topic": model_to_dict(topic), - "problem": [model_to_dict(i) for i in topicProblem] + "topic": topic_ser.data, + "collections": sorted(populate_collections,key=lambda collection: collection['order']) },status=status.HTTP_200_OK) elif request.method == PUT: - topic.name = request.data.get("name",topic.name) - topic.description = request.data.get("description",topic.description) - topic.is_active = request.data.get("is_active",topic.is_active) - topic.is_private = request.data.get("is_private",topic.is_private) - return Response({ - "topic": model_to_dict(topic) - },status=status.HTTP_200_OK) + topic_ser = TopicSerializer(topic,data=request.data,partial=True) + if topic_ser.is_valid(): + topic_ser.save() + return Response(topic_ser.data,status=status.HTTP_200_OK) + return Response(topic_ser.errors,status=status.HTTP_400_BAD_REQUEST) + elif request.method == DELETE: + topic.delete() + return Response(status=status.HTTP_204_NO_CONTENT) -@api_view([PUT,DELETE]) -def topic_problem(request,topic_id:int): +@api_view([PUT]) +def topic_collection(request,topic_id:int,method:str): topic = Topic.objects.get(topic_id=topic_id) - if request.method == PUT: - populated_problem = [] - for id in request.data['problems_id']: - problem = Problem.objects.get(problem_id=id) - if TopicProblem.objects.filter(topic_id=topic,problem_id=problem): - continue - topicProblem = TopicProblem( - topic_id=topic, - problem_id=problem - ) - topicProblem.save() - populated_problem.append(model_to_dict(problem)) + if method == "add": + populated_collections = [] + + index = 0 + for collection_id in request.data['collection_ids']: + collection = Collection.objects.get(collection_id=collection_id) + + alreadyExist = TopicCollection.objects.filter(topic_id=topic.topic_id,collection_id=collection.collection_id) + if alreadyExist: + alreadyExist.delete() + + topicCollection = TopicCollection( + topic=topic, + collection=collection, + order=index + ) + topicCollection.save() + index += 1 + tc_serialize = TopicCollectionSerializer(topicCollection) + populated_collections.append(tc_serialize.data) + return Response({ - "topic": model_to_dict(topic), - "problems": populated_problem + "topic": TopicSerializer(topic).data, + "collections": populated_collections },status=status.HTTP_201_CREATED) - elif request.method == DELETE: - problems = Problem.objects.filter(problem_id__in=request.data['problems_id']) - TopicProblem.objects.filter(topic_id=topic,problem_id__in=problems).delete() + elif method == "remove": + TopicCollection.objects.filter(topic_id=topic_id,collection_id__in=request.data['collection_ids']).delete() + # 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) \ No newline at end of file diff --git a/media/topic/.gitkeep b/media/topic/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/media/topic/40205234_1-klassic-wooden-bamboo-chopstick-kl-68.webp b/media/topic/40205234_1-klassic-wooden-bamboo-chopstick-kl-68.webp new file mode 100644 index 0000000..e8697fa Binary files /dev/null and b/media/topic/40205234_1-klassic-wooden-bamboo-chopstick-kl-68.webp differ diff --git a/media/topic/7lhb7brt-900.jpg b/media/topic/7lhb7brt-900.jpg new file mode 100644 index 0000000..57d71f5 Binary files /dev/null and b/media/topic/7lhb7brt-900.jpg differ diff --git a/media/topic/How-to-Learn-Python-1200x900.jpg b/media/topic/How-to-Learn-Python-1200x900.jpg new file mode 100644 index 0000000..0da0858 Binary files /dev/null and b/media/topic/How-to-Learn-Python-1200x900.jpg differ diff --git a/media/topic/How-to-Learn-Python-1200x900_qrHC8Ro.jpg b/media/topic/How-to-Learn-Python-1200x900_qrHC8Ro.jpg new file mode 100644 index 0000000..0da0858 Binary files /dev/null and b/media/topic/How-to-Learn-Python-1200x900_qrHC8Ro.jpg differ diff --git a/media/topic/Python-Symbol.png b/media/topic/Python-Symbol.png new file mode 100644 index 0000000..a9c8bca Binary files /dev/null and b/media/topic/Python-Symbol.png differ diff --git a/media/topic/g268.jpg b/media/topic/g268.jpg new file mode 100644 index 0000000..e19209f Binary files /dev/null and b/media/topic/g268.jpg differ diff --git a/media/topic/image0.jpg b/media/topic/image0.jpg new file mode 100644 index 0000000..d2b858a Binary files /dev/null and b/media/topic/image0.jpg differ diff --git a/media/topic/kanonkc_bfe6335e-b251-4da7-8935-2228a6cda169.png b/media/topic/kanonkc_bfe6335e-b251-4da7-8935-2228a6cda169.png new file mode 100644 index 0000000..962836c Binary files /dev/null and b/media/topic/kanonkc_bfe6335e-b251-4da7-8935-2228a6cda169.png differ diff --git a/media/topics/Screenshot_2.png b/media/topics/Screenshot_2.png deleted file mode 100644 index 637a27b..0000000 Binary files a/media/topics/Screenshot_2.png and /dev/null differ diff --git a/media/topics/Screenshot_2049.png b/media/topics/Screenshot_2049.png deleted file mode 100644 index b0c8889..0000000 Binary files a/media/topics/Screenshot_2049.png and /dev/null differ diff --git a/media/topics/Screenshot_2_YE3yD8G.png b/media/topics/Screenshot_2_YE3yD8G.png deleted file mode 100644 index 637a27b..0000000 Binary files a/media/topics/Screenshot_2_YE3yD8G.png and /dev/null differ diff --git a/media/topics/Screenshot_2_lOPdqxZ.png b/media/topics/Screenshot_2_lOPdqxZ.png deleted file mode 100644 index 637a27b..0000000 Binary files a/media/topics/Screenshot_2_lOPdqxZ.png and /dev/null differ diff --git a/requirements.txt b/requirements.txt index f92bc3f..9d956ad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,24 @@ +asgiref==3.5.2 +certifi==2022.9.24 +cffi==1.15.1 +charset-normalizer==2.1.1 +cryptography==38.0.3 Django==4.1.2 +django-cors-headers==3.13.0 +django-environ==0.9.0 django-filter==22.1 djangorestframework==3.13.1 -psycopg2-binary==2.9.3 +djangorestframework-jwt==1.11.0 +djangorestframework-simplejwt==5.2.2 +environ==1.0 +idna==3.4 +Pillow==9.4.0 +psycopg2==2.9.5 +pycparser==2.21 +PyJWT==1.7.1 +pytz==2022.6 +requests==2.28.1 sqlparse==0.4.3 -django-cors-headers==3.13.0 -django-environ==0.9.0 -environ -requests -djangorestframework-simplejwt -djangorestframework-jwt \ No newline at end of file +tzdata==2022.6 +urllib3==1.26.12 +python-decouple==3.8 \ No newline at end of file