Rename block, update frontend

This commit is contained in:
Christian Cueni 2019-08-08 12:58:37 +02:00
parent 8e0f9fd377
commit 2fa006d790
27 changed files with 410 additions and 210 deletions

View File

@ -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,

View File

@ -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,

View File

@ -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>

View File

@ -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 : ''
}
}
}

View File

@ -25,8 +25,10 @@
mounted () { mounted () {
if (this.avatarUrl !== '') { if (this.avatarUrl !== '') {
this.$refs.fakeImage.addEventListener('load', () => { this.$refs.fakeImage.addEventListener('load', () => {
if (this.$refs.fakeImage) {
this.$refs.fakeImage.remove(); this.$refs.fakeImage.remove();
this.isAvatarLoaded = true; this.isAvatarLoaded = true;
}
}); });
}; };
} }

View File

@ -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
}
}
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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',

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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";

View File

@ -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, AdminRoomsQuery 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, AdminRoomsQuery, 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()

View File

@ -99,7 +99,7 @@ class InstrumentTextBlock(blocks.StructBlock):
text = blocks.RichTextBlock(features=INSTRUMENTS_RICH_TEXT_FEATURES) text = blocks.RichTextBlock(features=INSTRUMENTS_RICH_TEXT_FEATURES)
class AdminRoomSlugBlock(blocks.StructBlock): class ModuleRoomSlugBlock(blocks.StructBlock):
class Meta: class Meta:
icon = 'link' icon = 'link'

View File

@ -1,4 +1,4 @@
# Generated by Django 2.0.6 on 2019-08-07 13:40 # Generated by Django 2.0.6 on 2019-08-08 06:49
import assignments.models import assignments.models
from django.db import migrations from django.db import migrations
@ -19,6 +19,6 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='contentblock', model_name='contentblock',
name='contents', 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())])), ('admin_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())])), ('admin_room_slug', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock())]))]))], blank=True, null=True), 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),
), ),
] ]

View File

@ -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, AdminRoomSlugBlock 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
@ -49,7 +49,7 @@ class ContentBlock(StrictHierarchyPage):
('infogram_block', InfogramBlock()), ('infogram_block', InfogramBlock()),
('genially_block', GeniallyBlock()), ('genially_block', GeniallyBlock()),
('subtitle', SubtitleBlock()), ('subtitle', SubtitleBlock()),
('admin_room_slug', AdminRoomSlugBlock()) ('module_room_slug', ModuleRoomSlugBlock())
] ]
content_list_item = StreamBlock(content_blocks) content_list_item = StreamBlock(content_blocks)

View File

@ -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

View File

@ -1,6 +1,6 @@
from django.contrib import admin from django.contrib import admin
from rooms.models import Room, RoomEntry, AdminGeneratedRoomSlug from rooms.models import Room, RoomEntry, ModuleRoomSlug
@admin.register(Room) @admin.register(Room)
@ -15,7 +15,7 @@ class RoomEntryAdmin(admin.ModelAdmin):
list_filter = ('room', 'author') list_filter = ('room', 'author')
@admin.register(AdminGeneratedRoomSlug) @admin.register(ModuleRoomSlug)
class AdminGeneratedRoomSlugAdmin(admin.ModelAdmin): class AdminGeneratedRoomSlugAdmin(admin.ModelAdmin):
list_display = ('id', 'slug', 'title') list_display = ('id', 'slug', 'title')
list_filter = ('slug', 'title') list_filter = ('slug', 'title')

View File

@ -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, AdminGeneratedRoomSlug from rooms.models import Room, RoomEntry, ModuleRoomSlug
from users.models import SchoolClass from users.models import SchoolClass
@ -79,9 +79,9 @@ class RoomEntryFactory(factory.django.DjangoModelFactory):
return cls._generate(CREATE_STRATEGY, kwargs) return cls._generate(CREATE_STRATEGY, kwargs)
class AdminGeneratedRoomSlugFactory(factory.django.DjangoModelFactory): class ModuleRoomSlugFactory(factory.django.DjangoModelFactory):
class Meta: class Meta:
model = AdminGeneratedRoomSlug model = ModuleRoomSlug
slug = factory.Sequence(lambda n: u'slug-{:d}'.format(n)) slug = factory.Sequence(lambda n: u'slug-{:d}'.format(n))
title = factory.Sequence(lambda n: u'Title {:d}'.format(n)) title = factory.Sequence(lambda n: u'Title {:d}'.format(n))

View File

@ -1,4 +1,4 @@
# Generated by Django 2.0.6 on 2019-08-07 13:23 # Generated by Django 2.0.6 on 2019-08-08 06:49
from django.db import migrations, models from django.db import migrations, models
import django_extensions.db.fields import django_extensions.db.fields
@ -12,7 +12,7 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='AdminGeneratedRoomSlug', name='ModuleRoomSlug',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255, verbose_name='title')), ('title', models.CharField(max_length=255, verbose_name='title')),

View File

@ -44,7 +44,7 @@ class RoomEntry(TitleSlugDescriptionModel):
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 AdminGeneratedRoomSlug(TitleSlugDescriptionModel): class ModuleRoomSlug(TitleSlugDescriptionModel):
def __str__(self): def __str__(self):
return 'AdmimSlug {}-{}'.format(self.id, self.title) return 'ModuleRoomSlug {}-{}'.format(self.id, self.title)

View File

@ -6,7 +6,7 @@ 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, AdminGeneratedRoomSlug from rooms.models import Room, RoomEntry, ModuleRoomSlug
from users.models import SchoolClass from users.models import SchoolClass
from users.schema import UserNode from users.schema import UserNode
@ -85,25 +85,27 @@ class RoomsQuery(object):
return RoomEntry.objects.all() return RoomEntry.objects.all()
class AdminRoomsQuery(object): class ModuleRoomsQuery(object):
admin_room = graphene.Field(RoomNode, slug=graphene.String(), class_id=graphene.ID()) module_room = graphene.Field(RoomNode, slug=graphene.String(), class_id=graphene.ID())
def resolve_admin_room(self, info, **kwargs): def resolve_module_room(self, info, **kwargs):
slug = kwargs.get('slug')
schoolclass = get_object(SchoolClass, kwargs.get('class_id')) schoolclass = get_object(SchoolClass, kwargs.get('class_id'))
try: try:
slug = AdminGeneratedRoomSlug.objects.get(slug=slug) slug = ModuleRoomSlug.objects.get(slug=kwargs.get('slug'))
except AdminGeneratedRoomSlug.DoesNotExist: except ModuleRoomSlug.DoesNotExist:
return None return None
if schoolclass is None or not schoolclass.is_user_in_schoolclass(info.context.user): if schoolclass is None or not schoolclass.is_user_in_schoolclass(info.context.user):
return None return None
room, created = Room.objects.get_or_create(school_class=schoolclass, user_created=False, slug=slug, room, created = Room.objects.get_or_create(school_class=schoolclass, user_created=False, slug=slug.slug,
title=slug.title, appearance='blue') 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): if not room.user_created and room.school_class.is_user_in_schoolclass(info.context.user):
return room return room
else: else:

View File

@ -13,7 +13,7 @@ from graphql_relay import to_global_id
from api.schema import schema from api.schema import schema
from core.factories import UserFactory from core.factories import UserFactory
from rooms.factories import RoomFactory, AdminGeneratedRoomSlugFactory from rooms.factories import RoomFactory, ModuleRoomSlugFactory
from users.factories import SchoolClassFactory from users.factories import SchoolClassFactory
@ -27,7 +27,7 @@ class AdminRoomQueryPermission(TestCase):
self.room1 = RoomFactory(school_class=self.sc1) self.room1 = RoomFactory(school_class=self.sc1)
self.room2 = RoomFactory(school_class=sc2) self.room2 = RoomFactory(school_class=sc2)
self.admin_slug = AdminGeneratedRoomSlugFactory(title='some title') self.module_room_slug = ModuleRoomSlugFactory(title='some title')
self.sc1_id = to_global_id('SchoolClass', self.sc1.pk) self.sc1_id = to_global_id('SchoolClass', self.sc1.pk)
self.sc2_id = to_global_id('SchoolClass', sc2.pk) self.sc2_id = to_global_id('SchoolClass', sc2.pk)
@ -37,8 +37,8 @@ class AdminRoomQueryPermission(TestCase):
self.client = Client(schema=schema, context_value=request) self.client = Client(schema=schema, context_value=request)
self.query = ''' self.query = '''
query AdminRoomQuery($slug: String, $classId: ID!) { query ModuleRoomEntriesQuery($slug: String, $classId: ID!) {
adminRoom(slug: $slug, classId: $classId) { moduleRoom(slug: $slug, classId: $classId) {
title title
} }
} }
@ -51,7 +51,7 @@ class AdminRoomQueryPermission(TestCase):
'classId': 'norealId' 'classId': 'norealId'
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.get('errors'))
self.assertIsNone(result.get('data').get('adminRoom')) self.assertIsNone(result.get('data').get('moduleRoom'))
def test_should_return_none_if_class_id_does_not_exist(self): def test_should_return_none_if_class_id_does_not_exist(self):
@ -60,32 +60,32 @@ class AdminRoomQueryPermission(TestCase):
'classId': 'norealId' 'classId': 'norealId'
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.get('errors'))
self.assertIsNone(result.get('data').get('adminRoom')) self.assertIsNone(result.get('data').get('moduleRoom'))
def test_user_should_not_be_able_to_create_room_for_other_class(self): def test_user_should_not_be_able_to_create_room_for_other_class(self):
result = self.client.execute(self.query, variables={ result = self.client.execute(self.query, variables={
'slug': self.admin_slug.slug, 'slug': self.module_room_slug.slug,
'classId': self.sc2_id 'classId': self.sc2_id
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.get('errors'))
self.assertIsNone(result.get('data').get('adminRoom')) self.assertIsNone(result.get('data').get('moduleRoom'))
def test_should_create_room_if_none_exists(self): def test_should_create_room_if_none_exists(self):
result = self.client.execute(self.query, variables={ result = self.client.execute(self.query, variables={
'slug': self.admin_slug.slug, 'slug': self.module_room_slug.slug,
'classId': self.sc1_id 'classId': self.sc1_id
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.get('errors'))
self.assertEqual(result.get('data').get('adminRoom').get('title'), self.admin_slug.title) self.assertEqual(result.get('data').get('moduleRoom').get('title'), self.module_room_slug.title)
def test_should_return_room_if_one_exists(self): def test_should_return_room_if_one_exists(self):
existing_room = RoomFactory(school_class=self.sc1, user_created=False) existing_room = RoomFactory(school_class=self.sc1, user_created=False)
admin_slug = AdminGeneratedRoomSlugFactory(slug=existing_room.slug, title=existing_room.title) admin_slug = ModuleRoomSlugFactory(slug=existing_room.slug, title=existing_room.title)
result = self.client.execute(self.query, variables={ result = self.client.execute(self.query, variables={
'slug': admin_slug.slug, 'slug': admin_slug.slug,
@ -93,4 +93,4 @@ class AdminRoomQueryPermission(TestCase):
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.get('errors'))
self.assertEqual(result.get('data').get('adminRoom').get('title'), existing_room.title) self.assertEqual(result.get('data').get('moduleRoom').get('title'), existing_room.title)

View File

@ -63,7 +63,7 @@ class RoomQueryPermission(TestCase):
def test_student_should_only_user_created_rooms(self): def test_student_should_only_user_created_rooms(self):
admin_room = RoomFactory(school_class=self.room1.school_class, user_created=False) modlue_room = RoomFactory(school_class=self.room1.school_class, user_created=False)
query = ''' query = '''
query { query {
@ -80,7 +80,7 @@ class RoomQueryPermission(TestCase):
result = self.client.execute(query) result = self.client.execute(query)
self.assertIsNone(result.get('errors')) self.assertIsNone(result.get('errors'))
self.assertEqual(len(result.get('data').get('rooms').get('edges')), 1) 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'), admin_room.title) self.assertNotEqual(result.get('data').get('rooms').get('edges')[0].get('node').get('title'), modlue_room.title)
class RoomEntryQueryPermissions(TestCase): class RoomEntryQueryPermissions(TestCase):

View File

@ -9,7 +9,7 @@
# @author: chrigu <christian.cueni@iterativ.ch> # @author: chrigu <christian.cueni@iterativ.ch>
from wagtail.core import hooks from wagtail.core import hooks
from rooms.models import AdminGeneratedRoomSlug from rooms.models import ModuleRoomSlug
@hooks.register('after_edit_page') @hooks.register('after_edit_page')
@ -21,13 +21,13 @@ def do_after_page_edit(request, page):
title = block[1]['title'] title = block[1]['title']
if isinstance(block, dict): if isinstance(block, dict):
title = block['value']['title'] title = block['value']['title']
AdminGeneratedRoomSlug.objects.get_or_create(title=title) ModuleRoomSlug.objects.get_or_create(title=title)
def get_room_blocks(page): def get_room_blocks(page):
top_level_admin_slug_blocks = get_block_from_stream_data(page.contents.stream_data, 'admin_room_slug') top_level_module_room_slug_blocks = get_block_from_stream_data(page.contents.stream_data, 'module_room_slug')
content_list_admin_slug_blocks = get_admin_slugs_from_content_list(page.contents.stream_data) content_list_module_room_slug_blocks = get_admin_slugs_from_content_list(page.contents.stream_data)
return top_level_admin_slug_blocks + content_list_admin_slug_blocks return top_level_module_room_slug_blocks + content_list_module_room_slug_blocks
def get_block_from_stream_data(stream_data, block_name): def get_block_from_stream_data(stream_data, block_name):
@ -39,9 +39,9 @@ def get_block_from_stream_data(stream_data, block_name):
def get_admin_slugs_from_content_list(stream_data): def get_admin_slugs_from_content_list(stream_data):
admin_slug_blocks = [] module_room_slug_blocks = []
content_list_items = get_block_from_stream_data(stream_data, 'content_list_item') content_list_items = get_block_from_stream_data(stream_data, 'content_list_item')
for content_list_item in content_list_items: for content_list_item in content_list_items:
admin_slug_blocks = admin_slug_blocks + get_block_from_stream_data(content_list_item[1].stream_data, module_room_slug_blocks = module_room_slug_blocks + get_block_from_stream_data(content_list_item[1].stream_data,
'admin_room_slug') 'module_room_slug')
return admin_slug_blocks return module_room_slug_blocks