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
  • AlekSIS/official/AlekSIS-App-Alsijil
  • sunweaver/AlekSIS-App-Alsijil
  • 8tincsoVluke/AlekSIS-App-Alsijil
  • perfreicpo/AlekSIS-App-Alsijil
  • noifobarep/AlekSIS-App-Alsijil
  • 7ingannisdo/AlekSIS-App-Alsijil
  • unmruntartpa/AlekSIS-App-Alsijil
  • balrorebta/AlekSIS-App-Alsijil
  • comliFdifwa/AlekSIS-App-Alsijil
  • 3ranaadza/AlekSIS-App-Alsijil
10 results
Show changes
Commits on Source (122)
Showing
with 1456 additions and 21 deletions
...@@ -38,6 +38,7 @@ Licence ...@@ -38,6 +38,7 @@ Licence
Copyright © 2020, 2021 Julian Leucker <leuckeju@katharineum.de> Copyright © 2020, 2021 Julian Leucker <leuckeju@katharineum.de>
Copyright © 2020, 2022 Hangzhi Yu <yuha@katharineum.de> Copyright © 2020, 2022 Hangzhi Yu <yuha@katharineum.de>
Copyright © 2021 Lloyd Meins <meinsll@katharineum.de> Copyright © 2021 Lloyd Meins <meinsll@katharineum.de>
Copyright © 2022 magicfelix <felix@felix-zauberer.de>
Licenced under the EUPL, version 1.2 or later, by Teckids e.V. (Bonn, Germany). Licenced under the EUPL, version 1.2 or later, by Teckids e.V. (Bonn, Germany).
......
...@@ -17,4 +17,5 @@ class AlsijilConfig(AppConfig): ...@@ -17,4 +17,5 @@ class AlsijilConfig(AppConfig):
([2020, 2021], "Julian Leucker", "leuckeju@katharineum.de"), ([2020, 2021], "Julian Leucker", "leuckeju@katharineum.de"),
([2020, 2022], "Hangzhi Yu", "yuha@katharineum.de"), ([2020, 2022], "Hangzhi Yu", "yuha@katharineum.de"),
([2021], "Lloyd Meins", "meinsll@katharineum.de"), ([2021], "Lloyd Meins", "meinsll@katharineum.de"),
([2022], "magicfelix", "felix@felix-zauberer.de"),
) )
export const
ERROR = "ERROR", // Something went wrong
SAVED = "SAVED", // Everything alright
UPDATING = "UPDATING", // We are sending something to the server
CHANGES = "CHANGES" // the user changed something, but it has not been saved yet
query CourseBook($lessonId: ID!) {
excuseTypes {
id
name
shortName
}
lesson: lessonById(id: $lessonId) {
groups {
name
shortName
members {
id
fullName
}
}
subject {
name
}
plannedLessonperiodsDatetimes {
year
week
datetimeStart
lessonPeriod{
id
period{
period
}
}
}
}
lessonDocumentations: lessonDocumentationsByLessonId(id: $lessonId) {
id
topic
homework
groupNote
year
week
lessonPeriod {
id
period {
id
period
}
}
event {
id
}
extraLesson {
id
}
period
date
personalNotes {
id
person {
id
fullName
}
tardiness
absent
excused
excuseType {
id
name
shortName
}
remarks
extraMarks {
id
name
shortName
}
}
}
extraMarks {
id
name
shortName
}
}
<template>
<ApolloQuery
:query="require('./CourseBook.graphql')"
:variables="{ lessonId: $route.params.lessonId }"
>
<template v-slot="{ result: { loading, error, data } }">
<!-- Error -->
<message-box v-if="error" type="error">{{ $t("alsijil.error_occurred") }}</message-box>
<!-- Result -->
<div v-else-if="data" class="result apollo">
<div class="d-flex justify-space-between">
<v-btn text color="primary" :href="$root.urls.select_coursebook()">
<v-icon left>mdi-chevron-left</v-icon>
{{ $t("alsijil.back") }}
</v-btn>
<update-indicator @manual-update="updateManually()" ref="indicator" :status="status"></update-indicator>
</div>
<v-row>
<v-col cols="12">
<lesson-documentations
:lesson-documentations="data.lessonDocumentations"
:planned-lesson-periods-date-times="data.lesson.plannedLessonperiodsDatetimes"
:groups="data.lesson.groups"
:excuse-types="data.excuseTypes"
:extra-marks="data.extraMarks"
:save-lesson-documentations-per-week="saveLessonDocumentationsPerWeek"
/>
</v-col>
</v-row>
</div>
<!-- No result or Loading -->
<div v-else class="text-center">
<v-progress-circular
indeterminate
color="primary"
class="ma-auto"
></v-progress-circular>
</div>
</template>
</ApolloQuery>
</template>
<script>
import {CHANGES, SAVED, UPDATING} from "../../UpdateStatuses.js";
import UpdateIndicator from "./UpdateIndicator.vue";
import LessonDocumentations from "./LessonDocumentations.vue";
export default {
components: {
UpdateIndicator,
LessonDocumentations,
},
props: [ "saveLessonDocumentationsPerWeek" ],
methods: {
processDataChange(event) {
this.status = CHANGES;
// alert("Probably save the data");
console.log(event);
setTimeout(() => {
this.status = UPDATING;
}, 500)
setTimeout(() => {
this.status = SAVED;
}, 1000)
},
updateManually(event) {
alert("Data sync triggered manually");
this.status = UPDATING;
setTimeout(() => {
this.status = SAVED;
}, 500)
},
},
name: "course-book",
data: () => {
return {
ping: "ping",
status: SAVED,
}
}
}
</script>
mutation UpdateOrCreateLessonDocumentation($year:Int!, $week:Int!, $lessonPeriodId:ID, $topic:String, $homework:String, $groupNote:String) {
updateOrCreateLessonDocumentation(year:$year, week:$week, lessonPeriodId:$lessonPeriodId, topic:$topic, homework:$homework, groupNote:$groupNote) {
lessonDocumentation{
id
topic
homework
groupNote
date
personalNotes {
id
person {
id
fullName
}
tardiness
absent
excused
excuseType {
id
name
shortName
}
remarks
extraMarks {
id
name
shortName
}
}
}
}
}
<template>
<ApolloMutation
:mutation="require('./LessonDocumentation.graphql')"
:variables=lessonDocumentationEdit
@done="onDone"
>
<template v-slot="{ mutate, loading, error }">
<v-card elevation="2" :loading="loading">
<v-form v-model="valid">
<v-card-title v-if="saveLessonDocumentationsPerWeek === 'True'">
<span
v-text="getWeekText(lessonDocumentationEdit)"
class="ma-1 text-h5">
</span>
</v-card-title>
<v-card-title v-else>
<v-hover v-slot="{ hover }">
<div>
<v-menu
v-model="showPicker"
:close-on-content-click="false"
transition="scale-transition"
offset-y
min-width="auto"
>
<template v-slot:activator="{ on, attrs }">
<span>
<span v-text="$d(new Date(lessonDocumentationEdit.date), 'short')"
class="ma-1 text-h5"></span>
<v-btn right v-bind="attrs" v-on="on" icon v-if="hover && dateAndPeriodEditable">
<v-icon>mdi-pencil-outline</v-icon>
</v-btn>
</span>
</template>
<v-date-picker
scrollable
no-title
@input="showPicker = false; $emit('change-date', $event)"
v-model="lessonDocumentationEdit.date"
></v-date-picker>
</v-menu>
</div>
</v-hover>
<v-hover v-slot="{ hover }" v-if="!(saveLessonDocumentationsPerWeek === 'True')">
<div>
<v-menu offset-y>
<template v-slot:activator="{ on, attrs }">
<span>
<span
v-text="$t('alsijil.period_number', {number: lessonDocumentationEdit.period})"
class="ma-1 text-h5"></span>
<v-btn
right
v-bind="attrs"
v-on="on"
icon
v-if="hover && dateAndPeriodEditable"
>
<v-icon>mdi-pencil-outline</v-icon>
</v-btn>
</span>
</template>
<v-list>
<!-- Fixme: load valid lessons -->
<v-list-item
v-for="(item, index) in [1, 2, 3, 4, 5, 6, 7, 8, 9]"
:key="index"
>
<v-list-item-title>{{ item }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</div>
</v-hover>
</v-card-title>
<v-card-text>
<v-row>
<v-col cols="12" md="12" lg="12">
<message-box type="error" v-if="error">{{ $t("alsijil.error_updating") }}</message-box>
<v-textarea
name="input-7-1"
:label="$t('alsijil.lesson_documentation.topic')"
rows="1"
auto-grow
required
v-model="lessonDocumentationEdit.topic"
></v-textarea>
<v-textarea
name="input-7-1"
:label="$t('alsijil.lesson_documentation.homework')"
rows="1"
auto-grow
v-model="lessonDocumentationEdit.homework"
></v-textarea>
<v-textarea
name="input-7-1"
:label="$t('alsijil.lesson_documentation.group_note')"
rows="1"
auto-grow
v-model="lessonDocumentationEdit.groupNote"
></v-textarea>
</v-col>
<v-col v-if="!(saveLessonDocumentationsPerWeek === 'True')" cols="12" md="4" lg="4">
Personal notes
<personal-notes
:lesson-documentation-id="lessonDocumentationEdit.id"
:groups="groups"
:excuse-types="excuseTypes"
:extra-marks="extraMarks"
v-model="lessonDocumentationEdit.personalNotes"
@change="$emit('change-personal-notes', $event)"
></personal-notes>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="error"
outlined
@click="$emit('cancel-lesson-documentation-dialog', $event)"
>
{{ $t('alsijil.cancel') }}
</v-btn>
<v-btn
color="success"
@click="mutate()"
>
{{ $t('alsijil.save') }}
</v-btn>
</v-card-actions>
</v-form>
</v-card>
</template>
</ApolloMutation>
</template>
<script>
import PersonalNotes from "./PersonalNotes.vue";
export default {
components: {PersonalNotes},
props: ["lessonDocumentationEdit", "groups", "excuseTypes", "extraMarks", "saveLessonDocumentationsPerWeek", "getWeekText"],
name: "lesson-documentation",
data() {
return {
dateAndPeriodEditable: false,
showPicker: false,
//lessonDocumentationEdit: {},
}
},
//created() {
//this.lessonDocumentationEdit = this.lessonDocumentation
//}
}
</script>
<template><div>
<v-dialog
v-model="dialog"
max-width="800"
>
<template v-slot:activator="{ on, attrs }">
<v-row>
<v-col cols="12" md="6" class="pb-0 pb-md-3">
<v-select
v-if="saveLessonDocumentationsPerWeek === 'True'"
:items="emptyLessonPeriods"
:label="$t('alsijil.coursebook.choose_week')"
:item-text="getWeekText"
v-model="selectedLessonPeriodDatetime"
return-object
></v-select>
<v-select
v-else
:items="emptyLessonPeriods"
:label="$t('alsijil.coursebook.choose_lesson_date')"
:item-text="getLessonText"
v-model="selectedLessonPeriodDatetime"
return-object
></v-select>
</v-col>
<v-col cols="12" md="6" class="pt-0 pt-md-3">
<v-btn
color="primary"
dark
v-bind="attrs"
v-on="on"
@click="createLessonDocumentation()"
>
{{ $t("alsijil.coursebook.create_documentation") }}
</v-btn>
</v-col>
</v-row>
</template>
<lesson-documentation
:lesson-documentation-edit="lessonDocumentationEdit"
:groups="groups"
:excuse-types="excuseTypes"
:extra-marks="extraMarks"
:save-lesson-documentations-per-week="saveLessonDocumentationsPerWeek"
:get-week-text="getWeekText"
@cancel-lesson-documentation-dialog="cancelDialog"
/>
</v-dialog>
<v-data-table
:headers="headers"
:items="computedLessonDocumentations"
@click:row="editLessonDocumentation"
class="elevation-1 my-3"
:expanded.sync="expanded"
show-expand
multi-sort
:sort-by="['year','week']"
:sort-desc="[true, true]"
>
<template v-slot:item.period="{ item }">
<span class="text-no-wrap">{{ (saveLessonDocumentationsPerWeek === "True") ? getWeekText(item) : getLessonText(item) }}</span>
</template>
<template v-slot:expanded-item="{ headers, item }">
<td :colspan="headers.length">
<template v-if="saveLessonDocumentationsPerWeek === 'True'" v-for="lessonDocumentation in item.documentations">
<v-list-item>
<v-list-item-content>
<v-list-item-title>{{ getLessonText(lessonDocumentation) }}</v-list-item-title>
<v-list-item-action>
<personal-notes
:lesson-documentation-id="lessonDocumentation.id"
:groups="groups"
:excuse-types="excuseTypes"
:extra-marks="extraMarks"
v-model="lessonDocumentation.personalNotes"
@change="$emit('change-personal-notes', $event)"
></personal-notes>
</v-list-item-action>
</v-list-item-content>
</v-list-item>
<v-divider></v-divider>
</template>
<template v-else v-for="personalNote in item.personalNotes">
<!-- FIXME: Add edit and delete functionality to personal note chips-->
<v-chip class="ma-1" v-if="personalNoteString(personalNote)">
{{ personalNote.person.fullName }}: {{ personalNoteString(personalNote) }}
</v-chip>
</template>
</td>
</template>
</v-data-table>
</div></template>
<script>
import LessonDocumentation from "./LessonDocumentation.vue";
import PersonalNotes from "./PersonalNotes.vue";
export default {
components: {LessonDocumentation, PersonalNotes},
props: [ "lessonDocumentations", "plannedLessonPeriodsDateTimes", "groups", "excuseTypes", "extraMarks", "saveLessonDocumentationsPerWeek" ],
name: "lesson-documentations",
data () {
return {
dialog: false,
expanded: [],
headers: [
{ text: this.$t("alsijil.period"), value: "period" },
{ text: this.$t("alsijil.lesson_documentation.topic"), value: "topic" },
{ text: this.$t("alsijil.lesson_documentation.homework"), value: "homework" },
{ text: this.$t("alsijil.lesson_documentation.group_note"), value: "groupNote" },
{ text: this.$t("alsijil.personal_note.title_plural"), value: "data-table-expand" }
],
lessonDocumentationEdit: {},
selectedLessonPeriodDatetime: {},
recordedWeeks: [],
}
},
computed: {
emptyLessonPeriods() {
if (this.saveLessonDocumentationsPerWeek === "True") {
let currentDatetime = new Date()
let weeks = {}
let lpdts = this.plannedLessonPeriodsDateTimes.filter(lp => new Date(lp.datetimeStart) > currentDatetime)
for (let ldIndex in lpdts) {
let ld = lpdts[ldIndex]
if (ld.week in weeks) {
weeks[ld.week]["planned"].push(ld)
} else {
weeks[ld.week] = {
"year": ld.year,
"week": ld.week,
"startDate": this.calculateStartDateOfCW(ld.year, ld.week),
"datetimeStart": ld.datetimeStart,
"lessonPeriod": ld.lessonPeriod,
"planned": [ld]
}
}
}
return Object.values(weeks) // FIXME sort by date
} else {
let currentDatetime = new Date()
return this.plannedLessonPeriodsDateTimes.filter(lp => new Date(lp.datetimeStart) > currentDatetime)
}
},
computedLessonDocumentations() {
if (this.saveLessonDocumentationsPerWeek === "True") {
let weeks = {}
for (let ldIndex in this.lessonDocumentations) {
let ld = this.lessonDocumentations[ldIndex]
if (ld.week in weeks) {
weeks[ld.week]["documentations"].push(ld)
} else {
weeks[ld.week] = {
"id": ld.id,
"startDate": this.calculateStartDateOfCW(ld.year, ld.week),
"year": ld.year,
"week": ld.week,
"topic": ld.topic,
"homework": ld.homework,
"groupNote": ld.groupNote,
"documentations": [ld]
}
}
}
return Object.values(weeks)
} else {
return this.lessonDocumentations
}
}
},
methods: {
cancelDialog() {
this.dialog = false;
this.lessonDocumentationEdit = {};
},
recordDocumentation(item) {
if (this.recordedWeeks.includes(item.week)) {
return false
}
this.recordedWeeks.push(item.week)
return true
},
async loadLessonDocumentation(item) {
const result = await this.$apollo.mutate({
mutation: require("./LessonDocumentation.graphql"),
variables: {
year: item.year,
week: item.week,
lessonPeriodId: item.lessonPeriod ? item.lessonPeriod.id : null,
eventId: item.event ? item.event.id : null,
extraLessonId: item.extraLesson ? item.extraLesson.id : null,
},
})
let lessonDocumentation = result.data.updateOrCreateLessonDocumentation.lessonDocumentation
this.lessonDocumentationEdit = {
id: lessonDocumentation.id,
year: item.year,
week: item.week,
date: lessonDocumentation.date,
period: item.period,
lessonPeriodId: item.lessonPeriod ? item.lessonPeriod.id : null,
eventId: item.event ? item.event.id : null,
extraLessonId: item.extraLesson ? item.extraLesson.id : null,
topic: lessonDocumentation.topic,
homework: lessonDocumentation.homework,
groupNote: lessonDocumentation.groupNote,
personalNotes: lessonDocumentation.personalNotes,
}
},
editLessonDocumentation(item) {
if (this.saveLessonDocumentationsPerWeek === "True") {
this.loadLessonDocumentation(item.documentations[0])
} else {
this.loadLessonDocumentation(item)
}
this.dialog = true
},
createLessonDocumentation() { // FIXME: Update cache to show newly created LessonDocumentation in table
let lessonDocumentation = this.selectedLessonPeriodDatetime
lessonDocumentation["event"] = null
lessonDocumentation["extraLesson"] = null
this.loadLessonDocumentation(lessonDocumentation)
this.dialog = true
},
calculateStartDateOfCW(year, week){
let ld_date = new Date(Date.UTC(year, 0, 1 + (week - 1) * 7));
let dow = ld_date.getDay();
let start_date = ld_date;
if (dow <= 4)
return start_date.setDate(ld_date.getDate() - ld_date.getDay() + 1)
else
return start_date.setDate(ld_date.getDate() + 8 - ld_date.getDay())
},
getLessonText(item) {
let date_obj = new Date(item.hasOwnProperty("datetimeStart") ? item.datetimeStart : item.date)
let period = item.lessonPeriod ? ", " + this.$t('alsijil.period_number', {number: item.lessonPeriod.period.period}) : "" // FIXME: Cases without lessonPeriod
return this.$d(date_obj, "short") + period
},
getWeekText(item) {
if (item.hasOwnProperty("startDate")) {
var start_date = new Date(item.startDate)
} else {
let lesson_date = new Date(item.date)
var start_date = new Date(((lesson_date.getDay() || 7) !== 1) ? lesson_date.setHours(-24 * (lesson_date.getDay() - 1)) : lesson_date)
}
let end_date = new Date(start_date)
end_date.setDate(end_date.getDate() + 6)
return start_date.toLocaleDateString(this.$root.languageCode) + " - " + end_date.toLocaleDateString(this.$root.languageCode) + ", " + this.$root.django.gettext('CW') + " " + item.week
},
personalNoteString(personalNote) {
let personalNoteString = "";
if (personalNote.tardiness > 0) {
personalNoteString += personalNote.tardiness + " min. ";
}
if (personalNote.absent) {
personalNoteString += this.$t("absent") + ", ";
}
if (personalNote.excused) {
personalNoteString += this.$t("excused") + ", ";
}
if (personalNote.excuseType) {
personalNoteString += personalNote.excuseType.name;
}
if (personalNote.extraMarks.length > 0) {
personalNoteString += " (";
personalNote.extraMarks.forEach(item => {
personalNoteString += item.name + ", ";
});
personalNoteString = personalNoteString.substring(0, personalNoteString.length - 2);
personalNoteString += ") ";
}
if (personalNote.remarks) {
personalNoteString += "\"" + personalNote.remarks + "\" ";
}
return personalNoteString;
},
}
}
</script>
<template>
<v-dialog
v-model="dialog"
max-width="600px"
@click:outside="cancelDialog"
>
<template v-slot:activator="{ on, attrs }">
<div>
<template v-for="personalNote in personalNotes">
<v-chip class="ma-1" close @click="editPersonalNote(personalNote.person.id)"
@click:close="removePersonalNote(personalNote.person.id)" v-if="personalNoteString(personalNote)">
{{ personalNote.person.fullName }}: {{ personalNoteString(personalNote) }}
</v-chip>
</template>
</div>
<v-tooltip bottom>
<template v-slot:activator="{ on, attrs }">
<v-btn
class="ma-1"
color="primary"
icon
outlined
v-bind="attrs"
v-on="on"
@click="createPersonalNote"
>
<v-icon>
mdi-plus
</v-icon>
</v-btn>
</template>
<span v-text="$t('alsijil.coursebook.add_personal_note')"></span>
</v-tooltip>
</template>
<v-card>
<v-card-title>
<span class="text-h5">Personal Note</span>
</v-card-title>
<v-card-text>
<v-container>
<v-select
item-text="fullName"
item-value="id"
:items="persons"
:label="$t('alsijil.personal_note.person')"
v-model="editedPersonID"
@input="updatePersonalNote"
></v-select>
<v-text-field
:label="$t('alsijil.personal_note.tardiness')"
suffix="min" type="number"
min="0"
:disabled="editedPersonID === ID_NO_PERSON"
v-model="editedTardiness"
></v-text-field>
<v-checkbox
:label="$t('alsijil.personal_note.absent')"
v-model="editedAbsent"
:disabled="editedPersonID === ID_NO_PERSON"
@change="editedExcused = false; editedExcuseType = null"
></v-checkbox>
<v-checkbox
:label="$t('alsijil.personal_note.excused')"
v-model="editedExcused"
:disabled="editedPersonID === ID_NO_PERSON || !editedAbsent"
@change="editedExcuseType = null"
></v-checkbox>
<v-select
:label="$t('alsijil.personal_note.excuse_type')"
v-model="editedExcuseType"
:items="excuseTypes"
item-text="name"
return-object
:disabled="editedPersonID === ID_NO_PERSON || !editedAbsent || !editedExcused"
></v-select>
<v-select
:label="$t('alsijil.personal_note.extra_marks')"
v-model="editedExtraMarks"
:items="extraMarks"
item-text="name"
return-object
:disabled="editedPersonID === ID_NO_PERSON"
multiple
chips
></v-select>
<v-text-field
:label="$t('alsijil.personal_note.remarks')"
v-model="editedRemarks"
:disabled="editedPersonID === ID_NO_PERSON"
></v-text-field>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="error"
outlined
@click="cancelDialog"
>
{{ $t("alsijil.cancel") }}
</v-btn>
<v-btn
color="success"
@click="saveDialog"
:disabled="editedPersonID === ID_NO_PERSON"
>
{{ $t("alsijil.save") }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
import gql from 'graphql-tag';
const ID_NO_PERSON = null;
export default {
model: {
prop: "personalNotes",
event: "change",
},
created() {
this.ID_NO_PERSON = ID_NO_PERSON;
},
methods: {
removePersonalNote(personID) {
if (personID === ID_NO_PERSON) {
return
}
console.log("removing personal note of person", personID);
this.editedPersonID = personID;
this.editedTardiness = 0;
this.editedAbsent = false;
this.editedExcused = false;
this.editedExcuseType = null;
this.editedExtraMarks = [];
this.editedRemarks = "";
this.savePersonalNote();
},
editPersonalNote(personID) {
console.log("editing personal note of person", personID);
this.editedPersonID = personID;
this.updatePersonalNote();
this.dialog = true;
},
updatePersonalNote() {
let personalNote = this.personalNoteByStudentID(this.editedPersonID);
this.editedTardiness = personalNote.tardiness || 0;
this.editedAbsent = personalNote.absent || false;
this.editedExcused = personalNote.excused || false;
this.editedExcuseType = personalNote.excuseType || null;
this.editedExtraMarks = personalNote.extraMarks || [];
this.editedRemarks = personalNote.remarks || "";
this.newPersonalNote = !!(personalNote && Object.keys(personalNote).length === 0 && Object.getPrototypeOf(personalNote) === Object.prototype);
},
createPersonalNote() {
this.editedPersonID = ID_NO_PERSON;
this.editedTardiness = 0;
this.editedAbsent = false;
this.editedExcused = false;
this.editedExcuseType = null;
this.editedExtraMarks = [];
this.editedRemarks = "";
this.newPersonalNote = true;
this.dialog = true;
},
personalNoteByStudentID(studentID) {
if (this.editedPersonID === ID_NO_PERSON) {
return {};
}
return this.personalNotes.filter(item => item.person.id === studentID)[0] || {};
},
savePersonalNote() {
if (this.editedPersonID === ID_NO_PERSON) {
return
}
let editedExcuseTypeID = (this.editedExcuseType) ? this.editedExcuseType.id : null;
let editedExtraMarksIDs = [];
this.editedExtraMarks.forEach(item => {editedExtraMarksIDs.push(item.id);});
// We save the user input in case of an error
const variables = {
"personId": this.editedPersonID,
"tardiness": this.editedTardiness,
"absent": this.editedAbsent,
"excused": this.editedExcused,
"excuseType": editedExcuseTypeID,
"extraMarks": editedExtraMarksIDs,
"remarks": this.editedRemarks,
"lessonDocumentation": this.lessonDocumentationId,
}
console.log(variables)
// Call to the graphql mutation
this.$apollo.mutate({
// Query
mutation: gql`mutation updateOrCreatePersonalNote(
$personId: ID!,
$lessonDocumentation: ID!,
$tardiness: Int,
$absent: Boolean,
$excused: Boolean,
$excuseType: ID,
$extraMarks: [ID],
$remarks: String
) {
updateOrCreatePersonalNote(personId: $personId,
lessonDocumentation: $lessonDocumentation,
tardiness: $tardiness,
absent: $absent,
excused: $excused,
excuseType: $excuseType,
extraMarks: $extraMarks,
remarks: $remarks
) {
personalNote {
id
person {
id
fullName
}
tardiness
remarks
absent
excused
excuseType {
id
}
extraMarks {
id
}
}
}
}
`,
// Parameters
variables: variables,
}).then((data) => {
// Result
console.log(data)
// FIXME: check if data changed (?), display success message
}).catch((error) => {
// Error
console.error(error)
// FIXME: Notify the user about the error, maybe retry
})
if (this.newPersonalNote) {
this.personalNotes.push({
person: {
id: this.editedPersonID,
fullName: this.studentNameByID(this.editedPersonID)
},
tardiness: this.editedTardiness,
absent: this.editedAbsent,
excused: this.editedExcused,
excuseType: this.editedExcuseType,
extraMarks: this.editedExtraMarks,
remarks: this.editedRemarks,
});
} else {
// Loop through all personal notes and update the ones that match the editedPersonID
this.personalNotes.forEach(item => {
if (item.person.id === this.editedPersonID) {
item.tardiness = this.editedTardiness;
item.absent = this.editedAbsent;
item.excused = this.editedExcused;
item.excuseType = this.editedExcuseType;
item.extraMarks = this.editedExtraMarks;
item.remarks = this.editedRemarks;
}
});
}
this.$emit('change', this.personalNotes)
},
cancelDialog() {
this.dialog = false;
this.editedPersonID = ID_NO_PERSON;
},
saveDialog() {
this.savePersonalNote();
this.dialog = false;
this.editedPersonID = ID_NO_PERSON;
},
personalNoteString(personalNote) {
let personalNoteString = "";
if (personalNote.tardiness > 0) {
personalNoteString += personalNote.tardiness + " min. ";
}
if (personalNote.absent) {
personalNoteString += $t("alsijil.absent") + " ";
}
if (personalNote.excused) {
personalNoteString += $t("alsijil.excused") + " ";
}
if (personalNote.excuseType) {
personalNoteString += personalNote.excuseType.name;
}
if (personalNote.extraMarks.length > 0) {
personalNoteString += " (";
personalNote.extraMarks.forEach(item => {
personalNoteString += item.name + ", ";
});
personalNoteString = personalNoteString.substring(0, personalNoteString.length - 2);
personalNoteString += ") ";
}
if (personalNote.remarks) {
personalNoteString += "\"" + personalNote.remarks + "\" ";
}
return personalNoteString;
},
studentNameByID(studentID) {
try {
return this.persons.filter(item => item.id === studentID)[0].fullName;
} catch (TypeError) {
return "";
}
}
},
props: ["lessonDocumentationId", "personalNotes", "groups", "excuseTypes", "extraMarks"],
name: "personal-notes",
data: () => {
return {
dialog: false,
// Absent versp. exc. type hw note
editPersonalNoteId: null,
editedPersonID: ID_NO_PERSON,
editedTardiness: 0,
editedAbsent: false,
editedExcused: false,
editedExcuseType: null,
editedExtraMarks: [],
editedRemarks: "",
newPersonalNote: false,
}
},
computed: {
persons() {
// go through each group and get the students
// use the group names as headers for the v-select
return this.groups.map(
group => {
return [
{header: group.name, id: group.shortName},
group.members
]
}
).flat(2);
}
}
}
</script>
<template>
<v-tooltip bottom>
<template v-slot:activator="{ on, attrs }">
<v-btn
right
icon
v-bind="attrs"
v-on="on"
@click="() => {isAbleToClick ? $emit('manual-update') : null}"
:loading="status === UPDATING"
>
<v-icon
v-if="status !== UPDATING"
:color="color"
>
{{ icon }}
</v-icon>
</v-btn>
</template>
<span>{{ text }}</span>
</v-tooltip>
</template>
<script>
import {CHANGES, ERROR, SAVED, UPDATING} from "../../UpdateStatuses.js";
export default {
created() {
this.ERROR = ERROR;
this.SAVED = SAVED;
this.UPDATING = UPDATING;
this.CHANGES = CHANGES;
},
name: "update-indicator",
emits: ["manual-update"],
props: ["status"],
computed: {
text() {
switch (this.status) {
case SAVED:
return this.$t("alsijil.coursebook.sync.saved");
case UPDATING:
return this.$t("alsijil.coursebook.sync.updating");
case CHANGES:
return this.$t("alsijil.coursebook.sync.changes");
default:
return this.$t("alsijil.coursebook.sync.error");
}
},
color() {
switch (this.status) {
case SAVED:
return "success";
case CHANGES:
return "secondary";
case UPDATING:
return "secondary";
default:
return "error";
}
},
icon() {
// FIXME use app sdhasdhahsdhsadhsadh
switch (this.status) {
case SAVED:
return "mdi-check-circle-outline";
case CHANGES:
return "mdi-dots-horizontal";
default:
return "mdi-alert-outline";
}
},
isAbleToClick() {
return this.status === CHANGES || this.status === ERROR;
}
},
}
</script>
export default [
{ path: "/coursebook/:lessonId", component: () => import("./components/coursebook/CourseBook.vue"), props: true },
];
{
"en": {
"alsijil": {
"coursebook": {
"title": "Coursebook",
"create_documentation": "Create documentation",
"choose_week": "Choose week",
"choose_lesson_date": "Choose lesson date",
"sync": {
"saved": "All changes are saved.",
"updating": "Changes are being synced.",
"changes": "You have unsaved changes. Click to save them immediately.",
"error": "There has been an error while saving the latest changes."
}
},
"period": "Period",
"period_number": "{number}. period",
"lesson_documentation": {
"topic": "Topic",
"homework": "Homework",
"group_note": "Group note"
},
"calendar_week": "Calendar week",
"calendar_week_short": "Week",
"personal_note": {
"title": "Personal Note",
"title_plural": "Personal Notes",
"absent_title": "Absent",
"excused_title": "Excused",
"absent": "absent",
"excused": "excused",
"person": "Person",
"tardiness": "Tardiness",
"excuse_type": "Excuse type",
"extra_marks": "Extra marks",
"remarks": "Remarks",
"actions": {
"add": "Add personal note"
}
},
"error_occurred": "An error occurred",
"error_updating": "Error updating data",
"cancel": "Cancel",
"save": "Save",
"back": "Back"
}
},
"de": {
"coursebook": {
"title": "Kursbuch"
}
}
}
...@@ -47,7 +47,7 @@ class LessonDocumentationForm(forms.ModelForm): ...@@ -47,7 +47,7 @@ class LessonDocumentationForm(forms.ModelForm):
self.fields["homework"].label = _("Homework for the next lesson") self.fields["homework"].label = _("Homework for the next lesson")
if ( if (
self.instance.lesson_period self.instance.lesson_period
and get_site_preferences()["alsijil__allow_carry_over_same_week"] and get_site_preferences()["alsijil__save_lesson_documentations_by_week"]
): ):
self.fields["carry_over_week"] = forms.BooleanField( self.fields["carry_over_week"] = forms.BooleanField(
label=_("Carry over data to all other lessons with the same subject in this week"), label=_("Carry over data to all other lessons with the same subject in this week"),
...@@ -58,7 +58,7 @@ class LessonDocumentationForm(forms.ModelForm): ...@@ -58,7 +58,7 @@ class LessonDocumentationForm(forms.ModelForm):
def save(self, **kwargs): def save(self, **kwargs):
lesson_documentation = super(LessonDocumentationForm, self).save(commit=True) lesson_documentation = super(LessonDocumentationForm, self).save(commit=True)
if ( if (
get_site_preferences()["alsijil__allow_carry_over_same_week"] get_site_preferences()["alsijil__save_lesson_documentations_by_week"]
and self.cleaned_data["carry_over_week"] and self.cleaned_data["carry_over_week"]
and ( and (
lesson_documentation.topic lesson_documentation.topic
...@@ -68,7 +68,7 @@ class LessonDocumentationForm(forms.ModelForm): ...@@ -68,7 +68,7 @@ class LessonDocumentationForm(forms.ModelForm):
and lesson_documentation.lesson_period and lesson_documentation.lesson_period
): ):
lesson_documentation.carry_over_data( lesson_documentation.carry_over_data(
LessonPeriod.objects.filter(lesson=lesson_documentation.lesson_period.lesson) LessonPeriod.objects.filter(lesson=lesson_documentation.lesson_period.lesson), True
) )
......
...@@ -6,16 +6,30 @@ MENUS = { ...@@ -6,16 +6,30 @@ MENUS = {
"name": _("Class register"), "name": _("Class register"),
"url": "#", "url": "#",
"svg_icon": "mdi:book-open-outline", "svg_icon": "mdi:book-open-outline",
"vuetify_icon": "mdi-book-open-outline",
"root": True, "root": True,
"validators": [ "validators": [
"menu_generator.validators.is_authenticated", "menu_generator.validators.is_authenticated",
"aleksis.core.util.core_helpers.has_person", "aleksis.core.util.core_helpers.has_person",
], ],
"submenu": [ "submenu": [
{
"name": _("Coursebook"),
"url": "select_coursebook",
"svg_icon": "mdi:book-education-outline",
"vuetify_icon": "mdi-book-education-outline",
"validators": [
(
"aleksis.core.util.predicates.permission_validator",
"alsijil.view_coursebook_rule",
),
],
},
{ {
"name": _("Current lesson"), "name": _("Current lesson"),
"url": "lesson_period", "url": "lesson_period",
"svg_icon": "mdi:alarm", "svg_icon": "mdi:alarm",
"vuetify_icon": "mdi-alarm",
"validators": [ "validators": [
( (
"aleksis.core.util.predicates.permission_validator", "aleksis.core.util.predicates.permission_validator",
...@@ -27,6 +41,7 @@ MENUS = { ...@@ -27,6 +41,7 @@ MENUS = {
"name": _("Current week"), "name": _("Current week"),
"url": "week_view", "url": "week_view",
"svg_icon": "mdi:view-week-outline", "svg_icon": "mdi:view-week-outline",
"vuetify_icon": "mdi-view-week-outline",
"validators": [ "validators": [
( (
"aleksis.core.util.predicates.permission_validator", "aleksis.core.util.predicates.permission_validator",
...@@ -38,6 +53,7 @@ MENUS = { ...@@ -38,6 +53,7 @@ MENUS = {
"name": _("My groups"), "name": _("My groups"),
"url": "my_groups", "url": "my_groups",
"svg_icon": "mdi:account-multiple-outline", "svg_icon": "mdi:account-multiple-outline",
"vuetify_icon": "mdi-account-multiple-outline",
"validators": [ "validators": [
( (
"aleksis.core.util.predicates.permission_validator", "aleksis.core.util.predicates.permission_validator",
...@@ -49,6 +65,7 @@ MENUS = { ...@@ -49,6 +65,7 @@ MENUS = {
"name": _("My overview"), "name": _("My overview"),
"url": "overview_me", "url": "overview_me",
"svg_icon": "mdi:chart-box-outline", "svg_icon": "mdi:chart-box-outline",
"vuetify_icon": "mdi-chart-box-outline",
"validators": [ "validators": [
( (
"aleksis.core.util.predicates.permission_validator", "aleksis.core.util.predicates.permission_validator",
...@@ -60,6 +77,7 @@ MENUS = { ...@@ -60,6 +77,7 @@ MENUS = {
"name": _("My students"), "name": _("My students"),
"url": "my_students", "url": "my_students",
"svg_icon": "mdi:account-school-outline", "svg_icon": "mdi:account-school-outline",
"vuetify_icon": "mdi-account-school-outline",
"validators": [ "validators": [
( (
"aleksis.core.util.predicates.permission_validator", "aleksis.core.util.predicates.permission_validator",
...@@ -71,6 +89,7 @@ MENUS = { ...@@ -71,6 +89,7 @@ MENUS = {
"name": _("Assign group role"), "name": _("Assign group role"),
"url": "assign_group_role_multiple", "url": "assign_group_role_multiple",
"svg_icon": "mdi:clipboard-account-outline", "svg_icon": "mdi:clipboard-account-outline",
"vuetify_icon": "mdi-clipboard-account-outline",
"validators": [ "validators": [
( (
"aleksis.core.util.predicates.permission_validator", "aleksis.core.util.predicates.permission_validator",
...@@ -82,6 +101,7 @@ MENUS = { ...@@ -82,6 +101,7 @@ MENUS = {
"name": _("All lessons"), "name": _("All lessons"),
"url": "all_register_objects", "url": "all_register_objects",
"svg_icon": "mdi:format-list-text", "svg_icon": "mdi:format-list-text",
"vuetify_icon": "mdi-format-list-text",
"validators": [ "validators": [
( (
"aleksis.core.util.predicates.permission_validator", "aleksis.core.util.predicates.permission_validator",
...@@ -92,7 +112,8 @@ MENUS = { ...@@ -92,7 +112,8 @@ MENUS = {
{ {
"name": _("Register absence"), "name": _("Register absence"),
"url": "register_absence", "url": "register_absence",
"icon": "rate_review", "svg_icon": "mdi:message-draw",
"vuetify_icon": "mdi-message-draw",
"validators": [ "validators": [
( (
"aleksis.core.util.predicates.permission_validator", "aleksis.core.util.predicates.permission_validator",
...@@ -104,6 +125,7 @@ MENUS = { ...@@ -104,6 +125,7 @@ MENUS = {
"name": _("Excuse types"), "name": _("Excuse types"),
"url": "excuse_types", "url": "excuse_types",
"svg_icon": "mdi:label-outline", "svg_icon": "mdi:label-outline",
"vuetify_icon": "mdi-label-outline",
"validators": [ "validators": [
( (
"aleksis.core.util.predicates.permission_validator", "aleksis.core.util.predicates.permission_validator",
...@@ -115,6 +137,7 @@ MENUS = { ...@@ -115,6 +137,7 @@ MENUS = {
"name": _("Extra marks"), "name": _("Extra marks"),
"url": "extra_marks", "url": "extra_marks",
"svg_icon": "mdi:label-variant-outline", "svg_icon": "mdi:label-variant-outline",
"vuetify_icon": "mdi-label-variant-outline",
"validators": [ "validators": [
( (
"aleksis.core.util.predicates.permission_validator", "aleksis.core.util.predicates.permission_validator",
...@@ -126,6 +149,7 @@ MENUS = { ...@@ -126,6 +149,7 @@ MENUS = {
"name": _("Manage group roles"), "name": _("Manage group roles"),
"url": "group_roles", "url": "group_roles",
"svg_icon": "mdi:clipboard-plus-outline", "svg_icon": "mdi:clipboard-plus-outline",
"vuetify_icon": "mdi-clipboard-plus-outline",
"validators": [ "validators": [
( (
"aleksis.core.util.predicates.permission_validator", "aleksis.core.util.predicates.permission_validator",
......
...@@ -461,7 +461,9 @@ def generate_person_list_with_class_register_statistics( ...@@ -461,7 +461,9 @@ def generate_person_list_with_class_register_statistics(
), ),
tardiness=Sum("filtered_personal_notes__tardiness"), tardiness=Sum("filtered_personal_notes__tardiness"),
tardiness_count=Count( tardiness_count=Count(
"filtered_personal_notes", filter=Q(filtered_personal_notes__late__gt=0), distinct=True "filtered_personal_notes",
filter=Q(filtered_personal_notes__tardiness__gt=0),
distinct=True,
), ),
) )
......
...@@ -347,11 +347,13 @@ class LessonDocumentation(RegisterObjectRelatedMixin, ExtensibleModel): ...@@ -347,11 +347,13 @@ class LessonDocumentation(RegisterObjectRelatedMixin, ExtensibleModel):
homework = models.CharField(verbose_name=_("Homework"), max_length=200, blank=True) homework = models.CharField(verbose_name=_("Homework"), max_length=200, blank=True)
group_note = models.CharField(verbose_name=_("Group note"), max_length=200, blank=True) group_note = models.CharField(verbose_name=_("Group note"), max_length=200, blank=True)
def carry_over_data(self, all_periods_of_lesson: LessonPeriod): def carry_over_data(self, all_periods_of_lesson: LessonPeriod, force: bool):
"""Carry over data to given periods in this lesson if data is not already set. """Carry over data to given periods in this lesson.
Does overwrite existing data in case ``force`` is set to ``True``.
Both forms of carrying over data can be deactivated using site preferences Both forms of carrying over data can be deactivated using site preferences
``alsijil__carry_over_next_periods`` and ``alsijil__allow_carry_over_same_week`` ``alsijil__carry_over_next_periods`` and ``alsijil__save_lesson_documentations_by_week``
respectively. respectively.
""" """
for period in all_periods_of_lesson: for period in all_periods_of_lesson:
...@@ -361,15 +363,15 @@ class LessonDocumentation(RegisterObjectRelatedMixin, ExtensibleModel): ...@@ -361,15 +363,15 @@ class LessonDocumentation(RegisterObjectRelatedMixin, ExtensibleModel):
changed = False changed = False
if not lesson_documentation.topic: if not lesson_documentation.topic or force:
lesson_documentation.topic = self.topic lesson_documentation.topic = self.topic
changed = True changed = True
if not lesson_documentation.homework: if not lesson_documentation.homework or force:
lesson_documentation.homework = self.homework lesson_documentation.homework = self.homework
changed = True changed = True
if not lesson_documentation.group_note: if not lesson_documentation.group_note or force:
lesson_documentation.group_note = self.group_note lesson_documentation.group_note = self.group_note
changed = True changed = True
...@@ -390,7 +392,8 @@ class LessonDocumentation(RegisterObjectRelatedMixin, ExtensibleModel): ...@@ -390,7 +392,8 @@ class LessonDocumentation(RegisterObjectRelatedMixin, ExtensibleModel):
LessonPeriod.objects.filter( LessonPeriod.objects.filter(
lesson=self.lesson_period.lesson, lesson=self.lesson_period.lesson,
period__weekday=self.lesson_period.period.weekday, period__weekday=self.lesson_period.period.weekday,
) ),
False,
) )
super().save(*args, **kwargs) super().save(*args, **kwargs)
......
...@@ -67,17 +67,11 @@ class CarryOverDataToNextPeriods(BooleanPreference): ...@@ -67,17 +67,11 @@ class CarryOverDataToNextPeriods(BooleanPreference):
@site_preferences_registry.register @site_preferences_registry.register
class AllowCarryOverLessonDocumentationToCurrentWeek(BooleanPreference): class SaveLessonDocumentationsPerWeek(BooleanPreference):
section = alsijil section = alsijil
name = "allow_carry_over_same_week" name = "save_lesson_documentations_by_week"
default = False default = False
verbose_name = _( verbose_name = _("Save lesson documentations per week instead of per lesson period")
"Allow carrying over data from any lesson period to all other lesson \
periods with the same lesson and in the same week"
)
help_text = _(
"This will carry over data only if the data in the aforementioned periods are empty."
)
@site_preferences_registry.register @site_preferences_registry.register
......
...@@ -204,6 +204,11 @@ view_students_list_predicate = view_my_groups_predicate & ( ...@@ -204,6 +204,11 @@ view_students_list_predicate = view_my_groups_predicate & (
) )
add_perm("alsijil.view_students_list_rule", view_students_list_predicate) add_perm("alsijil.view_students_list_rule", view_students_list_predicate)
# View CourseBook
view_coursebook_predicate = has_person & is_teacher
add_perm("alsijil.view_coursebook_rule", view_my_students_predicate)
# View person overview # View person overview
view_person_overview_predicate = has_person & ( view_person_overview_predicate = has_person & (
(is_current_person & is_site_preference_set("alsijil", "view_own_personal_notes")) (is_current_person & is_site_preference_set("alsijil", "view_own_personal_notes"))
......
from datetime import datetime
import graphene
from graphene_django import DjangoObjectType
from aleksis.apps.chronos.models import Lesson
from aleksis.core.models import Group, Person
from aleksis.core.util.core_helpers import get_site_preferences
from .models import (
Event,
ExcuseType,
ExtraLesson,
ExtraMark,
LessonDocumentation,
LessonPeriod,
PersonalNote,
)
class ExcuseTypeType(DjangoObjectType):
class Meta:
model = ExcuseType
class PersonalNoteType(DjangoObjectType):
class Meta:
model = PersonalNote
class LessonDocumentationType(DjangoObjectType):
class Meta:
model = LessonDocumentation
personal_notes = graphene.List(PersonalNoteType)
date = graphene.Field(graphene.Date)
period = graphene.Field(graphene.Int)
def resolve_personal_notes(root: LessonDocumentation, info, **kwargs):
persons = Person.objects.filter(
member_of__in=Group.objects.filter(pk__in=root.register_object.get_groups().all())
)
return PersonalNote.objects.filter(
week=root.week,
year=root.year,
lesson_period=root.lesson_period,
person__in=persons,
)
def resolve_period(root: LessonDocumentation, info, **kwargs):
return root.period.period
def resolve_date(root: LessonDocumentation, info, **kwargs):
return root.date
class ExtraMarkType(DjangoObjectType):
class Meta:
model = ExtraMark
class LessonDocumentationMutation(graphene.Mutation):
class Arguments:
year = graphene.Int(required=True)
week = graphene.Int(required=True)
lesson_period_id = graphene.ID(required=False)
event_id = graphene.ID(required=False)
extra_lesson_id = graphene.ID(required=False)
lesson_documentation_id = graphene.ID(required=False)
topic = graphene.String(required=False)
homework = graphene.String(required=False)
group_note = graphene.String(required=False)
lesson_documentation = graphene.Field(LessonDocumentationType)
@classmethod
def mutate(
cls,
root,
info,
year,
week,
lesson_period_id=None,
event_id=None,
extra_lesson_id=None,
lesson_documentation_id=None,
topic=None,
homework=None,
group_note=None,
):
lesson_period = LessonPeriod.objects.filter(pk=lesson_period_id).first()
event = Event.objects.filter(pk=event_id).first()
extra_lesson = ExtraLesson.objects.filter(pk=extra_lesson_id).first()
lesson_documentation, created = LessonDocumentation.objects.get_or_create(
year=year,
week=week,
lesson_period=lesson_period,
event=event,
extra_lesson=extra_lesson,
)
if topic is not None:
lesson_documentation.topic = topic
if homework is not None:
lesson_documentation.homework = homework
if group_note is not None:
lesson_documentation.group_note = group_note
lesson_documentation.save()
if (
get_site_preferences()["alsijil__save_lesson_documentations_by_week"]
and (
lesson_documentation.topic
or lesson_documentation.homework
or lesson_documentation.group_note
)
and lesson_documentation.lesson_period
):
lesson_documentation.carry_over_data(
LessonPeriod.objects.filter(lesson=lesson_documentation.lesson_period.lesson), True
)
return LessonDocumentationMutation(lesson_documentation=lesson_documentation)
class PersonalNoteMutation(graphene.Mutation):
class Arguments:
person_id = graphene.ID(required=True)
lesson_documentation = graphene.ID(required=True)
personal_note_id = graphene.ID(required=False) # Update or create personal note
late = graphene.Int(required=False)
absent = graphene.Boolean(required=False)
excused = graphene.Boolean(required=False)
excuse_type = graphene.ID(required=False)
remarks = graphene.String(required=False)
extra_marks = graphene.List(graphene.ID, required=False)
personal_note = graphene.Field(PersonalNoteType)
@classmethod
def mutate(
cls,
root,
info,
person_id,
lesson_documentation,
personal_note_id=None,
late=None,
absent=None,
excused=None,
excuse_type=None,
remarks=None,
extra_marks=None,
):
person = Person.objects.get(pk=person_id)
lesson_documentation = LessonDocumentation.objects.get(pk=lesson_documentation)
personal_note, created = PersonalNote.objects.get_or_create(
person=person,
event=lesson_documentation.event,
extra_lesson=lesson_documentation.extra_lesson,
lesson_period=lesson_documentation.lesson_period,
week=lesson_documentation.week,
year=lesson_documentation.year,
)
if late is not None:
personal_note.late = late
if absent is not None:
personal_note.absent = absent
if excused is not None:
personal_note.excused = excused
if excuse_type is not None:
personal_note.excuse_type = ExcuseType.objects.get(pk=excuse_type)
if remarks is not None:
personal_note.remarks = remarks
if created:
personal_note.groups_of_person.set(person.member_of.all())
personal_note.save()
if extra_marks is not None:
extra_marks = ExtraMark.objects.filter(pk__in=extra_marks)
personal_note.extra_marks.set(extra_marks)
personal_note.save()
return PersonalNoteMutation(personal_note=personal_note)
class Mutation(graphene.ObjectType):
update_or_create_lesson_documentation = LessonDocumentationMutation.Field()
update_or_create_personal_note = PersonalNoteMutation.Field()
# update_personal_note = PersonalNoteMutation.Field()
class Query(graphene.ObjectType):
excuse_types = graphene.List(ExcuseTypeType)
lesson_documentations = graphene.List(LessonDocumentationType)
lesson_documentation_by_id = graphene.Field(LessonDocumentationType, id=graphene.ID())
lesson_documentations_by_lesson_id = graphene.List(LessonDocumentationType, id=graphene.ID())
personal_notes = graphene.List(PersonalNoteType)
extra_marks = graphene.List(ExtraMarkType)
def resolve_excuse_types(root, info, **kwargs):
# FIXME do permission stuff
return ExcuseType.objects.all()
def resolve_lesson_documentations(root, info, **kwargs):
# FIXME do permission stuff
return LessonDocumentation.objects.all().order_by(
"-year", "-week", "-lesson_period__period__weekday", "-lesson_period__period__period"
)
def resolve_lesson_documentation_by_id(root, info, id, **kwargs): # noqa
return LessonDocumentation.objects.get(id=id)
def resolve_lesson_documentations_by_lesson_id(root, info, id, **kwargs): # noqa
lesson = Lesson.objects.get(id=id)
now = datetime.now()
for equal_lesson in lesson._equal_lessons:
for planned in equal_lesson.planned_lessonperiods_datetimes:
if planned["datetime_start"] <= now:
LessonDocumentation.objects.get_or_create(
week=planned["week"],
year=planned["year"],
lesson_period=planned["lesson_period"],
) # FIXME: Queries shouldn't alter data
return LessonDocumentation.objects.filter(
lesson_period_id__in=LessonPeriod.objects.filter(
lesson__in=lesson._equal_lessons
).values_list("id", flat=True)
).order_by(
"-year", "-week", "-lesson_period__period__weekday", "-lesson_period__period__period"
)
def resolve_personal_notes(root, info, **kwargs):
# FIXME do permission stuff
return PersonalNote.objects.all()
def resolve_extra_marks(root, info, **kwargs):
return ExtraMark.objects.all()
{% extends "core/vue_base.html" %}
{% load static i18n %}
{% load render_bundle from webpack_loader %}
{% block page_title %}
{% trans "Coursebook" %}
{% endblock %}
{% block browser_title %}{% trans "Coursebook" %} {{ lesson }}{% endblock %}
{% block content %}
<div class="text-h5">{{ lesson }}</div>
<router-view save-lesson-documentations-per-week={{ SITE_PREFERENCES.alsijil__save_lesson_documentations_by_week }} />
{% endblock %}
{% block extra_body %}
{% render_bundle "aleksis.apps.alsijil" %}
{% endblock %}