From 6f0cb0dd881c186f18b5ea38940a547d14243794 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Mon, 16 Dec 2019 15:24:46 +0100 Subject: [PATCH 01/17] Add command for exporting assignments --- .../management/commands/export_assignments.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 server/core/management/commands/export_assignments.py diff --git a/server/core/management/commands/export_assignments.py b/server/core/management/commands/export_assignments.py new file mode 100644 index 00000000..1015e6a6 --- /dev/null +++ b/server/core/management/commands/export_assignments.py @@ -0,0 +1,29 @@ +from django.core.management import BaseCommand + +from assignments.models import Assignment, StudentSubmission +import random +import json +from django.db.models import Q + +class Command(BaseCommand): + def add_arguments(self, parser): + parser.add_argument('jsonfile', type=str) + + def handle(self, *args, **options): + jsonfile = options['jsonfile'] + self.stdout.write("Exporting assignments") + assignment_list = [] + for assignment in Assignment.objects.filter(user_created=False): + assignment_dict = { + "title": assignment.title, + "id": assignment.id, + "text": assignment.assignment, + } + submissions = assignment.submissions.filter(text__isnull=False).filter(~Q(text='')) + if submissions.count() > 0: + example = random.choice(submissions) + assignment_dict['example'] = example.text + assignment_list.append(assignment_dict) + + with open(jsonfile, 'w') as f: + f.write(json.dumps(assignment_list, indent=2, sort_keys=True)) From 1549cde15128acb0712928a8671aba7bed54ece4 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 18 Dec 2019 13:11:32 +0100 Subject: [PATCH 02/17] Update MyActivity query to include more activities --- server/api/schema.py | 4 +-- server/assignments/schema/queries.py | 8 ------ server/books/schema/queries.py | 37 +++++++++++++++++++++++++++- server/notes/schema.py | 4 +++ server/surveys/schema.py | 1 + server/users/schema.py | 10 ++++++-- 6 files changed, 51 insertions(+), 13 deletions(-) diff --git a/server/api/schema.py b/server/api/schema.py index 5e5f0d12..f265e893 100644 --- a/server/api/schema.py +++ b/server/api/schema.py @@ -6,7 +6,7 @@ from graphene_django.debug import DjangoDebug # noinspection PyUnresolvedReferences from api import graphene_wagtail # Keep this import exactly here, it's necessary for StreamField conversion from assignments.schema.mutations import AssignmentMutations -from assignments.schema.queries import AssignmentsQuery, StudentSubmissionQuery, MyActivityQuery +from assignments.schema.queries import AssignmentsQuery, StudentSubmissionQuery from basicknowledge.queries import BasicKnowledgeQuery from books.schema.mutations.main import BookMutations from books.schema.queries import BookQuery @@ -26,7 +26,7 @@ from registration.mutations_public import RegistrationMutations class Query(UsersQuery, AllUsersQuery, ModuleRoomsQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery, - StudentSubmissionQuery, BasicKnowledgeQuery, PortfolioQuery, MyActivityQuery, SurveysQuery, + StudentSubmissionQuery, BasicKnowledgeQuery, PortfolioQuery, SurveysQuery, graphene.ObjectType): node = relay.Node.Field() diff --git a/server/assignments/schema/queries.py b/server/assignments/schema/queries.py index 7f9ac9da..ae25ddf2 100644 --- a/server/assignments/schema/queries.py +++ b/server/assignments/schema/queries.py @@ -12,11 +12,3 @@ class AssignmentsQuery(object): class StudentSubmissionQuery(object): student_submission = relay.Node.Field(StudentSubmissionNode) - - -class MyActivityQuery(object): - my_activity = DjangoFilterConnectionField(StudentSubmissionNode) - - def resolve_my_activity(self, info, **kwargs): - user = info.context.user - return StudentSubmission.objects.filter(student=user) diff --git a/server/books/schema/queries.py b/server/books/schema/queries.py index cf241d3e..53d0e55f 100644 --- a/server/books/schema/queries.py +++ b/server/books/schema/queries.py @@ -4,10 +4,14 @@ from graphene_django import DjangoObjectType from graphene_django.filter import DjangoFilterConnectionField from api.utils import get_object +from assignments.models import StudentSubmission +from assignments.schema.types import StudentSubmissionNode from books.utils import are_solutions_enabled_for from notes.models import ContentBlockBookmark, ChapterBookmark, ModuleBookmark from notes.schema import ContentBlockBookmarkNode, ChapterBookmarkNode, ModuleBookmarkNode from rooms.models import ModuleRoomSlug +from surveys.models import Answer +from surveys.schema import AnswerNode from ..models import Book, Topic, Module, Chapter, ContentBlock @@ -103,6 +107,7 @@ class ChapterNode(DjangoObjectType): chapter=self ).first() + class ModuleNode(DjangoObjectType): pk = graphene.Int() chapters = DjangoFilterConnectionField(ChapterNode) @@ -110,6 +115,10 @@ class ModuleNode(DjangoObjectType): hero_image = graphene.String() solutions_enabled = graphene.Boolean() bookmark = graphene.Field(ModuleBookmarkNode) + my_submissions = DjangoFilterConnectionField(StudentSubmissionNode) + my_answers = DjangoFilterConnectionField(AnswerNode) + my_content_bookmarks = DjangoFilterConnectionField(ContentBlockBookmarkNode) + my_chapter_bookmarks = DjangoFilterConnectionField(ChapterBookmarkNode) class Meta: model = Module @@ -145,6 +154,32 @@ class ModuleNode(DjangoObjectType): module=self ).first() + def resolve_my_submissions(self, info, **kwargs): + user = info.context.user + return StudentSubmission.objects.filter(student=user, assignment__module=self) + # we want: + # StudentSubmission + + def resolve_my_answers(self, info, **kwargs): + user = info.context.user + return Answer.objects.filter(owner=user, survey__module=self) + # Survey + + def resolve_my_content_bookmarks(self, info, **kwargs): + user = info.context.user + content_blocks = ContentBlock.objects.live().descendant_of(self) + return ContentBlockBookmark.objects.filter(content_block__in=content_blocks, user=user) + # Bookmark Text + # Bookmark Image etc + # Bookmark Other + # Note + # + + def resolve_my_chapter_bookmarks(self, info, **kwargs): + user = info.context.user + chapters = Chapter.objects.live().descendant_of(self) + return ChapterBookmark.objects.filter(chapter__in=chapters, user=user) + class TopicNode(DjangoObjectType): pk = graphene.Int() @@ -196,7 +231,7 @@ class FilteredChapterNode(DjangoObjectType): class Meta: model = Chapter only_fields = [ - 'slug', 'title', + 'slug', 'title', 'description', ] filter_fields = [ 'slug', 'title', diff --git a/server/notes/schema.py b/server/notes/schema.py index 08119227..818d44e7 100644 --- a/server/notes/schema.py +++ b/server/notes/schema.py @@ -23,6 +23,8 @@ class ContentBlockBookmarkNode(DjangoObjectType): class Meta: model = ContentBlockBookmark + filter_fields = [] + interfaces = (relay.Node,) class ModuleBookmarkNode(DjangoObjectType): @@ -37,3 +39,5 @@ class ChapterBookmarkNode(DjangoObjectType): class Meta: model = ChapterBookmark + filter_fields = [] + interfaces = (relay.Node,) diff --git a/server/surveys/schema.py b/server/surveys/schema.py index 701e76cd..9e5cdc37 100644 --- a/server/surveys/schema.py +++ b/server/surveys/schema.py @@ -14,6 +14,7 @@ class AnswerNode(DjangoObjectType): class Meta: model = Answer interfaces = (relay.Node,) + filter_fields = [] def resolve_pk(self, *args, **kwargs): return self.id diff --git a/server/users/schema.py b/server/users/schema.py index 2511b7c3..11192d55 100644 --- a/server/users/schema.py +++ b/server/users/schema.py @@ -3,6 +3,10 @@ from graphene import relay from graphene_django import DjangoObjectType from graphene_django.filter import DjangoFilterConnectionField +from assignments.models import StudentSubmission +from assignments.schema.types import StudentSubmissionNode +from books.models import Module +from books.schema.queries import ModuleNode from users.models import User, SchoolClass @@ -43,6 +47,7 @@ class UserNode(DjangoObjectType): class UsersQuery(object): me = graphene.Field(UserNode) all_users = DjangoFilterConnectionField(UserNode) + my_activity = DjangoFilterConnectionField(ModuleNode) def resolve_me(self, info, **kwargs): return info.context.user @@ -53,6 +58,9 @@ class UsersQuery(object): else: return User.objects.all() + def resolve_my_activity(self, info, **kwargs): + return Module.objects.all() + class AllUsersQuery(object): me = graphene.Field(UserNode) @@ -63,5 +71,3 @@ class AllUsersQuery(object): return User.objects.none() else: return User.objects.all() - - From cb13aa1ea5e4a84157621814453b096b54c2a11d Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 18 Dec 2019 13:17:08 +0100 Subject: [PATCH 03/17] Update my activity in client --- .../components/profile/ContentBookmark.vue | 50 +++++++++ .../src/components/profile/ModuleActivity.vue | 105 ++++++++++++------ client/src/graphql/gql/myActivity.gql | 83 ++++++++++++-- client/src/pages/activity.vue | 31 +----- 4 files changed, 198 insertions(+), 71 deletions(-) create mode 100644 client/src/components/profile/ContentBookmark.vue diff --git a/client/src/components/profile/ContentBookmark.vue b/client/src/components/profile/ContentBookmark.vue new file mode 100644 index 00000000..701fa71e --- /dev/null +++ b/client/src/components/profile/ContentBookmark.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/client/src/components/profile/ModuleActivity.vue b/client/src/components/profile/ModuleActivity.vue index 7af3595d..2cb96f9e 100644 --- a/client/src/components/profile/ModuleActivity.vue +++ b/client/src/components/profile/ModuleActivity.vue @@ -1,38 +1,82 @@ diff --git a/server/books/schema/queries.py b/server/books/schema/queries.py index 53d0e55f..18b86e46 100644 --- a/server/books/schema/queries.py +++ b/server/books/schema/queries.py @@ -225,34 +225,35 @@ class BookNode(DjangoObjectType): return Topic.get_by_parent(self) -class FilteredChapterNode(DjangoObjectType): - content_blocks = DjangoFilterConnectionField(ContentBlockNode) - - class Meta: - model = Chapter - only_fields = [ - 'slug', 'title', 'description', - ] - filter_fields = [ - 'slug', 'title', - ] - interfaces = (relay.Node,) - - def resolve_content_blocks(self, *args, **kwargs): - return ContentBlock.get_by_parent(self) +# todo: do we need this? +# class FilteredChapterNode(DjangoObjectType): +# content_blocks = DjangoFilterConnectionField(ContentBlockNode) +# +# class Meta: +# model = Chapter +# only_fields = [ +# 'slug', 'title', 'description', +# ] +# filter_fields = [ +# 'slug', 'title', +# ] +# interfaces = (relay.Node,) +# +# def resolve_content_blocks(self, *args, **kwargs): +# return ContentBlock.get_by_parent(self) class BookQuery(object): book = relay.Node.Field(BookNode) topic = graphene.Field(TopicNode, slug=graphene.String()) module = graphene.Field(ModuleNode, slug=graphene.String(), id=graphene.ID()) - chapter = relay.Node.Field(FilteredChapterNode) + chapter = relay.Node.Field(ChapterNode) content_block = relay.Node.Field(ContentBlockNode) books = DjangoFilterConnectionField(BookNode) topics = DjangoFilterConnectionField(TopicNode) modules = DjangoFilterConnectionField(ModuleNode) - chapters = DjangoFilterConnectionField(FilteredChapterNode) + chapters = DjangoFilterConnectionField(ChapterNode) def resolve_books(self, *args, **kwargs): return Book.objects.filter(**kwargs).live() From e45847fe1e70beb5cd4f2f047d0de45f889ec533 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Thu, 19 Dec 2019 09:46:55 +0100 Subject: [PATCH 09/17] Fix unit tests --- .../assignments/tests/test_myassignments.py | 78 +++++++++++++++++-- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/server/assignments/tests/test_myassignments.py b/server/assignments/tests/test_myassignments.py index f3efd115..ff9399fe 100644 --- a/server/assignments/tests/test_myassignments.py +++ b/server/assignments/tests/test_myassignments.py @@ -39,20 +39,88 @@ class MyAssignemntsText(DefaultUserTestCase): def query_my_assignments(self): query = ''' - query { + query MyActivityQuery { myActivity { edges { node { id - text - assignment { + title + slug + metaTitle + mySubmissions { + edges { + node { + id + text + assignment { + id + title + } + } + } + } + myAnswers { + edges { + node { + id + survey { + id + title + } + } + } + } + myContentBookmarks { + edges { + node { + id + uuid + note { + id + text + } + contentBlock { + id + type + contents + } + } + } + } + myChapterBookmarks { + edges { + node { + id + note { + id + text + } + chapter { + id + title + description + } + } + } + } + bookmark { id - title + note { + id + text + } + module { + id + teaser + metaTitle + intro + } } } } } } + ''' result = self.client.execute(query) @@ -68,5 +136,5 @@ class MyAssignemntsText(DefaultUserTestCase): result = self.query_my_assignments() contents = self.get_content(result) self.assertEqual(len(contents), 1) - self.assertEquals(contents[0].get('node').get('text'), self.submission1.text) + self.assertEquals(contents[0].get('node').get('mySubmissions').get('edges')[0].get('node').get('text'), self.submission1.text) From ddf3bde07685c65cdb4c1a55a1fd6e80db565ac5 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Thu, 19 Dec 2019 11:34:20 +0100 Subject: [PATCH 10/17] Add clickable link to my activities --- .../src/components/profile/ActivityEntry.vue | 64 ++++++++++++++++ .../components/profile/ContentBookmark.vue | 11 --- .../src/components/profile/ModuleActivity.vue | 73 ++++++++----------- 3 files changed, 95 insertions(+), 53 deletions(-) create mode 100644 client/src/components/profile/ActivityEntry.vue diff --git a/client/src/components/profile/ActivityEntry.vue b/client/src/components/profile/ActivityEntry.vue new file mode 100644 index 00000000..cbf27894 --- /dev/null +++ b/client/src/components/profile/ActivityEntry.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/client/src/components/profile/ContentBookmark.vue b/client/src/components/profile/ContentBookmark.vue index 701fa71e..bfefc191 100644 --- a/client/src/components/profile/ContentBookmark.vue +++ b/client/src/components/profile/ContentBookmark.vue @@ -1,6 +1,5 @@ @@ -53,13 +40,15 @@ import {mapActions} from 'vuex' import ContentBookmark from '@/components/profile/ContentBookmark'; + import ActivityEntry from '@/components/profile/ActivityEntry'; import SCROLL_TO_MUTATION from '@/graphql/gql/local/mutations/scrollTo.gql'; export default { props: ['module'], components: { - ContentBookmark + ContentBookmark, + ActivityEntry }, computed: { empty() { From 64db0487cae47438af4855d309c17709235305c8 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Thu, 19 Dec 2019 11:35:47 +0100 Subject: [PATCH 11/17] Refresh my activities periodically --- client/src/pages/activity.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/pages/activity.vue b/client/src/pages/activity.vue index c7dcc6d1..4a649395 100644 --- a/client/src/pages/activity.vue +++ b/client/src/pages/activity.vue @@ -23,7 +23,8 @@ query: MY_ACTIVITY_QUERY, update(data) { return this.$getRidOfEdges(data).myActivity; - } + }, + pollInterval: 5000, } }, From de5882f644a10b3e5deba336ff29805d3df57008 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Thu, 19 Dec 2019 11:36:29 +0100 Subject: [PATCH 12/17] Add anchor for notes --- client/src/components/notes/BookmarkActions.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/notes/BookmarkActions.vue b/client/src/components/notes/BookmarkActions.vue index 4c5b74d4..cb6c7c11 100644 --- a/client/src/components/notes/BookmarkActions.vue +++ b/client/src/components/notes/BookmarkActions.vue @@ -7,7 +7,7 @@ - + From a76b27bf0b0fb5a2920329eaee5ffc55404ff43c Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Thu, 19 Dec 2019 11:37:31 +0100 Subject: [PATCH 13/17] Fix margin on link block in my activities --- client/src/components/content-blocks/LinkBlock.vue | 13 +++++++++++-- client/src/components/profile/ContentBookmark.vue | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/client/src/components/content-blocks/LinkBlock.vue b/client/src/components/content-blocks/LinkBlock.vue index 03d7a8ae..e36655a7 100644 --- a/client/src/components/content-blocks/LinkBlock.vue +++ b/client/src/components/content-blocks/LinkBlock.vue @@ -1,5 +1,5 @@