Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 16 additions & 63 deletions api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@
StudentSignupSerializer, StudentSerializer,
ClassroomCreateSerializer, ClassroomJoincodeSerializer, ClassroomSerializer,
AssignmentSerializer,
QuestionSerializer,
QuestionSerializer,
SolutionSerializer,
)
import coderunner
from utilities.judge import run_code, submit_code


@api_view(['GET'])
@permission_classes([AllowAny])
Expand All @@ -33,13 +34,13 @@ def index(request):
'logout': reverse('logout', request=request),
'students': reverse('students', request=request),
'professors': reverse('professors', request=request),
'create-classroom' : reverse('classroom-create', request=request),
'create-classroom': reverse('classroom-create', request=request),
'join-classroom': reverse('classroom-join', request=request),
'classrooms' : reverse('classrooms', request=request),
'create-assignment' : reverse('assignment-create', request=request),
'create-question' : reverse('question-create', request=request),
'run-code' : reverse('run-code', request=request),
'submit-solution' : reverse('submission-create', request=request),
'classrooms': reverse('classrooms', request=request),
'create-assignment': reverse('assignment-create', request=request),
'create-question': reverse('question-create', request=request),
'run-code': reverse('run-code', request=request),
'submit-solution': reverse('submission-create', request=request),
})


Expand Down Expand Up @@ -156,6 +157,7 @@ class ClassroomViewSet(viewsets.ReadOnlyModelViewSet):
authentication_classes = [SessionAuthentication, BasicAuthentication]
permission_classes = [IsAuthenticated]


class AssignmentView(views.APIView):
'''API view for listing assignments.'''
serializer_class = AssignmentSerializer
Expand All @@ -173,7 +175,7 @@ class AssignmentCreateView(views.APIView):
serializer_class = AssignmentSerializer
authentication_classes = [SessionAuthentication, BasicAuthentication]
permission_classes = [IsAuthenticated]

def post(self, request):
serializer = AssignmentSerializer(data=request.data)

Expand All @@ -200,7 +202,7 @@ class QuestionCreateView(views.APIView):
serializer_class = QuestionSerializer
authentication_classes = [SessionAuthentication, BasicAuthentication]
permission_classes = [IsAuthenticated]

def post(self, request):
serializer = QuestionSerializer(data=request.data)

Expand All @@ -220,35 +222,9 @@ def post(self, request):
lang = request.data["language"]
question = request.data["question_id"]

expected_output = Question.objects.filter(question__id = question).get('sample_output')
standard_input = Question.objects.filter(question__id = question).get('sample_input')

if standard_input.exists():
r = coderunner.Run(code, lang, standard_input, expected_output, path=False)
else:
r = coderunner.Run(code, lang, expected_output, path=False)

submission_status = r.getStatus()
standard_output = r.getOutput()

if submission_status == "Accepted":
content = {
'status': 'Accepted',
'output': standard_output
}
elif submission_status == "Wrong Answer":
content = {
'status': 'Wrong Answer'
}
else:
error = r.getError()
content = {
'status': 'Error Occured',
'output': standard_output,
'error': error
}
return Response(content, status=status.HTTP_200_OK)
content = run_code(code, lang, question)

return Response(content, status=status.HTTP_200_OK)


class SolutionView(views.APIView):
Expand All @@ -258,7 +234,6 @@ class SolutionView(views.APIView):
permission_classes = [IsAuthenticated]
parser_classes = (MultiPartParser, FormParser)


def post(self, request):
serializer = SolutionSerializer(data=request.data)

Expand All @@ -267,32 +242,10 @@ def post(self, request):
assignment = request.data['assignment']

file_obj = request.FILES['submission']
code = str(file_obj.read().decode())

expected_output = Question.objects.filter(id=question).values('sample_output')
standard_input = Question.objects.filter(id=question).values('sample_input')
lang = Assignment.objects.filter(id=assignment).values('language')
lang = lang[0]['language']

expected_output = expected_output[0]['sample_output']

if standard_input.exists():
standard_input = standard_input[0]['sample_input']
r = coderunner.Run(code, lang, standard_input, expected_output, path=False)
else:
r = coderunner.Run(code, lang, expected_output, path=False)

submission_status = r.getStatus()
standard_output = r.getOutput()

if submission_status == "Accepted":
execution_status = "accepted"
elif submission_status == "Wrong Answer":
execution_status = "wrong"
else:
execution_status = "not-attempted"
code = str(file_obj.read().decode())

context = { "status": submission_status }
context, execution_status = submit_code(question, assignment, code)

serializer.save(status=execution_status)

Expand Down
37 changes: 29 additions & 8 deletions app/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Generated by Django 2.2.6 on 2019-11-26 14:40
# Generated by Django 2.2.9 on 2020-01-09 07:03

import app.models
import app.storage
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
Expand All @@ -21,15 +23,16 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200)),
('created_date', models.DateTimeField(default=django.utils.timezone.now)),
('deadline', models.DateTimeField(default=django.utils.timezone.now)),
('language', models.CharField(choices=[('Python', 'Python'), ('Java', 'Java'), ('C++', 'C++'), ('C', 'C')], max_length=50)),
('language', models.CharField(choices=[('Python3', 'Python3'), ('Java', 'Java'), ('C++', 'C++'), ('C', 'C'), ('PHP', 'PHP'), ('Bash', 'Bash')], max_length=50)),
],
),
migrations.CreateModel(
name='Institution',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, max_length=200)),
('name', models.CharField(blank=True, max_length=200, null=True)),
],
),
migrations.CreateModel(
Expand All @@ -40,16 +43,17 @@ class Migration(migrations.Migration):
('description', models.TextField(blank=True)),
('sample_input', models.TextField(blank=True)),
('sample_output', models.TextField(blank=True)),
('marks', models.IntegerField()),
('marks', models.IntegerField(blank=True, null=True)),
('draft', models.BooleanField(default=False)),
('created_date', models.DateTimeField(default=django.utils.timezone.now)),
('assignment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.Assignment')),
],
),
migrations.CreateModel(
name='Student',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('profile_pic', models.ImageField(blank=True, upload_to='StudentProfilePic')),
('profile_pic', models.ImageField(blank=True, null=True, upload_to='StudentProfilePic')),
('course', models.CharField(blank=True, max_length=50)),
('roll_no', models.IntegerField()),
('institution', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='app.Institution')),
Expand All @@ -61,8 +65,8 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('sub_date', models.DateTimeField(default=django.utils.timezone.now)),
('status', models.CharField(choices=[('accepted', 'Accepted'), ('partially-submit', 'Partially Submitted'), ('wrong', 'Wrong Answer'), ('not-attempted', 'Not Attempted')], max_length=100)),
('submission', models.FileField(upload_to=app.models.submission_directory_path)),
('status', models.CharField(choices=[('accepted', 'Accepted'), ('partially-submit', 'Partially Submitted'), ('wrong', 'Wrong Answer'), ('not-attempted', 'Not Attempted')], default='not-attempted', max_length=100)),
('submission', models.FileField(storage=app.storage.OverwriteStorage(), upload_to=app.models.submission_directory_path)),
('remark', models.CharField(blank=True, max_length=500)),
('assignment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.Assignment')),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.Question')),
Expand All @@ -73,16 +77,33 @@ class Migration(migrations.Migration):
name='Professor',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('profile_pic', models.ImageField(blank=True, upload_to='ProfessorProfilePic')),
('profile_pic', models.ImageField(blank=True, null=True, upload_to='ProfessorProfilePic')),
('moss_id', models.CharField(blank=True, max_length=50, null=True)),
('institution', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='app.Institution')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='PlagResult',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('perc_1', models.IntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])),
('perc_2', models.IntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])),
('lines_matched', models.CharField(max_length=100)),
('lines_match_count', models.IntegerField()),
('moss_page_url', models.URLField()),
('created_date', models.DateTimeField(default=django.utils.timezone.now)),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.Question')),
('solution_1', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='solution_1', to='app.Solution')),
('solution_2', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='solution_2', to='app.Solution')),
],
),
migrations.CreateModel(
name='Classroom',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200)),
('created_date', models.DateTimeField(default=django.utils.timezone.now)),
('join_code', models.CharField(default=app.models.random_code, editable=False, max_length=5, unique=True)),
('professor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.Professor')),
('students', models.ManyToManyField(blank=True, to='app.Student')),
Expand Down
47 changes: 35 additions & 12 deletions app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone

from django.core.validators import MaxValueValidator, MinValueValidator
from app.storage import OverwriteStorage

def submission_directory_path(instance, filename):
"""
Expand All @@ -14,13 +15,13 @@ def submission_directory_path(instance, filename):


def random_code(length=5):
'''Returns a random code of given length.'''
'''Returns a random code of given length'''
code_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
return ''.join(random.choices(population=code_chars, k=length))


class Institution(models.Model):
name = models.CharField(max_length=200, blank=True)
name = models.CharField(max_length=200, blank=True, null=True)

def __str__(self):
return self.name
Expand All @@ -29,19 +30,20 @@ def __str__(self):
class Professor(models.Model):
'''Assuming a professor can create multiple classrooms and each classroom can have
multiple assignments. if a classroom is deleted, all of the assignments of that class will
delete automatically*.
delete automatically
'''
user = models.OneToOneField(to=User, on_delete=models.CASCADE)
profile_pic = models.ImageField(upload_to='ProfessorProfilePic', blank=True)
profile_pic = models.ImageField(upload_to='ProfessorProfilePic', blank=True, null=True)
institution = models.ForeignKey(to=Institution, blank=True, null=True, on_delete=models.CASCADE)
moss_id = models.CharField(max_length=50, blank=True, null=True)

def __str__(self):
return self.user.username


class Student(models.Model):
user = models.OneToOneField(to=User, on_delete=models.CASCADE)
profile_pic = models.ImageField(upload_to='StudentProfilePic', blank=True)
profile_pic = models.ImageField(upload_to='StudentProfilePic', blank=True, null=True)
institution = models.ForeignKey(to=Institution, blank=True, null=True, on_delete=models.CASCADE)
course = models.CharField(max_length=50, blank=True)
roll_no = models.IntegerField()
Expand All @@ -54,6 +56,7 @@ class Classroom(models.Model):
professor = models.ForeignKey(to=Professor, on_delete=models.CASCADE)
title = models.CharField(max_length=200, blank=False)
students = models.ManyToManyField(to=Student, blank=True)
created_date = models.DateTimeField(default=timezone.now)
# because a student can join multiple classrooms and each classroom can have multiple students
join_code = models.CharField(max_length=5, default=random_code, editable=False, unique=True)

Expand All @@ -67,16 +70,19 @@ def __str__(self):

class Assignment(models.Model):
LANGUAGE = (
('Python', 'Python'),
('Python3', 'Python3'),
('Java', 'Java'),
('C++', 'C++'),
('C', 'C')
('C', 'C'),
('PHP', 'PHP'),
('Bash', 'Bash')
)
classroom = models.ForeignKey(to=Classroom, on_delete=models.CASCADE)
title = models.CharField(max_length=200, blank=False)
created_date = models.DateTimeField(default=timezone.now)
deadline = models.DateTimeField(default=timezone.now)
language = models.CharField(max_length=50, choices=LANGUAGE)

def __str__(self):
return self.title

Expand All @@ -87,8 +93,9 @@ class Question(models.Model):
description = models.TextField(blank=True)
sample_input = models.TextField(blank=True)
sample_output = models.TextField(blank=True)
marks = models.IntegerField()
marks = models.IntegerField(blank=True, null=True)
draft = models.BooleanField(default=False)
created_date = models.DateTimeField(default=timezone.now)

def __str__(self):
return self.title
Expand All @@ -111,10 +118,26 @@ class Solution(models.Model):
assignment = models.ForeignKey(to=Assignment, on_delete=models.CASCADE)
student = models.ForeignKey(to=Student, on_delete=models.CASCADE)
sub_date = models.DateTimeField(default=timezone.now)
status = models.CharField(max_length=100, choices=STATUS)
status = models.CharField(max_length=100, choices=STATUS, default=STATUS[3][0])
submission = models.FileField(
upload_to=submission_directory_path,
blank=False
blank=False,
storage=OverwriteStorage()
)
remark = models.CharField(max_length=500, blank=True)
# this field may be filled by prof as remark


class PlagResult(models.Model):
question = models.ForeignKey(to=Question, on_delete=models.CASCADE)
solution_1 = models.ForeignKey(to=Solution, on_delete=models.CASCADE, related_name='solution_1')
solution_2 = models.ForeignKey(to=Solution, on_delete=models.CASCADE, related_name='solution_2')
perc_1 = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(100)])
perc_2 = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(100)])
lines_matched = models.CharField(max_length=100)
lines_match_count = models.IntegerField()
moss_page_url = models.URLField()
created_date = models.DateTimeField(default=timezone.now)

def __str__(self):
return "{}-{}".format(self.solution_1, self.solution_2)
3 changes: 3 additions & 0 deletions app/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,6 @@ class SolutionSerializer(serializers.ModelSerializer):
class Meta:
model = Solution
fields = ['question', 'assignment', 'student', 'sub_date', 'submission', 'remark']

def create(self, validated_data):
return Solution.objects.create(**validated_data)
11 changes: 11 additions & 0 deletions app/storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from django.conf import settings
from django.core.files.storage import FileSystemStorage
import os


class OverwriteStorage(FileSystemStorage):
'''Remove file if already exists and save new one'''
def get_available_name(self, name, max_length=None):
if self.exists(name):
os.remove(os.path.join(settings.MEDIA_ROOT, name))
return name
5 changes: 3 additions & 2 deletions codeclassroom/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

DEBUG = os.environ['DEBUG']

ALLOWED_HOSTS = ['codeclassroom.pythonanywhere.com']
ALLOWED_HOSTS = ['codeclassroom.pythonanywhere.com', '127.0.0.1']


# Application definition
Expand All @@ -27,7 +27,8 @@
'rest_framework',
'corsheaders',
'app',
'api'
'api',
'utilities'
]

MIDDLEWARE = [
Expand Down
Loading