Skip to content
Snippets Groups Projects
Verified Commit b736beb1 authored by Jan Maximilian Michal's avatar Jan Maximilian Michal
Browse files

Update visibility of comment via PATCH, importer improvements

parent bf0799ed
No related branches found
No related tags found
No related merge requests found
Pipeline #
from .common_serializers import * # noqa from .common_serializers import * # noqa
from .feedback import FeedbackSerializer # noqa from .feedback import FeedbackSerializer, FeedbackCommentSerializer # noqa
from .subscription import * # noqa from .subscription import * # noqa
from .student import * # noqa from .student import * # noqa
from .submission import * # noqa from .submission import * # noqa
import unittest import unittest
from rest_framework import status from rest_framework import status
from rest_framework.test import (APIRequestFactory, APITestCase) from rest_framework.test import APIRequestFactory, APITestCase
from core import models from core import models
from core.models import Feedback, FeedbackComment, Submission, SubmissionType from core.models import Feedback, FeedbackComment, Submission, SubmissionType
...@@ -447,12 +447,12 @@ class FeedbackCommentApiEndpointTest(APITestCase): ...@@ -447,12 +447,12 @@ class FeedbackCommentApiEndpointTest(APITestCase):
comment = FeedbackComment.objects.get(of_tutor=self.tutor02) comment = FeedbackComment.objects.get(of_tutor=self.tutor02)
self.client.force_authenticate(self.tutor01) self.client.force_authenticate(self.tutor01)
response = self.client.delete(self.url % comment.pk) response = self.client.delete(self.url % comment.pk)
self.assertEqual(status.HTTP_403_FORBIDDEN, response.status_code) self.assertEqual(status.HTTP_404_NOT_FOUND, response.status_code)
def test_reviewer_can_delete_everything_they_want(self): def test_reviewer_can_delete_everything_they_want(self):
reviewer = self.data['reviewers'][0] reviewer = self.data['reviewers'][0]
self.client.force_authenticate(user=reviewer) self.client.force_authenticate(user=reviewer)
comment01 = FeedbackComment.objects.get(of_tutor=self.tutor02) comment01 = FeedbackComment.objects.get(of_tutor=self.tutor01)
comment02 = FeedbackComment.objects.get(of_tutor=self.tutor02) comment02 = FeedbackComment.objects.get(of_tutor=self.tutor02)
response = self.client.delete(self.url % comment01.pk) response = self.client.delete(self.url % comment01.pk)
...@@ -460,3 +460,26 @@ class FeedbackCommentApiEndpointTest(APITestCase): ...@@ -460,3 +460,26 @@ class FeedbackCommentApiEndpointTest(APITestCase):
response = self.client.delete(self.url % comment02.pk) response = self.client.delete(self.url % comment02.pk)
self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code) self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code)
try:
FeedbackComment.objects.get(of_tutor=self.tutor01)
FeedbackComment.objects.get(of_tutor=self.tutor02)
except FeedbackComment.DoesNotExist:
pass
else:
self.fail('No exception raised')
def test_reviewer_can_set_comment_visibility(self):
reviewer = self.data['reviewers'][0]
self.client.force_authenticate(user=reviewer)
comment = FeedbackComment.objects.get(of_tutor=self.tutor01)
self.assertTrue(comment.visible_to_student)
data = {
'visible_to_student': False
}
response = self.client.patch(self.url % comment.pk, data)
self.assertFalse(response.data['visible_to_student'])
comment.refresh_from_db()
self.assertFalse(comment.visible_to_student)
...@@ -112,16 +112,27 @@ class FeedbackApiView( ...@@ -112,16 +112,27 @@ class FeedbackApiView(
return Response(serializer.data) return Response(serializer.data)
class FeedbackCommentApiView(viewsets.GenericViewSet): class FeedbackCommentApiView(
mixins.DestroyModelMixin,
viewsets.GenericViewSet):
""" Gets a list of an individual exam by Id if provided """ """ Gets a list of an individual exam by Id if provided """
permission_classes = (permissions.IsTutorOrReviewer,) permission_classes = (permissions.IsTutorOrReviewer,)
queryset = models.FeedbackComment.objects.all() queryset = models.FeedbackComment.objects.all()
serializer_class = serializers.FeedbackCommentSerializer
def destroy(self, request, *args, **kwargs): def get_queryset(self):
comment = self.get_object() user = self.request.user
if user.role == models.UserAccount.REVIEWER:
return self.queryset
return self.queryset.filter(of_tutor=user)
user = request.user def partial_update(self, request, **kwargs):
if user.role == models.UserAccount.TUTOR and user != comment.of_tutor: keys = self.request.data.keys()
raise PermissionDenied(detail='Can only delete your own commits.') if keys - {'visible_to_student', 'of_line', 'text'}:
raise PermissionDenied('These fields cannot be changed.')
return Response(status=status.HTTP_204_NO_CONTENT) comment = self.get_object()
serializer = self.get_serializer(comment, request.data, partial=True)
serializer.is_valid()
serializer.save()
return Response(serializer.data)
...@@ -60,7 +60,7 @@ parser.add_argument( ...@@ -60,7 +60,7 @@ parser.add_argument(
user_t = namedtuple('user_head', 'name matrikel_no') user_t = namedtuple('user_head', 'name matrikel_no')
# one task has a title and id and hpfly code # one task has a title and id and hpfly code
task_head_re = re.compile(r'^Quellcode Frage (?P<title>.*) ?(\d{8})?$') task_head_re = re.compile(r'^Quellcode Frage (?P<title>.*?) ?(\d{8})?$')
# nor parsing the weird mat no # nor parsing the weird mat no
matno_re = re.compile(r'^(?P<matrikel_no>\d{8})-(\d+)-(\d+)$') matno_re = re.compile(r'^(?P<matrikel_no>\d{8})-(\d+)-(\d+)$')
...@@ -73,8 +73,9 @@ def converter(infile, usernames=None, number_of_tasks=0,): ...@@ -73,8 +73,9 @@ def converter(infile, usernames=None, number_of_tasks=0,):
def sheet_iter_meta(sheet): def sheet_iter_meta(sheet):
""" yield first and second col entry as tuple of (name, matnr) """ """ yield first and second col entry as tuple of (name, matnr) """
for row in (sheet.row(i) for i in range(1, sheet.nrows)): for row in (sheet.row(i) for i in range(1, sheet.nrows)):
m = re.search(matno_re, row[1].value) match = re.search(matno_re, row[1].value)
yield row[0].value, m.group('matrikel_no') if m else row[1].value if match:
yield row[0].value, match.group('matrikel_no')
def sheet_iter_data(sheet): def sheet_iter_data(sheet):
""" yields all source code titel and code tuples """ """ yields all source code titel and code tuples """
...@@ -90,7 +91,7 @@ def converter(infile, usernames=None, number_of_tasks=0,): ...@@ -90,7 +91,7 @@ def converter(infile, usernames=None, number_of_tasks=0,):
# nice! # nice!
name2mat = dict(sheet_iter_meta(meta)) name2mat = dict(sheet_iter_meta(meta))
assert meta.nrows - 1 == len(name2mat), f'{meta.nrows} != {len(name2mat)}' assert len(name2mat) == len(data), f'{len(name2mat)} names != {len(data)} sheets' # noqa
# from xls to lists and namedtuples # from xls to lists and namedtuples
# [ [user0, task0_h, code0, ..., taskn, coden ], ..., [...] ] # [ [user0, task0_h, code0, ..., taskn, coden ], ..., [...] ]
......
...@@ -216,7 +216,7 @@ def do_load_submission_types(): ...@@ -216,7 +216,7 @@ def do_load_submission_types():
for row in csv_rows: for row in csv_rows:
tid, name, score = (col.strip() for col in row) tid, name, score = (col.strip() for col in row)
with \ with \
open(os.path.join(lsg_dir, tid + '-lsg.c'), open(os.path.join(lsg_dir, tid + '.c'),
encoding='utf-8') as lsg, \ encoding='utf-8') as lsg, \
open(os.path.join(desc_dir, tid + '.html'), open(os.path.join(desc_dir, tid + '.html'),
encoding='utf-8') as desc: encoding='utf-8') as desc:
......
import abc import abc
import hashlib import hashlib
import json import json
import logging
import os import os
import re import re
import shutil import shutil
...@@ -14,6 +15,8 @@ try: ...@@ -14,6 +15,8 @@ try:
except ModuleNotFoundError: except ModuleNotFoundError:
from util import testcases from util import testcases
log = logging.getLogger(__name__)
def run_cmd(cmd, stdin=None, check=False, timeout=1): def run_cmd(cmd, stdin=None, check=False, timeout=1):
return subprocess.run( return subprocess.run(
...@@ -37,6 +40,12 @@ def sha1(submission_obj): ...@@ -37,6 +40,12 @@ def sha1(submission_obj):
return hashlib.sha1(submission_obj['code'].encode()).hexdigest() return hashlib.sha1(submission_obj['code'].encode()).hexdigest()
def get_submission_id(submission_obj):
t = submission_obj['type']
m = re.search(r'(a0\d)', t)
return m.group(0)
class Test(metaclass=abc.ABCMeta): class Test(metaclass=abc.ABCMeta):
"""docstring for IliasQuestion""" """docstring for IliasQuestion"""
...@@ -127,11 +136,11 @@ class LinkTest(Test): ...@@ -127,11 +136,11 @@ class LinkTest(Test):
def run_test(self, submission_obj): def run_test(self, submission_obj):
t = submission_obj['type'] if submission_obj['type'] not in testcases_dict:
m = re.search(r'(a0\d)', t) return False, 'This program was not required to be executable.'
ret = run_cmd( cid = get_submission_id(submission_obj)
f"gcc-7 -o code objects/{m.group(0)}-testing.o code.o") ret = run_cmd(f"gcc-7 -o ./bin/{cid} objects/{cid}-testing.o code.o")
return not ret.returncode, ret.stderr return not ret.returncode, ret.stderr
...@@ -143,9 +152,9 @@ class UnitTestTest(Test): ...@@ -143,9 +152,9 @@ class UnitTestTest(Test):
label_failure = 'UNITTEST_FAILED' label_failure = 'UNITTEST_FAILED'
@staticmethod @staticmethod
def testcase(i, args, stdout): def testcase(i, args, stdout, cid):
try: try:
ret = run_cmd("./code %s" % args, check=True, timeout=0.1) ret = run_cmd("./bin/%s %s" % (cid, args), check=True, timeout=0.1)
assert ret.stdout == stdout assert ret.stdout == stdout
except AssertionError: except AssertionError:
return False, "Case #{}: [ASSERT FAIL] ./prog {:>2} WAS '{}' SHOULD '{}'".format( # noqa: E501 return False, "Case #{}: [ASSERT FAIL] ./prog {:>2} WAS '{}' SHOULD '{}'".format( # noqa: E501
...@@ -160,10 +169,13 @@ class UnitTestTest(Test): ...@@ -160,10 +169,13 @@ class UnitTestTest(Test):
def run_test(self, submission_obj): def run_test(self, submission_obj):
task = self.testcases_dict[submission_obj['type']] task = testcases_dict[submission_obj['type']]
results, messages = zip(*list(self.testcase(i, case, result) cid = get_submission_id(submission_obj)
for i, (case, result) in enumerate( return_data = [self.testcase(i, case, result, cid)
zip(task['cases'], task['results'])))) for i, (case, result) in enumerate(zip(task['cases'],
task['results']))
]
results, messages = zip(*return_data)
return all(results), '\n'.join(messages) return all(results), '\n'.join(messages)
...@@ -171,8 +183,8 @@ class UnitTestTest(Test): ...@@ -171,8 +183,8 @@ class UnitTestTest(Test):
def process(descfile, binaries, objects, submissions, header, highest_test): def process(descfile, binaries, objects, submissions, header, highest_test):
if isinstance(highest_test, str): if isinstance(highest_test, str):
highestTestClass = Test.available_tests()[highest_test] highestTestClass = Test.available_tests()[highest_test]
highestTestClass.testcases_dict = testcases.evaluated_testcases( global testcases_dict
descfile, binaries) testcases_dict = testcases.evaluated_testcases(descfile, binaries)
with open(submissions) as submission_file: with open(submissions) as submission_file:
submissions_json = json.JSONDecoder().decode( submissions_json = json.JSONDecoder().decode(
...@@ -180,10 +192,11 @@ def process(descfile, binaries, objects, submissions, header, highest_test): ...@@ -180,10 +192,11 @@ def process(descfile, binaries, objects, submissions, header, highest_test):
# Get something disposable # Get something disposable
path = tempfile.mkdtemp() path = tempfile.mkdtemp()
run_cmd(f'cp -r {objects} {path}') run_cmd(f'cp -r {objects} {path}')
run_cmd(f'cp -r {binaries} {path}') run_cmd(f'cp -r {binaries} {path}')
run_cmd(f'cp -r {header} {path}') run_cmd(f'cp -r {header} {path}')
os.chdir(path) os.chdir(path)
os.makedirs('bin')
def iterate_submissions(): def iterate_submissions():
yield from (obj yield from (obj
...@@ -207,6 +220,7 @@ def parseme(): ...@@ -207,6 +220,7 @@ def parseme():
parser.add_argument('objects') parser.add_argument('objects')
parser.add_argument('submissions') parser.add_argument('submissions')
parser.add_argument('header') parser.add_argument('header')
parser.add_argument('test')
return parser.parse_args() return parser.parse_args()
...@@ -214,10 +228,12 @@ if __name__ == '__main__': ...@@ -214,10 +228,12 @@ if __name__ == '__main__':
args = parseme() args = parseme()
testcases_dict = testcases.evaluated_testcases(args.descfile, testcases_dict = testcases.evaluated_testcases(args.descfile,
args.binaries) args.binaries)
print(json.dumps(process(args.descfile, print(json.dumps(process(args.descfile,
args.binaries, args.binaries,
args.objects, args.objects,
args.submissions, args.submissions,
args.header, UnitTestTest), args.header,
args.test),
sort_keys=True, sort_keys=True,
indent=4)) indent=4))
...@@ -62,6 +62,10 @@ def testcases_generator(task, n=10): ...@@ -62,6 +62,10 @@ def testcases_generator(task, n=10):
if not syntax: if not syntax:
return return
if syntax == 'NO INPUT':
yield 'NO INPUT'
return
yield '' yield ''
yield '0' yield '0'
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment