Merge branch 'develop'
This commit is contained in:
commit
1774921dca
|
|
@ -10,6 +10,7 @@
|
||||||
import DefaultLayout from '@/layouts/DefaultLayout';
|
import DefaultLayout from '@/layouts/DefaultLayout';
|
||||||
import SimpleLayout from '@/layouts/SimpleLayout';
|
import SimpleLayout from '@/layouts/SimpleLayout';
|
||||||
import BlankLayout from '@/layouts/BlankLayout';
|
import BlankLayout from '@/layouts/BlankLayout';
|
||||||
|
import FullScreenLayout from '@/layouts/FullScreenLayout';
|
||||||
import Modal from '@/components/Modal';
|
import Modal from '@/components/Modal';
|
||||||
import MobileNavigation from '@/components/MobileNavigation';
|
import MobileNavigation from '@/components/MobileNavigation';
|
||||||
import NewContentBlockWizard from '@/components/content-block-form/NewContentBlockWizard';
|
import NewContentBlockWizard from '@/components/content-block-form/NewContentBlockWizard';
|
||||||
|
|
@ -33,6 +34,7 @@
|
||||||
DefaultLayout,
|
DefaultLayout,
|
||||||
SimpleLayout,
|
SimpleLayout,
|
||||||
BlankLayout,
|
BlankLayout,
|
||||||
|
FullScreenLayout,
|
||||||
Modal,
|
Modal,
|
||||||
MobileNavigation,
|
MobileNavigation,
|
||||||
NewContentBlockWizard,
|
NewContentBlockWizard,
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@
|
||||||
import EyeIcon from '@/components/icons/EyeIcon';
|
import EyeIcon from '@/components/icons/EyeIcon';
|
||||||
import PenIcon from '@/components/icons/PenIcon';
|
import PenIcon from '@/components/icons/PenIcon';
|
||||||
import TrashIcon from '@/components/icons/TrashIcon';
|
import TrashIcon from '@/components/icons/TrashIcon';
|
||||||
|
import ModuleRoomSlug from '@/components/content-blocks/ModuleRoomSlug'
|
||||||
|
|
||||||
import CHAPTER_QUERY from '@/graphql/gql/chapterQuery.gql';
|
import CHAPTER_QUERY from '@/graphql/gql/chapterQuery.gql';
|
||||||
import DELETE_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/deleteContentBlock.gql';
|
import DELETE_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/deleteContentBlock.gql';
|
||||||
|
|
@ -93,6 +94,7 @@
|
||||||
'genially_block': GeniallyBlock,
|
'genially_block': GeniallyBlock,
|
||||||
'subtitle': SubtitleBlock,
|
'subtitle': SubtitleBlock,
|
||||||
'content_list': ContentListBlock,
|
'content_list': ContentListBlock,
|
||||||
|
'module_room_slug': ModuleRoomSlug,
|
||||||
Survey,
|
Survey,
|
||||||
Solution,
|
Solution,
|
||||||
Assignment,
|
Assignment,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<template>
|
||||||
|
<div class="module-slug">
|
||||||
|
<router-link class="button button--primary" :to="{name: 'moduleRoom', params: { slug: value.slug }}">Raum anzeigen
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: ['value']
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/styles/_variables.scss";
|
||||||
|
|
||||||
|
.module-slug {
|
||||||
|
margin-bottom: $large-spacing;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import AddRoomEntryButton from '@/components/rooms/AddRoomEntryButton.vue';
|
||||||
|
import RoomEntry from '@/components/rooms/RoomEntry.vue';
|
||||||
|
import RoomGroupWidget from '@/components/rooms/RoomGroupWidget';
|
||||||
|
import EntryCountWidget from '@/components/rooms/EntryCountWidget';
|
||||||
|
import RoomActions from '@/components/rooms/RoomActions';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EntryCountWidget,
|
||||||
|
RoomGroupWidget,
|
||||||
|
AddRoomEntryButton,
|
||||||
|
RoomEntry,
|
||||||
|
RoomActions
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.$store.dispatch('setSpecialContainerClass', '');
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
room: [],
|
||||||
|
entries: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
roomEntryCount() {
|
||||||
|
return (this.room && this.room.roomEntries) ? this.room.roomEntries.length : 0
|
||||||
|
},
|
||||||
|
roomAppearance() {
|
||||||
|
return this.room ? this.room.appearance : ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
:key="assignment.id"
|
:key="assignment.id"
|
||||||
class="module-navigation__anchor sub-navigation-item__link"
|
class="module-navigation__anchor sub-navigation-item__link"
|
||||||
exact-active-class="module-navigation__anchor--active"
|
exact-active-class="module-navigation__anchor--active"
|
||||||
>{{assignmentTitle(assignment)}}
|
>{{assignment.value.assignment}}
|
||||||
</router-link>
|
</router-link>
|
||||||
</sub-navigation-item>
|
</sub-navigation-item>
|
||||||
<div class="module-navigation__module-content" v-if="false"> <!-- Do not display this for now, might be used later again though -->
|
<div class="module-navigation__module-content" v-if="false"> <!-- Do not display this for now, might be used later again though -->
|
||||||
|
|
@ -41,8 +41,6 @@
|
||||||
class="module-navigation__solution-toggle"
|
class="module-navigation__solution-toggle"
|
||||||
data-cy="toggle-enable-solutions"></toggle-solutions-for-module>
|
data-cy="toggle-enable-solutions"></toggle-solutions-for-module>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -79,13 +77,14 @@
|
||||||
showResults() {
|
showResults() {
|
||||||
return this.me.permissions.includes('users.can_manage_school_class_content');
|
return this.me.permissions.includes('users.can_manage_school_class_content');
|
||||||
},
|
},
|
||||||
assignments() {
|
|
||||||
return [...this.module.assignments].sort((a, b) => {
|
|
||||||
return a.title.toLowerCase() > b.title.toLowerCase() ? 1 : -1;
|
|
||||||
})
|
|
||||||
},
|
|
||||||
canManageContent() {
|
canManageContent() {
|
||||||
return this.me.permissions.includes('users.can_manage_school_class_content');
|
return this.me.permissions.includes('users.can_manage_school_class_content');
|
||||||
|
},
|
||||||
|
assignments() {
|
||||||
|
if (!this.module.chapters) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return this.extractAssignmentsFromChapters(this.module.chapters, []);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -105,10 +104,47 @@
|
||||||
return `#chapter-${index}`
|
return `#chapter-${index}`
|
||||||
},
|
},
|
||||||
submissionsLink(assignment) {
|
submissionsLink(assignment) {
|
||||||
return `/module/${this.module.slug}/submissions/${assignment.id}`;
|
return `/module/${this.module.slug}/submissions/${assignment.value.id}`;
|
||||||
},
|
},
|
||||||
assignmentTitle(assignment) {
|
extractAssignmentsFromChapters(chapters, assignments) {
|
||||||
return assignment.assignment.length > 25 ? assignment.assignment.substring(0, 22) + '...' : assignment.assignment;
|
chapters.forEach(node => {
|
||||||
|
if (node.contentBlocks) { // in chapter node
|
||||||
|
// if chapter information is required then do it here like so:
|
||||||
|
// assignments.push({
|
||||||
|
// chapterTitle: node.title
|
||||||
|
// });
|
||||||
|
// return this.extractAssignmentsFromChapters(node.contentBlocks, assignments);
|
||||||
|
assignments = this.extractAssignmentsFromChapters(node.contentBlocks, assignments);
|
||||||
|
} else if (node.contents) {
|
||||||
|
let foundAssignments = [];
|
||||||
|
node.contents.forEach(contentNode => {
|
||||||
|
foundAssignments = this.concatAssignments(foundAssignments, contentNode);
|
||||||
|
});
|
||||||
|
assignments = [...assignments, ...foundAssignments];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return assignments;
|
||||||
|
},
|
||||||
|
concatAssignments(foundAssignments, node) {
|
||||||
|
let foundAssignment = this.findAssignment(node);
|
||||||
|
return foundAssignment ? [...foundAssignments, ...foundAssignment] : foundAssignments;
|
||||||
|
},
|
||||||
|
findAssignment(node) {
|
||||||
|
if (node.type && node.type === 'assignment') {
|
||||||
|
return [node];
|
||||||
|
} else if (node.type && node.type === 'content_list_item') {
|
||||||
|
let foundAssignments = [];
|
||||||
|
node.value.forEach(contentNode => {
|
||||||
|
foundAssignments = this.concatAssignments(foundAssignments, contentNode);
|
||||||
|
});
|
||||||
|
return this.flattenArray(foundAssignments)
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
flattenArray(arrayToFlatten) {
|
||||||
|
// https://stackoverflow.com/questions/10865025/merge-flatten-an-array-of-arrays
|
||||||
|
return [].concat.apply([], arrayToFlatten);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -156,6 +192,9 @@
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
line-height: 1.2rem;
|
line-height: 1.2rem;
|
||||||
margin-bottom: .6875rem;
|
margin-bottom: .6875rem;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
&--active {
|
&--active {
|
||||||
color: $color-brand;
|
color: $color-brand;
|
||||||
|
|
|
||||||
|
|
@ -59,8 +59,8 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
grid-template-rows: 150px 1fr;
|
grid-template-rows: 175px 1fr;
|
||||||
-ms-grid-rows: 150px 48px;
|
-ms-grid-rows: 175px 48px;
|
||||||
-ms-grid-columns: 1fr;
|
-ms-grid-columns: 1fr;
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,10 @@
|
||||||
mounted () {
|
mounted () {
|
||||||
if (this.avatarUrl !== '') {
|
if (this.avatarUrl !== '') {
|
||||||
this.$refs.fakeImage.addEventListener('load', () => {
|
this.$refs.fakeImage.addEventListener('load', () => {
|
||||||
this.$refs.fakeImage.remove();
|
if (this.$refs.fakeImage) {
|
||||||
this.isAvatarLoaded = true;
|
this.$refs.fakeImage.remove();
|
||||||
|
this.isAvatarLoaded = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<checkbox v-if="canToggleSolutions" label="Lösungen für Schüler anzeigen" :checked="enabled"
|
<checkbox v-if="canToggleSolutions" label="Lösungen für Lernende anzeigen" :checked="enabled"
|
||||||
@input="toggleSolutions"></checkbox>
|
@input="toggleSolutions"></checkbox>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
#import "./fragments/roomParts.gql"
|
||||||
|
#import "./fragments/roomEntryParts.gql"
|
||||||
|
query ModuleRoomEntriesQuery($slug: String, $classId: ID!) {
|
||||||
|
moduleRoom(slug: $slug, classId: $classId) {
|
||||||
|
...RoomParts
|
||||||
|
roomEntries {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
...RoomEntryParts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -35,74 +35,6 @@
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "@/styles/_variables.scss";
|
@import "@/styles/_variables.scss";
|
||||||
@import "@/styles/_mixins.scss";
|
@import "@/styles/_mixins.scss";
|
||||||
|
@import "@/styles/_default-layout.scss";
|
||||||
|
|
||||||
.skillbox {
|
|
||||||
margin: 0 auto;
|
|
||||||
width: 100%;
|
|
||||||
@supports (display: grid) {
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
grid-template-rows: auto 1fr;
|
|
||||||
min-height: 100vh;
|
|
||||||
grid-auto-rows: 1fr;
|
|
||||||
|
|
||||||
grid-template-areas: "h" "c";
|
|
||||||
padding-bottom: 50px;
|
|
||||||
|
|
||||||
&--show-filter {
|
|
||||||
grid-template-rows: auto auto 1fr;
|
|
||||||
-ms-grid-rows: 50px 50px 30px 1fr; // 1 extra row for gap
|
|
||||||
grid-template-areas: "h" "." "c";
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* For IE10+
|
|
||||||
*/
|
|
||||||
|
|
||||||
&--show-filter &__content {
|
|
||||||
-ms-grid-row: 4;
|
|
||||||
-ms-grid-column: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--show-filter &__filter-bar {
|
|
||||||
-ms-grid-row: 2;
|
|
||||||
-ms-grid-column: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* For IE10+
|
|
||||||
*/
|
|
||||||
display: -ms-grid;
|
|
||||||
-ms-grid-rows: 50px 30px auto; // 1 extra row for gap
|
|
||||||
-ms-grid-columns: 1fr;
|
|
||||||
|
|
||||||
@include skillbox-colors;
|
|
||||||
|
|
||||||
&__header {
|
|
||||||
grid-area: h;
|
|
||||||
-ms-grid-row: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__content {
|
|
||||||
-ms-grid-row: 3;
|
|
||||||
-ms-grid-column: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__footer {
|
|
||||||
grid-area: f;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* For IE10+
|
|
||||||
*/
|
|
||||||
& > :nth-child(2) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
& > :nth-child(3) {
|
|
||||||
-ms-grid-row: 4;
|
|
||||||
-ms-grid-column: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
<template>
|
||||||
|
<div class="container skillbox" :class="specialContainerClass">
|
||||||
|
<div class="close-button" v-on:click="back">
|
||||||
|
<cross class="close-button__icon"></cross>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<router-view class="skillbox__content"></router-view>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Cross from '@/components/icons/Cross';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Cross
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
specialContainerClass() {
|
||||||
|
let cls = this.$store.state.specialContainerClass;
|
||||||
|
return [cls ? `skillbox--${cls}` : '']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
back() {
|
||||||
|
this.$router.go(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "@/styles/_default-layout.scss";
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
margin-top: $medium-spacing;
|
||||||
|
margin-right: $medium-spacing;
|
||||||
|
justify-self: end;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
display:flex;
|
||||||
|
justify-content:end;
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss" scoped>
|
||||||
@import "@/styles/_mixins.scss";
|
@import "@/styles/_mixins.scss";
|
||||||
|
|
||||||
.layout {
|
.layout {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
<template>
|
||||||
|
<div class="room">
|
||||||
|
<div class="room__header">
|
||||||
|
<h1 class="room__title">{{room.title}}</h1>
|
||||||
|
<p class="room__intro">
|
||||||
|
{{room.description}}
|
||||||
|
</p>
|
||||||
|
<div class="room__meta">
|
||||||
|
<room-group-widget v-bind="room.schoolClass"></room-group-widget>
|
||||||
|
<entry-count-widget :entry-count="roomEntryCount"></entry-count-widget>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="room__content">
|
||||||
|
<add-room-entry-button :parent="room" v-if="room.id">
|
||||||
|
<!--
|
||||||
|
the v-if is there for the case where the room hasn't loaded yet, but there is already an attempt to create
|
||||||
|
a new room entry. mainly happens during cypress testing, but could also happen on a very slow connection
|
||||||
|
-->
|
||||||
|
</add-room-entry-button>
|
||||||
|
<room-entry v-for="entry in room.roomEntries" v-bind="entry" :key="entry.id"></room-entry>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import MODULE_ROOM_ENTRIES_QUERY from '@/graphql/gql/moduleRoomEntryQuery.gql';
|
||||||
|
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
||||||
|
import roomMixin from '@/components/mixins/room'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['slug'],
|
||||||
|
mixins: [roomMixin],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
room: [],
|
||||||
|
entries: [],
|
||||||
|
me: {
|
||||||
|
selectedClass: {
|
||||||
|
id: ''
|
||||||
|
},
|
||||||
|
permissions: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
apollo: {
|
||||||
|
moduleRoom: {
|
||||||
|
query: MODULE_ROOM_ENTRIES_QUERY,
|
||||||
|
variables() {
|
||||||
|
return {
|
||||||
|
slug: this.slug,
|
||||||
|
classId: this.me.selectedClass.id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// manual: true,
|
||||||
|
// todo: do we really need manual here? update should do the trick too
|
||||||
|
result({data, loading, networkStatus}) {
|
||||||
|
if (!loading) {
|
||||||
|
this.room = Object.assign({}, this.$getRidOfEdges(data).moduleRoom);
|
||||||
|
this.$store.dispatch('setSpecialContainerClass', this.room.appearance);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pollInterval: 5000,
|
||||||
|
},
|
||||||
|
me: {
|
||||||
|
query: ME_QUERY,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/styles/_room.scss";
|
||||||
|
</style>
|
||||||
|
|
@ -66,7 +66,7 @@
|
||||||
grid-template-columns: minmax(max-content, 840px);
|
grid-template-columns: minmax(max-content, 840px);
|
||||||
}
|
}
|
||||||
grid-row-gap: 30px;
|
grid-row-gap: 30px;
|
||||||
grid-auto-rows: 200px;
|
grid-auto-rows: 225px;
|
||||||
max-width: 840px;
|
max-width: 840px;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
/*justify-self: center;*/
|
/*justify-self: center;*/
|
||||||
|
|
|
||||||
|
|
@ -25,46 +25,11 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ROOM_ENTRIES_QUERY from '@/graphql/gql/roomEntriesQuery.gql';
|
import ROOM_ENTRIES_QUERY from '@/graphql/gql/roomEntriesQuery.gql';
|
||||||
|
import roomMixin from '@/components/mixins/room'
|
||||||
import AddRoomEntryButton from '@/components/rooms/AddRoomEntryButton.vue';
|
|
||||||
import RoomEntry from '@/components/rooms/RoomEntry.vue';
|
|
||||||
import RoomGroupWidget from '@/components/rooms/RoomGroupWidget';
|
|
||||||
import EntryCountWidget from '@/components/rooms/EntryCountWidget';
|
|
||||||
import RoomActions from '@/components/rooms/RoomActions';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['slug'],
|
props: ['slug'],
|
||||||
|
mixins: [roomMixin],
|
||||||
components: {
|
|
||||||
EntryCountWidget,
|
|
||||||
RoomGroupWidget,
|
|
||||||
AddRoomEntryButton,
|
|
||||||
RoomEntry,
|
|
||||||
RoomActions
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeDestroy() {
|
|
||||||
this.$store.dispatch('setSpecialContainerClass', '');
|
|
||||||
},
|
|
||||||
|
|
||||||
created() {
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
room: [],
|
|
||||||
entries: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
roomEntryCount() {
|
|
||||||
return (this.room && this.room.roomEntries) ? this.room.roomEntries.length : 0
|
|
||||||
},
|
|
||||||
roomAppearance() {
|
|
||||||
return this.room ? this.room.appearance : ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
apollo: {
|
apollo: {
|
||||||
modules: {
|
modules: {
|
||||||
|
|
@ -89,54 +54,5 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@import "@/styles/_variables.scss";
|
@import "@/styles/_room.scss";
|
||||||
@import "@/styles/_functions.scss";
|
|
||||||
@import "@/styles/_mixins.scss";
|
|
||||||
|
|
||||||
.room {
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: auto 1fr;
|
|
||||||
margin-bottom: -50px;
|
|
||||||
|
|
||||||
&__header {
|
|
||||||
padding: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__intro {
|
|
||||||
font-family: $sans-serif-font-family;
|
|
||||||
font-weight: $font-weight-regular;
|
|
||||||
font-size: toRem(17px);
|
|
||||||
max-width: 900px;
|
|
||||||
line-height: 1.5;
|
|
||||||
margin-bottom: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__meta {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
@include desktop {
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
}
|
|
||||||
justify-content: start;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
& > :first-child {
|
|
||||||
margin-left: $large-spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > :nth-child(2) {
|
|
||||||
margin-left: $large-spacing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__content {
|
|
||||||
padding: 50px 15px;
|
|
||||||
background-color: rgba($color-charcoal-dark, 0.18);
|
|
||||||
@include desktop {
|
|
||||||
columns: 4;
|
|
||||||
padding: 50px 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -35,16 +35,16 @@
|
||||||
<portfolio-illustration></portfolio-illustration>
|
<portfolio-illustration></portfolio-illustration>
|
||||||
</section-block>
|
</section-block>
|
||||||
</div>
|
</div>
|
||||||
<div class="start-page__news news">
|
<!-- <div class="start-page__news news">-->
|
||||||
<h2 class="news__title">News</h2>
|
<!-- <h2 class="news__title">News</h2>-->
|
||||||
<news-teaser date="19. Dezember 2018" title="Bilder eines Jahres"
|
<!-- <news-teaser date="19. Dezember 2018" title="Bilder eines Jahres"-->
|
||||||
url="https://www.brennpunkt-welt.ch/jahresrückblick-2018/"></news-teaser>
|
<!-- url="https://www.brennpunkt-welt.ch/jahresrückblick-2018/"></news-teaser>-->
|
||||||
<news-teaser date="20. November 2018" title="100 Jahre Erster Weltkrieg"
|
<!-- <news-teaser date="20. November 2018" title="100 Jahre Erster Weltkrieg"-->
|
||||||
url="http://abunews-1f178193a10edaabff3ab828e30af44.webflow.io/"></news-teaser>
|
<!-- url="http://abunews-1f178193a10edaabff3ab828e30af44.webflow.io/"></news-teaser>-->
|
||||||
<news-teaser date="31. Oktober 2018" title="Sommerzeit - Festivalzeit"
|
<!-- <news-teaser date="31. Oktober 2018" title="Sommerzeit - Festivalzeit"-->
|
||||||
url="https://abunews.webflow.io/"></news-teaser>
|
<!-- url="https://abunews.webflow.io/"></news-teaser>-->
|
||||||
<div class="news__more">Mehr...</div>
|
<!-- <div class="news__more">Mehr...</div>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import editProject from '@/pages/editProject'
|
||||||
import newProject from '@/pages/newProject'
|
import newProject from '@/pages/newProject'
|
||||||
import surveyPage from '@/pages/survey'
|
import surveyPage from '@/pages/survey'
|
||||||
import styleGuidePage from '@/pages/styleguide'
|
import styleGuidePage from '@/pages/styleguide'
|
||||||
|
import moduleRoom from '@/pages/moduleRoom'
|
||||||
|
|
||||||
import store from '@/store/index';
|
import store from '@/store/index';
|
||||||
|
|
||||||
|
|
@ -46,13 +47,20 @@ const routes = [
|
||||||
name: 'submissions',
|
name: 'submissions',
|
||||||
component: submissions,
|
component: submissions,
|
||||||
meta: {filter: true}
|
meta: {filter: true}
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{path: '/rooms', name: 'rooms', component: rooms, meta: {filter: true}},
|
{path: '/rooms', name: 'rooms', component: rooms, meta: {filter: true}},
|
||||||
{path: '/new-room/', name: 'new-room', component: newRoom},
|
{path: '/new-room/', name: 'new-room', component: newRoom},
|
||||||
{path: '/edit-room/:id', name: 'edit-room', component: editRoom, props: true},
|
{path: '/edit-room/:id', name: 'edit-room', component: editRoom, props: true},
|
||||||
{path: '/room/:slug', name: 'room', component: room, props: true},
|
{path: '/room/:slug', name: 'room', component: room, props: true},
|
||||||
|
{
|
||||||
|
path: '/module-room/:slug',
|
||||||
|
name: 'moduleRoom',
|
||||||
|
component: moduleRoom,
|
||||||
|
props: true,
|
||||||
|
meta: {layout: 'fullScreen'}
|
||||||
|
},
|
||||||
{path: '/article/:slug', name: 'article', component: article, meta: {layout: 'simple'}},
|
{path: '/article/:slug', name: 'article', component: article, meta: {layout: 'simple'}},
|
||||||
{
|
{
|
||||||
path: '/instruments/:slug',
|
path: '/instruments/:slug',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
@import "@/styles/_variables.scss";
|
||||||
|
@import "@/styles/_mixins.scss";
|
||||||
|
|
||||||
|
.skillbox {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
@supports (display: grid) {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
min-height: 100vh;
|
||||||
|
grid-auto-rows: 1fr;
|
||||||
|
|
||||||
|
grid-template-areas: "h" "c";
|
||||||
|
padding-bottom: 50px;
|
||||||
|
|
||||||
|
&--show-filter {
|
||||||
|
grid-template-rows: auto auto 1fr;
|
||||||
|
-ms-grid-rows: 50px 50px 30px 1fr; // 1 extra row for gap
|
||||||
|
grid-template-areas: "h" "." "c";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For IE10+
|
||||||
|
*/
|
||||||
|
|
||||||
|
&--show-filter &__content {
|
||||||
|
-ms-grid-row: 4;
|
||||||
|
-ms-grid-column: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--show-filter &__filter-bar {
|
||||||
|
-ms-grid-row: 2;
|
||||||
|
-ms-grid-column: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For IE10+
|
||||||
|
*/
|
||||||
|
display: -ms-grid;
|
||||||
|
-ms-grid-rows: 50px 30px auto; // 1 extra row for gap
|
||||||
|
-ms-grid-columns: 1fr;
|
||||||
|
|
||||||
|
@include skillbox-colors;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
grid-area: h;
|
||||||
|
-ms-grid-row: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
-ms-grid-row: 3;
|
||||||
|
-ms-grid-column: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
grid-area: f;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For IE10+
|
||||||
|
*/
|
||||||
|
& > :nth-child(2) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
& > :nth-child(3) {
|
||||||
|
-ms-grid-row: 4;
|
||||||
|
-ms-grid-column: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
@import "@/styles/_variables.scss";
|
||||||
|
@import "@/styles/_functions.scss";
|
||||||
|
@import "@/styles/_mixins.scss";
|
||||||
|
|
||||||
|
.room {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
margin-bottom: -50px;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__intro {
|
||||||
|
font-family: $sans-serif-font-family;
|
||||||
|
font-weight: $font-weight-regular;
|
||||||
|
font-size: toRem(17px);
|
||||||
|
max-width: 900px;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
@include desktop {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
justify-content: start;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
& > :first-child {
|
||||||
|
margin-left: $large-spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > :nth-child(2) {
|
||||||
|
margin-left: $large-spacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
padding: 50px 15px;
|
||||||
|
background-color: rgba($color-charcoal-dark, 0.18);
|
||||||
|
@include desktop {
|
||||||
|
columns: 4;
|
||||||
|
padding: 50px 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
@import "reset";
|
@import "reset";
|
||||||
@import "typography";
|
@import "typography";
|
||||||
@import "variables";
|
@import "variables";
|
||||||
|
@import "default-layout";
|
||||||
@import "buttons";
|
@import "buttons";
|
||||||
@import "forms";
|
@import "forms";
|
||||||
@import "uploadcare_overwrite";
|
@import "uploadcare_overwrite";
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,12 @@ from portfolio.schema import PortfolioQuery
|
||||||
from surveys.schema import SurveysQuery
|
from surveys.schema import SurveysQuery
|
||||||
from surveys.mutations import SurveysMutations
|
from surveys.mutations import SurveysMutations
|
||||||
from rooms.mutations import RoomMutations
|
from rooms.mutations import RoomMutations
|
||||||
from rooms.schema import RoomsQuery
|
from rooms.schema import RoomsQuery, ModuleRoomsQuery
|
||||||
from users.schema import UsersQuery
|
from users.schema import UsersQuery
|
||||||
from users.mutations import ProfileMutations
|
from users.mutations import ProfileMutations
|
||||||
|
|
||||||
|
|
||||||
class Query(UsersQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery, StudentSubmissionQuery,
|
class Query(UsersQuery, ModuleRoomsQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery, StudentSubmissionQuery,
|
||||||
BasicKnowledgeQuery, PortfolioQuery, MyActivityQuery, SurveysQuery, graphene.ObjectType):
|
BasicKnowledgeQuery, PortfolioQuery, MyActivityQuery, SurveysQuery, graphene.ObjectType):
|
||||||
node = relay.Node.Field()
|
node = relay.Node.Field()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,13 @@ class InstrumentTextBlock(blocks.StructBlock):
|
||||||
|
|
||||||
text = blocks.RichTextBlock(features=INSTRUMENTS_RICH_TEXT_FEATURES)
|
text = blocks.RichTextBlock(features=INSTRUMENTS_RICH_TEXT_FEATURES)
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleRoomSlugBlock(blocks.StructBlock):
|
||||||
|
class Meta:
|
||||||
|
icon = 'link'
|
||||||
|
|
||||||
|
title = blocks.TextBlock()
|
||||||
|
|
||||||
# 'text_block' 'task' 'basic_knowledge' 'student_entry' 'image_block'
|
# 'text_block' 'task' 'basic_knowledge' 'student_entry' 'image_block'
|
||||||
#
|
#
|
||||||
# url = blocks.URLBlock()
|
# url = blocks.URLBlock()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Generated by Django 2.0.6 on 2019-08-08 06:49
|
||||||
|
|
||||||
|
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', '0012_auto_20190722_0932'),
|
||||||
|
]
|
||||||
|
|
||||||
|
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())])), ('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())])), ('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),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -7,7 +7,7 @@ from wagtail.core.fields import StreamField
|
||||||
from wagtail.images.blocks import ImageChooserBlock
|
from wagtail.images.blocks import ImageChooserBlock
|
||||||
|
|
||||||
from books.blocks import TextBlock, BasicKnowledgeBlock, LinkBlock, VideoBlock, DocumentBlock, \
|
from books.blocks import TextBlock, BasicKnowledgeBlock, LinkBlock, VideoBlock, DocumentBlock, \
|
||||||
ImageUrlBlock, AssignmentBlock, InfogramBlock, GeniallyBlock, SubtitleBlock, SurveyBlock
|
ImageUrlBlock, AssignmentBlock, InfogramBlock, GeniallyBlock, SubtitleBlock, SurveyBlock, ModuleRoomSlugBlock
|
||||||
from core.wagtail_utils import StrictHierarchyPage
|
from core.wagtail_utils import StrictHierarchyPage
|
||||||
from users.models import SchoolClass
|
from users.models import SchoolClass
|
||||||
|
|
||||||
|
|
@ -48,7 +48,8 @@ class ContentBlock(StrictHierarchyPage):
|
||||||
('document_block', DocumentBlock()),
|
('document_block', DocumentBlock()),
|
||||||
('infogram_block', InfogramBlock()),
|
('infogram_block', InfogramBlock()),
|
||||||
('genially_block', GeniallyBlock()),
|
('genially_block', GeniallyBlock()),
|
||||||
('subtitle', SubtitleBlock())
|
('subtitle', SubtitleBlock()),
|
||||||
|
('module_room_slug', ModuleRoomSlugBlock())
|
||||||
]
|
]
|
||||||
|
|
||||||
content_list_item = StreamBlock(content_blocks)
|
content_list_item = StreamBlock(content_blocks)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ from graphene_django.filter import DjangoFilterConnectionField
|
||||||
|
|
||||||
from api.utils import get_object
|
from api.utils import get_object
|
||||||
from books.utils import are_solutions_enabled_for
|
from books.utils import are_solutions_enabled_for
|
||||||
|
from rooms.models import ModuleRoomSlug
|
||||||
from ..models import Book, Topic, Module, Chapter, ContentBlock
|
from ..models import Book, Topic, Module, Chapter, ContentBlock
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -25,9 +26,23 @@ class ContentBlockNode(DjangoObjectType):
|
||||||
return self.owner is not None and self.owner.pk == info.context.user.pk
|
return self.owner is not None and self.owner.pk == info.context.user.pk
|
||||||
|
|
||||||
def resolve_contents(self, info, **kwargs):
|
def resolve_contents(self, info, **kwargs):
|
||||||
if not are_solutions_enabled_for(info.context.user, self.module):
|
updated_stream_data = []
|
||||||
self.contents.stream_data = [content for content in self.contents.stream_data if
|
for content in self.contents.stream_data:
|
||||||
content['type'] != 'solution']
|
if not are_solutions_enabled_for(info.context.user, self.module) and content['type'] == 'solution':
|
||||||
|
continue
|
||||||
|
|
||||||
|
if content['type'] == 'module_room_slug':
|
||||||
|
try:
|
||||||
|
module_room_slug = ModuleRoomSlug.objects.get(title=content['value']['title'])
|
||||||
|
content['value'] = {
|
||||||
|
'title': content['value']['title'],
|
||||||
|
'slug': module_room_slug.slug
|
||||||
|
}
|
||||||
|
except ModuleRoomSlug.DoesNotExist:
|
||||||
|
pass
|
||||||
|
updated_stream_data.append(content)
|
||||||
|
|
||||||
|
self.contents.stream_data = updated_stream_data
|
||||||
return self.contents
|
return self.contents
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from rooms.models import Room, RoomEntry
|
from rooms.models import Room, RoomEntry, ModuleRoomSlug
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Room)
|
@admin.register(Room)
|
||||||
|
|
@ -13,3 +13,9 @@ class RoomAdmin(admin.ModelAdmin):
|
||||||
class RoomEntryAdmin(admin.ModelAdmin):
|
class RoomEntryAdmin(admin.ModelAdmin):
|
||||||
list_display = ('id', 'slug', 'title', 'room', 'author')
|
list_display = ('id', 'slug', 'title', 'room', 'author')
|
||||||
list_filter = ('room', 'author')
|
list_filter = ('room', 'author')
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(ModuleRoomSlug)
|
||||||
|
class AdminGeneratedRoomSlugAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('id', 'slug', 'title')
|
||||||
|
list_filter = ('slug', 'title')
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ from wagtail.core.rich_text import RichText
|
||||||
|
|
||||||
from books.factories import TextBlockFactory, ImageUrlBlockFactory, LinkBlockFactory
|
from books.factories import TextBlockFactory, ImageUrlBlockFactory, LinkBlockFactory
|
||||||
from core.factories import fake, fake_paragraph
|
from core.factories import fake, fake_paragraph
|
||||||
from rooms.models import Room, RoomEntry
|
from rooms.models import Room, RoomEntry, ModuleRoomSlug
|
||||||
from users.models import SchoolClass
|
from users.models import SchoolClass
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -77,3 +77,12 @@ class RoomEntryFactory(factory.django.DjangoModelFactory):
|
||||||
def create(cls, **kwargs):
|
def create(cls, **kwargs):
|
||||||
cls.stream_field_magic(kwargs, 'contents')
|
cls.stream_field_magic(kwargs, 'contents')
|
||||||
return cls._generate(CREATE_STRATEGY, kwargs)
|
return cls._generate(CREATE_STRATEGY, kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleRoomSlugFactory(factory.django.DjangoModelFactory):
|
||||||
|
class Meta:
|
||||||
|
model = ModuleRoomSlug
|
||||||
|
|
||||||
|
slug = factory.Sequence(lambda n: u'slug-{:d}'.format(n))
|
||||||
|
title = factory.Sequence(lambda n: u'Title {:d}'.format(n))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Generated by Django 2.0.6 on 2019-08-08 06:49
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django_extensions.db.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('rooms', '0006_auto_20190722_0932'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ModuleRoomSlug',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('title', models.CharField(max_length=255, verbose_name='title')),
|
||||||
|
('description', models.TextField(blank=True, null=True, verbose_name='description')),
|
||||||
|
('slug', django_extensions.db.fields.AutoSlugField(blank=True, editable=False, populate_from='title', verbose_name='slug')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='room',
|
||||||
|
name='user_created',
|
||||||
|
field=models.BooleanField(default=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -15,6 +15,7 @@ class Room(TitleSlugDescriptionModel):
|
||||||
|
|
||||||
school_class = models.ForeignKey(SchoolClass, blank=False, null=False, on_delete=models.CASCADE, related_name='rooms')
|
school_class = models.ForeignKey(SchoolClass, blank=False, null=False, on_delete=models.CASCADE, related_name='rooms')
|
||||||
appearance = models.CharField(blank=True, null=False, max_length=255)
|
appearance = models.CharField(blank=True, null=False, max_length=255)
|
||||||
|
user_created = models.BooleanField(blank=False, null=False, default=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return 'Room {}-{}-{}'.format(self.id, self.title, self.school_class)
|
return 'Room {}-{}-{}'.format(self.id, self.title, self.school_class)
|
||||||
|
|
@ -41,3 +42,9 @@ class RoomEntry(TitleSlugDescriptionModel):
|
||||||
|
|
||||||
def can_user_see_entry(self, user):
|
def can_user_see_entry(self, user):
|
||||||
return user.is_superuser or self.room.school_class.is_user_in_schoolclass(user)
|
return user.is_superuser or self.room.school_class.is_user_in_schoolclass(user)
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleRoomSlug(TitleSlugDescriptionModel):
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return 'ModuleRoomSlug {}-{}'.format(self.id, self.title)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@ from graphene_django import DjangoObjectType
|
||||||
from graphene_django.filter import DjangoFilterConnectionField
|
from graphene_django.filter import DjangoFilterConnectionField
|
||||||
|
|
||||||
from api.utils import get_object, get_by_id_or_slug
|
from api.utils import get_object, get_by_id_or_slug
|
||||||
from rooms.models import Room, RoomEntry
|
from rooms.models import Room, RoomEntry, ModuleRoomSlug
|
||||||
|
from users.models import SchoolClass
|
||||||
from users.schema import UserNode
|
from users.schema import UserNode
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -53,7 +54,7 @@ class RoomsQuery(object):
|
||||||
user = info.context.user
|
user = info.context.user
|
||||||
if user.is_superuser:
|
if user.is_superuser:
|
||||||
return Room.objects.all()
|
return Room.objects.all()
|
||||||
return Room.objects.filter(school_class__in=user.school_classes.all())
|
return Room.objects.filter(school_class__in=user.school_classes.all()).exclude(user_created=False)
|
||||||
|
|
||||||
def resolve_room(self, info, **kwargs):
|
def resolve_room(self, info, **kwargs):
|
||||||
room = get_by_id_or_slug(Room, **kwargs)
|
room = get_by_id_or_slug(Room, **kwargs)
|
||||||
|
|
@ -82,3 +83,30 @@ class RoomsQuery(object):
|
||||||
return RoomEntry.objects.none()
|
return RoomEntry.objects.none()
|
||||||
else:
|
else:
|
||||||
return RoomEntry.objects.all()
|
return RoomEntry.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleRoomsQuery(object):
|
||||||
|
|
||||||
|
module_room = graphene.Field(RoomNode, slug=graphene.String(), class_id=graphene.ID())
|
||||||
|
|
||||||
|
def resolve_module_room(self, info, **kwargs):
|
||||||
|
schoolclass = get_object(SchoolClass, kwargs.get('class_id'))
|
||||||
|
|
||||||
|
try:
|
||||||
|
slug = ModuleRoomSlug.objects.get(slug=kwargs.get('slug'))
|
||||||
|
except ModuleRoomSlug.DoesNotExist:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if schoolclass is None or not schoolclass.is_user_in_schoolclass(info.context.user):
|
||||||
|
return None
|
||||||
|
|
||||||
|
room, created = Room.objects.get_or_create(school_class=schoolclass, user_created=False, slug=slug.slug,
|
||||||
|
title=slug.title, appearance='blue')
|
||||||
|
if created:
|
||||||
|
room.slug = slug.slug
|
||||||
|
room.save()
|
||||||
|
|
||||||
|
if not room.user_created and room.school_class.is_user_in_schoolclass(info.context.user):
|
||||||
|
return room
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# ITerativ GmbH
|
||||||
|
# http://www.iterativ.ch/
|
||||||
|
#
|
||||||
|
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||||
|
#
|
||||||
|
# Created on 2019-08-07
|
||||||
|
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||||
|
from django.test import TestCase, RequestFactory
|
||||||
|
from graphene.test import Client
|
||||||
|
from graphql_relay import to_global_id
|
||||||
|
|
||||||
|
from api.schema import schema
|
||||||
|
from core.factories import UserFactory
|
||||||
|
from rooms.factories import RoomFactory, ModuleRoomSlugFactory
|
||||||
|
from users.factories import SchoolClassFactory
|
||||||
|
|
||||||
|
|
||||||
|
class AdminRoomQueryPermission(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.user = UserFactory(username='aschi')
|
||||||
|
self.another_user = UserFactory(username='pesche')
|
||||||
|
self.sc1 = SchoolClassFactory(users=[self.user])
|
||||||
|
sc2 = SchoolClassFactory(users=[self.another_user])
|
||||||
|
self.room1 = RoomFactory(school_class=self.sc1)
|
||||||
|
self.room2 = RoomFactory(school_class=sc2)
|
||||||
|
|
||||||
|
self.module_room_slug = ModuleRoomSlugFactory(title='some title')
|
||||||
|
|
||||||
|
self.sc1_id = to_global_id('SchoolClass', self.sc1.pk)
|
||||||
|
self.sc2_id = to_global_id('SchoolClass', sc2.pk)
|
||||||
|
|
||||||
|
request = RequestFactory().get('/')
|
||||||
|
request.user = self.user
|
||||||
|
self.client = Client(schema=schema, context_value=request)
|
||||||
|
|
||||||
|
self.query = '''
|
||||||
|
query ModuleRoomEntriesQuery($slug: String, $classId: ID!) {
|
||||||
|
moduleRoom(slug: $slug, classId: $classId) {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
|
||||||
|
def test_should_return_none_if_slug_does_not_exist(self):
|
||||||
|
|
||||||
|
result = self.client.execute(self.query, variables={
|
||||||
|
'slug': 'no-slug',
|
||||||
|
'classId': 'norealId'
|
||||||
|
})
|
||||||
|
self.assertIsNone(result.get('errors'))
|
||||||
|
self.assertIsNone(result.get('data').get('moduleRoom'))
|
||||||
|
|
||||||
|
def test_should_return_none_if_class_id_does_not_exist(self):
|
||||||
|
|
||||||
|
result = self.client.execute(self.query, variables={
|
||||||
|
'slug': 'no-slug',
|
||||||
|
'classId': 'norealId'
|
||||||
|
})
|
||||||
|
self.assertIsNone(result.get('errors'))
|
||||||
|
self.assertIsNone(result.get('data').get('moduleRoom'))
|
||||||
|
|
||||||
|
def test_user_should_not_be_able_to_create_room_for_other_class(self):
|
||||||
|
|
||||||
|
result = self.client.execute(self.query, variables={
|
||||||
|
'slug': self.module_room_slug.slug,
|
||||||
|
'classId': self.sc2_id
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertIsNone(result.get('errors'))
|
||||||
|
self.assertIsNone(result.get('data').get('moduleRoom'))
|
||||||
|
|
||||||
|
def test_should_create_room_if_none_exists(self):
|
||||||
|
|
||||||
|
result = self.client.execute(self.query, variables={
|
||||||
|
'slug': self.module_room_slug.slug,
|
||||||
|
'classId': self.sc1_id
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertIsNone(result.get('errors'))
|
||||||
|
self.assertEqual(result.get('data').get('moduleRoom').get('title'), self.module_room_slug.title)
|
||||||
|
|
||||||
|
def test_should_return_room_if_one_exists(self):
|
||||||
|
|
||||||
|
existing_room = RoomFactory(school_class=self.sc1, user_created=False)
|
||||||
|
admin_slug = ModuleRoomSlugFactory(slug=existing_room.slug, title=existing_room.title)
|
||||||
|
|
||||||
|
result = self.client.execute(self.query, variables={
|
||||||
|
'slug': admin_slug.slug,
|
||||||
|
'classId': self.sc1_id
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertIsNone(result.get('errors'))
|
||||||
|
self.assertEqual(result.get('data').get('moduleRoom').get('title'), existing_room.title)
|
||||||
|
|
@ -61,6 +61,27 @@ class RoomQueryPermission(TestCase):
|
||||||
self.assertIsNone(result.get('errors'))
|
self.assertIsNone(result.get('errors'))
|
||||||
self.assertEqual(result.get('data').get('room'), None)
|
self.assertEqual(result.get('data').get('room'), None)
|
||||||
|
|
||||||
|
def test_student_should_only_user_created_rooms(self):
|
||||||
|
|
||||||
|
modlue_room = RoomFactory(school_class=self.room1.school_class, user_created=False)
|
||||||
|
|
||||||
|
query = '''
|
||||||
|
query {
|
||||||
|
rooms {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
|
||||||
|
result = self.client.execute(query)
|
||||||
|
self.assertIsNone(result.get('errors'))
|
||||||
|
self.assertEqual(len(result.get('data').get('rooms').get('edges')), 1)
|
||||||
|
self.assertNotEqual(result.get('data').get('rooms').get('edges')[0].get('node').get('title'), modlue_room.title)
|
||||||
|
|
||||||
|
|
||||||
class RoomEntryQueryPermissions(TestCase):
|
class RoomEntryQueryPermissions(TestCase):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# ITerativ GmbH
|
||||||
|
# http://www.iterativ.ch/
|
||||||
|
#
|
||||||
|
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||||
|
#
|
||||||
|
# Created on 2019-08-05
|
||||||
|
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||||
|
from wagtail.core import hooks
|
||||||
|
|
||||||
|
from rooms.models import ModuleRoomSlug
|
||||||
|
|
||||||
|
|
||||||
|
@hooks.register('after_edit_page')
|
||||||
|
@hooks.register('after_create_page')
|
||||||
|
def do_after_page_edit(request, page):
|
||||||
|
blocks = get_room_blocks(page)
|
||||||
|
for block in blocks:
|
||||||
|
if isinstance(block, tuple):
|
||||||
|
title = block[1]['title']
|
||||||
|
if isinstance(block, dict):
|
||||||
|
title = block['value']['title']
|
||||||
|
ModuleRoomSlug.objects.get_or_create(title=title)
|
||||||
|
|
||||||
|
|
||||||
|
def get_room_blocks(page):
|
||||||
|
top_level_module_room_slug_blocks = get_block_from_stream_data(page.contents.stream_data, 'module_room_slug')
|
||||||
|
content_list_module_room_slug_blocks = get_admin_slugs_from_content_list(page.contents.stream_data)
|
||||||
|
return top_level_module_room_slug_blocks + content_list_module_room_slug_blocks
|
||||||
|
|
||||||
|
|
||||||
|
def get_block_from_stream_data(stream_data, block_name):
|
||||||
|
if isinstance(stream_data[0], tuple):
|
||||||
|
return [block for block in stream_data if block[0] in [block_name]]
|
||||||
|
if isinstance(stream_data[0], dict):
|
||||||
|
return [block for block in stream_data if block['type'] in [block_name]]
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def get_admin_slugs_from_content_list(stream_data):
|
||||||
|
module_room_slug_blocks = []
|
||||||
|
content_list_items = get_block_from_stream_data(stream_data, 'content_list_item')
|
||||||
|
for content_list_item in content_list_items:
|
||||||
|
module_room_slug_blocks = module_room_slug_blocks + get_block_from_stream_data(content_list_item[1].stream_data,
|
||||||
|
'module_room_slug')
|
||||||
|
return module_room_slug_blocks
|
||||||
Loading…
Reference in New Issue