Merge branch 'develop'
This commit is contained in:
commit
8bcb76709c
15
README.md
15
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 <appname>`
|
||||
|
||||
The following command will provide a URL to where the backup can be downloaded (expires after 60 minutes)
|
||||
|
||||
`heroku pg:backups:url b001 --app <appname>`
|
||||
|
||||
To restore a backup, use
|
||||
|
||||
`heroku pg:backups:restore b001 DATABASE_URL --app <appname>`
|
||||
|
||||
To see the backup schedule
|
||||
|
||||
`heroku pg:backus:schedules --app <appname>`
|
||||
|
||||
## AWS
|
||||
|
||||
|
|
|
|||
|
|
@ -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)(\?.*)?$/,
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
@ -108,5 +110,11 @@ export default {
|
|||
RoomEntryNode: () => ({
|
||||
title: 'A Room Entry',
|
||||
contents: [],
|
||||
}),
|
||||
InstrumentNode: () => ({
|
||||
contents: [],
|
||||
title: 'instrument-title',
|
||||
slug: 'instrument-slug',
|
||||
id: getInstrumentId(),
|
||||
})
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
describe('Instruments Page', () => {
|
||||
beforeEach(() => {
|
||||
cy.setup();
|
||||
});
|
||||
|
||||
it('opens the instruments page', () => {
|
||||
const analyse = {
|
||||
name: 'Analyse',
|
||||
category: 'LANGUAGE_COMMUNICATION',
|
||||
type: 'analyse',
|
||||
};
|
||||
|
||||
const argumentation = {
|
||||
name: 'Argumentation',
|
||||
category: 'LANGUAGE_COMMUNICATION',
|
||||
type: 'argumentation',
|
||||
};
|
||||
|
||||
const ethik = {
|
||||
name: 'Ethik',
|
||||
category: 'SOCIETY',
|
||||
type: 'ethik',
|
||||
};
|
||||
|
||||
cy.mockGraphqlOps({
|
||||
operations: {
|
||||
MeQuery: {},
|
||||
InstrumentsQuery: {
|
||||
instruments: [
|
||||
{
|
||||
type: analyse,
|
||||
title: 'Instrument: Analyse',
|
||||
slug: 'analyse',
|
||||
},
|
||||
{
|
||||
type: argumentation,
|
||||
title: 'Instrument: Argumentation',
|
||||
slug: 'argumentation',
|
||||
},
|
||||
{
|
||||
type: ethik,
|
||||
title: 'Instrument: Ethik',
|
||||
slug: 'ethik',
|
||||
}
|
||||
],
|
||||
},
|
||||
InstrumentTypesQuery: {
|
||||
instrumentTypes: [
|
||||
analyse,
|
||||
argumentation,
|
||||
{
|
||||
name: 'Beschreibung',
|
||||
category: 'LANGUAGE_COMMUNICATION',
|
||||
type: 'beschreibung',
|
||||
},
|
||||
ethik,
|
||||
{
|
||||
name: 'Identität und Sozialisation',
|
||||
category: 'SOCIETY',
|
||||
type: 'identitt-und-sozialisation',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
cy.visit('instruments/');
|
||||
|
||||
cy.getByDataCy('instrument').should('have.length', 3);
|
||||
|
||||
cy.getByDataCy('filter-language-communication').click();
|
||||
cy.getByDataCy('instrument').should('have.length', 2);
|
||||
|
||||
cy.getByDataCy('filter-society').click();
|
||||
cy.getByDataCy('instrument').should('have.length', 1);
|
||||
|
||||
cy.getByDataCy('filter-interdisciplinary').click();
|
||||
cy.getByDataCy('instrument').should('have.length', 0);
|
||||
|
||||
cy.getByDataCy('filter-analyse').click();
|
||||
cy.getByDataCy('instrument').should('have.length', 1);
|
||||
|
||||
cy.getByDataCy('filter-ethik').click();
|
||||
cy.getByDataCy('instrument').should('have.length', 1);
|
||||
|
||||
cy.getByDataCy('filter-all-instruments').click();
|
||||
cy.getByDataCy('instrument').should('have.length', 3);
|
||||
});
|
||||
});
|
||||
|
|
@ -6,21 +6,34 @@ describe('Sidebar', () => {
|
|||
});
|
||||
|
||||
it('should open sidebar and stay open', () => {
|
||||
const {me} = getMinimalMe({});
|
||||
const operations = {
|
||||
MeQuery: getMinimalMe({}),
|
||||
MeQuery: {
|
||||
me: {
|
||||
...me,
|
||||
schoolClasses: {
|
||||
edges: [
|
||||
...me.schoolClasses.edges,
|
||||
{node: {}},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
ProjectsQuery: {
|
||||
projects: []
|
||||
}
|
||||
projects: [],
|
||||
},
|
||||
};
|
||||
|
||||
cy.mockGraphqlOps({
|
||||
operations
|
||||
operations,
|
||||
});
|
||||
|
||||
cy.visit('/portfolio');
|
||||
cy.getByDataCy('sidebar').should('not.exist');
|
||||
cy.getByDataCy('user-widget-avatar').click();
|
||||
cy.getByDataCy('sidebar').should('exist');
|
||||
cy.getByDataCy('class-selection').click();
|
||||
cy.getByDataCy('class-selection-entry').should('have.length', 2);
|
||||
cy.getByDataCy('close-profile-sidebar-link').click();
|
||||
cy.getByDataCy('sidebar').should('not.exist');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -43746,6 +43746,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": {
|
||||
|
|
@ -45176,6 +45183,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": {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,152 @@
|
|||
<template>
|
||||
<a
|
||||
:class="typeClass"
|
||||
class="filter-entry">
|
||||
<span class="filter-entry__text">{{ text }}</span>
|
||||
<span class="filter-entry__icon-wrapper">
|
||||
<chevron-right class="filter-entry__icon"/>
|
||||
</span>
|
||||
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ChevronRight from '@/components/icons/ChevronRight';
|
||||
import {INTERDISCIPLINARY, LANGUAGE_COMMUNICATION, SOCIETY} from '@/consts/instrument.consts';
|
||||
import INSTRUMENT_FILTER_QUERY from 'gql/local/instrumentFiler.gql';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
text: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isCategory: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
category: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
ChevronRight,
|
||||
},
|
||||
|
||||
apollo: {
|
||||
instrumentFilter: {
|
||||
query: INSTRUMENT_FILTER_QUERY
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
instrumentFilter: {
|
||||
currentFilter: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
isActive() {
|
||||
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;
|
||||
},
|
||||
typeClass() {
|
||||
return {
|
||||
'filter-entry--language-communication': this.category === LANGUAGE_COMMUNICATION,
|
||||
'filter-entry--society': this.category === SOCIETY,
|
||||
'filter-entry--interdisciplinary': this.category === INTERDISCIPLINARY,
|
||||
'filter-entry--active': this.isActive,
|
||||
'filter-entry--category': this.isCategory,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~styles/helpers';
|
||||
|
||||
.filter-entry {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
&__text {
|
||||
@include sub-heading;
|
||||
line-height: 1.5;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
&__icon-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
$root: &;
|
||||
|
||||
@mixin filter-block($color) {
|
||||
&#{$root}--category {
|
||||
color: $color;
|
||||
}
|
||||
|
||||
&#{$root}--active {
|
||||
#{$root}__icon-wrapper {
|
||||
background-color: $color;
|
||||
}
|
||||
}
|
||||
|
||||
#{$root}__icon {
|
||||
fill: $color;
|
||||
}
|
||||
}
|
||||
|
||||
&--language-communication {
|
||||
@include filter-block($color-accent-2-dark);
|
||||
}
|
||||
|
||||
&--society {
|
||||
@include filter-block($color-accent-1-dark);
|
||||
}
|
||||
|
||||
&--interdisciplinary {
|
||||
@include filter-block($color-accent-4-dark);
|
||||
}
|
||||
|
||||
&--active {
|
||||
#{$root}__text {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#{$root}__icon-wrapper {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
#{$root}__icon {
|
||||
fill: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
<template>
|
||||
<div class="filter-group">
|
||||
<filter-entry
|
||||
:text="title"
|
||||
v-bind="$attrs"
|
||||
:type="category"
|
||||
:category="category"
|
||||
:is-category="true"
|
||||
@click.native="setCategoryFilter(category)"/>
|
||||
<div class="filter-group__children">
|
||||
<filter-entry
|
||||
:key="type.id"
|
||||
:data-cy="`filter-${type.type}`"
|
||||
:category="type.category"
|
||||
:text="type.name"
|
||||
:type="type.type"
|
||||
v-for="type in types"
|
||||
@click.native="setFilter(`type:${type.type}`)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ChevronRight from '@/components/icons/ChevronRight';
|
||||
import FilterEntry from '@/components/instruments/FilterEntry';
|
||||
|
||||
import SET_FILTER_MUTATION from 'gql/local/mutations/setInstrumentFilter.gql';
|
||||
import INSTRUMENT_FILTER_QUERY from 'gql/local/instrumentFiler.gql';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
types: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
category: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
FilterEntry,
|
||||
ChevronRight,
|
||||
},
|
||||
|
||||
apollo: {
|
||||
instrumentFilter: {
|
||||
query: INSTRUMENT_FILTER_QUERY
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
instrumentFilter: {
|
||||
currentFilter: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
inheritAttrs: false,
|
||||
|
||||
methods: {
|
||||
setCategoryFilter(category) {
|
||||
if (category) {
|
||||
this.setFilter(`category:${category}`);
|
||||
} else {
|
||||
this.setFilter(``);
|
||||
}
|
||||
},
|
||||
setFilter(filter) {
|
||||
this.$apollo.mutate({
|
||||
mutation: SET_FILTER_MUTATION,
|
||||
variables: {
|
||||
filter
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~styles/helpers';
|
||||
|
||||
.filter-group {
|
||||
border-bottom: 1px solid $color-silver;
|
||||
padding: $medium-spacing 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&__children {
|
||||
padding-left: $medium-spacing;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
<template>
|
||||
<div
|
||||
:class="typeClass"
|
||||
class="instrument-entry">
|
||||
<h4 class="instrument-entry__category">{{ categoryName }}</h4>
|
||||
<h3 class="instrument-entry__title">{{ instrument.title }}</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {INTERDISCIPLINARY, LANGUAGE_COMMUNICATION, SOCIETY} from '@/consts/instrument.consts';
|
||||
import instrumentType from '@/helpers/instrumentType';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
instrument: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
typeClass() {
|
||||
return {
|
||||
'instrument-entry__language-communication': this.instrument.type.category === LANGUAGE_COMMUNICATION,
|
||||
'instrument-entry__society': this.instrument.type.category === SOCIETY,
|
||||
'instrument-entry__interdisciplinary': this.instrument.type.category === INTERDISCIPLINARY,
|
||||
};
|
||||
},
|
||||
categoryName() {
|
||||
return instrumentType(this.instrument);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~styles/helpers';
|
||||
|
||||
.instrument-entry {
|
||||
padding: $medium-spacing;
|
||||
margin-bottom: $medium-spacing;
|
||||
border-radius: 8px;
|
||||
|
||||
&__title {
|
||||
@include heading-3;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&__category {
|
||||
@include sub-heading;
|
||||
margin-bottom: $small-spacing;
|
||||
}
|
||||
|
||||
$root: &;
|
||||
|
||||
&__language-communication {
|
||||
background-color: $color-accent-2-light;
|
||||
|
||||
#{$root}__category {
|
||||
color: $color-accent-2-dark;
|
||||
}
|
||||
}
|
||||
|
||||
&__society {
|
||||
background-color: $color-accent-1-light;
|
||||
|
||||
#{$root}__category {
|
||||
color: $color-accent-1-dark;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&__interdisciplinary {
|
||||
background-color: $color-accent-4-light;
|
||||
|
||||
#{$root}__category {
|
||||
color: $color-accent-4-dark;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -1,97 +1,90 @@
|
|||
<template>
|
||||
<div class="instrument-filter">
|
||||
<checkbox
|
||||
:checked="type.enabled"
|
||||
:class="`instrument-filter__checkbox--${type.cls}`"
|
||||
:label="type.label"
|
||||
:key="i"
|
||||
class="instrument-filter__checkbox"
|
||||
v-for="(type, i) in types"
|
||||
@input="change($event, i)"
|
||||
<filter-group
|
||||
title="Alle Instrumente"
|
||||
data-cy="filter-all-instruments"
|
||||
/>
|
||||
|
||||
<filter-group
|
||||
:types="languageCommunicationTypes"
|
||||
:category="LANGUAGE_COMMUNICATION"
|
||||
title="Sprache und Kommunikation"
|
||||
data-cy="filter-language-communication"
|
||||
class="instrument-filter__group--language"
|
||||
/>
|
||||
|
||||
<filter-group
|
||||
:types="societyTypes"
|
||||
:category="SOCIETY"
|
||||
title="Gesellschaft"
|
||||
data-cy="filter-society"
|
||||
/>
|
||||
|
||||
<filter-group
|
||||
:category="INTERDISCIPLINARY"
|
||||
title="Überfachliche Instrumente"
|
||||
data-cy="filter-interdisciplinary"
|
||||
/>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {INTERDISCIPLINARY, LANGUAGE_COMMUNICATION, SOCIETY} from '@/consts/instrument.consts';
|
||||
|
||||
import Checkbox from '@/components/ui/Checkbox';
|
||||
import FilterGroup from '@/components/instruments/FilterGroup';
|
||||
|
||||
import INSTRUMENT_TYPES_QUERY from 'gql/queries/instrumentTypesQuery';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
Checkbox
|
||||
FilterGroup,
|
||||
Checkbox,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
filter: [],
|
||||
types: [
|
||||
{
|
||||
label: 'Sprache und Kommunikation',
|
||||
enabled: true,
|
||||
prop: 'LANGUAGE_COMMUNICATION',
|
||||
cls: 'language'
|
||||
},
|
||||
{
|
||||
label: 'Gesellschaft',
|
||||
enabled: true,
|
||||
prop: 'SOCIETY',
|
||||
cls: 'society'
|
||||
},
|
||||
{
|
||||
label: 'Überfachliches Instrument',
|
||||
enabled: true,
|
||||
prop: 'INTERDISCIPLINARY',
|
||||
cls: 'interdisciplinary'
|
||||
},
|
||||
]
|
||||
filter: '',
|
||||
LANGUAGE_COMMUNICATION,
|
||||
SOCIETY,
|
||||
INTERDISCIPLINARY,
|
||||
instrumentTypes: [],
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
change(enabled, index) {
|
||||
let type = this.types[index];
|
||||
this.types = [
|
||||
...this.types.slice(0, index),
|
||||
{
|
||||
...type,
|
||||
enabled
|
||||
computed: {
|
||||
languageCommunicationTypes() {
|
||||
return this.instrumentTypes.filter(t => t.category === 'LANGUAGE_COMMUNICATION');
|
||||
},
|
||||
...this.types.slice(index + 1),
|
||||
];
|
||||
this.$emit('filter',
|
||||
this.types
|
||||
.filter(t => t.enabled)
|
||||
.map(t => t.prop)
|
||||
);
|
||||
societyTypes() {
|
||||
return this.instrumentTypes.filter(t => t.category === 'SOCIETY');
|
||||
},
|
||||
interdisciplinaryTypes() {
|
||||
return this.instrumentTypes.filter(t => t.category === 'INTERDISCIPLINARY');
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
setFilter(filter) {
|
||||
this.filter = filter;
|
||||
this.$emit('filter', filter);
|
||||
}
|
||||
},
|
||||
|
||||
apollo: {
|
||||
instrumentTypes: {
|
||||
query: INSTRUMENT_TYPES_QUERY,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "~styles/helpers";
|
||||
|
||||
.instrument-filter {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
|
||||
&__checkbox {
|
||||
&--language {
|
||||
/deep/ input:checked + .checkbox {
|
||||
background-color: $color-accent-1-dark;
|
||||
}
|
||||
}
|
||||
|
||||
&--society {
|
||||
/deep/ input:checked + .checkbox {
|
||||
background-color: $color-accent-2-dark;
|
||||
}
|
||||
}
|
||||
|
||||
&--interdisciplinary {
|
||||
/deep/ input:checked + .checkbox {
|
||||
background-color: $color-accent-4-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
import ActivityEntry from '@/components/profile/ActivityEntry';
|
||||
|
||||
import SCROLL_TO_MUTATION from '@/graphql/gql/local/mutations/scrollTo.gql';
|
||||
import instrumentType from '@/helpers/instrumentType';
|
||||
|
||||
export default {
|
||||
props: ['instrument', 'filter'],
|
||||
|
|
@ -52,11 +53,7 @@
|
|||
return this.applyFilter('bookmarks') ? this.instrument.bookmarks : [];
|
||||
},
|
||||
type() {
|
||||
if (this.instrument.type === 'LANGUAGE_COMMUNICATION') {
|
||||
return 'Sprache & Kommunikation';
|
||||
} else {
|
||||
return 'Gesellschaft';
|
||||
}
|
||||
return instrumentType(this.instrument);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
export const LANGUAGE_COMMUNICATION = 'LANGUAGE_COMMUNICATION';
|
||||
export const SOCIETY = 'SOCIETY';
|
||||
export const INTERDISCIPLINARY = 'INTERDISCIPLINARY';
|
||||
|
|
@ -24,6 +24,10 @@ const writeLocalCache = cache => {
|
|||
__typename: 'HelloEmail',
|
||||
email: '',
|
||||
},
|
||||
instrumentFilter: {
|
||||
__typename: 'InstrumentFilter',
|
||||
currentFilter: 'abc'
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
query InstrumentFilter {
|
||||
instrumentFilter @client {
|
||||
currentFilter
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
mutation($filter: String!) {
|
||||
setInstrumentFilter(filter: $filter) @client
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
query InstrumentTypesQuery {
|
||||
instrumentTypes {
|
||||
name
|
||||
type
|
||||
category
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
query InstrumentQuery($type: String!){
|
||||
instruments(type: $type) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
contents
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,14 @@
|
|||
query InstrumentQuery {
|
||||
query InstrumentsQuery {
|
||||
instruments {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
contents
|
||||
slug
|
||||
type {
|
||||
id
|
||||
type
|
||||
}
|
||||
category
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ export const typeDefs = gql`
|
|||
profile: Boolean
|
||||
}
|
||||
|
||||
type InstrumentFilter {
|
||||
currentFilter: String!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
scrollTo(scrollTo: String!): ScrollPosition
|
||||
helloEmail(email: String!): HelloEmail
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
import {LANGUAGE_COMMUNICATION, SOCIETY} from '@/consts/instrument.consts';
|
||||
|
||||
const instrumentType = ({type: {category}}) => {
|
||||
if (category === LANGUAGE_COMMUNICATION) {
|
||||
return 'Sprache & Kommunikation';
|
||||
} else if (category === SOCIETY) {
|
||||
return 'Gesellschaft';
|
||||
} else {
|
||||
return 'Überfachliches Instrument';
|
||||
}
|
||||
};
|
||||
|
||||
export default instrumentType;
|
||||
|
|
@ -1,10 +1,5 @@
|
|||
<template>
|
||||
<div class="instrument-overview">
|
||||
<div class="instrument-overview__heading">
|
||||
<h1 class="instrument-overview__title">
|
||||
Instrumente
|
||||
</h1>
|
||||
</div>
|
||||
<instrument-filter
|
||||
class="instrument-overview__filter"
|
||||
@filter="updateFilter"/>
|
||||
|
|
@ -12,10 +7,10 @@
|
|||
<router-link
|
||||
:to="{name: 'instrument', params: {slug: instrument.slug}}"
|
||||
:key="instrument.id"
|
||||
class="instrument-overview__list-item"
|
||||
data-cy="instrument"
|
||||
tag="div"
|
||||
v-for="instrument in filteredInstruments">
|
||||
{{ instrument.title }}
|
||||
<instrument-entry :instrument="instrument"/>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -23,11 +18,15 @@
|
|||
|
||||
<script>
|
||||
import InstrumentFilter from '@/components/instruments/InstrumentFilter';
|
||||
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
|
||||
InstrumentFilter,
|
||||
InstrumentEntry,
|
||||
},
|
||||
|
||||
apollo: {
|
||||
|
|
@ -35,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: []
|
||||
filter: i => i, // identity
|
||||
instrumentFilter: {
|
||||
currentFilter: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
filteredInstruments() {
|
||||
return this.instruments.filter(instrument => this.filter.includes(instrument.type) || !this.filter.length);
|
||||
}
|
||||
return this.instruments.filter(i => this.filter(i));
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateFilter(filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -65,24 +80,14 @@
|
|||
|
||||
.instrument-overview {
|
||||
display: grid;
|
||||
grid-template-rows: auto auto 1fr;
|
||||
@include centered(800px);
|
||||
|
||||
&__heading {
|
||||
padding: 2*$large-spacing 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
&__title {
|
||||
max-width: $screen-width;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
//grid-template-rows: auto auto 1fr;
|
||||
grid-template-columns: 300px auto;
|
||||
grid-column-gap: $small-spacing;
|
||||
//@include centered(800px);
|
||||
padding: 0 $small-spacing;
|
||||
|
||||
&__filter {
|
||||
justify-self: start;
|
||||
|
||||
}
|
||||
|
||||
&__list {
|
||||
|
|
@ -91,6 +96,7 @@
|
|||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
&__list-item {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,12 @@
|
|||
font-size: toRem(18px);
|
||||
}
|
||||
|
||||
@mixin sub-heading {
|
||||
font-family: $sans-serif-font-family;
|
||||
font-weight: 400;
|
||||
font-size: toRem(16px);
|
||||
}
|
||||
|
||||
@mixin modal-heading {
|
||||
@include heading-2;
|
||||
margin-bottom: 0;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,26 @@
|
|||
### list backups
|
||||
### List backups
|
||||
|
||||
```
|
||||
heroku login
|
||||
heroku pg:backups --app skillbox-prod
|
||||
```
|
||||
|
||||
|
||||
### Create a backup
|
||||
|
||||
`heroku pg:backups:capture --app <appname>`
|
||||
|
||||
The following command will provide a URL to where the backup can be downloaded (expires after 60 minutes)
|
||||
|
||||
`heroku pg:backups:url b001 --app <appname>`
|
||||
|
||||
To restore a backup, use
|
||||
|
||||
`heroku pg:backups:restore b001 DATABASE_URL --app <appname>`
|
||||
|
||||
To see the backup schedule
|
||||
|
||||
`heroku pg:backus:schedules --app <appname>`
|
||||
|
||||
To download a backup use
|
||||
`heroku pg:backups:download --app <appname>`
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from graphene_django.debug import DjangoDebug
|
|||
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
|
||||
from basicknowledge.queries import BasicKnowledgeQuery
|
||||
from basicknowledge.queries import InstrumentQuery
|
||||
from books.schema.mutations import BookMutations
|
||||
from books.schema.queries import BookQuery
|
||||
from news.schema import AllNewsTeasersQuery
|
||||
|
|
@ -27,7 +27,7 @@ from users.mutations import ProfileMutations
|
|||
|
||||
|
||||
class CustomQuery(UsersQuery, AllUsersQuery, ModuleRoomsQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery,
|
||||
StudentSubmissionQuery, BasicKnowledgeQuery, PortfolioQuery, SurveysQuery, AllNewsTeasersQuery, graphene.ObjectType):
|
||||
StudentSubmissionQuery, InstrumentQuery, PortfolioQuery, SurveysQuery, AllNewsTeasersQuery, graphene.ObjectType):
|
||||
node = relay.Node.Field()
|
||||
|
||||
if settings.DEBUG:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
@ -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)
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# Generated by Django 2.2.24 on 2021-10-20 12:02
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('basicknowledge', '0007_basicknowledge_intro'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='InstrumentType',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('category', models.CharField(choices=[('language_communication', 'Sprache & Kommunikation'), ('society', 'Gesellschaft'), ('interdisciplinary', 'Überfachliches Instrument')], max_length=100)),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='basicknowledge',
|
||||
name='new_type',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='basicknowledge.InstrumentType'),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
# Generated by Django 2.2.24 on 2021-10-20 12:13
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
from basicknowledge.models import INTERDISCIPLINARY, LANGUAGE_COMMUNICATION, SOCIETY
|
||||
|
||||
|
||||
def create_types(apps, schema_editor):
|
||||
BasicKnowledge = apps.get_model('basicknowledge', 'BasicKnowledge')
|
||||
InstrumentType = apps.get_model('basicknowledge', 'InstrumentType')
|
||||
language_type=InstrumentType.objects.create(
|
||||
name='Sprache & Kommunikation',
|
||||
category=LANGUAGE_COMMUNICATION
|
||||
)
|
||||
society_type=InstrumentType.objects.create(
|
||||
name='Gesellschaft',
|
||||
category=SOCIETY
|
||||
)
|
||||
interdisciplinary_type=InstrumentType.objects.create(
|
||||
name='Überfachliches Instrument',
|
||||
category=INTERDISCIPLINARY
|
||||
)
|
||||
instruments = []
|
||||
for instrument in BasicKnowledge.objects.filter(type=LANGUAGE_COMMUNICATION):
|
||||
instrument.new_type=language_type
|
||||
instruments.append(instrument)
|
||||
for instrument in BasicKnowledge.objects.filter(type=SOCIETY):
|
||||
instrument.new_type=society_type
|
||||
instruments.append(instrument)
|
||||
for instrument in BasicKnowledge.objects.filter(type=INTERDISCIPLINARY):
|
||||
instrument.new_type=interdisciplinary_type
|
||||
instruments.append(instrument)
|
||||
|
||||
BasicKnowledge.objects.bulk_update(instruments, ['new_type'])
|
||||
|
||||
|
||||
def delete_types(apps, schema_editor):
|
||||
BasicKnowledge = apps.get_model('basicknowledge', 'BasicKnowledge')
|
||||
InstrumentType = apps.get_model('basicknowledge', 'InstrumentType')
|
||||
instruments = []
|
||||
for instrument in BasicKnowledge.objects.all():
|
||||
instrument.new_type = None
|
||||
instruments.append(instrument)
|
||||
BasicKnowledge.objects.bulk_update(instruments, ['new_type'])
|
||||
InstrumentType.objects.all().delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('basicknowledge', '0008_auto_20211020_1202'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_types, delete_types)
|
||||
]
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.24 on 2021-10-30 20:04
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('basicknowledge', '0009_auto_20211020_1213'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='basicknowledge',
|
||||
old_name='type',
|
||||
new_name='old_type',
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.24 on 2021-10-31 11:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('basicknowledge', '0010_auto_20211030_2004'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='instrumenttype',
|
||||
name='name',
|
||||
field=models.CharField(max_length=255, unique=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -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', 'Blog'
|
||||
]
|
||||
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)
|
||||
]
|
||||
|
|
@ -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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,13 +1,39 @@
|
|||
from django.db import models
|
||||
from django.utils.text import slugify
|
||||
from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel
|
||||
from wagtail.core.fields import StreamField, RichTextField
|
||||
from wagtail.core.fields import RichTextField, StreamField
|
||||
from wagtail.images.blocks import ImageChooserBlock
|
||||
|
||||
from books.blocks import LinkBlock, VideoBlock, DocumentBlock, SectionTitleBlock, InfogramBlock, \
|
||||
GeniallyBlock, InstrumentTextBlock, SubtitleBlock, ThinglinkBlock
|
||||
from books.blocks import DocumentBlock, GeniallyBlock, InfogramBlock, InstrumentTextBlock, LinkBlock, SectionTitleBlock, \
|
||||
SubtitleBlock, ThinglinkBlock, VideoBlock
|
||||
from core.constants import DEFAULT_RICH_TEXT_FEATURES
|
||||
from core.wagtail_utils import StrictHierarchyPage
|
||||
|
||||
LANGUAGE_COMMUNICATION = 'language_communication'
|
||||
SOCIETY = 'society'
|
||||
INTERDISCIPLINARY = 'interdisciplinary'
|
||||
|
||||
|
||||
class InstrumentType(models.Model):
|
||||
CATEGORY_CHOICES = (
|
||||
(LANGUAGE_COMMUNICATION, 'Sprache & Kommunikation'),
|
||||
(SOCIETY, 'Gesellschaft'),
|
||||
(INTERDISCIPLINARY, 'Überfachliches Instrument'),
|
||||
)
|
||||
|
||||
name = models.CharField(max_length=255, unique=True)
|
||||
category = models.CharField(
|
||||
max_length=100,
|
||||
choices=CATEGORY_CHOICES
|
||||
)
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return slugify(self.name.lower())
|
||||
|
||||
def __str__(self):
|
||||
return self.type
|
||||
|
||||
|
||||
class BasicKnowledge(StrictHierarchyPage):
|
||||
parent_page_types = ['books.book']
|
||||
|
|
@ -27,24 +53,16 @@ class BasicKnowledge(StrictHierarchyPage):
|
|||
('subtitle', SubtitleBlock()),
|
||||
], null=True, blank=True)
|
||||
|
||||
LANGUAGE_COMMUNICATION = 'language_communication'
|
||||
SOCIETY = 'society'
|
||||
INTERDISCIPLINARY = 'interdisciplinary'
|
||||
new_type = models.ForeignKey(InstrumentType, null=True, on_delete=models.PROTECT, related_name='instruments')
|
||||
|
||||
TYPE_CHOICES = (
|
||||
(LANGUAGE_COMMUNICATION, 'Sprache & Kommunikation'),
|
||||
(SOCIETY, 'Gesellschaft'),
|
||||
(INTERDISCIPLINARY, 'Überfachliches Instrument'),
|
||||
)
|
||||
|
||||
type = models.CharField(
|
||||
old_type = models.CharField(
|
||||
max_length=100,
|
||||
choices=TYPE_CHOICES
|
||||
choices=InstrumentType.CATEGORY_CHOICES
|
||||
)
|
||||
|
||||
content_panels = [
|
||||
FieldPanel('title', classname="full title"),
|
||||
FieldPanel('type'),
|
||||
FieldPanel('new_type'),
|
||||
FieldPanel('intro'),
|
||||
StreamFieldPanel('contents')
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,34 +1,54 @@
|
|||
import graphene
|
||||
from graphene import relay
|
||||
from graphene_django import DjangoObjectType
|
||||
from graphene_django.filter import DjangoFilterConnectionField
|
||||
|
||||
from api.utils import get_object
|
||||
from notes.models import InstrumentBookmark
|
||||
from notes.schema import InstrumentBookmarkNode
|
||||
from .models import BasicKnowledge
|
||||
from .models import BasicKnowledge, InstrumentType
|
||||
|
||||
|
||||
class InstrumentTypeNode(DjangoObjectType):
|
||||
type = graphene.String(required=True)
|
||||
|
||||
class Meta:
|
||||
model = InstrumentType
|
||||
only_fields = [
|
||||
'name', 'category', 'type', 'id'
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def resolve_type(root: InstrumentType, info, **kwargs):
|
||||
return root.type
|
||||
|
||||
|
||||
class InstrumentNode(DjangoObjectType):
|
||||
bookmarks = graphene.List(InstrumentBookmarkNode)
|
||||
type = graphene.Field(InstrumentTypeNode)
|
||||
|
||||
class Meta:
|
||||
model = BasicKnowledge
|
||||
filter_fields = ['slug', 'type']
|
||||
filter_fields = ['slug']
|
||||
interfaces = (relay.Node,)
|
||||
only_fields = [
|
||||
'slug', 'title', 'intro', 'type', 'contents',
|
||||
'slug', 'title', 'intro', 'contents',
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def resolve_type(root: BasicKnowledge, info, **kwargs):
|
||||
return root.new_type
|
||||
|
||||
def resolve_bookmarks(self, info, **kwargs):
|
||||
return InstrumentBookmark.objects.filter(
|
||||
user=info.context.user,
|
||||
instrument=self
|
||||
)
|
||||
|
||||
class BasicKnowledgeQuery(object):
|
||||
|
||||
class InstrumentQuery(object):
|
||||
instrument = graphene.Field(InstrumentNode, slug=graphene.String(), id=graphene.ID())
|
||||
instruments = DjangoFilterConnectionField(InstrumentNode)
|
||||
instruments = graphene.List(InstrumentNode)
|
||||
instrument_types = graphene.List(InstrumentTypeNode)
|
||||
|
||||
def resolve_instrument(self, info, **kwargs):
|
||||
slug = kwargs.get('slug')
|
||||
|
|
@ -42,3 +62,6 @@ class BasicKnowledgeQuery(object):
|
|||
|
||||
def resolve_instruments(self, info, **kwargs):
|
||||
return BasicKnowledge.objects.all().live()
|
||||
|
||||
def resolve_instrument_types(self, info, **kwargs):
|
||||
return InstrumentType.objects.filter(instruments__isnull=False)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -206,12 +206,6 @@ type AssignmentNodeEdge {
|
|||
cursor: String!
|
||||
}
|
||||
|
||||
enum BasicKnowledgeType {
|
||||
LANGUAGE_COMMUNICATION
|
||||
SOCIETY
|
||||
INTERDISCIPLINARY
|
||||
}
|
||||
|
||||
type ChapterBookmarkNode implements Node {
|
||||
user: PrivateUserNode!
|
||||
note: NoteNode
|
||||
|
|
@ -461,7 +455,8 @@ type CustomQuery {
|
|||
project(id: ID, slug: String): ProjectNode
|
||||
projects: [ProjectNode]
|
||||
instrument(slug: String, id: ID): InstrumentNode
|
||||
instruments(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, type: String): InstrumentNodeConnection
|
||||
instruments: [InstrumentNode]
|
||||
instrumentTypes: [InstrumentTypeNode]
|
||||
studentSubmission(id: ID!): StudentSubmissionNode
|
||||
assignment(id: ID!): AssignmentNode
|
||||
assignments(offset: Int, before: String, after: String, first: Int, last: Int): AssignmentNodeConnection
|
||||
|
|
@ -484,7 +479,7 @@ type CustomQuery {
|
|||
me: PrivateUserNode
|
||||
allUsers(offset: Int, before: String, after: String, first: Int, last: Int, username: String, email: String): PrivateUserNodeConnection
|
||||
myActivity(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, slug_Icontains: String, slug_In: [String], title: String, title_Icontains: String, title_In: [String]): ModuleNodeConnection
|
||||
myInstrumentActivity(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, type: String): InstrumentNodeConnection
|
||||
myInstrumentActivity(offset: Int, before: String, after: String, first: Int, last: Int, slug: String): InstrumentNodeConnection
|
||||
_debug: DjangoDebug
|
||||
}
|
||||
|
||||
|
|
@ -609,9 +604,9 @@ type InstrumentNode implements Node {
|
|||
slug: String!
|
||||
intro: String!
|
||||
contents: GenericStreamFieldType
|
||||
type: BasicKnowledgeType!
|
||||
id: ID!
|
||||
bookmarks: [InstrumentBookmarkNode]
|
||||
type: InstrumentTypeNode
|
||||
}
|
||||
|
||||
type InstrumentNodeConnection {
|
||||
|
|
@ -624,6 +619,19 @@ type InstrumentNodeEdge {
|
|||
cursor: String!
|
||||
}
|
||||
|
||||
enum InstrumentTypeCategory {
|
||||
LANGUAGE_COMMUNICATION
|
||||
SOCIETY
|
||||
INTERDISCIPLINARY
|
||||
}
|
||||
|
||||
type InstrumentTypeNode {
|
||||
id: ID!
|
||||
name: String!
|
||||
category: InstrumentTypeCategory!
|
||||
type: String!
|
||||
}
|
||||
|
||||
scalar JSONString
|
||||
|
||||
input JoinClassInput {
|
||||
|
|
|
|||
Loading…
Reference in New Issue