From b4c9e01fcb540b9b8b5f5e8434a7f264ac6dae00 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Mon, 18 Oct 2021 15:38:57 +0200 Subject: [PATCH 01/20] Add command to export assignments --- server/assignments/admin.py | 12 +++------ server/assignments/helpers.py | 13 +++++++++ server/assignments/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../commands/export_assignment_submissions.py | 27 +++++++++++++++++++ 5 files changed, 43 insertions(+), 9 deletions(-) create mode 100644 server/assignments/helpers.py create mode 100644 server/assignments/management/__init__.py create mode 100644 server/assignments/management/commands/__init__.py create mode 100644 server/assignments/management/commands/export_assignment_submissions.py diff --git a/server/assignments/admin.py b/server/assignments/admin.py index e6b8e1e0..2a19d341 100644 --- a/server/assignments/admin.py +++ b/server/assignments/admin.py @@ -7,6 +7,7 @@ from django.http import HttpResponse from django.urls import reverse from django.utils.html import format_html +from assignments.helpers import write_assignments_to_csv, write_submissions_to_csv from assignments.models import Assignment, StudentSubmission, SubmissionFeedback @@ -38,10 +39,7 @@ class AssignmentAdmin(admin.ModelAdmin): response['Content-Disposition'] = 'attachment;filename=assignment-export.csv' writer = csv.writer(response) - field_names = ['ID', 'Titel', 'Auftragstext', 'Modul'] - writer.writerow(field_names) - for assignment in queryset.all(): - writer.writerow([assignment.id, assignment.title, assignment.assignment, assignment.module]) + write_assignments_to_csv(writer, queryset) return response @@ -52,11 +50,7 @@ class AssignmentAdmin(admin.ModelAdmin): response['Content-Disposition'] = 'attachment;filename=assignment-submission-export.csv' writer = csv.writer(response) - field_names = ['Assignment-ID', 'Text', 'Mit Lehrer geteilt',] - writer.writerow(field_names) - for assignment in queryset.all(): - for submission in assignment.submissions.filter(final=True): - writer.writerow([submission.assignment.id, submission.text, submission.final]) + write_submissions_to_csv(writer, queryset) return response diff --git a/server/assignments/helpers.py b/server/assignments/helpers.py new file mode 100644 index 00000000..2ab0860a --- /dev/null +++ b/server/assignments/helpers.py @@ -0,0 +1,13 @@ + + +def write_assignments_to_csv(writer, queryset): + field_names = ['ID', 'Titel', 'Auftragstext', 'Modul'] + writer.writerow(field_names) + for assignment in queryset.all(): + writer.writerow([assignment.id, assignment.title, assignment.assignment, assignment.module]) + +def write_submissions_to_csv(writer, queryset): + field_names = ['Assignment-ID', 'Text', 'Mit Lehrer geteilt', ] + writer.writerow(field_names) + for submission in queryset.all(): + writer.writerow([submission.assignment.id, submission.text, submission.final]) diff --git a/server/assignments/management/__init__.py b/server/assignments/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/assignments/management/commands/__init__.py b/server/assignments/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/assignments/management/commands/export_assignment_submissions.py b/server/assignments/management/commands/export_assignment_submissions.py new file mode 100644 index 00000000..bf903456 --- /dev/null +++ b/server/assignments/management/commands/export_assignment_submissions.py @@ -0,0 +1,27 @@ +import csv + +from django.core.management.base import BaseCommand + +from assignments.helpers import write_assignments_to_csv, write_submissions_to_csv +from assignments.models import Assignment, StudentSubmission + + +class Command(BaseCommand): + help = """ + Export assignments with submissions + """ + + def handle(self, *args, **options): + ids = [171, 112, 113, 114, 272, 246, 250, 348, 598] + + assignments = Assignment.objects.filter(id__in=ids) + + with open('./export-assignments.csv', 'w') as f: + writer = csv.writer(f) + write_assignments_to_csv(writer, assignments) + + submissions = StudentSubmission.objects.filter(assignment__id__in=ids) + + with open('./export-submissions.csv', 'w') as f: + writer = csv.writer(f) + write_submissions_to_csv(writer, submissions) From 60e3cc264c63c15c1524d8eb1a6585d214e12995 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Mon, 18 Oct 2021 17:30:56 +0200 Subject: [PATCH 02/20] Update documentation --- README.md | 15 +-------------- docs/heroku-backup.md | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index d03b1e65..f3db19b6 100644 --- a/README.md +++ b/README.md @@ -128,21 +128,8 @@ Change DATABASE URL (e.g after a rollback) ### Backup -Create a backup +See [Docs](./docs/heroku-backup.md) -`heroku pg:backups:capture --app ` - -The following command will provide a URL to where the backup can be downloaded (expires after 60 minutes) - -`heroku pg:backups:url b001 --app ` - -To restore a backup, use - -`heroku pg:backups:restore b001 DATABASE_URL --app ` - -To see the backup schedule - -`heroku pg:backus:schedules --app ` ## AWS diff --git a/docs/heroku-backup.md b/docs/heroku-backup.md index b7ee97f5..a3b72d9b 100644 --- a/docs/heroku-backup.md +++ b/docs/heroku-backup.md @@ -1,6 +1,26 @@ -### list backups +### List backups ``` heroku login heroku pg:backups --app skillbox-prod ``` + + +### Create a backup + +`heroku pg:backups:capture --app ` + +The following command will provide a URL to where the backup can be downloaded (expires after 60 minutes) + +`heroku pg:backups:url b001 --app ` + +To restore a backup, use + +`heroku pg:backups:restore b001 DATABASE_URL --app ` + +To see the backup schedule + +`heroku pg:backus:schedules --app ` + +To download a backup use +`heroku pg:backups:download --app ` From 3c03e3c741b25a83632ddd3d3812dd0e3ec5cf19 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Mon, 18 Oct 2021 17:46:45 +0200 Subject: [PATCH 03/20] Add new test, update naming --- .../frontend/instruments-page.spec.js | 17 +++++++++++++++++ .../gql/queries/instrumentsByTypeQuery.gql | 2 +- .../graphql/gql/queries/instrumentsQuery.gql | 16 ++++++---------- 3 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 client/cypress/integration/frontend/instruments-page.spec.js diff --git a/client/cypress/integration/frontend/instruments-page.spec.js b/client/cypress/integration/frontend/instruments-page.spec.js new file mode 100644 index 00000000..1a7cfa19 --- /dev/null +++ b/client/cypress/integration/frontend/instruments-page.spec.js @@ -0,0 +1,17 @@ +describe('Instruments Page', () => { + beforeEach(() => { + cy.setup(); + }); + + it('opens the instruments page', () => { + cy.mockGraphqlOps({ + operations: { + MeQuery: {}, + InstrumentsQuery: { + instruments: [] + } + } + }); + cy.visit('instruments/'); + }); +}); diff --git a/client/src/graphql/gql/queries/instrumentsByTypeQuery.gql b/client/src/graphql/gql/queries/instrumentsByTypeQuery.gql index 6024c799..aa84aca6 100644 --- a/client/src/graphql/gql/queries/instrumentsByTypeQuery.gql +++ b/client/src/graphql/gql/queries/instrumentsByTypeQuery.gql @@ -1,4 +1,4 @@ -query InstrumentQuery($type: String!){ +query InstrumentsQuery($type: String!){ instruments(type: $type) { edges { node { diff --git a/client/src/graphql/gql/queries/instrumentsQuery.gql b/client/src/graphql/gql/queries/instrumentsQuery.gql index bed2a520..c87588ba 100644 --- a/client/src/graphql/gql/queries/instrumentsQuery.gql +++ b/client/src/graphql/gql/queries/instrumentsQuery.gql @@ -1,13 +1,9 @@ -query InstrumentQuery { +query InstrumentsQuery { instruments { - edges { - node { - id - title - contents - slug - type - } - } + id + title + contents + slug + type } } From 5fd5a5be4ad428c1506e20abfe5ed441914450dc Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 20 Oct 2021 13:57:32 +0200 Subject: [PATCH 04/20] Add new test for instruments page, start refactor and style changes --- client/build/webpack.base.conf.js | 10 ++-- client/cypress/fixtures/mocks.js | 8 ++++ .../frontend/instruments-page.spec.js | 12 ++++- client/package-lock.json | 14 ++++++ .../components/instruments/FilterGroup.vue | 25 ++++++++++ .../instruments/InstrumentFilter.vue | 48 +++++++++++-------- client/src/pages/instrumentOverview.vue | 25 ++-------- 7 files changed, 96 insertions(+), 46 deletions(-) create mode 100644 client/src/components/instruments/FilterGroup.vue diff --git a/client/build/webpack.base.conf.js b/client/build/webpack.base.conf.js index b70630b9..81b79aca 100644 --- a/client/build/webpack.base.conf.js +++ b/client/build/webpack.base.conf.js @@ -2,7 +2,7 @@ const path = require('path'); const config = require('../config'); -const VueLoaderPlugin = require('vue-loader/lib/plugin'); +const {VueLoaderPlugin} = require('vue-loader'); const {isDev, styleRule, assetsPath} = require('./utils'); @@ -42,7 +42,7 @@ module.exports = { alias: { '@': resolve('src'), styles: resolve('src/styles'), - gql: resolve('src/graphql/gql') + gql: resolve('src/graphql/gql'), }, }, module: { @@ -64,9 +64,9 @@ module.exports = { test: /\.tsx?$/, loader: 'ts-loader', options: { - appendTsSuffixTo: [/\.vue$/] + appendTsSuffixTo: [/\.vue$/], }, - exclude: /node_modules/ + exclude: /node_modules/, }, { test: /\.js$/, @@ -79,7 +79,7 @@ module.exports = { { test: /\.(gql|graphql)$/, loader: 'graphql-tag/loader', - exclude: /node_modules/ + exclude: /node_modules/, }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, diff --git a/client/cypress/fixtures/mocks.js b/client/cypress/fixtures/mocks.js index f66111a1..61cddee1 100644 --- a/client/cypress/fixtures/mocks.js +++ b/client/cypress/fixtures/mocks.js @@ -20,11 +20,13 @@ const classMemberIdIterator = idGenerator('ClassMemberNode'); const chapterIdIterator = idGenerator('ChapterNode'); const moduleIdIterator = idGenerator('ModuleNode'); const contentBlockIdIterator = idGenerator('ContentBlockNode'); +const instrumentIdGenerator = idGenerator('InstrumentNode'); const getClassMemberId = () => classMemberIdIterator.next().value; const getChapterId = () => chapterIdIterator.next().value; const getModuleId = () => moduleIdIterator.next().value; const getContentBlockId = () => contentBlockIdIterator.next().value; +const getInstrumentId = () => instrumentIdGenerator.next().value; export default { UUID: () => '123-456-789', @@ -109,4 +111,10 @@ export default { title: 'A Room Entry', contents: [], }), + InstrumentNode: () => ({ + contents: [], + title: 'instrument-title', + slug: 'instrument-slug', + id: getInstrumentId(), + }) }; diff --git a/client/cypress/integration/frontend/instruments-page.spec.js b/client/cypress/integration/frontend/instruments-page.spec.js index 1a7cfa19..66e15e2b 100644 --- a/client/cypress/integration/frontend/instruments-page.spec.js +++ b/client/cypress/integration/frontend/instruments-page.spec.js @@ -8,10 +8,20 @@ describe('Instruments Page', () => { operations: { MeQuery: {}, InstrumentsQuery: { - instruments: [] + instruments: [ + {}, + {} + ] } } }); cy.visit('instruments/'); + + cy.getByDataCy('filter-all-instruments').should('exist'); + cy.getByDataCy('filter-language-communication').should('exist'); + cy.getByDataCy('filter-society').should('exist'); + cy.getByDataCy('filter-interdisciplinary').should('exist'); + cy.getByDataCy('filter-analysis').should('exist'); + cy.getByDataCy('filter-ethics').should('exist'); }); }); diff --git a/client/package-lock.json b/client/package-lock.json index d8001d86..723c1d84 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -18294,6 +18294,13 @@ "integrity": "sha512-q8GgAIPU7xHCsUhB1PUgR//8GoI0bUdMRUKd69jw2UcKy7pGuu0NbJsOGqdSpdpvhO4LY/dgqohPEkE1TrBwKQ==", "requires": { "vue": "^2.1.10" + }, + "dependencies": { + "vue": { + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz", + "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==" + } } }, "svgo": { @@ -19724,6 +19731,13 @@ "babel-preset-env": "^1.6.0", "rollup-plugin-babel": "^3.0.2", "vue": "^2.4.4" + }, + "dependencies": { + "vue": { + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz", + "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==" + } } }, "vuejs-logger": { diff --git a/client/src/components/instruments/FilterGroup.vue b/client/src/components/instruments/FilterGroup.vue new file mode 100644 index 00000000..e9313a58 --- /dev/null +++ b/client/src/components/instruments/FilterGroup.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/client/src/components/instruments/InstrumentFilter.vue b/client/src/components/instruments/InstrumentFilter.vue index 82f8e082..9591c40c 100644 --- a/client/src/components/instruments/InstrumentFilter.vue +++ b/client/src/components/instruments/InstrumentFilter.vue @@ -1,24 +1,33 @@ diff --git a/client/src/components/instruments/FilterGroup.vue b/client/src/components/instruments/FilterGroup.vue index e9313a58..a4ff330e 100644 --- a/client/src/components/instruments/FilterGroup.vue +++ b/client/src/components/instruments/FilterGroup.vue @@ -1,18 +1,38 @@ @@ -21,5 +41,11 @@ .filter-group { border-bottom: 1px solid black; + display: flex; + flex-direction: column; + + &__children { + padding-left: $medium-spacing; + } } diff --git a/client/src/components/instruments/InstrumentEntry.vue b/client/src/components/instruments/InstrumentEntry.vue new file mode 100644 index 00000000..ea127195 --- /dev/null +++ b/client/src/components/instruments/InstrumentEntry.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/client/src/components/instruments/InstrumentFilter.vue b/client/src/components/instruments/InstrumentFilter.vue index 9e7e356b..514b9c46 100644 --- a/client/src/components/instruments/InstrumentFilter.vue +++ b/client/src/components/instruments/InstrumentFilter.vue @@ -2,24 +2,37 @@
+ data-cy="filter-all-instruments" + @click="setFilter(i=>i)"/> + data-cy="filter-language-communication" + @filter="setFilter($event)" + @click="setFilter(i=>i.type.category===LANGUAGE_COMMUNICATION)" + /> + data-cy="filter-society" + @filter="setFilter($event)" + @click="setFilter(i=>i.type.category===SOCIETY)"/> + data-cy="filter-interdisciplinary" + @filter="setFilter($event)" + @click="setFilter(i=>i.type.category===INTERDISCIPLINARY)"/>
@@ -31,11 +50,24 @@ &__text { @include sub-heading; line-height: 1.5; + color: inherit; } &__icon { width: 10px; height: 10px; } + + &--language-communication { + color: $color-accent-2-dark; + } + + &--society { + color: $color-accent-1-dark; + } + + &--interdisciplinary { + color: $color-accent-4-dark; + } } diff --git a/client/src/components/instruments/FilterGroup.vue b/client/src/components/instruments/FilterGroup.vue index a4ff330e..7250b051 100644 --- a/client/src/components/instruments/FilterGroup.vue +++ b/client/src/components/instruments/FilterGroup.vue @@ -3,6 +3,7 @@
@@ -30,6 +31,10 @@ type: Array, default: () => [], }, + type: { + type: String, + default: '' + } }, components: {FilterEntry, ChevronRight}, inheritAttrs: false, @@ -40,7 +45,7 @@ @import '~styles/helpers'; .filter-group { - border-bottom: 1px solid black; + border-bottom: 1px solid $color-silver; display: flex; flex-direction: column; diff --git a/client/src/components/instruments/InstrumentFilter.vue b/client/src/components/instruments/InstrumentFilter.vue index 514b9c46..49e346e6 100644 --- a/client/src/components/instruments/InstrumentFilter.vue +++ b/client/src/components/instruments/InstrumentFilter.vue @@ -7,14 +7,17 @@ diff --git a/client/src/pages/instrumentOverview.vue b/client/src/pages/instrumentOverview.vue index b92efc2f..cd4928ed 100644 --- a/client/src/pages/instrumentOverview.vue +++ b/client/src/pages/instrumentOverview.vue @@ -64,7 +64,9 @@ display: grid; //grid-template-rows: auto auto 1fr; grid-template-columns: 300px auto; + grid-column-gap: $small-spacing; //@include centered(800px); + padding: 0 $small-spacing; &__filter { @@ -76,6 +78,7 @@ width: 100%; display: flex; flex-direction: column; + justify-self: center; } &__list-item { From 812021cb54ebbf7acb70c9438d7d4d82696442bb Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Sun, 31 Oct 2021 22:12:57 +0100 Subject: [PATCH 16/20] Fix unit tests --- server/books/factories.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/server/books/factories.py b/server/books/factories.py index 2eb8b4c5..4e90311c 100644 --- a/server/books/factories.py +++ b/server/books/factories.py @@ -9,10 +9,10 @@ from wagtail.core.models import Page, Site from wagtail.core.rich_text import RichText from assignments.models import Assignment -from basicknowledge.models import BasicKnowledge -from books.blocks import BasicKnowledgeBlock, ImageUrlBlock, LinkBlock, AssignmentBlock, VideoBlock -from books.models import Book, Topic, Module, Chapter, ContentBlock, TextBlock -from core.factories import BasePageFactory, fake, DummyImageFactory, fake_paragraph, fake_title +from basicknowledge.models import BasicKnowledge, INTERDISCIPLINARY, InstrumentType, LANGUAGE_COMMUNICATION, SOCIETY +from books.blocks import AssignmentBlock, BasicKnowledgeBlock, ImageUrlBlock, LinkBlock, VideoBlock +from books.models import Book, Chapter, ContentBlock, Module, TextBlock, Topic +from core.factories import BasePageFactory, DummyImageFactory, fake, fake_paragraph, fake_title class BookFactory(BasePageFactory): @@ -70,9 +70,18 @@ class TextBlockFactory(wagtail_factories.StructBlockFactory): model = TextBlock +class InstrumentTypeFactory(factory.DjangoModelFactory): + class Meta: + model = InstrumentType + + category = factory.Iterator([LANGUAGE_COMMUNICATION, SOCIETY, INTERDISCIPLINARY]) + name = factory.LazyAttribute(lambda x: fake.text(max_nb_chars=20)) + + class InstrumentFactory(BasePageFactory): title = factory.LazyAttribute(fake_title) - type = factory.Iterator([BasicKnowledge.LANGUAGE_COMMUNICATION, BasicKnowledge.SOCIETY, BasicKnowledge.INTERDISCIPLINARY]) + old_type = factory.Iterator([LANGUAGE_COMMUNICATION, SOCIETY, INTERDISCIPLINARY]) + new_type = factory.SubFactory(InstrumentTypeFactory) class Meta: model = BasicKnowledge @@ -83,7 +92,6 @@ class InstrumentFactory(BasePageFactory): return super()._create(model_class, *args, **kwargs) - class BasicKnowledgeBlockFactory(wagtail_factories.StructBlockFactory): description = factory.LazyAttribute(fake_paragraph) basic_knowledge = factory.SubFactory(InstrumentFactory) From 3e8238929989b199d56a28950c731af123f5de1f Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Mon, 1 Nov 2021 11:57:07 +0100 Subject: [PATCH 17/20] Refactor current instrument filter query, add styling for active state --- .../components/instruments/FilterEntry.vue | 93 +++++++++++++++++-- .../components/instruments/FilterGroup.vue | 57 ++++++++++-- .../instruments/InstrumentFilter.vue | 17 ++-- client/src/graphql/client.js | 4 + .../src/graphql/gql/local/instrumentFiler.gql | 5 + .../local/mutations/setInstrumentFilter.gql | 3 + client/src/graphql/resolvers.js | 11 ++- client/src/graphql/typedefs.js | 4 + client/src/pages/instrumentOverview.vue | 30 ++++-- 9 files changed, 191 insertions(+), 33 deletions(-) create mode 100644 client/src/graphql/gql/local/instrumentFiler.gql create mode 100644 client/src/graphql/gql/local/mutations/setInstrumentFilter.gql diff --git a/client/src/components/instruments/FilterEntry.vue b/client/src/components/instruments/FilterEntry.vue index 285fb13e..9b6cbc6f 100644 --- a/client/src/components/instruments/FilterEntry.vue +++ b/client/src/components/instruments/FilterEntry.vue @@ -3,13 +3,17 @@ :class="typeClass" class="filter-entry"> {{ text }} - + + + + @@ -46,6 +88,7 @@ .filter-group { border-bottom: 1px solid $color-silver; + padding: $medium-spacing 0; display: flex; flex-direction: column; diff --git a/client/src/components/instruments/InstrumentFilter.vue b/client/src/components/instruments/InstrumentFilter.vue index 49e346e6..c7381d28 100644 --- a/client/src/components/instruments/InstrumentFilter.vue +++ b/client/src/components/instruments/InstrumentFilter.vue @@ -3,33 +3,28 @@ + /> + /> + />
diff --git a/client/src/graphql/client.js b/client/src/graphql/client.js index 2bfabfc8..ede1035c 100644 --- a/client/src/graphql/client.js +++ b/client/src/graphql/client.js @@ -24,6 +24,10 @@ const writeLocalCache = cache => { __typename: 'HelloEmail', email: '', }, + instrumentFilter: { + __typename: 'InstrumentFilter', + currentFilter: 'abc' + } }, }); }; diff --git a/client/src/graphql/gql/local/instrumentFiler.gql b/client/src/graphql/gql/local/instrumentFiler.gql new file mode 100644 index 00000000..fe00a4be --- /dev/null +++ b/client/src/graphql/gql/local/instrumentFiler.gql @@ -0,0 +1,5 @@ +query InstrumentFilter { + instrumentFilter @client { + currentFilter + } +} diff --git a/client/src/graphql/gql/local/mutations/setInstrumentFilter.gql b/client/src/graphql/gql/local/mutations/setInstrumentFilter.gql new file mode 100644 index 00000000..bfc35931 --- /dev/null +++ b/client/src/graphql/gql/local/mutations/setInstrumentFilter.gql @@ -0,0 +1,3 @@ +mutation($filter: String!) { + setInstrumentFilter(filter: $filter) @client +} diff --git a/client/src/graphql/resolvers.js b/client/src/graphql/resolvers.js index 2dd4abeb..2d0c2620 100644 --- a/client/src/graphql/resolvers.js +++ b/client/src/graphql/resolvers.js @@ -1,6 +1,7 @@ import SCROLL_POSITION from '@/graphql/gql/local/scrollPosition.gql'; import HELLO_EMAIL from '@/graphql/gql/local/helloEmail.gql'; import SIDEBAR from '@/graphql/gql/local/sidebar.gql'; +import INSTRUMENT_FILTER from '@/graphql/gql/local/instrumentFiler.gql'; export const resolvers = { Mutation: { @@ -16,6 +17,12 @@ export const resolvers = { cache.writeQuery({query: HELLO_EMAIL, data}); return data.helloEmail; }, + setInstrumentFilter: (_, {filter}, {cache}) => { + const data = cache.readQuery({query: INSTRUMENT_FILTER}); + data.instrumentFilter.currentFilter = filter; + cache.writeQuery({query: INSTRUMENT_FILTER, data}); + return data.instrumentFilter; + }, toggleSidebar: (_, {sidebar: {profile, navigation}}, {cache}) => { const data = cache.readQuery({query: SIDEBAR}); if (typeof profile !== 'undefined') { @@ -26,6 +33,6 @@ export const resolvers = { } cache.writeQuery({query: SIDEBAR, data}); return data.sidebar; - } - } + }, + }, }; diff --git a/client/src/graphql/typedefs.js b/client/src/graphql/typedefs.js index ecad752c..9528fc5e 100644 --- a/client/src/graphql/typedefs.js +++ b/client/src/graphql/typedefs.js @@ -19,6 +19,10 @@ export const typeDefs = gql` profile: Boolean } + type InstrumentFilter { + currentFilter: String! + } + type Mutation { scrollTo(scrollTo: String!): ScrollPosition helloEmail(email: String!): HelloEmail diff --git a/client/src/pages/instrumentOverview.vue b/client/src/pages/instrumentOverview.vue index cd4928ed..a339e23b 100644 --- a/client/src/pages/instrumentOverview.vue +++ b/client/src/pages/instrumentOverview.vue @@ -21,10 +21,12 @@ import InstrumentEntry from '@/components/instruments/InstrumentEntry'; import INSTRUMENTS_QUERY from '@/graphql/gql/queries/instrumentsQuery.gql'; + import INSTRUMENT_FILTER_QUERY from 'gql/local/instrumentFiler.gql'; + export default { components: { InstrumentFilter, - InstrumentEntry + InstrumentEntry, }, apollo: { @@ -32,27 +34,43 @@ query: INSTRUMENTS_QUERY, update(data) { return this.$getRidOfEdges(data).instruments; - } - } + }, + }, + instrumentFilter: { + query: INSTRUMENT_FILTER_QUERY, + update({instrumentFilter}) { + const {currentFilter} = instrumentFilter; + if (currentFilter && currentFilter.indexOf(':') > -1) { + const [filterType, identifier] = currentFilter.split(':'); + this.filter = i => i.type[filterType] === identifier; + } else { + this.filter = i => i; // identity + } + return instrumentFilter; + }, + }, }, data() { return { instruments: [], - filter: i => i + filter: i => i, // identity + instrumentFilter: { + currentFilter: '', + }, }; }, computed: { filteredInstruments() { return this.instruments.filter(i => this.filter(i)); - } + }, }, methods: { updateFilter(filter) { this.filter = filter; - } + }, }, }; From 885d9d4f8828fd3725f7009126baebc61e0c4331 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Mon, 1 Nov 2021 11:57:42 +0100 Subject: [PATCH 18/20] Add migration for new instrument type population --- .../migrations/0012_auto_20211101_1008.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 server/basicknowledge/migrations/0012_auto_20211101_1008.py diff --git a/server/basicknowledge/migrations/0012_auto_20211101_1008.py b/server/basicknowledge/migrations/0012_auto_20211101_1008.py new file mode 100644 index 00000000..929e4e92 --- /dev/null +++ b/server/basicknowledge/migrations/0012_auto_20211101_1008.py @@ -0,0 +1,37 @@ +# Generated by Django 2.2.24 on 2021-11-01 10:08 + +from django.db import migrations + +from basicknowledge.models import LANGUAGE_COMMUNICATION, SOCIETY +from core.logger import get_logger + +logger = get_logger(__name__) + + +def create_new_types(apps, schema_editor): + InstrumentType = apps.get_model('basicknowledge', 'InstrumentType') + language_communication_types = [ + 'Analyse', 'Argumentation', 'Beschreibung', 'Grafiken', 'Interview', + 'Kommunikation', 'Korrespondenz', 'Orthografie', 'Präsentation', + 'Struktur', 'Umfrage', 'Zusammenfassung' + ] + society_types = [ + 'Ethik', 'Identität und Sozialisation', 'Kultur', 'Ökologie', + 'Politik', 'Recht', 'Technologie', 'Wirtschaft' + ] + + for type_name in language_communication_types: + obj, created = InstrumentType.objects.get_or_create(name=type_name, category=LANGUAGE_COMMUNICATION) + + for type_name in society_types: + obj, created = InstrumentType.objects.get_or_create(name=type_name, category=SOCIETY) + + +class Migration(migrations.Migration): + dependencies = [ + ('basicknowledge', '0011_auto_20211031_1144'), + ] + + operations = [ + migrations.RunPython(create_new_types, migrations.RunPython.noop) + ] From dc5d9224695b92fef272cf6567bfe23ad99ed0f0 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Mon, 1 Nov 2021 12:05:59 +0100 Subject: [PATCH 19/20] Fix es-lint issue and remove console log --- client/src/components/instruments/FilterEntry.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/instruments/FilterEntry.vue b/client/src/components/instruments/FilterEntry.vue index 9b6cbc6f..8f0a56e6 100644 --- a/client/src/components/instruments/FilterEntry.vue +++ b/client/src/components/instruments/FilterEntry.vue @@ -55,10 +55,10 @@ computed: { isActive() { - console.log(this.instrumentFilter.currentFilter); if (!this.instrumentFilter.currentFilter) { return this.type === ''; } + // eslint-disable-next-line const [_, identifier] = this.instrumentFilter.currentFilter.split(':'); console.log(identifier, this.type); return this.type === identifier; From f03beb8b347e7560687d0a0f16b5998c1d022c6e Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 10 Nov 2021 12:47:52 +0100 Subject: [PATCH 20/20] Return only instrument types with assigned instruments --- .../migrations/0012_auto_20211101_1008.py | 2 +- .../migrations/0013_auto_20211110_1140.py | 19 +++++++++++++ server/basicknowledge/models.py | 2 +- server/basicknowledge/queries.py | 2 +- server/basicknowledge/tests/__init__.py | 0 .../tests/test_instrument_types_query.py | 27 +++++++++++++++++++ server/core/tests/base_test.py | 2 +- 7 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 server/basicknowledge/migrations/0013_auto_20211110_1140.py create mode 100644 server/basicknowledge/tests/__init__.py create mode 100644 server/basicknowledge/tests/test_instrument_types_query.py diff --git a/server/basicknowledge/migrations/0012_auto_20211101_1008.py b/server/basicknowledge/migrations/0012_auto_20211101_1008.py index 929e4e92..89096978 100644 --- a/server/basicknowledge/migrations/0012_auto_20211101_1008.py +++ b/server/basicknowledge/migrations/0012_auto_20211101_1008.py @@ -13,7 +13,7 @@ def create_new_types(apps, schema_editor): language_communication_types = [ 'Analyse', 'Argumentation', 'Beschreibung', 'Grafiken', 'Interview', 'Kommunikation', 'Korrespondenz', 'Orthografie', 'Präsentation', - 'Struktur', 'Umfrage', 'Zusammenfassung' + 'Struktur', 'Umfrage', 'Zusammenfassung', 'Blog' ] society_types = [ 'Ethik', 'Identität und Sozialisation', 'Kultur', 'Ökologie', diff --git a/server/basicknowledge/migrations/0013_auto_20211110_1140.py b/server/basicknowledge/migrations/0013_auto_20211110_1140.py new file mode 100644 index 00000000..1918a76c --- /dev/null +++ b/server/basicknowledge/migrations/0013_auto_20211110_1140.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.24 on 2021-11-10 11:40 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('basicknowledge', '0012_auto_20211101_1008'), + ] + + operations = [ + migrations.AlterField( + model_name='basicknowledge', + name='new_type', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='instruments', to='basicknowledge.InstrumentType'), + ), + ] diff --git a/server/basicknowledge/models.py b/server/basicknowledge/models.py index 3c987493..5b5ae791 100644 --- a/server/basicknowledge/models.py +++ b/server/basicknowledge/models.py @@ -53,7 +53,7 @@ class BasicKnowledge(StrictHierarchyPage): ('subtitle', SubtitleBlock()), ], null=True, blank=True) - new_type = models.ForeignKey(InstrumentType, null=True, on_delete=models.PROTECT) + new_type = models.ForeignKey(InstrumentType, null=True, on_delete=models.PROTECT, related_name='instruments') old_type = models.CharField( max_length=100, diff --git a/server/basicknowledge/queries.py b/server/basicknowledge/queries.py index 0b27f10b..8304d3e0 100644 --- a/server/basicknowledge/queries.py +++ b/server/basicknowledge/queries.py @@ -64,4 +64,4 @@ class InstrumentQuery(object): return BasicKnowledge.objects.all().live() def resolve_instrument_types(self, info, **kwargs): - return InstrumentType.objects.all() + return InstrumentType.objects.filter(instruments__isnull=False) diff --git a/server/basicknowledge/tests/__init__.py b/server/basicknowledge/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/basicknowledge/tests/test_instrument_types_query.py b/server/basicknowledge/tests/test_instrument_types_query.py new file mode 100644 index 00000000..d90b3f4e --- /dev/null +++ b/server/basicknowledge/tests/test_instrument_types_query.py @@ -0,0 +1,27 @@ +from books.factories import InstrumentFactory, InstrumentTypeFactory +from core.tests.base_test import SkillboxTestCase + +INSTRUMENT_TYPES_QUERY = """ +query InstrumentTypesQuery { + instrumentTypes { + name + type + category + } +} +""" + + +class InstrumentTypesQueryTestCase(SkillboxTestCase): + def setUp(self) -> None: + self.createDefault() + self.type = InstrumentTypeFactory(name='Type O Negative') + second_type = InstrumentTypeFactory(name='Typecast') + InstrumentTypeFactory(name='Guitar') + self.instrument = InstrumentFactory(new_type=self.type) + InstrumentFactory(new_type=second_type) + + def test_instrument_types_empty_not_returned(self): + result = self.get_client().get_result(INSTRUMENT_TYPES_QUERY) + self.assertIsNone(result.errors) + self.assertEqual(len(result.data['instrumentTypes']), 2) diff --git a/server/core/tests/base_test.py b/server/core/tests/base_test.py index 63468630..2532be57 100644 --- a/server/core/tests/base_test.py +++ b/server/core/tests/base_test.py @@ -23,7 +23,7 @@ class SkillboxTestCase(TestCase): self.school_class = SchoolClass.objects.get(name='skillbox') - def get_client(self, user=None) -> Client: + def get_client(self, user=None) -> GQLClient: request = RequestFactory().get('/') if user is None: user = self.teacher