Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • j.michal/grady
1 result
Show changes
Commits on Source (14)
Showing
with 106 additions and 116 deletions
......@@ -96,7 +96,9 @@ class ExportInstanceTest(APITestCase):
def setUp(self):
self.client = APIClient()
self.client.force_login(user=self.data['reviewers'][0])
self.response = self.client.get('/api/instance/export/')
self.response = self.client.get('/api/instance/export/',
data={'setPasswords': True,
'selected_exam': self.data['exams'][0].exam_type_id})
def test_can_access(self):
self.assertEqual(status.HTTP_200_OK, self.response.status_code)
......@@ -180,7 +182,8 @@ class ExportJSONTest(APITestCase):
def setUp(self):
self.client = APIClient()
self.client.force_login(user=self.data['reviewers'][0])
self.response = self.client.post('/api/export/json/')
self.response = self.client.post('/api/export/json/',
data={'selected_exam': self.data['exams'][0].exam_type_id})
def test_can_access(self):
self.assertEqual(status.HTTP_200_OK, self.response.status_code)
......@@ -194,8 +197,8 @@ class ExportJSONTest(APITestCase):
self.assertEqual('', student1['Name'])
self.assertEqual('', student2['Name'])
self.assertEqual('Test Exam 01', student1['Exams'][0]['exam']['module_reference'])
self.assertEqual('Test Exam 01', student2['Exams'][0]['exam']['module_reference'])
self.assertEqual('Test Exam 01', student1['Exam'])
self.assertEqual('Test Exam 01', student2['Exam'])
self.assertEqual('student01', student1['Username'])
self.assertEqual('student02', student2['Username'])
......@@ -203,17 +206,17 @@ class ExportJSONTest(APITestCase):
self.assertEqual('********', student2['Password'])
self.assertEqual('********', student1['Password'])
self.assertEqual('01. Sort', student1['Scores'][0]['submissions'][0]['type'])
self.assertEqual('01. Sort', student2['Scores'][0]['submissions'][0]['type'])
self.assertEqual('01. Sort', student1['Scores'][0]['type'])
self.assertEqual('01. Sort', student2['Scores'][0]['type'])
self.assertEqual('02. Shuffle', student1['Scores'][0]['submissions'][1]['type'])
self.assertEqual('02. Shuffle', student2['Scores'][0]['submissions'][1]['type'])
self.assertEqual('02. Shuffle', student1['Scores'][1]['type'])
self.assertEqual('02. Shuffle', student2['Scores'][1]['type'])
self.assertEqual(5, student1['Scores'][0]['submissions'][0]['score'])
self.assertEqual(0, student2['Scores'][0]['submissions'][0]['score'])
self.assertEqual(5, student1['Scores'][0]['score'])
self.assertEqual(0, student2['Scores'][0]['score'])
self.assertEqual(0, student2['Scores'][0]['submissions'][1]['score'])
self.assertEqual(0, student2['Scores'][0]['submissions'][1]['score'])
self.assertEqual(0, student2['Scores'][1]['score'])
self.assertEqual(0, student2['Scores'][1]['score'])
class ExportJSONAndSetPasswordsTest(APITestCase):
......@@ -225,7 +228,8 @@ class ExportJSONAndSetPasswordsTest(APITestCase):
self.client = APIClient()
self.client.force_login(user=self.data['reviewers'][0])
self.response = self.client.post('/api/export/json/',
data={'setPasswords': True})
data={'setPasswords': True,
'selected_exam': self.data['exams'][0].exam_type_id})
def test_can_access(self):
self.assertEqual(status.HTTP_200_OK, self.response.status_code)
......
......@@ -9,7 +9,7 @@ from core.models import StudentInfo, UserAccount, ExamType, SubmissionType
from core.permissions import IsReviewer
from core.serializers import SubmissionTypeSerializer, \
ExamSerializer, UserAccountSerializer
from core.serializers.student import StudentExportSerializer, ExamInfoSerializer
from core.serializers.student import StudentExportSerializer
from core.serializers.tutor import CorrectorSerializer
words = xp.generate_wordlist(wordfile=xp.locate_wordfile(), min_length=5, max_length=8)
......@@ -34,25 +34,24 @@ class StudentJSONExport(APIView):
def post(self, request, format=None):
set_passwords = request.data.get('set_passwords')
passwords = _set_student_passwords() if set_passwords else None
selected_exam = request.data.get('selected_exam')
result = ExamType.objects.get(exam_type_id=selected_exam)
content = [
{'Matrikel': student.matrikel_no,
'Name': student.user.fullname,
'Username': student.user.username,
'Email': student.user.email,
'Exams': ExamInfoSerializer(student.exams.all(), many=True).data,
'Exam': result.module_reference,
'Password': passwords[student.user.pk] if set_passwords else '********',
'Scores': [
{
'exam': exam_info.exam.module_reference,
'submissions': [
{
'type': submission_type,
'score': score
} for submission_type, score in exam_info.score_per_submission().items()]
} for exam_info in student.exams.all()]
'type': submission_type,
'score': score
} for submission_type, score in student.exams.get(
exam__exam_type_id=selected_exam).score_per_submission().items()]
} for student
in StudentInfo.objects.all()]
in StudentInfo.objects.all().filter(exams__exam__exam_type_id=selected_exam)]
return Response(content)
......
......@@ -13,6 +13,7 @@
"axios": "^0.18.0",
"file-saver": "^2.0.2",
"highlight.js": "^9.12.0",
"marked":"^4.0.18",
"v-clipboard": "^2.0.1",
"vue": "^2.6.12",
"vue-class-component": "^6.0.0",
......
......@@ -288,7 +288,7 @@ export async function fetchReleases () {
return (await ax.get(url)).data as GitlabRelease[]
}
export interface StudentExportOptions { setPasswords?: boolean }
export interface StudentExportOptions { setPasswords?: boolean, selectedExam: string }
export interface StudentExportItem {
Matrikel: string,
Name: string,
......
......@@ -4,6 +4,8 @@ import { fetchStudentExportData, StudentExportItem, InstanceExportData, fetchIns
import { getters } from '@/store/getters'
import { mutations as mut } from '@/store/mutations'
import { saveAs } from 'file-saver'
import { Exam } from '@/models'
import { ConfigModule } from '../../store/modules/config'
let download = saveAs
......@@ -30,9 +32,11 @@ export class exportMixin extends Vue {
async getExportFile (type: string) {
this.loading = true
let selected_exam = ConfigModule.state.config.examId
let studentData
if (type === 'data') {
studentData = await fetchStudentExportData({ setPasswords: this.setPasswords })
studentData = await fetchStudentExportData({ setPasswords: this.setPasswords, selectedExam: selected_exam })
} else if (type === 'instance') {
studentData = await fetchInstanceExportData()
} else {
......
......@@ -5,21 +5,21 @@
<h3>Tips on using the correction interface</h3>
</v-card-title>
<v-card-text>
Markdown is rendered by default. <br>
Select the Σ button to choose to have math rendered or not. <br>
In case you need the un-rendered markdown, click the "Copy to Clipboard" button. <br>
Cick on the individual line numbers in order to add feedback for a specific line. <br>
After adding feedback to a line, clicking the feedback button will hide it or show it. <br>
When feedback is hidden, the lines that contain feedback will be highlighted in red. <br>
<!-- ------THE OLD FLAVOR TEXT, SAVED IN CASE IT IS NEEDED FOR SOME REASON (it is very cool) -------
Never trade an ale.
The sea-dog leads with yellow fever, crush the captain's quarters until it waves.<br>
Ho-ho-ho! malaria of life.<br>
Halitosis, adventure, and yellow fever.<br>
The girl drinks with halitosis, pull the galley before it laughs.<br>
The moon fires with life, vandalize the bikini atoll before it travels.<br>
The tuna blows with fight, haul the freighter before it whines.<br>
The cannibal robs with hunger, fire the lighthouse until it whines.<br>
The captain loves with death, vandalize the lighthouse before it whines.<br>
The anchor loots with treasure, raid the freighter before it grows.<br>
The reef commands with endurance, view the quarter-deck until it whines.<br>
The scallywag loots with passion, crush the bikini atoll before it falls.<br>
The sea leads with treasure, ransack the brig until it dies.<br>
The parrot robs with desolation, view the seychelles before it screams.<br>
The warm anchor quirky blows the landlubber.<br>
The warm anchor quirky blows the landlubber.<br> -->
</v-card-text>
</v-card>
</template>
......
......@@ -16,7 +16,11 @@
</td>
<td class="code-cell-content pl-2">
<!-- eslint-disable-next-line -->
<span class="code-line" :key="key" v-html="code"/>
<span
:key="key"
class="code-line"
v-html="html"
/>
<slot />
</td>
</div>
......@@ -46,7 +50,8 @@ export default {
},
data () {
return {
key: 0
key: 0,
html:''
}
},
computed: {
......@@ -58,6 +63,8 @@ export default {
subNotesEventBus.$on('resetSubmission', () => {
this.key++
})
const {marked} = require('marked')
this.html = marked.parse(this.code)
},
methods: {
toggleEditor () {
......@@ -77,7 +84,7 @@ export default {
}
.code-line {
white-space: pre-wrap;
white-space: normal;
font-family: monospace;
}
......
......@@ -23,12 +23,35 @@
Score: {{ score }} </span>
<v-spacer />
<toggle-feedback-visibility-button />
&nbsp;
<div v-if="isMarkdown">
<v-btn @click="$emit('input', !mathIsRendered)">
<!-- <v-btn @click="$emit('input', !mathIsRendered)">
{{ mathIsRendered ? 'Reset Math' : 'Render Math' }}
</v-btn> -->
<v-btn
v-if="mathIsRendered"
text
color="info"
title="Math is being rendered"
:style="{backgroundColor: '#cce7ff'}"
@click="$emit('input', !mathIsRendered)"
>
<v-icon>
functions
</v-icon>
</v-btn>
<v-btn
v-else
text
color="grey"
title="Math is not being rendered"
@click="$emit('input', !mathIsRendered)"
>
<v-icon>
functions
</v-icon>
</v-btn>
</div>
<v-spacer />
<v-tooltip
v-if="sourceCodeAvailable"
top
......
<template>
<v-btn
v-if="showFeedback"
id="feedback-visibility-toggle"
text
color="info"
title="Feedback is being shown"
:style="{backgroundColor: '#cce7ff'}"
@click="showFeedback = !showFeedback"
>
<div v-if="showFeedback">
Hide Feedback
</div>
<div v-else>
Show Feedback
</div>
<v-icon>
rate_review
</v-icon>
</v-btn>
<v-btn
v-else
id="feedback-visibility-toggle"
text
color="grey"
title="Feedback is not being shown"
@click="showFeedback = !showFeedback"
>
<v-icon>
rate_review
</v-icon>
</v-btn>
</template>
......
......@@ -2416,7 +2416,7 @@ domain-browser@^1.1.1:
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==
domelementtype@1, domelementtype@^1.3.1:
domelementtype@1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
......@@ -2569,26 +2569,6 @@ entities@^2.0.0:
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
entities@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
entities@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
entities@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
entities@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
errno@^0.1.3, errno@~0.1.7:
version "0.1.8"
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f"
......@@ -4298,48 +4278,6 @@ is-negative-zero@^2.0.1:
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==
is-number-object@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0"
integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==
dependencies:
has-tostringtag "^1.0.0"
is-negative-zero@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==
is-number-object@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197"
integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==
is-negative-zero@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==
is-number-object@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197"
integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==
is-negative-zero@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==
is-number-object@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197"
integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==
is-negative-zero@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==
is-number-object@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197"
......@@ -4834,6 +4772,11 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
marked@^4.0.18:
version "4.1.0"
resolved "https://registry.yarnpkg.com/marked/-/marked-4.1.0.tgz#3fc6e7485f21c1ca5d6ec4a39de820e146954796"
integrity sha512-+Z6KDjSPa6/723PQYyc1axYZpYYpDnECDaU6hkaf5gqBieBkMKYReL5hteF2QizhlMbgbo8umXl/clZ67+GlsA==
md5.js@^1.3.4:
version "1.3.5"
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
......
......@@ -115,10 +115,9 @@ class ExportTestModal(GradyTestCase):
with open(JSON_EXPORT_FILE) as f:
data = json.load(f)
self.assertEqual('B.Inf.4242 Test Module',
data[0]['Exams'][0]['exam']['moduleReference'])
self.assertEqual('B.Inf.4242 Test Module', data[0]['Exam'])
except Exception as e:
print(data)
raise e
finally:
os.remove(JSON_EXPORT_FILE)
......
......@@ -182,7 +182,7 @@ def reconstruct_code_from_table(table_el):
# https://github.com/SeleniumHQ/selenium/issues/2608
line.find_element_by_class_name('code-cell-content')
.find_element_by_class_name('code-line')
.get_attribute('textContent'))
.get_attribute('textContent').rstrip())
for line in lines
]
line_no_code_pairs.sort(key=lambda x: x[0]) # sort by ids
......
......@@ -32,9 +32,7 @@ except (IOError, Exception):
to generate your secret key!' % SECRET_FILE)
# adjust this setting to your needs
ALLOWED_HOSTS = [
'localhost', '.grady.janmax.org', 'grady.informatik.uni-goettingen.de'
]
ALLOWED_HOSTS = [ 'localhost', '.informatik.uni-goettingen.de' ]
# sample postgres sql database configuration
DATABASES = {
......
......@@ -25,7 +25,7 @@ class SubmissionTypeFactory(DjangoModelFactory):
full_score = 15
description = factory.Sequence(
lambda n: f'Type {n} \n<h1>This</h1> is a description containing html')
solution = factory.Sequence(lambda n: f'//This is a solution\n#include<stdio.h>\n\nint main() {{\n\tprintf("Hello World\\n");\n\treturn {n};\n}}') # noqa
solution = factory.Sequence(lambda n: f'test case {n}') # noqa
programming_language = models.SubmissionType.C
exam_type = factory.SubFactory(ExamTypeFactory)
......@@ -87,7 +87,7 @@ class SubmissionFactory(DjangoModelFactory):
class Meta:
model = models.Submission
text = factory.Sequence(lambda n: f'#include<stdio.h>\n\nint main() {{\n\tprintf("Hello World\\n");\n\treturn {n};\n}}') # noqa
text = factory.Sequence(lambda n: f'test case {n}\nx\nx\nx\ntest case {n}') # noqa
type = factory.SubFactory(SubmissionTypeFactory)
student = factory.SubFactory(StudentInfoFactory)
......