From 7c534cbe5c1184da7aedbf0ef1b6c5d651c45f1c Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Mon, 2 Dec 2019 13:56:52 +0100 Subject: [PATCH] Add first raw implementation for adtl. bookmark types --- client/src/components/Chapter.vue | 19 +++++++++ .../graphql/gql/fragments/chapterParts.gql | 6 +++ .../src/graphql/gql/fragments/moduleParts.gql | 6 +++ .../gql/mutations/updateChapterBookmark.gql | 5 +++ .../migrations/0004_auto_20191128_1601.py | 21 ++++++++++ .../migrations/0016_auto_20191128_1601.py | 24 +++++++++++ server/books/schema/queries.py | 17 +++++++- .../0002_chapterbookmark_modulebookmark.py | 41 +++++++++++++++++++ server/notes/models.py | 10 ++++- server/notes/mutations.py | 35 +++++++++++++++- server/notes/schema.py | 16 +++++++- 11 files changed, 194 insertions(+), 6 deletions(-) create mode 100644 client/src/graphql/gql/mutations/updateChapterBookmark.gql create mode 100644 server/basicknowledge/migrations/0004_auto_20191128_1601.py create mode 100644 server/books/migrations/0016_auto_20191128_1601.py create mode 100644 server/notes/migrations/0002_chapterbookmark_modulebookmark.py diff --git a/client/src/components/Chapter.vue b/client/src/components/Chapter.vue index 780a5abe..bd1f30c9 100644 --- a/client/src/components/Chapter.vue +++ b/client/src/components/Chapter.vue @@ -2,6 +2,8 @@

{{chapter.title}}

+

Bookmark: {{chapter.bookmark}} Click

+

{{chapter.description}}

@@ -23,6 +25,8 @@ import {isHidden} from '@/helpers/content-block'; import {meQuery} from '@/graphql/queries'; + import UPDATE_CHAPTER_BOOKMARK_MUTATION from '@/graphql/gql/mutations/updateChapterBookmark.gql'; + export default { props: ['chapter', 'index'], @@ -53,6 +57,21 @@ } }, + methods: { + bookmark() { + console.log('bookmark'); + this.$apollo.mutate({ + mutation: UPDATE_CHAPTER_BOOKMARK_MUTATION, + variables: { + input: { + chapter: this.chapter.id, + bookmarked: true + } + } + }); + } + }, + apollo: { me: meQuery } diff --git a/client/src/graphql/gql/fragments/chapterParts.gql b/client/src/graphql/gql/fragments/chapterParts.gql index cb4f391f..b3d41af0 100644 --- a/client/src/graphql/gql/fragments/chapterParts.gql +++ b/client/src/graphql/gql/fragments/chapterParts.gql @@ -3,6 +3,12 @@ fragment ChapterParts on ChapterNode { id title description + bookmark { + note { + id + text + } + } contentBlocks { edges { node { diff --git a/client/src/graphql/gql/fragments/moduleParts.gql b/client/src/graphql/gql/fragments/moduleParts.gql index bcf58fed..f4053a49 100644 --- a/client/src/graphql/gql/fragments/moduleParts.gql +++ b/client/src/graphql/gql/fragments/moduleParts.gql @@ -7,4 +7,10 @@ fragment ModuleParts on ModuleNode { slug heroImage solutionsEnabled + bookmark { + note { + id + text + } + } } diff --git a/client/src/graphql/gql/mutations/updateChapterBookmark.gql b/client/src/graphql/gql/mutations/updateChapterBookmark.gql new file mode 100644 index 00000000..9d09380d --- /dev/null +++ b/client/src/graphql/gql/mutations/updateChapterBookmark.gql @@ -0,0 +1,5 @@ +mutation UpdateChapterBookmark($input: UpdateChapterBookmarkInput!) { + updateChapterBookmark(input: $input) { + success + } +} diff --git a/server/basicknowledge/migrations/0004_auto_20191128_1601.py b/server/basicknowledge/migrations/0004_auto_20191128_1601.py new file mode 100644 index 00000000..5e8f6ba9 --- /dev/null +++ b/server/basicknowledge/migrations/0004_auto_20191128_1601.py @@ -0,0 +1,21 @@ +# Generated by Django 2.0.6 on 2019-11-28 16:01 + +from django.db import migrations +import wagtail.core.blocks +import wagtail.core.fields +import wagtail.images.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('basicknowledge', '0003_auto_20190912_1228'), + ] + + operations = [ + migrations.AlterField( + model_name='basicknowledge', + name='contents', + field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['bold', 'ul']))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('section_title', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())]))], blank=True, null=True), + ), + ] diff --git a/server/books/migrations/0016_auto_20191128_1601.py b/server/books/migrations/0016_auto_20191128_1601.py new file mode 100644 index 00000000..bd80085a --- /dev/null +++ b/server/books/migrations/0016_auto_20191128_1601.py @@ -0,0 +1,24 @@ +# Generated by Django 2.0.6 on 2019-11-28 16:01 + +import assignments.models +from django.db import migrations +import surveys.models +import wagtail.core.blocks +import wagtail.core.fields +import wagtail.images.blocks +import wagtail.snippets.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('books', '0015_contentblock_bookmarks'), + ] + + operations = [ + migrations.AlterField( + model_name='contentblock', + name='contents', + field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.core.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('module_room_slug', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock())])), ('content_list_item', wagtail.core.blocks.StreamBlock([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.core.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('module_room_slug', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock())]))]))], blank=True, null=True), + ), + ] diff --git a/server/books/schema/queries.py b/server/books/schema/queries.py index 3ac6288b..cf241d3e 100644 --- a/server/books/schema/queries.py +++ b/server/books/schema/queries.py @@ -5,8 +5,8 @@ from graphene_django.filter import DjangoFilterConnectionField from api.utils import get_object from books.utils import are_solutions_enabled_for -from notes.models import ContentBlockBookmark -from notes.schema import ContentBlockBookmarkNode +from notes.models import ContentBlockBookmark, ChapterBookmark, ModuleBookmark +from notes.schema import ContentBlockBookmarkNode, ChapterBookmarkNode, ModuleBookmarkNode from rooms.models import ModuleRoomSlug from ..models import Book, Topic, Module, Chapter, ContentBlock @@ -66,6 +66,7 @@ class ContentBlockNode(DjangoObjectType): class ChapterNode(DjangoObjectType): content_blocks = DjangoFilterConnectionField(ContentBlockNode) + bookmark = graphene.Field(ChapterBookmarkNode) class Meta: model = Chapter @@ -96,6 +97,11 @@ class ChapterNode(DjangoObjectType): return publisher_content_blocks.union(user_created_content_blocks) + def resolve_bookmark(self, info, **kwags): + return ChapterBookmark.objects.filter( + user=info.context.user, + chapter=self + ).first() class ModuleNode(DjangoObjectType): pk = graphene.Int() @@ -103,6 +109,7 @@ class ModuleNode(DjangoObjectType): topic = graphene.Field('books.schema.queries.TopicNode') hero_image = graphene.String() solutions_enabled = graphene.Boolean() + bookmark = graphene.Field(ModuleBookmarkNode) class Meta: model = Module @@ -132,6 +139,12 @@ class ModuleNode(DjangoObjectType): teacher = info.context.user.get_teacher() return self.solutions_enabled_by.filter(pk=teacher.pk).exists() if teacher is not None else False + def resolve_bookmark(self, info, **kwags): + return ModuleBookmark.objects.filter( + user=info.context.user, + module=self + ).first() + class TopicNode(DjangoObjectType): pk = graphene.Int() diff --git a/server/notes/migrations/0002_chapterbookmark_modulebookmark.py b/server/notes/migrations/0002_chapterbookmark_modulebookmark.py new file mode 100644 index 00000000..fb9a8664 --- /dev/null +++ b/server/notes/migrations/0002_chapterbookmark_modulebookmark.py @@ -0,0 +1,41 @@ +# Generated by Django 2.0.6 on 2019-11-28 16:01 + +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), + ('books', '0016_auto_20191128_1601'), + ('notes', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='ChapterBookmark', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('chapter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='books.Chapter')), + ('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, + }, + ), + migrations.CreateModel( + name='ModuleBookmark', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('module', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='books.Module')), + ('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, + }, + ), + ] diff --git a/server/notes/models.py b/server/notes/models.py index 0404c54d..72eea13e 100644 --- a/server/notes/models.py +++ b/server/notes/models.py @@ -10,7 +10,6 @@ class Note(models.Model): class Bookmark(models.Model): - uuid = models.UUIDField(unique=True) user = models.ForeignKey(User, on_delete=models.CASCADE) note = models.OneToOneField(Note, null=True, on_delete=models.SET_NULL) @@ -19,4 +18,13 @@ class Bookmark(models.Model): class ContentBlockBookmark(Bookmark): + uuid = models.UUIDField(unique=True) content_block = models.ForeignKey('books.ContentBlock', on_delete=models.CASCADE) + + +class ModuleBookmark(Bookmark): + module = models.ForeignKey('books.Module', on_delete=models.CASCADE) + + +class ChapterBookmark(Bookmark): + chapter = models.ForeignKey('books.Chapter', on_delete=models.CASCADE) diff --git a/server/notes/mutations.py b/server/notes/mutations.py index 8c1587e0..64e62267 100644 --- a/server/notes/mutations.py +++ b/server/notes/mutations.py @@ -5,9 +5,9 @@ import json from graphene import relay from api.utils import get_object -from books.models import ContentBlock +from books.models import ContentBlock, Chapter from notes.inputs import AddNoteArgument, UpdateNoteArgument -from notes.models import ContentBlockBookmark, Note +from notes.models import ContentBlockBookmark, Note, ChapterBookmark from notes.schema import NoteNode @@ -95,8 +95,39 @@ class UpdateNote(relay.ClientIDMutation): note.save() return cls(note=note) +class UpdateChapterBookmark(relay.ClientIDMutation): + class Input: + chapter = graphene.ID(required=True) + bookmarked = graphene.Boolean(required=True) + + success = graphene.Boolean() + + @classmethod + def mutate_and_get_payload(cls, root, info, **kwargs): + user = info.context.user + content_block_id = kwargs.get('chapter') + bookmarked = kwargs.get('bookmarked') + + chapter = get_object(Chapter, content_block_id) + + if bookmarked: + ChapterBookmark.objects.create( + chapter=chapter, + user=user + ) + else: + ChapterBookmark.objects.get( + chapter=chapter, + user=user + ).delete() + + return cls(success=True) + + + class NoteMutations: add_note = AddNote.Field() update_note = UpdateNote.Field() update_content_bookmark = UpdateContentBookmark.Field() + update_chapter_bookmark = UpdateChapterBookmark.Field() diff --git a/server/notes/schema.py b/server/notes/schema.py index 7c008951..08119227 100644 --- a/server/notes/schema.py +++ b/server/notes/schema.py @@ -2,7 +2,7 @@ import graphene from graphene import relay from graphene_django import DjangoObjectType -from notes.models import Note, ContentBlockBookmark +from notes.models import Note, ContentBlockBookmark, ModuleBookmark, ChapterBookmark class NoteNode(DjangoObjectType): @@ -23,3 +23,17 @@ class ContentBlockBookmarkNode(DjangoObjectType): class Meta: model = ContentBlockBookmark + + +class ModuleBookmarkNode(DjangoObjectType): + note = graphene.Field(NoteNode) + + class Meta: + model = ModuleBookmark + + +class ChapterBookmarkNode(DjangoObjectType): + note = graphene.Field(NoteNode) + + class Meta: + model = ChapterBookmark