commit
92d03dbe50
|
|
@ -1,24 +1,35 @@
|
|||
<template>
|
||||
<div class="assignment-with-submissions">
|
||||
<!--<h1 class="assignment-with-submissions__title">{{assignment.assignment}}</h1>-->
|
||||
<h4 class="assignment-with-submissions__heading">Aufgabe</h4>
|
||||
|
||||
<p class="assignment-with-submissions__text">{{assignment.assignment}}</p>
|
||||
|
||||
<div>
|
||||
<a class="button button--primary submissions-page__back" @click="$emit('back')">Aufgabe im Modul anzeigen</a>
|
||||
</div>
|
||||
|
||||
<div class="assignment-with-submissions__solution" v-if="assignment.solution">
|
||||
<h4 class="assignment-with-submissions__heading">Lösung</h4>
|
||||
<p class="assignment-with-submissions__solution-text">{{assignment.solution}}</p>
|
||||
</div>
|
||||
<p v-if="!assignment.submissions.length">Zu diesem Auftrag sind noch keine Ergebnisse vorhanden</p>
|
||||
<p class="assignment-with-submissions__no-submissions" v-if="!assignment.submissions.length">Zu diesem Auftrag sind noch keine Ergebnisse vorhanden</p>
|
||||
|
||||
<div v-if="assignment.submissions.length" class="assignment-with-submissions__submissions submissions">
|
||||
<div class="submissions__header student-submission-row submission-header">
|
||||
<p class="submission-header__title">Lernende</p>
|
||||
<p class="submission-header__title">Ergebnisse</p>
|
||||
<p class="submission-header__title">Feedback</p>
|
||||
</div>
|
||||
<router-link
|
||||
:to="submissionLink(submission)"
|
||||
v-for="(submission, index) in submissions"
|
||||
v-for="submission in submissions"
|
||||
class="assignment-with-submissions__link"
|
||||
:key="index">
|
||||
:key="submission.id">
|
||||
<student-submission class="assignment-with-submissions__submission"
|
||||
:submission="submission"
|
||||
>
|
||||
</student-submission>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -80,6 +91,7 @@
|
|||
}
|
||||
|
||||
&__text {
|
||||
font-size: toRem(26px);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
|
|
@ -95,10 +107,27 @@
|
|||
|
||||
&__link {
|
||||
display: block;
|
||||
&:first-of-type {
|
||||
border-top: 1px solid $color-silver-dark;
|
||||
}
|
||||
}
|
||||
|
||||
&__submissions {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
&__no-submissions {
|
||||
margin-top: $large-spacing;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.submissions {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.submission-header {
|
||||
&__title {
|
||||
color: $color-silver-dark;
|
||||
font-family: $sans-serif-font-family;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="student-submission">
|
||||
<div class="student-submission student-submission-row">
|
||||
<div class="student-submission__student-name">
|
||||
{{name}}
|
||||
</div>
|
||||
|
|
@ -9,6 +9,9 @@
|
|||
<student-submission-document :document="submission.document" class="entry-document"></student-submission-document>
|
||||
</p>
|
||||
</div>
|
||||
<div class="student-submission__feedback entry" v-if="submission.submissionFeedback">
|
||||
<p class="entry__text" :class="{'entry__text--final': submission.submissionFeedback.final}">{{submission.submissionFeedback.text | trimToLength(50)}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -50,18 +53,13 @@
|
|||
@import "@/styles/_functions.scss";
|
||||
|
||||
.student-submission {
|
||||
display: grid;
|
||||
grid-template-columns: 170px 1fr;
|
||||
grid-column-gap: 80px;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid $color-silver-dark;
|
||||
padding: 15px 0;
|
||||
|
||||
&__student-name {
|
||||
font-size: toRem(17px);
|
||||
font-weight: 800;
|
||||
font-family: $sans-serif-font-family;
|
||||
}
|
||||
|
||||
&__entry {
|
||||
font-size: toRem(14px);
|
||||
font-family: $sans-serif-font-family;
|
||||
|
|
@ -71,4 +69,13 @@
|
|||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.entry {
|
||||
&__text {
|
||||
color: $color-silver-dark;
|
||||
&--final {
|
||||
color: $color-charcoal-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -7,39 +7,22 @@
|
|||
<solution :value="solution" v-if="assignment.solution"></solution>
|
||||
|
||||
<template v-if="isStudent">
|
||||
<div class="assignment__submission">
|
||||
<submission-form
|
||||
v-if="isStudent"
|
||||
@turnIn="turnIn"
|
||||
@saveInput="saveInput"
|
||||
@reopen="reopen"
|
||||
@changeDocumentUrl="changeDocumentUrl"
|
||||
:user-input="submission"
|
||||
placeholder="Ergebnis erfassen"
|
||||
action="Ergebnis mit Lehrperson teilen"
|
||||
shared-msg="Das Ergebnis wurde mit der Lehrperson geteilt."
|
||||
:saved="!unsaved"
|
||||
>
|
||||
</submission-form>
|
||||
|
||||
<div class="assignment__inputs">
|
||||
<submission-form
|
||||
@input="saveInput"
|
||||
:submission="submission"
|
||||
:saved="!unsaved"
|
||||
:final="final"
|
||||
></submission-form>
|
||||
</div>
|
||||
|
||||
<div class="assignment__actions" v-if="!final">
|
||||
<button
|
||||
class="assignment__submit button button--primary button--white-bg"
|
||||
@click="turnIn"
|
||||
>Ergebnis mit Lehrperson teilen
|
||||
</button>
|
||||
|
||||
<div v-if="assignment.submission.document">
|
||||
<document-block
|
||||
:value="{url: assignment.submission.document}"
|
||||
show-trash-icon
|
||||
v-on:trash="changeDocumentUrl('')"
|
||||
></document-block>
|
||||
</div>
|
||||
|
||||
<simple-file-upload
|
||||
v-on:link-change-url="changeDocumentUrl"
|
||||
:value="assignment.submission.document"
|
||||
></simple-file-upload>
|
||||
</div>
|
||||
|
||||
<final-submission :submission="assignment.submission" v-if="final" @reopen="reopen"></final-submission>
|
||||
<div v-if="this.assignment.submission.submissionFeedback" class="assignment__feedback">
|
||||
<p>{{feedbackText}}</p>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="!isStudent">
|
||||
|
|
@ -59,6 +42,7 @@
|
|||
import cloneDeep from 'lodash/cloneDeep'
|
||||
|
||||
import FinalSubmission from '@/components/content-blocks/assignment/FinalSubmission';
|
||||
import SubmissionInput from '@/components/content-blocks/assignment/SubmissionInput';
|
||||
import SubmissionForm from '@/components/content-blocks/assignment/SubmissionForm';
|
||||
import DocumentForm from '@/components/content-forms/DocumentForm';
|
||||
import DocumentBlock from '@/components/content-blocks/DocumentBlock';
|
||||
|
|
@ -71,10 +55,11 @@
|
|||
components: {
|
||||
DocumentBlock,
|
||||
DocumentForm,
|
||||
SubmissionForm,
|
||||
SubmissionInput,
|
||||
FinalSubmission,
|
||||
Solution,
|
||||
SimpleFileUpload
|
||||
SimpleFileUpload,
|
||||
SubmissionForm
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
|
@ -95,6 +80,10 @@
|
|||
},
|
||||
id() {
|
||||
return this.assignment.id ? this.assignment.id.replace(/=/g, '') : ''
|
||||
},
|
||||
feedbackText() {
|
||||
let feedback = this.assignment.submission.submissionFeedback;
|
||||
return `Feedback von ${feedback.teacher.firstName} ${feedback.teacher.lastName}: ${feedback.text}`;
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -266,25 +255,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
&__submission {
|
||||
@include input-box-shadow;
|
||||
background-color: $color-white;
|
||||
border-radius: $input-border-radius;
|
||||
border: 1px solid $color-silver;
|
||||
padding: $medium-spacing;
|
||||
}
|
||||
|
||||
&__inputs {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
&__submit {
|
||||
margin-right: $medium-spacing;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&__feedback {
|
||||
margin-top: $medium-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<div class="final-submission">
|
||||
<document-block
|
||||
v-if="submission.document"
|
||||
:value="{url: submission.document}"
|
||||
v-if="userInput.document"
|
||||
:value="{url: userInput.document}"
|
||||
class="final-submission__document"
|
||||
></document-block>
|
||||
<div class="final-submission__explanation">
|
||||
<info-icon class="final-submission__explanation-icon"></info-icon>
|
||||
<span class="final-submission__explanation-text">Das Ergebnis wurde mit der Lehrperson geteilt</span>
|
||||
<span class="final-submission__explanation-text">{{sharedMsg}}</span>
|
||||
<a class="final-submission__reopen" @click="$emit('reopen')">Bearbeiten</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
import {newLineToParagraph} from '@/helpers/text';
|
||||
|
||||
export default {
|
||||
props: ['submission'],
|
||||
props: ['userInput', 'sharedMsg'],
|
||||
|
||||
components: {
|
||||
InfoIcon,
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
|
||||
computed: {
|
||||
text() {
|
||||
return newLineToParagraph(this.submission.text);
|
||||
return newLineToParagraph(this.userInput.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,77 +1,123 @@
|
|||
<template>
|
||||
<div class="submission-form__text-answer submission-form">
|
||||
<textarea
|
||||
v-auto-grow
|
||||
rows="1"
|
||||
class="submission-form__textarea"
|
||||
placeholder="Ergebnis erfassen"
|
||||
:readonly="final"
|
||||
:value="submission.text"
|
||||
@input="$emit('input', $event.target.value)"
|
||||
></textarea>
|
||||
<div class="submission-form__save-status submission-form__save-status--saved" v-if="saved">
|
||||
<tick-circle-icon class="submission-form__save-status-icon"></tick-circle-icon>
|
||||
<div class="feedback__submission submission-form-container">
|
||||
<div class="submission-form-container__inputs">
|
||||
<submission-input
|
||||
@input="saveInput"
|
||||
:input-text="userInput.text"
|
||||
:saved="saved"
|
||||
:final="final"
|
||||
:placeholder="placeholder"
|
||||
:reopen="reopenSubmission"
|
||||
></submission-input>
|
||||
</div>
|
||||
|
||||
<div class="submission-form-container__actions" v-if="!final">
|
||||
<button class="submission-form-container__submit button button--primary button--white-bg"
|
||||
@click="$emit('turnIn')"
|
||||
>{{action}}
|
||||
</button>
|
||||
<div v-if="userInput.document">
|
||||
<document-block
|
||||
:value="{url: userInput.document}"
|
||||
show-trash-icon
|
||||
v-on:trash="changeDocumentUrl('')"
|
||||
></document-block>
|
||||
</div>
|
||||
|
||||
<simple-file-upload
|
||||
v-if="allowsDocuments"
|
||||
v-on:link-change-url="changeDocumentUrl"
|
||||
:value="userInput.document"
|
||||
class="submission-form-container__document"
|
||||
></simple-file-upload>
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
<final-submission
|
||||
v-if="final"
|
||||
:user-input="userInput"
|
||||
:shared-msg="sharedMsg"
|
||||
@reopen="$emit('reopen')"></final-submission>
|
||||
</div>
|
||||
<div class="submission-form__save-status submission-form__save-status--unsaved" v-if="!saved">
|
||||
<loading-icon class="submission-form__save-status-icon submission-form__saving-icon"></loading-icon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TickCircleIcon from '@/components/icons/TickCircleIcon';
|
||||
import LoadingIcon from '@/components/icons/LoadingIcon';
|
||||
import SubmissionInput from '@/components/content-blocks/assignment/SubmissionInput';
|
||||
import FinalSubmission from '@/components/content-blocks/assignment/FinalSubmission';
|
||||
import SimpleFileUpload from '@/components/SimpleFileUpload';
|
||||
import DocumentBlock from '@/components/content-blocks/DocumentBlock';
|
||||
|
||||
export default {
|
||||
props: ['submission', 'saved', 'final'],
|
||||
|
||||
components: {
|
||||
TickCircleIcon,
|
||||
LoadingIcon
|
||||
}
|
||||
SubmissionInput,
|
||||
FinalSubmission,
|
||||
SimpleFileUpload,
|
||||
DocumentBlock
|
||||
},
|
||||
|
||||
props: {
|
||||
userInput: Object,
|
||||
saved: Boolean,
|
||||
placeholder: String,
|
||||
action: String,
|
||||
reopen: Function,
|
||||
document: String,
|
||||
sharedMsg: String
|
||||
},
|
||||
|
||||
computed: {
|
||||
final() {
|
||||
return !!this.userInput && this.userInput.final
|
||||
},
|
||||
allowsDocuments() {
|
||||
return 'document' in this.userInput;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
reopenSubmission() {
|
||||
this.$emit('reopen');
|
||||
},
|
||||
saveInput(input) {
|
||||
this.$emit('saveInput', input);
|
||||
},
|
||||
changeDocumentUrl(documentUrl) {
|
||||
this.$emit('changeDocumentUrl', documentUrl);
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
@import '@/styles/_mixins.scss';
|
||||
|
||||
.submission-form {
|
||||
.submission-form-container {
|
||||
|
||||
@include input-box-shadow;
|
||||
background-color: $color-white;
|
||||
border-radius: $input-border-radius;
|
||||
border: 1px solid $color-silver;
|
||||
padding: $medium-spacing;
|
||||
|
||||
&__inputs {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
&__submit {
|
||||
margin-right: $medium-spacing;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__textarea {
|
||||
display: flex;
|
||||
width: 95%;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
line-height: 1.5;
|
||||
border: 0;
|
||||
min-height: 110px;
|
||||
}
|
||||
|
||||
&__save-status {
|
||||
position: relative;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__save-status-icon {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
fill: $color-silver-dark;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
&__saving-icon {
|
||||
animation: spin 2.5s linear infinite;
|
||||
&__document {
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
<template>
|
||||
<div class="submission-form__text-answer submission-form">
|
||||
<textarea
|
||||
v-auto-grow
|
||||
rows="1"
|
||||
class="submission-form__textarea"
|
||||
:placeholder="placeholder"
|
||||
:readonly="final"
|
||||
:value="inputText"
|
||||
@input="$emit('input', $event.target.value)"
|
||||
></textarea>
|
||||
<div class="submission-form__save-status submission-form__save-status--saved" v-if="saved">
|
||||
<tick-circle-icon class="submission-form__save-status-icon"></tick-circle-icon>
|
||||
</div>
|
||||
<div class="submission-form__save-status submission-form__save-status--unsaved" v-if="!saved">
|
||||
<loading-icon class="submission-form__save-status-icon submission-form__saving-icon"></loading-icon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TickCircleIcon from '@/components/icons/TickCircleIcon';
|
||||
import LoadingIcon from '@/components/icons/LoadingIcon';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
inputText: String,
|
||||
saved: Boolean,
|
||||
final: Boolean,
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: 'Ergebnis erfassen'
|
||||
}
|
||||
},
|
||||
components: {
|
||||
TickCircleIcon,
|
||||
LoadingIcon
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
|
||||
.submission-form {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
&__textarea {
|
||||
display: flex;
|
||||
width: 95%;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
line-height: 1.5;
|
||||
border: 0;
|
||||
min-height: 110px;
|
||||
}
|
||||
|
||||
&__save-status {
|
||||
position: relative;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__save-status-icon {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
fill: $color-silver-dark;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
&__saving-icon {
|
||||
animation: spin 2.5s linear infinite;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -19,6 +19,11 @@ query AssignmentWithSubmissions($id: ID!) {
|
|||
}
|
||||
}
|
||||
}
|
||||
submissionFeedback {
|
||||
id
|
||||
text
|
||||
final
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,5 +8,13 @@ fragment AssignmentParts on AssignmentNode {
|
|||
text
|
||||
final
|
||||
document
|
||||
submissionFeedback {
|
||||
id
|
||||
text
|
||||
teacher {
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
mutation UpdateSubmissionFeedback($input: UpdateSubmissionFeedbackInput!) {
|
||||
updateSubmissionFeedback(input: $input){
|
||||
successful
|
||||
updatedSubmissionFeedback {
|
||||
id
|
||||
text
|
||||
final
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,12 @@ query StudentSubmissions($id: ID!) {
|
|||
}
|
||||
assignment {
|
||||
title
|
||||
assignment
|
||||
}
|
||||
submissionFeedback {
|
||||
id
|
||||
text
|
||||
final
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,37 +1,68 @@
|
|||
<template>
|
||||
<div class="article submission-page">
|
||||
<div class="article__header">
|
||||
<h1 class="article__title">{{studentSubmission.assignment.title}}</h1>
|
||||
<h2 class="article__subtitle">{{fullName}}</h2>
|
||||
<div class="submission-page">
|
||||
<div class="submission-page__header submission-header">
|
||||
<h2>Aufgabe</h2>
|
||||
<p>{{studentSubmission.assignment.assignment}}</p>
|
||||
</div>
|
||||
<div class="article__content article-content">
|
||||
<div class="submission-page__content submission-content">
|
||||
<h2>Ergebnis von {{fullName}}</h2>
|
||||
<p v-html="text"></p>
|
||||
<p v-if="studentSubmission.document && studentSubmission.document.length > 0" class="article-content__document">
|
||||
<a :href="studentSubmission.document" class="entry-document__link link" target="_blank">
|
||||
<student-submission-document :document="studentSubmission.document"></student-submission-document>
|
||||
</a>
|
||||
</p>
|
||||
<p class="article-content__text" v-html="text"></p>
|
||||
</div>
|
||||
<div class="submission-page__feedback feedback">
|
||||
<submission-form
|
||||
v-if="studentSubmission"
|
||||
@turnIn="turnIn"
|
||||
@saveInput="saveInput"
|
||||
@reopen="reopen"
|
||||
:user-input="feedback"
|
||||
placholder="Feedback erfassen"
|
||||
action="Feedback teilen"
|
||||
shared-msg="Dieses Feedback wurde geteilt."
|
||||
:saved="!unsaved"
|
||||
>
|
||||
<div v-if="!final" class="feedback-submission__emojis emojis">
|
||||
<span v-for="(emoji, index) in emojis"
|
||||
:key="index"
|
||||
@click="addEmoji(emoji)"
|
||||
class="emojis__emoji">{{emoji}}</span>
|
||||
</div>
|
||||
</submission-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {newLineToParagraph} from '@/helpers/text';
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
import StudentSubmissionDocument from '@/components/StudentSubmissionDocument';
|
||||
import STUDENT_SUBMISSIONS_QUERY from '@/graphql/gql/studentSubmissionQuery.gql';
|
||||
import UPDATE_FEEDBACK_MUTATION from '@/graphql/gql/mutations/updateFeedback.gql';
|
||||
import SubmissionForm from '@/components/content-blocks/assignment/SubmissionForm';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
StudentSubmissionDocument
|
||||
StudentSubmissionDocument,
|
||||
SubmissionForm
|
||||
},
|
||||
|
||||
computed: {
|
||||
final() {
|
||||
return !!this.studentSubmission && this.studentSubmission.final;
|
||||
},
|
||||
text() {
|
||||
return newLineToParagraph(this.studentSubmission.text);
|
||||
},
|
||||
fullName() {
|
||||
return `${this.studentSubmission.student.firstName} ${this.studentSubmission.student.lastName}`
|
||||
},
|
||||
feedback() {
|
||||
return this.studentSubmission.submissionFeedback ? this.studentSubmission.submissionFeedback : {text: '', final: false};
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -48,6 +79,98 @@
|
|||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
addEmoji(emoji) {
|
||||
const feedbackText = this.feedback.text + emoji;
|
||||
this.updateFeedbackText(feedbackText);
|
||||
},
|
||||
_save: debounce(function () {
|
||||
this.saving++;
|
||||
this.$apollo.mutate({
|
||||
mutation: UPDATE_FEEDBACK_MUTATION,
|
||||
variables: {
|
||||
input: {
|
||||
submissionFeedback: {
|
||||
studentSubmission: this.studentSubmission.id,
|
||||
text: this.studentSubmission.submissionFeedback.text,
|
||||
}
|
||||
}
|
||||
},
|
||||
update: this.updateCache
|
||||
}).then(() => {
|
||||
this.saving--;
|
||||
if (this.saving === 0) {
|
||||
this.unsaved = false;
|
||||
}
|
||||
});
|
||||
}, 500),
|
||||
saveInput: function (feedbackText) {
|
||||
this.unsaved = true;
|
||||
/*
|
||||
We update the assignment on this component, so the changes are reflected on it. The server does not return
|
||||
the updated entity, to prevent the UI to update when the user is entering his input
|
||||
*/
|
||||
this.updateFeedbackText(feedbackText);
|
||||
this._save();
|
||||
},
|
||||
turnIn() {
|
||||
this.$apollo.mutate({
|
||||
mutation: UPDATE_FEEDBACK_MUTATION,
|
||||
variables: {
|
||||
input: {
|
||||
submissionFeedback: {
|
||||
studentSubmission: this.studentSubmission.id,
|
||||
text: this.studentSubmission.submissionFeedback.text,
|
||||
final: true
|
||||
}
|
||||
}
|
||||
},
|
||||
update: this.updateCache
|
||||
});
|
||||
},
|
||||
reopen() {
|
||||
if (!this.studentSubmission.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$apollo.mutate({
|
||||
mutation: UPDATE_FEEDBACK_MUTATION,
|
||||
variables: {
|
||||
input: {
|
||||
submissionFeedback: {
|
||||
studentSubmission: this.studentSubmission.id,
|
||||
text: this.studentSubmission.submissionFeedback.text,
|
||||
final: false
|
||||
}
|
||||
}
|
||||
},
|
||||
update: this.updateCache
|
||||
});
|
||||
},
|
||||
updateCache(store, {data: {updateSubmissionFeedback: {successful, updatedSubmissionFeedback}}}) {
|
||||
try {
|
||||
if (successful) {
|
||||
const query = STUDENT_SUBMISSIONS_QUERY;
|
||||
const variables = {
|
||||
id: this.studentSubmission.id
|
||||
};
|
||||
const data = store.readQuery({query, variables});
|
||||
|
||||
data.studentSubmission.submissionFeedback = Object.assign({}, updatedSubmissionFeedback);
|
||||
store.writeQuery({query, variables, data});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
// Query did not exist in the cache, and apollo throws a generic Error. Do nothing
|
||||
}
|
||||
},
|
||||
updateFeedbackText(text) {
|
||||
this.studentSubmission = Object.assign({}, this.studentSubmission, {
|
||||
submissionFeedback: Object.assign({}, this.studentSubmission.submissionFeedback, {text: text})
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
studentSubmission: {
|
||||
|
|
@ -59,21 +182,39 @@
|
|||
lastName: ''
|
||||
},
|
||||
text: '',
|
||||
document: ''
|
||||
}
|
||||
document: '',
|
||||
submissionFeedback: {
|
||||
text: '',
|
||||
final: false
|
||||
}
|
||||
},
|
||||
unsaved: false,
|
||||
saving: 0,
|
||||
emojis: ['😀', '🤮', '🤩', '😎', '🤔', '👍🏻', '👎🏻']
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.article-content {
|
||||
&__document {
|
||||
margin-bottom: 1rem;
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_functions.scss";
|
||||
|
||||
.submission-page {
|
||||
&__content {
|
||||
margin-top: 1.5*$large-spacing;
|
||||
}
|
||||
|
||||
&__text /deep/ > p {
|
||||
margin-bottom: 1em;
|
||||
&__feedback {
|
||||
margin-top: $large-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
.emojis {
|
||||
font-size: toRem(32px);
|
||||
|
||||
&__emoji {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,15 @@
|
|||
<template>
|
||||
<div class="submissions-page">
|
||||
<div>
|
||||
<a class="button button--primary submissions-page__back" @click="back">Zurück zur Aufgabe</a>
|
||||
</div>
|
||||
|
||||
<h2 class="submissions-page__heading">Aufgabe</h2>
|
||||
<assignment-with-submissions v-if="!$apollo.queries.assignment.loading"
|
||||
:assignment="assignment"></assignment-with-submissions>
|
||||
:assignment="assignment"
|
||||
@back="back"></assignment-with-submissions>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AssignmentWithSubmissions from '@/components/AssignmentWithSubmissions';
|
||||
|
||||
import ASSIGNMENT_WITH_SUBMISSIONS_QUERY from '@/graphql/gql/assignmentWithSubmissionsQuery.gql';
|
||||
|
||||
export default {
|
||||
|
|
@ -54,15 +51,21 @@
|
|||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_mixins.scss";
|
||||
@import "@/styles/_variables.scss";
|
||||
|
||||
.submissions-page {
|
||||
display: grid;
|
||||
|
||||
grid-row-gap: $large-spacing;
|
||||
grid-template-rows: auto 1fr;
|
||||
|
||||
margin-top: 2rem;
|
||||
margin-left: $large-spacing;
|
||||
margin-right: $large-spacing;
|
||||
|
||||
@include desktop {
|
||||
width: 800px;
|
||||
margin-left: $medium-spacing;
|
||||
margin-right: $medium-spacing;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
|
||||
.student-submission-row {
|
||||
display: grid;
|
||||
grid-template-columns: 100px 1fr 1fr;
|
||||
grid-column-gap: 20px;
|
||||
|
||||
@include desktop {
|
||||
grid-template-columns: 170px 1fr 1fr;
|
||||
grid-column-gap: 80px;
|
||||
}
|
||||
|
||||
align-items: center;
|
||||
border-bottom: 1px solid $color-silver-dark;
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
|
@ -20,3 +20,4 @@
|
|||
@import "solutions";
|
||||
@import "password_forms";
|
||||
@import "public-page";
|
||||
@import "student-submission";
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -3,7 +3,7 @@ import random
|
|||
import factory
|
||||
|
||||
from books.factories import ModuleFactory
|
||||
from .models import Assignment, StudentSubmission
|
||||
from .models import Assignment, StudentSubmission, SubmissionFeedback
|
||||
|
||||
from core.factories import fake
|
||||
|
||||
|
|
@ -24,3 +24,12 @@ class StudentSubmissionFactory(factory.django.DjangoModelFactory):
|
|||
text = factory.LazyAttribute(lambda x: fake.sentence(nb_words=random.randint(4, 8)))
|
||||
assignment = factory.SubFactory(AssignmentFactory)
|
||||
final = False
|
||||
|
||||
|
||||
class SubmissionFeedbackFactory(factory.django.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = SubmissionFeedback
|
||||
|
||||
text = factory.LazyAttribute(lambda x: fake.sentence(nb_words=random.randint(4, 8)))
|
||||
student_submission = factory.SubFactory(StudentSubmissionFactory)
|
||||
final = False
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
# Generated by Django 2.0.6 on 2019-11-12 14:13
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django_extensions.db.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('assignments', '0005_assignment_solution'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SubmissionFeedback',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, verbose_name='created')),
|
||||
('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, verbose_name='modified')),
|
||||
('text', models.TextField(blank=True)),
|
||||
('student_submission', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='feedback', to='assignments.StudentSubmission')),
|
||||
('teacher', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feedbacks', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('-modified', '-created'),
|
||||
'get_latest_by': 'modified',
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.0.6 on 2019-11-13 12:47
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assignments', '0006_submissionfeedback'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='submissionfeedback',
|
||||
name='final',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 2.0.6 on 2019-11-13 14:30
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assignments', '0007_submissionfeedback_final'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='submissionfeedback',
|
||||
name='student_submission',
|
||||
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='assignments.StudentSubmission'),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 2.0.6 on 2019-11-13 14:33
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assignments', '0008_auto_20191113_1430'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='submissionfeedback',
|
||||
name='id',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='submissionfeedback',
|
||||
name='student_submission',
|
||||
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='assignments.StudentSubmission'),
|
||||
),
|
||||
]
|
||||
|
|
@ -37,3 +37,11 @@ class StudentSubmission(TimeStampedModel):
|
|||
|
||||
def __str__(self):
|
||||
return '{} - {}'.format(self.student.full_name, self.text)
|
||||
|
||||
|
||||
class SubmissionFeedback(TimeStampedModel):
|
||||
text = models.TextField(blank=True)
|
||||
teacher = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='feedbacks')
|
||||
student_submission = models.OneToOneField(StudentSubmission, on_delete=models.CASCADE, primary_key=True, related_name='submission_feedback')
|
||||
final = models.BooleanField(default=False)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,3 +7,10 @@ class AssignmentInput(InputObjectType):
|
|||
answer = graphene.String(required=True)
|
||||
document = graphene.String()
|
||||
final = graphene.Boolean()
|
||||
|
||||
|
||||
class SubmissionFeedbackInput(InputObjectType):
|
||||
id = graphene.ID()
|
||||
student_submission = graphene.ID(required=True)
|
||||
text = graphene.String(required=True)
|
||||
final = graphene.Boolean()
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import graphene
|
||||
from graphene import relay
|
||||
from graphql_relay import from_global_id
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
from api.utils import get_object
|
||||
from assignments.models import Assignment
|
||||
from assignments.schema.types import AssignmentNode, StudentSubmissionNode
|
||||
from .inputs import AssignmentInput
|
||||
from assignments.models import Assignment, SubmissionFeedback
|
||||
from assignments.schema.types import AssignmentNode, StudentSubmissionNode, SubmissionFeedbackNode
|
||||
from .inputs import AssignmentInput, SubmissionFeedbackInput
|
||||
|
||||
|
||||
class UpdateAssignment(relay.ClientIDMutation):
|
||||
|
|
@ -30,5 +32,35 @@ class UpdateAssignment(relay.ClientIDMutation):
|
|||
return cls(successful=True, updated_assignment=assignment, submission=submission, errors=None)
|
||||
|
||||
|
||||
class UpdateSubmissionFeedback(relay.ClientIDMutation):
|
||||
class Input:
|
||||
submission_feedback = graphene.Argument(SubmissionFeedbackInput)
|
||||
|
||||
updated_submission_feedback = graphene.Field(SubmissionFeedbackNode)
|
||||
successful = graphene.Boolean()
|
||||
errors = graphene.List(graphene.String)
|
||||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||
submission_feedback_data = kwargs.get('submission_feedback')
|
||||
user = info.context.user
|
||||
student_submission_id = from_global_id(submission_feedback_data['student_submission'])[1]
|
||||
|
||||
if not user.has_perm('users.can_manage_school_class_content'):
|
||||
raise PermissionDenied('Missing permissions')
|
||||
|
||||
(submission_feedback, created) = SubmissionFeedback.objects.get_or_create(teacher=user,
|
||||
student_submission_id=student_submission_id)
|
||||
|
||||
final = submission_feedback_data.get('final') if 'final' in submission_feedback_data else submission_feedback.final
|
||||
|
||||
submission_feedback.final = final
|
||||
submission_feedback.text = submission_feedback_data.get('text')
|
||||
submission_feedback.save()
|
||||
|
||||
return cls(successful=True, updated_submission_feedback=submission_feedback, errors=None)
|
||||
|
||||
|
||||
class AssignmentMutations(object):
|
||||
update_assignment = UpdateAssignment.Field()
|
||||
update_submission_feedback = UpdateSubmissionFeedback.Field()
|
||||
|
|
|
|||
|
|
@ -1,17 +1,48 @@
|
|||
import graphene
|
||||
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
||||
from graphene import relay
|
||||
from graphene_django import DjangoObjectType
|
||||
|
||||
from assignments.models import Assignment, StudentSubmission
|
||||
from assignments.models import Assignment, StudentSubmission, SubmissionFeedback
|
||||
from books.utils import are_solutions_enabled_for
|
||||
|
||||
|
||||
class SubmissionFeedbackNode(DjangoObjectType):
|
||||
class Meta:
|
||||
model = SubmissionFeedback
|
||||
filter_fields = []
|
||||
interfaces = (relay.Node,)
|
||||
|
||||
|
||||
class StudentSubmissionNode(DjangoObjectType):
|
||||
submission_feedback = graphene.Field(SubmissionFeedbackNode)
|
||||
|
||||
class Meta:
|
||||
model = StudentSubmission
|
||||
filter_fields = []
|
||||
interfaces = (relay.Node,)
|
||||
|
||||
def resolve_submission_feedback(self, info, **kwargs):
|
||||
|
||||
user = info.context.user
|
||||
|
||||
if not hasattr(self, 'submission_feedback'):
|
||||
return None
|
||||
|
||||
# teacher path
|
||||
if user.has_perm('users.can_manage_school_class_content'):
|
||||
if self.submission_feedback.teacher == user:
|
||||
return self.submission_feedback
|
||||
else:
|
||||
raise PermissionDenied('Missing permissions')
|
||||
|
||||
# student path
|
||||
|
||||
if self.submission_feedback.final:
|
||||
return self.submission_feedback
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class AssignmentNode(DjangoObjectType):
|
||||
submission = graphene.Field(StudentSubmissionNode)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ class AssignmentPermissionsTestCase(DefaultUserTestCase):
|
|||
self.assignment_id = to_global_id('AssignmentNode', self.assignment.pk)
|
||||
self.module_id = to_global_id('ModuleNode', self.assignment.module.pk)
|
||||
|
||||
|
||||
def _submit_submission(self, user=None):
|
||||
mutation = '''
|
||||
mutation UpdateAssignment($input: UpdateAssignmentInput!) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,203 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-11-13
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
|
||||
from graphql_relay import to_global_id
|
||||
|
||||
from api.test_utils import create_client, DefaultUserTestCase
|
||||
from assignments.models import Assignment, StudentSubmission
|
||||
from users.factories import SchoolClassFactory
|
||||
from ..factories import AssignmentFactory, StudentSubmissionFactory, SubmissionFeedbackFactory
|
||||
|
||||
|
||||
class SubmissionFeedbackTestCase(DefaultUserTestCase):
|
||||
def setUp(self):
|
||||
super(SubmissionFeedbackTestCase, self).setUp()
|
||||
|
||||
self.assignment = AssignmentFactory(
|
||||
owner=self.teacher
|
||||
)
|
||||
self.assignment_id = to_global_id('AssignmentNode', self.assignment.pk)
|
||||
|
||||
self.student_submission = StudentSubmissionFactory(assignment=self.assignment, student=self.student1, final=False)
|
||||
self.student_submission_id = to_global_id('StudentSubmissionNode', self.student_submission.pk)
|
||||
|
||||
school_class = SchoolClassFactory()
|
||||
school_class.users.add(self.student1)
|
||||
school_class.users.add(self.teacher)
|
||||
school_class.users.add(self.teacher2)
|
||||
|
||||
def _create_submission_feedback(self, user, final, text, student_submission_id):
|
||||
mutation = '''
|
||||
mutation UpdateSubmissionFeedback($input: UpdateSubmissionFeedbackInput!) {
|
||||
updateSubmissionFeedback(input: $input){
|
||||
updatedSubmissionFeedback {
|
||||
id
|
||||
text
|
||||
final
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
client = create_client(user)
|
||||
|
||||
return client.execute(mutation, variables={
|
||||
'input': {
|
||||
"submissionFeedback": {
|
||||
"studentSubmission": student_submission_id,
|
||||
"text": text,
|
||||
"final": final
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
def _fetch_assignment_student(self, user):
|
||||
client = create_client(user)
|
||||
query = '''
|
||||
query AssignmentWithSubmissions($id: ID!) {
|
||||
assignment(id: $id) {
|
||||
title
|
||||
submission {
|
||||
id
|
||||
text
|
||||
document
|
||||
submissionFeedback {
|
||||
text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
return client.execute(query, variables={
|
||||
'id': self.assignment_id
|
||||
})
|
||||
|
||||
def _fetch_assignment_teacher(self, user):
|
||||
client = create_client(user)
|
||||
query = '''
|
||||
query AssignmentWithSubmissions($id: ID!) {
|
||||
assignment(id: $id) {
|
||||
title
|
||||
submissions {
|
||||
id
|
||||
text
|
||||
document
|
||||
submissionFeedback {
|
||||
text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
return client.execute(query, variables={
|
||||
'id': self.assignment_id
|
||||
})
|
||||
|
||||
def _fetch_submission_feedback(self, user):
|
||||
client = create_client(user)
|
||||
query = '''
|
||||
query AssignmentWithSubmissions($id: ID!) {
|
||||
assignment(id: $id) {
|
||||
title
|
||||
submissions {
|
||||
id
|
||||
text
|
||||
document
|
||||
submissionFeedback {
|
||||
text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
return client.execute(query, variables={
|
||||
'id': self.assignment_id
|
||||
})
|
||||
|
||||
def test_teacher_can_create_feedback(self):
|
||||
|
||||
result = self._create_submission_feedback(self.teacher, False, 'Balalal', self.student_submission_id)
|
||||
|
||||
self.assertIsNone(result.get('errors'))
|
||||
self.assertIsNotNone(result.get('data').get('updateSubmissionFeedback').get('updatedSubmissionFeedback').get('id'))
|
||||
|
||||
def test_student_cannot_create_feedback(self):
|
||||
|
||||
result = self._create_submission_feedback(self.student1, False, 'Balalal', self.student_submission_id)
|
||||
self.assertIsNotNone(result.get('errors'))
|
||||
|
||||
def test_teacher_can_update_feedback(self):
|
||||
|
||||
assignment = AssignmentFactory(
|
||||
owner=self.teacher
|
||||
)
|
||||
|
||||
student_submission = StudentSubmissionFactory(assignment=assignment, student=self.student1, final=False)
|
||||
submission_feedback = SubmissionFeedbackFactory(teacher=self.teacher, final=False,
|
||||
student_submission=student_submission)
|
||||
submission_feedback_id = to_global_id('SubmissionFeedback', submission_feedback.pk)
|
||||
|
||||
result = self._create_submission_feedback(self.teacher, True, 'Some', submission_feedback_id)
|
||||
|
||||
self.assertIsNone(result.get('errors'))
|
||||
|
||||
submission_feedback_response = result.get('data').get('updateSubmissionFeedback').get('updatedSubmissionFeedback')
|
||||
|
||||
self.assertTrue(submission_feedback_response.get('final'))
|
||||
self.assertEqual(submission_feedback_response.get('text'), 'Some')
|
||||
|
||||
def test_rogue_teacher_cannot_update_feedback(self):
|
||||
|
||||
assignment = AssignmentFactory(
|
||||
owner=self.teacher
|
||||
)
|
||||
|
||||
student_submission = StudentSubmissionFactory(assignment=assignment, student=self.student1, final=False)
|
||||
submission_feedback = SubmissionFeedbackFactory(teacher=self.teacher, final=False,
|
||||
student_submission=student_submission)
|
||||
submission_feedback_id = to_global_id('SubmissionFeedback', submission_feedback.pk)
|
||||
|
||||
result = self._create_submission_feedback(self.teacher2, True, 'Some', submission_feedback_id)
|
||||
|
||||
self.assertIsNotNone(result.get('errors'))
|
||||
|
||||
def test_student_does_not_see_non_final_feedback(self):
|
||||
|
||||
SubmissionFeedbackFactory(teacher=self.teacher, final=False, student_submission=self.student_submission)
|
||||
result = self._fetch_assignment_student(self.student1)
|
||||
|
||||
self.assertIsNone(result.get('data').get('submissionFeedback'))
|
||||
|
||||
def test_student_does_see_final_feedback(self):
|
||||
|
||||
submission_feedback = SubmissionFeedbackFactory(teacher=self.teacher, final=True,
|
||||
student_submission=self.student_submission)
|
||||
result = self._fetch_assignment_student(self.student1)
|
||||
self.assertEqual(result.get('data').get('assignment').get('submission').get('submissionFeedback')
|
||||
.get('text'), submission_feedback.text)
|
||||
|
||||
def test_teacher_can_see_feedback_for_submission(self):
|
||||
submission_feedback = SubmissionFeedbackFactory(teacher=self.teacher, final=False,
|
||||
student_submission=self.student_submission)
|
||||
self.student_submission.final = True
|
||||
self.student_submission.save()
|
||||
|
||||
result = self._fetch_assignment_teacher(self.teacher)
|
||||
self.assertEqual(result.get('data').get('assignment').get('submissions')[0].get('submissionFeedback')
|
||||
.get('text'), submission_feedback.text)
|
||||
|
||||
def test_rogue_teacher_cannot_see_feedback(self):
|
||||
SubmissionFeedbackFactory(teacher=self.teacher, final=False,
|
||||
student_submission=self.student_submission)
|
||||
self.student_submission.final = True
|
||||
self.student_submission.save()
|
||||
|
||||
result = self._fetch_assignment_teacher(self.teacher2)
|
||||
self.assertIsNone(result.get('data').get('assignment').get('submissions')[0].get('submissionFeedback'))
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# Generated by Django 2.0.6 on 2019-11-12 14:13
|
||||
|
||||
from django.db import migrations
|
||||
import wagtail.core.blocks
|
||||
import wagtail.core.fields
|
||||
import wagtail.images.blocks
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('basicknowledge', '0003_auto_20190912_1228'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='basicknowledge',
|
||||
name='contents',
|
||||
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['bold', 'ul']))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('section_title', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())]))], blank=True, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# Generated by Django 2.0.6 on 2019-11-12 14:13
|
||||
|
||||
import assignments.models
|
||||
from django.db import migrations
|
||||
import surveys.models
|
||||
import wagtail.core.blocks
|
||||
import wagtail.core.fields
|
||||
import wagtail.images.blocks
|
||||
import wagtail.snippets.blocks
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('books', '0015_contentblock_bookmarks'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='contentblock',
|
||||
name='contents',
|
||||
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.core.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('module_room_slug', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock())])), ('content_list_item', wagtail.core.blocks.StreamBlock([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.core.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('module_room_slug', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock())]))]))], blank=True, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -44,7 +44,7 @@ class User(AbstractUser):
|
|||
def get_teacher(self):
|
||||
if self.user_roles.filter(role__key='teacher').exists():
|
||||
return self
|
||||
elif self.school_classes.count()>0:
|
||||
elif self.school_classes.count() > 0:
|
||||
return self.school_classes.first().get_teacher()
|
||||
else:
|
||||
return None
|
||||
|
|
|
|||
Loading…
Reference in New Issue