-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
45 changed files
with
1,007 additions
and
168 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,14 @@ | ||
from rest_framework import serializers | ||
|
||
from programdom.models import Submission | ||
from programdom.models import Submission, Problem | ||
|
||
|
||
class SubmissionSerializer(serializers.ModelSerializer): | ||
class Meta: | ||
model = Submission | ||
fields = ('id', 'problem', 'user', 'code', 'options') | ||
fields = ('id', 'problem', 'user', 'code', 'workshop', 'options') | ||
|
||
|
||
class ProblemSerializer(serializers.ModelSerializer): | ||
class Meta: | ||
model = Problem | ||
fields = ('id', 'title', 'skeleton', 'language') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,13 @@ | ||
from rest_framework import viewsets, mixins | ||
from rest_framework.generics import CreateAPIView | ||
from rest_framework.views import APIView | ||
|
||
from programdom.api.serializers import SubmissionSerializer | ||
from programdom.models import Submission | ||
from rest_framework import viewsets | ||
from .serializers import SubmissionSerializer, ProblemSerializer | ||
from programdom.models import Submission, Problem | ||
|
||
|
||
class SubmissionView(viewsets.ModelViewSet): | ||
queryset = Submission.objects.all() | ||
serializer_class = SubmissionSerializer | ||
|
||
|
||
class ProblemView(viewsets.ModelViewSet): | ||
queryset = Problem.objects.all() | ||
serializer_class = ProblemSerializer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,83 +1,85 @@ | ||
import asyncio | ||
import os | ||
from pprint import pprint | ||
import logging | ||
|
||
import aiofiles as aiofiles | ||
from asgiref.sync import sync_to_async | ||
from channels.consumer import AsyncConsumer | ||
from channels.db import database_sync_to_async | ||
from django.core.cache import cache | ||
|
||
from channels.layers import get_channel_layer | ||
from django.conf import settings | ||
import judge0api as api | ||
from programdom.bridge.client import client | ||
from programdom.models import Problem, SubmissionTestResult | ||
from programdom.models import SubmissionTestResult, Submission | ||
|
||
channel_layer = get_channel_layer() | ||
|
||
class ProgramdomBridgeConsumer(AsyncConsumer): | ||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class ProgramdomBridgeConsumer(AsyncConsumer): | ||
submission = None | ||
problem = None | ||
workshop_id = None | ||
session_id = None | ||
channel_name = None | ||
file = None | ||
|
||
async def evaluate(self, message): | ||
""" | ||
Sends a message to judge0api, and then processes the result | ||
:param message: a dict containing the following: | ||
message = { | ||
"type": "solution.evaluate", # To match to this method | ||
"problem_id": The Mooshak ID of the problem this solution is for | ||
"code_url": The URL of the code for this submission | ||
"submission_id": the PK of the submission object | ||
"session_id": The clients session_id - used to send messages back to the user | ||
"workshop_id": the PK of the workshop associated with this submission - used for stats tracking | ||
} | ||
""" | ||
|
||
self.problem = Problem.objects.get(id=message["problem_id"]) | ||
self.workshop_id = message["workshop_id"] | ||
self.submission = await database_sync_to_async(Submission.objects.get)(pk=message["submission_id"]) | ||
self.problem = self.submission.problem | ||
self.workshop_id = self.submission.workshop.id | ||
self.session_id = message["session_id"] | ||
self.channel_name = cache.get(f"session_{self.session_id}_channel-name") | ||
|
||
url = message["code_url"] | ||
|
||
|
||
# Our old system downloaded files and then sent them off. We will probably go back to this once in prod and have | ||
# an actual object storage system working. However, ATM we can just use the local files. | ||
|
||
# async with aiohttp.ClientSession() as session: | ||
# # TODO: Check file is accessable (aka http 200) | ||
# async with session.get(f"{url}") as response: | ||
# data = await response.read() | ||
|
||
# TODO: This is horrible, and should be changed | ||
async with aiofiles.open(str(settings.APPS_DIR(url[1:])), mode='rb') as f: | ||
self.file = await f.read() | ||
|
||
for test in self.problem.problemtest_set.all(): | ||
submission = sync_to_async(api.submission.submit)(client, self.file, self.problem.language.judge_zero_id, stdin=test.std_in, expected_output=test.std_out) | ||
|
||
client_result = dict(vars(submission)) | ||
|
||
await self.handle_state(client_result) | ||
|
||
test_result = SubmissionTestResult() | ||
|
||
client_result.update({"type": "submission.status"}) | ||
client_result.update({"test": test}) | ||
|
||
|
||
async def handle_state(self, message): | ||
# Sends a message to the end user saying what is happening | ||
await channel_layer.send(self.channel_name, message) | ||
# Update the lecturers graph | ||
await channel_layer.group_send(f"workshop_{self.workshop_id}_control", {"type": "graph.update"}) | ||
|
||
def submit_allowed(self): | ||
""" | ||
Checks if the current session is able to submit, by sesing if their answer has been approved | ||
""" | ||
return True | ||
|
||
self.channel_name = await sync_to_async(cache.get)(f"session_{self.session_id}_channel-name") | ||
|
||
logger.debug(f"Evaluating {self.submission}") | ||
|
||
with await sync_to_async(open)(self.submission.code.path, 'rb') as f: | ||
source_code = await sync_to_async(f.read)() | ||
|
||
loop = asyncio.get_event_loop() | ||
for test in await database_sync_to_async(self.problem.problemtest_set.all)(): | ||
loop.create_task(self.test_submit(source_code, test)) | ||
|
||
async def test_submit(self, source_code, test): | ||
submission = await sync_to_async(api.submission.submit)( | ||
client, | ||
source_code, | ||
self.problem.language.judge_zero_id, | ||
stdin=test.std_in.encode(), | ||
expected_output=test.std_out.encode() | ||
) | ||
await sync_to_async(submission.load)(client) | ||
logger.debug(f"Running test {test} for {self.submission}") | ||
|
||
# TODO: Cleanup | ||
test_result = await database_sync_to_async(SubmissionTestResult)(submission=self.submission, test=test, result_data=dict(submission)) | ||
await database_sync_to_async(test_result.save)() | ||
await sync_to_async(test_result.send_user_status)(self.channel_name) | ||
|
||
# If we don't have an actual result yet, then | ||
if test_result.result_data["status"]["id"] in [1, 2]: | ||
await self.test_schedule(test_result) | ||
|
||
async def test_reload(self, test): | ||
data = await sync_to_async(api.submission.get)(client, test.result_data["token"]) | ||
if data.status["id"] != test.result_data["status"]["id"]: | ||
test.result_data.update(**dict(data)) | ||
await database_sync_to_async(test.save)() | ||
await sync_to_async(test.send_user_status)(self.channel_name) | ||
if data.status["id"] in [2]: | ||
await self.test_schedule(test) | ||
else: | ||
await self.test_schedule(test) | ||
|
||
async def test_schedule(self, test): | ||
await asyncio.sleep(0.5) | ||
await self.test_reload(test) |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
[ | ||
{ | ||
"model": "programdom.ProblemTest", | ||
"fields": { | ||
"name": "New Test", | ||
"std_in": "Bob", | ||
"std_out": "Hello Bob", | ||
"problem": 1 | ||
} | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
[ | ||
{ | ||
"model": "programdom.Problem", | ||
"fields": { | ||
"title": "Test Problem", | ||
"skeleton": "print('this is a test')", | ||
"language": 3 | ||
} | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
[ | ||
{ | ||
"model": "programdom.Workshop", | ||
"fields": { | ||
"title": "Test Workshop" | ||
} | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,24 @@ | ||
from django.http import HttpResponseForbidden | ||
from django.shortcuts import redirect | ||
from django.conf import settings | ||
from django.urls import resolve | ||
|
||
|
||
class LoginRequiredMiddleware: | ||
def __init__(self, get_response): | ||
self.get_response = get_response | ||
self.login_url = settings.LOGIN_URL | ||
self.student_views = settings.STUDENT_VIEWS | ||
self.open_urls = [self.login_url] + \ | ||
getattr(settings, 'OPEN_URLS', []) | ||
|
||
def __call__(self, request): | ||
if not (request.user.is_authenticated or request.session.get("current_workshop_id")) and not request.path_info in self.open_urls: | ||
return redirect(self.login_url+'?next='+request.path) | ||
else: | ||
if request.session.get("current_workshop_id") and not self._is_student_url(request.path_info): | ||
return HttpResponseForbidden() | ||
return self.get_response(request) | ||
|
||
return self.get_response(request) | ||
def _is_student_url(self, path): | ||
return resolve(path).view_name in self.student_views |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# Generated by Django 2.1.7 on 2019-04-07 15:06 | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('programdom', '0001_initial'), | ||
] | ||
|
||
operations = [ | ||
migrations.AddField( | ||
model_name='problemtest', | ||
name='name', | ||
field=models.CharField(default='New Test', max_length=100), | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# Generated by Django 2.1.7 on 2019-04-08 15:05 | ||
|
||
from django.db import migrations, models | ||
import django.utils.timezone | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('programdom', '0002_problemtest_name'), | ||
] | ||
|
||
operations = [ | ||
migrations.AddField( | ||
model_name='submission', | ||
name='date', | ||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), | ||
preserve_default=False, | ||
), | ||
] |
Oops, something went wrong.