Merged in feature/instrument-notes (pull request #43)

Feature/instrument notes

Approved-by: Christian Cueni <christian.cueni@iterativ.ch>
This commit is contained in:
Ramon Wenger 2020-01-16 13:13:38 +00:00
commit 0d6a7522f2
22 changed files with 394 additions and 189 deletions

View File

@ -24,7 +24,7 @@
methods: { methods: {
hideModal() { hideModal() {
this.$store.dispatch('resetCurrentContentBlock'); this.$store.dispatch('resetCurrentNoteBlock');
this.$store.dispatch('hideModal'); this.$store.dispatch('hideModal');
}, },
saveContentBlock(contentBlock) { saveContentBlock(contentBlock) {
@ -67,7 +67,7 @@
return { return {
query: CONTENT_BLOCK_QUERY, query: CONTENT_BLOCK_QUERY,
variables: { variables: {
id: store.state.currentContentBlock id: store.state.currentNoteBlock
} }
} }
} }

View File

@ -39,8 +39,7 @@
import Solution from '@/components/content-blocks/Solution'; import Solution from '@/components/content-blocks/Solution';
import BookmarkActions from '@/components/notes/BookmarkActions'; import BookmarkActions from '@/components/notes/BookmarkActions';
import UPDATE_CONTENT_BOOKMARK from '@/graphql/gql/mutations/updateContentBookmark.gql'; import {constructContentComponentBookmarkMutation} from '@/helpers/update-content-bookmark-mutation';
import CONTENT_BLOCK_QUERY from '@/graphql/gql/contentBlockQuery.gql';
export default { export default {
props: ['component', 'parent', 'bookmarks', 'notes', 'root'], props: ['component', 'parent', 'bookmarks', 'notes', 'root'],
@ -84,63 +83,15 @@
addNote(id) { addNote(id) {
this.$store.dispatch('addNote', { this.$store.dispatch('addNote', {
content: id, content: id,
contentBlock: this.root type: this.parent.__typename,
block: this.root
}); });
}, },
editNote() { editNote() {
this.$store.dispatch('editNote', this.note); this.$store.dispatch('editNote', this.note);
}, },
bookmarkContent(uuid, bookmarked) { bookmarkContent(uuid, bookmarked) {
this.$apollo.mutate({ this.$apollo.mutate(constructContentComponentBookmarkMutation(uuid, bookmarked, this.parent, this.root));
mutation: UPDATE_CONTENT_BOOKMARK,
variables: {
input: {
uuid,
contentBlock: this.root,
bookmarked
}
},
update: (store, response) => {
const query = CONTENT_BLOCK_QUERY;
const variables = {id: this.root};
const data = store.readQuery({
query,
variables
});
const bookmarks = data.contentBlock.bookmarks;
if (bookmarked) {
bookmarks.push({
note: null,
uuid: uuid,
__typename: 'ContentBlockBookmarkNode'
});
} else {
let index = bookmarks.findIndex(element => {
return element.uuid === uuid;
});
if (index > -1) {
bookmarks.splice(index, 1);
}
}
data.contentBlock.bookmarks = bookmarks;
store.writeQuery({
data,
query,
variables
});
},
optimisticResponse: {
__typename: 'Mutation',
updateContentBookmark: {
__typename: 'UpdateContentBookmarkPayload',
success: true
}
}
});
} }
} }
}; };

View File

@ -7,6 +7,7 @@
import UPDATE_NOTE_MUTATION from '@/graphql/gql/mutations/updateNote.gql'; import UPDATE_NOTE_MUTATION from '@/graphql/gql/mutations/updateNote.gql';
import MODULE_DETAILS_QUERY from '@/graphql/gql/moduleDetailsQuery.gql'; import MODULE_DETAILS_QUERY from '@/graphql/gql/moduleDetailsQuery.gql';
import INSTRUMENT_QUERY from '@/graphql/gql/instrumentQuery.gql';
import {mapGetters} from 'vuex'; import {mapGetters} from 'vuex';
@ -21,6 +22,9 @@
methods: { methods: {
editNote(note) { editNote(note) {
const slug = this.$route.params.slug;
const routeName = this.$route.name;
this.$apollo.mutate({ this.$apollo.mutate({
mutation: UPDATE_NOTE_MUTATION, mutation: UPDATE_NOTE_MUTATION,
variables: { variables: {
@ -28,12 +32,25 @@
note note
} }
}, },
refetchQueries: [{ refetchQueries() {
query: MODULE_DETAILS_QUERY, let query;
variables: { if (routeName === 'instrument') {
slug: this.$route.params.slug query = {
query: INSTRUMENT_QUERY,
variables: {
slug
}
}
} else {
query = {
query: MODULE_DETAILS_QUERY,
variables: {
slug
}
}
} }
}] return [query];
}
}).then(() => { }).then(() => {
this.$store.dispatch('hideModal'); this.$store.dispatch('hideModal');
}); });

View File

@ -20,21 +20,23 @@
}, },
computed: { computed: {
...mapGetters(['currentContent', 'currentContentBlock', 'currentNoteParent']) ...mapGetters(['currentContent', 'currentNoteBlock', 'currentNoteParent', 'noteType'])
}, },
methods: { methods: {
addNote(n) { addNote(n) {
const content = this.currentContent; const content = this.currentContent;
const contentBlock = this.currentContentBlock; const block = this.currentNoteBlock;
const parent = this.currentNoteParent; const parent = this.currentNoteParent;
const type = this.noteType;
const text = n.text; const text = n.text;
let note = {}; let note = {};
if (content > '') { if (content > '') {
note = { note = {
content, content,
contentBlock, block,
text text,
type
} }
} else { } else {
note = { note = {

View File

@ -1,4 +1,4 @@
import {InMemoryCache} from 'apollo-cache-inmemory/lib/index' import {InMemoryCache, defaultDataIdFromObject} from 'apollo-cache-inmemory/lib/index'
import {createHttpLink} from 'apollo-link-http' import {createHttpLink} from 'apollo-link-http'
import {ApolloClient} from 'apollo-client' import {ApolloClient} from 'apollo-client'
import {ApolloLink} from 'apollo-link' import {ApolloLink} from 'apollo-link'
@ -43,6 +43,14 @@ export default function (uri) {
const composedLink = ApolloLink.from([createOmitTypenameLink, consoleLink, httpLink]); const composedLink = ApolloLink.from([createOmitTypenameLink, consoleLink, httpLink]);
const cache = new InMemoryCache({ const cache = new InMemoryCache({
dataIdFromObject: obj => {
switch (obj.__typename) {
case 'InstrumentNode':
return `${obj.__typename}:${obj.slug}`;
default:
return defaultDataIdFromObject(obj);
}
},
cacheRedirects: { cacheRedirects: {
Query: { Query: {
contentBlock: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ContentBlockNode', id: args.id}), contentBlock: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ContentBlockNode', id: args.id}),
@ -51,7 +59,7 @@ export default function (uri) {
objective: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ObjectiveNode', id: args.id}), objective: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ObjectiveNode', id: args.id}),
objectiveGroup: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ObjectiveGroupNode', id: args.id}), objectiveGroup: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ObjectiveGroupNode', id: args.id}),
// todo: remove, the new client seems to cache this correctly by itself // todo: remove, the new client seems to cache this correctly by itself
// module: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ModuleNode', id: args.id}), // module: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ModuleNode', id: args.slug}),
projectEntry: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ProjectEntryNode', id: args.id}), projectEntry: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ProjectEntryNode', id: args.id}),
} }
} }

View File

@ -1,32 +0,0 @@
query BookQuery {
books {
edges {
node {
id
title
topics {
edges {
node {
id
title
slug
teaser
description
modules {
edges {
node {
id
title
slug
teaser
heroImage
}
}
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,14 @@
fragment InstrumentParts on InstrumentNode {
id
title
slug
bookmarks {
uuid
note {
id
text
}
}
type
contents
}

View File

@ -1,9 +1,6 @@
#import "./fragments/instrumentParts.gql"
query InstrumentQuery($slug: String!){ query InstrumentQuery($slug: String!){
instrument(slug: $slug) { instrument(slug: $slug) {
id ...InstrumentParts
title
slug
type
contents
} }
} }

View File

@ -0,0 +1,6 @@
#import "./fragments/instrumentParts.gql"
query InstrumentQuery($id: ID!){
instrument(id: $id) {
...InstrumentParts
}
}

View File

@ -0,0 +1,5 @@
mutation UpdateInstrumentBookmark($input: UpdateInstrumentBookmarkInput!) {
updateInstrumentBookmark(input: $input) {
success
}
}

View File

@ -2,41 +2,68 @@ import ADD_NOTE_MUTATION from '@/graphql/gql/mutations/addNote.gql';
import CONTENT_BLOCK_QUERY from '@/graphql/gql/contentBlockQuery.gql'; import CONTENT_BLOCK_QUERY from '@/graphql/gql/contentBlockQuery.gql';
import CHAPTER_QUERY from '@/graphql/gql/chapterQuery.gql'; import CHAPTER_QUERY from '@/graphql/gql/chapterQuery.gql';
import MODULE_QUERY from '@/graphql/gql/moduleByIdQuery.gql'; import MODULE_QUERY from '@/graphql/gql/moduleByIdQuery.gql';
import INSTRUMENT_FRAGMENT from '@/graphql/gql/fragments/instrumentParts.gql';
const getBlockType = id => atob(id).split(':')[0] const getBlockType = id => atob(id).split(':')[0];
const compareUuid = note => element => element.uuid === note.content;
export const constructNoteMutation = (n) => { export const constructNoteMutation = (n) => {
let update = () => { let update = () => {
}; };
if (n.contentBlock) { // has a content block, so it is a content block bookmark if (n.block) { // has a block, so it is a content block or instrument bookmark
update = (store, {data: {addNote: {note}}}) => { update = (store, {data: {addNote: {note}}}) => {
const query = CONTENT_BLOCK_QUERY; if (n.type === 'ContentBlockNode') {
const variables = {id: n.contentBlock}; const query = CONTENT_BLOCK_QUERY;
const data = store.readQuery({ const variables = {id: n.block};
query, const data = store.readQuery({
variables query,
}); variables
});
const bookmarks = data.contentBlock.bookmarks; const bookmarks = data.contentBlock.bookmarks;
let index = bookmarks.findIndex(element => { let index = bookmarks.findIndex(compareUuid(n));
return element.uuid === n.content;
});
if (index > -1) { if (index > -1) {
let el = bookmarks[index]; let el = bookmarks[index];
el.note = note; el.note = note;
bookmarks.splice(index, 1, el); bookmarks.splice(index, 1, el);
}
data.contentBlock.bookmarks = bookmarks;
store.writeQuery({
data,
query,
variables
});
} else {
const fragment = INSTRUMENT_FRAGMENT;
const id = `InstrumentNode:${n.block}`;
const data = store.readFragment({
fragment,
id
});
const bookmarks = data.bookmarks;
let index = bookmarks.findIndex(compareUuid(n));
if (index > -1) {
let el = bookmarks[index];
el.note = note;
bookmarks.splice(index, 1, el);
}
data.bookmarks = bookmarks;
store.writeFragment({
data,
fragment,
id
});
} }
data.contentBlock.bookmarks = bookmarks;
store.writeQuery({
data,
query,
variables
});
}; };
} else { // it's a chapter bookmark or a module bookmark } else { // it's a chapter bookmark or a module bookmark
update = (store, {data: {addNote: {note}}}) => { update = (store, {data: {addNote: {note}}}) => {

View File

@ -0,0 +1,111 @@
import UPDATE_CONTENT_BOOKMARK from '@/graphql/gql/mutations/updateContentBookmark.gql';
import UPDATE_INSTRUMENT_BOOKMARK from '@/graphql/gql/mutations/updateInstrumentBookmark.gql';
import CONTENT_BLOCK_QUERY from '@/graphql/gql/contentBlockQuery.gql';
import INSTRUMENT_FRAGMENT from '@/graphql/gql/fragments/instrumentParts.gql';
const compareUuid = uuid => element => element.uuid === uuid;
export const constructContentComponentBookmarkMutation = (uuid, bookmarked, parent, root) => {
let mutation = {};
if (parent.__typename === 'ContentBlockNode') {
mutation = {
mutation: UPDATE_CONTENT_BOOKMARK,
variables: {
input: {
uuid,
contentBlock: root,
bookmarked
}
},
update: (store, response) => {
const query = CONTENT_BLOCK_QUERY;
const variables = {id: root};
const data = store.readQuery({
query,
variables
});
const bookmarks = data.contentBlock.bookmarks;
if (bookmarked) {
bookmarks.push({
note: null,
uuid,
__typename: 'ContentBlockBookmarkNode'
});
} else {
let index = bookmarks.findIndex(compareUuid(uuid));
if (index > -1) {
bookmarks.splice(index, 1);
}
}
data.contentBlock.bookmarks = bookmarks;
store.writeQuery({
data,
query,
variables
});
},
optimisticResponse: {
__typename: 'Mutation',
updateContentBookmark: {
__typename: 'UpdateContentBookmarkPayload',
success: true
}
}
}
} else {
mutation = {
mutation: UPDATE_INSTRUMENT_BOOKMARK,
variables: {
input: {
uuid,
instrument: root,
bookmarked
}
},
update: (store, response) => {
const fragment = INSTRUMENT_FRAGMENT;
const id = `InstrumentNode:${root}`;
const data = store.readFragment({
fragment,
id
});
const bookmarks = data.bookmarks;
if (bookmarked) {
bookmarks.push({
note: null,
uuid,
__typename: 'InstrumentBookmarkNode'
})
} else {
let index = bookmarks.findIndex(compareUuid(uuid));
if (index > -1) {
bookmarks.splice(index, 1);
}
}
data.bookmarks = bookmarks;
store.writeFragment({
data,
fragment,
id
});
},
optimisticResponse: {
__typename: 'Mutation',
updateInstrumentBookmark: {
__typename: 'UpdateInstrumentBookmarkPayload',
success: true
}
}
};
}
return mutation;
};

View File

@ -2,11 +2,15 @@
<div class="instrument"> <div class="instrument">
<h1 class="instrument__title">{{instrument.title}}</h1> <h1 class="instrument__title">{{instrument.title}}</h1>
<component v-for="component in instrument.contents" <content-component v-for="component in instrument.contents"
:key="component.id" :key="component.id"
:is="component.type" :component="component"
v-bind="component"> :root="instrument.slug"
</component> :parent="instrument"
:bookmarks="instrument.bookmarks"
:notes="instrument.notes"
>
</content-component>
</div> </div>
</template> </template>
@ -14,17 +18,7 @@
<script> <script>
import INSTRUMENT_QUERY from '@/graphql/gql/instrumentQuery.gql'; import INSTRUMENT_QUERY from '@/graphql/gql/instrumentQuery.gql';
import TextBlock from '@/components/content-blocks/TextBlock'; import ContentComponent from '@/components/content-blocks/ContentComponent';
import InstrumentWidget from '@/components/content-blocks/InstrumentWidget';
import ImageBlock from '@/components/content-blocks/ImageBlock';
import ImageUrlBlock from '@/components/content-blocks/ImageUrlBlock';
import VideoBlock from '@/components/content-blocks/VideoBlock';
import LinkBlock from '@/components/content-blocks/LinkBlock';
import DocumentBlock from '@/components/content-blocks/DocumentBlock';
import SectionTitleBlock from '@/components/content-blocks/SectionTitleBlock';
import SubtitleBlock from '@/components/content-blocks/SubtitleBlock';
import GeniallyBlock from '@/components/content-blocks/GeniallyBlock';
import ThinglinkBlock from '@/components/content-blocks/ThinglinkBlock';
export default { export default {
apollo: { apollo: {
@ -39,18 +33,7 @@
}, },
components: { components: {
'text_block': TextBlock, ContentComponent
'basic_knowledge': InstrumentWidget, // for legacy
'instrument': InstrumentWidget,
'image_block': ImageBlock,
'image_url_block': ImageUrlBlock,
'video_block': VideoBlock,
'link_block': LinkBlock,
'document_block': DocumentBlock,
'section_title': SectionTitleBlock,
'subtitle': SubtitleBlock,
'genially_block': GeniallyBlock,
'thinglink_block': ThinglinkBlock
}, },
data() { data() {

View File

@ -14,7 +14,8 @@ export default new Vuex.Store({
contentBlockPosition: {}, contentBlockPosition: {},
scrollPosition: 0, scrollPosition: 0,
currentContent: '', currentContent: '',
currentContentBlock: '', currentNoteBlock: '',
noteType: '',
currentRoomEntry: '', currentRoomEntry: '',
parentRoom: null, parentRoom: null,
parentModule: '', parentModule: '',
@ -46,9 +47,10 @@ export default new Vuex.Store({
editModule: state => state.editModule, editModule: state => state.editModule,
currentObjectiveGroup: state => state.currentObjectiveGroup, currentObjectiveGroup: state => state.currentObjectiveGroup,
currentContent: state => state.currentContent, currentContent: state => state.currentContent,
currentContentBlock: state => state.currentContentBlock, currentNoteBlock: state => state.currentNoteBlock,
currentNote: state => state.currentNote, currentNote: state => state.currentNote,
currentNoteParent: state => state.currentNoteParent, currentNoteParent: state => state.currentNoteParent,
noteType: state => state.noteType,
}, },
actions: { actions: {
@ -63,7 +65,7 @@ export default new Vuex.Store({
resetModalState({commit}) { resetModalState({commit}) {
commit('setCurrentRoomEntry', ''); commit('setCurrentRoomEntry', '');
commit('setCurrentContent', ''); commit('setCurrentContent', '');
commit('setCurrentContentBlock', ''); commit('setCurrentNoteBlock', '');
commit('setCurrentNoteParent', ''); commit('setCurrentNoteParent', '');
commit('setContentBlockPosition', {}); commit('setContentBlockPosition', {});
commit('setParentRoom', null); commit('setParentRoom', null);
@ -80,15 +82,16 @@ export default new Vuex.Store({
}); });
commit('setVimeoId', null); commit('setVimeoId', null);
commit('setCurrentNote', null); commit('setCurrentNote', null);
commit('setNoteType', '');
}, },
resetContentBlockPosition({commit}) { resetContentBlockPosition({commit}) {
commit('setContentBlockPosition', {}); commit('setContentBlockPosition', {});
}, },
resetCurrentContentBlock({commit}) { resetCurrentNoteBlock({commit}) {
commit('setCurrentContentBlock', ''); commit('setCurrentNoteBlock', '');
}, },
editContentBlock({commit, dispatch}, payload) { editContentBlock({commit, dispatch}, payload) {
commit('setCurrentContentBlock', payload); commit('setCurrentNoteBlock', payload);
dispatch('showModal', 'edit-content-block-wizard'); dispatch('showModal', 'edit-content-block-wizard');
}, },
addContentBlock({commit, dispatch}, payload) { addContentBlock({commit, dispatch}, payload) {
@ -130,8 +133,9 @@ export default new Vuex.Store({
dispatch('showModal', 'edit-project-entry-wizard'); dispatch('showModal', 'edit-project-entry-wizard');
}, },
addNote({commit, dispatch}, payload) { addNote({commit, dispatch}, payload) {
if (payload.contentBlock) { if (payload.block) {
commit('setCurrentContentBlock', payload.contentBlock); commit('setCurrentNoteBlock', payload.block);
commit('setNoteType', payload.type);
commit('setCurrentContent', payload.content); commit('setCurrentContent', payload.content);
} else { } else {
commit('setCurrentNoteParent', payload.parent); commit('setCurrentNoteParent', payload.parent);
@ -197,8 +201,8 @@ export default new Vuex.Store({
setCurrentContent(state, payload) { setCurrentContent(state, payload) {
state.currentContent = payload; state.currentContent = payload;
}, },
setCurrentContentBlock(state, payload) { setCurrentNoteBlock(state, payload) {
state.currentContentBlock = payload; state.currentNoteBlock = payload;
}, },
setParentRoom(state, payload) { setParentRoom(state, payload) {
state.parentRoom = payload; state.parentRoom = payload;
@ -251,6 +255,9 @@ export default new Vuex.Store({
}, },
setCurrentNoteParent(state, payload) { setCurrentNoteParent(state, payload) {
state.currentNoteParent = payload; state.currentNoteParent = payload;
},
setNoteType(state, payload) {
state.noteType = payload;
} }
} }
}) })

View File

@ -4,10 +4,14 @@ from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField from graphene_django.filter import DjangoFilterConnectionField
from api.utils import get_object from api.utils import get_object
from notes.models import InstrumentBookmark
from notes.schema import InstrumentBookmarkNode
from .models import BasicKnowledge from .models import BasicKnowledge
class BasicKnowledgeNode(DjangoObjectType): class InstrumentNode(DjangoObjectType):
bookmarks = graphene.List(InstrumentBookmarkNode)
class Meta: class Meta:
model = BasicKnowledge model = BasicKnowledge
filter_fields = ['slug', 'type'] filter_fields = ['slug', 'type']
@ -16,17 +20,23 @@ class BasicKnowledgeNode(DjangoObjectType):
'slug', 'title', 'type', 'contents', 'slug', 'title', 'type', 'contents',
] ]
def resolve_bookmarks(self, info, **kwargs):
return InstrumentBookmark.objects.filter(
user=info.context.user,
instrument=self
)
class BasicKnowledgeQuery(object): class BasicKnowledgeQuery(object):
instrument = graphene.Field(BasicKnowledgeNode, slug=graphene.String(), id=graphene.ID()) instrument = graphene.Field(InstrumentNode, slug=graphene.String(), id=graphene.ID())
instruments = DjangoFilterConnectionField(BasicKnowledgeNode) instruments = DjangoFilterConnectionField(InstrumentNode)
def resolve_instrument(self, info, **kwargs): def resolve_instrument(self, info, **kwargs):
slug = kwargs.get('slug') slug = kwargs.get('slug')
room_id = kwargs.get('id') instrument_id = kwargs.get('id')
if room_id is not None: if instrument_id is not None:
return get_object(BasicKnowledge, room_id) return get_object(BasicKnowledge, instrument_id)
if slug is not None: if slug is not None:
return BasicKnowledge.objects.get(slug=slug) return BasicKnowledge.objects.get(slug=slug)
return None return None

View File

@ -48,7 +48,7 @@ class ContentBlockNode(DjangoObjectType):
def resolve_contents(self, info, **kwargs): def resolve_contents(self, info, **kwargs):
updated_stream_data = [] updated_stream_data = []
for content in self.contents.stream_data: for content in self.contents.stream_data:
if not are_solutions_enabled_for(info.context.user, self.module) and content['type'] == 'solution': if content['type'] == 'solution' and not are_solutions_enabled_for(info.context.user, self.module):
continue continue
if content['type'] == 'content_list_item': if content['type'] == 'content_list_item':
@ -85,18 +85,21 @@ class ChapterNode(DjangoObjectType):
def resolve_content_blocks(self, info, **kwargs): def resolve_content_blocks(self, info, **kwargs):
user = info.context.user user = info.context.user
school_classes = user.school_classes.values_list('pk') school_classes = user.school_classes.values_list('pk')
by_parent = ContentBlock.get_by_parent(self).prefetch_related(
'visible_for__schoolclass').prefetch_related(
'hidden_for__schoolclass')
if user.has_perm('users.can_manage_school_class_content'): # teacher if user.has_perm('users.can_manage_school_class_content'): # teacher
publisher_content_blocks = ContentBlock.get_by_parent(self).filter(user_created=False) publisher_content_blocks = by_parent.filter(user_created=False)
user_created_content_blocks = ContentBlock.get_by_parent(self).filter(user_created=True, owner=user) user_created_content_blocks = by_parent.filter(user_created=True, owner=user)
else: # student else: # student
publisher_content_blocks = ContentBlock.get_by_parent(self).filter(user_created=False).exclude( publisher_content_blocks = by_parent.filter(user_created=False).exclude(
hidden_for__in=school_classes) hidden_for__in=school_classes)
self_created_content_blocks = ContentBlock.get_by_parent(self).filter(user_created=True, owner=user) self_created_content_blocks = by_parent.filter(user_created=True, owner=user)
user_created_content_blocks = ContentBlock.get_by_parent(self).filter(user_created=True, user_created_content_blocks = by_parent.filter(user_created=True,
visible_for__in=school_classes).union( visible_for__in=school_classes).union(
self_created_content_blocks) self_created_content_blocks)
return publisher_content_blocks.union(user_created_content_blocks) return publisher_content_blocks.union(user_created_content_blocks)
@ -180,6 +183,12 @@ class ModuleNode(DjangoObjectType):
chapters = Chapter.objects.live().descendant_of(self) chapters = Chapter.objects.live().descendant_of(self)
return ChapterBookmark.objects.filter(chapter__in=chapters, user=user) return ChapterBookmark.objects.filter(chapter__in=chapters, user=user)
def resolve_objective_groups(self, root, **kwargs):
return self.objective_groups.all() \
.prefetch_related('hidden_for__schoolclass') \
.prefetch_related('visible_for__schoolclass') \
.prefetch_related('objectives__objective_progress')
class TopicNode(DjangoObjectType): class TopicNode(DjangoObjectType):
pk = graphene.Int() pk = graphene.Int()
@ -278,6 +287,7 @@ class BookQuery(object):
elif slug is not None: elif slug is not None:
module = Module.objects.get(slug=slug) module = Module.objects.get(slug=slug)
return module return module
def resolve_topic(self, info, **kwargs): def resolve_topic(self, info, **kwargs):

View File

@ -94,6 +94,7 @@ if DEBUG:
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware',
'django.middleware.gzip.GZipMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware' 'django.contrib.sessions.middleware.SessionMiddleware'
] ]

View File

@ -4,7 +4,8 @@ from graphene import InputObjectType
class AddNoteArgument(InputObjectType): class AddNoteArgument(InputObjectType):
content = graphene.UUID() content = graphene.UUID()
content_block = graphene.ID() block = graphene.String()
type = graphene.String()
parent = graphene.ID() parent = graphene.ID()
text = graphene.String(required=True) text = graphene.String(required=True)

View File

@ -0,0 +1,30 @@
# Generated by Django 2.0.6 on 2020-01-08 12:54
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('basicknowledge', '0004_auto_20191128_1601'),
('notes', '0002_chapterbookmark_modulebookmark'),
]
operations = [
migrations.CreateModel(
name='InstrumentBookmark',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uuid', models.UUIDField(unique=True)),
('instrument', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='basicknowledge.BasicKnowledge')),
('note', models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, to='notes.Note')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
]

View File

@ -28,3 +28,7 @@ class ModuleBookmark(Bookmark):
class ChapterBookmark(Bookmark): class ChapterBookmark(Bookmark):
chapter = models.ForeignKey('books.Chapter', on_delete=models.CASCADE) chapter = models.ForeignKey('books.Chapter', on_delete=models.CASCADE)
class InstrumentBookmark(Bookmark):
uuid = models.UUIDField(unique=True)
instrument = models.ForeignKey('basicknowledge.BasicKnowledge', on_delete=models.CASCADE)

View File

@ -6,9 +6,10 @@ from graphene import relay
from graphql_relay import from_global_id from graphql_relay import from_global_id
from api.utils import get_object from api.utils import get_object
from basicknowledge.models import BasicKnowledge
from books.models import ContentBlock, Chapter, Module from books.models import ContentBlock, Chapter, Module
from notes.inputs import AddNoteArgument, UpdateNoteArgument from notes.inputs import AddNoteArgument, UpdateNoteArgument
from notes.models import ContentBlockBookmark, Note, ChapterBookmark, ModuleBookmark from notes.models import ContentBlockBookmark, Note, ChapterBookmark, ModuleBookmark, InstrumentBookmark
from notes.schema import NoteNode from notes.schema import NoteNode
@ -58,18 +59,27 @@ class AddNote(relay.ClientIDMutation):
note = kwargs.get('note') note = kwargs.get('note')
content_uuid = note.get('content', '') content_uuid = note.get('content', '')
content_block_id = note.get('content_block', '') content_block_id = note.get('block', '')
parent = note.get('parent') parent = note.get('parent')
text = note.get('text') text = note.get('text')
if content_uuid != '': if content_uuid != '':
content_block = get_object(ContentBlock, content_block_id) type = note.get('type')
if type == 'ContentBlockNode':
content_block = get_object(ContentBlock, content_block_id)
bookmark = ContentBlockBookmark.objects.get( bookmark = ContentBlockBookmark.objects.get(
content_block=content_block, content_block=content_block,
uuid=content_uuid, uuid=content_uuid,
user=user user=user
) )
else:
instrument = BasicKnowledge.objects.get(slug=content_block_id)
bookmark = InstrumentBookmark.objects.get(
instrument=instrument,
uuid=content_uuid,
user=user
)
else: else:
type, id = from_global_id(parent) type, id = from_global_id(parent)
if type == 'ModuleNode': if type == 'ModuleNode':
@ -172,9 +182,43 @@ class UpdateModuleBookmark(relay.ClientIDMutation):
return cls(success=True) return cls(success=True)
class UpdateInstrumentBookmark(relay.ClientIDMutation):
class Input:
uuid = graphene.UUID(required=True)
instrument = graphene.String(required=True)
bookmarked = graphene.Boolean(required=True)
success = graphene.Boolean()
@classmethod
def mutate_and_get_payload(cls, root, info, **kwargs):
user = info.context.user
instrument_slug = kwargs.get('instrument')
uuid = kwargs.get('uuid')
bookmarked = kwargs.get('bookmarked')
instrument = BasicKnowledge.objects.get(slug=instrument_slug)
if bookmarked:
InstrumentBookmark.objects.create(
instrument=instrument,
uuid=uuid,
user=user
)
else:
InstrumentBookmark.objects.get(
instrument=instrument,
uuid=uuid,
user=user
).delete()
return cls(success=True)
class NoteMutations: class NoteMutations:
add_note = AddNote.Field() add_note = AddNote.Field()
update_note = UpdateNote.Field() update_note = UpdateNote.Field()
update_content_bookmark = UpdateContentBookmark.Field() update_content_bookmark = UpdateContentBookmark.Field()
update_chapter_bookmark = UpdateChapterBookmark.Field() update_chapter_bookmark = UpdateChapterBookmark.Field()
update_module_bookmark = UpdateModuleBookmark.Field() update_module_bookmark = UpdateModuleBookmark.Field()
update_instrument_bookmark = UpdateInstrumentBookmark.Field()

View File

@ -2,7 +2,7 @@ import graphene
from graphene import relay from graphene import relay
from graphene_django import DjangoObjectType from graphene_django import DjangoObjectType
from notes.models import Note, ContentBlockBookmark, ModuleBookmark, ChapterBookmark from notes.models import Note, ContentBlockBookmark, ModuleBookmark, ChapterBookmark, InstrumentBookmark
class NoteNode(DjangoObjectType): class NoteNode(DjangoObjectType):
@ -17,7 +17,6 @@ class NoteNode(DjangoObjectType):
class ContentBlockBookmarkNode(DjangoObjectType): class ContentBlockBookmarkNode(DjangoObjectType):
# note = graphene.
uuid = graphene.UUID() uuid = graphene.UUID()
note = graphene.Field(NoteNode) note = graphene.Field(NoteNode)
@ -41,3 +40,13 @@ class ChapterBookmarkNode(DjangoObjectType):
model = ChapterBookmark model = ChapterBookmark
filter_fields = [] filter_fields = []
interfaces = (relay.Node,) interfaces = (relay.Node,)
class InstrumentBookmarkNode(DjangoObjectType):
uuid = graphene.UUID()
note = graphene.Field(NoteNode)
class Meta:
model = InstrumentBookmark
filter_fields = []
interfaces = (relay.Node,)