Merged in develop (pull request #127)

Develop
This commit is contained in:
Ramon Wenger 2023-04-25 09:19:29 +00:00
commit 2eaea595e1
115 changed files with 3513 additions and 1689 deletions

View File

@ -27,7 +27,7 @@ graphene-django = "==2.15.0"
django-filter = "~=21.1"
djangorestframework = "~=3.8"
pillow = "==9.1.0"
wagtail = "~=2.15"
wagtail = "~=4.2"
django-cors-headers = "~=3.0"
django-storages = "*"
boto3 = "*"
@ -35,7 +35,7 @@ django-compressor = "*"
django-libsass = "*"
bleach = "*"
newrelic = "*"
sentry-sdk = "==1.5.12"
sentry-sdk = "==1.17.0"
python-http-client = "==3.2.1"
ipython = "*"
requests = "*"
@ -44,3 +44,5 @@ django-silk = "*"
wagtail-autocomplete = "*"
jedi = "==0.17.2"
Authlib = "*"
django-stubs = {extras = ["compatible-mypy"], version = "*"}
black = "*"

1187
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -6,11 +6,9 @@
image:
name: iterativ/skillbox-test@sha256:60d6bb808a9f0ff7b158192866a18eb7a5381a12621184c17bf5a4fb55384362
clone:
depth: full
definitions:
services:
postgres:
@ -22,171 +20,175 @@ definitions:
cypress: $HOME/.cache/Cypress
clientmodules: client/node_modules
steps:
- &lint
name: lint
caches:
- clientmodules
script:
- npm install --prefix client
- npm run lint --prefix client
- &unittest-python
name: run python unit tests
caches:
- pip
services:
- postgres
script: # Modify the commands below to build your repository.
- &setup-tests source setup-for-tests.sh
- ./server/run_unittests_coverage.sh
- &cypress-test
name: run cypress end-to-end tests
caches:
- pip
- node
- clientmodules
- npm
- cypress
artifacts:
- client/cypress/**/*.png
- client/cypress/**/*.mp4
services:
- postgres
script:
- echo "This pipeline rules!"
- *setup-tests
- npm ci --prefix client
- npm run "install:cypress" --prefix client
- psql -U $DATABASE_USER -h $DATABASE_HOST -c "create database $DATABASE_NAME"
- python server/manage.py dummy_data
- python server/manage.py runserver 2>&1 > /dev/null &
- npm run build --prefix client
- curl http://localhost:8000/beta-login
- npm run --prefix client test:cypress:e2e
- &frontend-test
name: run cypress frontend tests
caches:
- node
- clientmodules
- npm
- cypress
artifacts:
- client/cypress/**/*.png
- client/cypress/**/*.mp4
script:
- npm ci --prefix client
- npm run "install:cypress" --prefix client
- npm run dev --prefix client 2>&1 > /dev/null &
- sleep 30
- curl http://localhost:8080/beta-login
- npm run --prefix client test:cypress:frontend
- &frontend-test-parallel
name: run cypress frontend tests
caches:
- node
- clientmodules
- npm
- cypress
artifacts:
- client/cypress/**/*.png
- client/cypress/**/*.mp4
script:
- npm ci --prefix client
- npm run "install:cypress" --prefix client
- npm run dev --prefix client 2>&1 > /dev/null &
- sleep 30
- curl http://localhost:8080/beta-login
- source bin/run-cypress-parallel.sh
- &jest-test
name: run jest tests
caches:
- node
- clientmodules
script:
- echo "This pipeline rules!"
- *setup-tests
- npm install --prefix client
- cd client
- npm run test:unit
- &deploy-dev
name: deploy to dev on Heroku
deployment: dev
trigger: manual
script:
- git push --force https://heroku:$HEROKU_API_KEY@git.heroku.com/skillbox-dev.git HEAD:master
- &deploy-academy
name: deploy to iterativ-academy on Heroku
deployment: academy-stage
script:
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/iterativ-academy.git HEAD:master
- &deploy-stage
name: deploy to stage on Heroku
deployment: stage
script:
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/skillbox-stage.git develop:master
- &deploy-preprod-manual
name: deploy to pre-prod environments on heroku
trigger: manual
deployment: preprod
script:
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/skillbox-preprod.git HEAD:master
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/my-kv-preprod.git HEAD:master
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/my-dha-preprod.git HEAD:master
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/my-dhf-preprod.git HEAD:master
- &deploy-prod-manual
name: deploy to prod environments on heroku
trigger: manual
deployment: prod
script:
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/skillbox-prod.git HEAD:master
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/my-kv-prod.git HEAD:master
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/my-dha-prod.git HEAD:master
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/my-dhf-prod.git HEAD:master
- &lint
name: lint
caches:
- clientmodules
script:
- npm install --prefix client
- npm run lint --prefix client
- &unittest-python
name: run python unit tests
caches:
- pip
services:
- postgres
script: # Modify the commands below to build your repository.
- &setup-tests source setup-for-tests.sh
- ./server/run_unittests_coverage.sh
- &cypress-test
name: run cypress end-to-end tests
caches:
- pip
- node
- clientmodules
- npm
- cypress
artifacts:
- client/cypress/**/*.png
- client/cypress/**/*.mp4
services:
- postgres
script:
- echo "This pipeline rules!"
- *setup-tests
- npm ci --prefix client
- npm run "install:cypress" --prefix client
- psql -U $DATABASE_USER -h $DATABASE_HOST -c "create database $DATABASE_NAME"
- python server/manage.py dummy_data
- python server/manage.py runserver 2>&1 > /dev/null &
- npm run build --prefix client
- curl http://localhost:8000/beta-login
- npm run --prefix client test:cypress:e2e
- &frontend-test
name: run cypress frontend tests
caches:
- node
- clientmodules
- npm
- cypress
artifacts:
- client/cypress/**/*.png
- client/cypress/**/*.mp4
script:
- npm ci --prefix client
- npm run "install:cypress" --prefix client
- npm run dev --prefix client 2>&1 > /dev/null &
- sleep 30
- curl http://localhost:8080/beta-login
- npm run --prefix client test:cypress:frontend
- &frontend-test-parallel
name: run cypress frontend tests
caches:
- node
- clientmodules
- npm
- cypress
artifacts:
- client/cypress/**/*.png
- client/cypress/**/*.mp4
script:
- npm ci --prefix client
- npm run "install:cypress" --prefix client
- npm run dev --prefix client 2>&1 > /dev/null &
- sleep 30
- curl http://localhost:8080/beta-login
- source bin/run-cypress-parallel.sh
- &jest-test
name: run jest tests
caches:
- node
- clientmodules
script:
- echo "This pipeline rules!"
- *setup-tests
- npm install --prefix client
- cd client
- npm run test:unit
- &deploy-dev
name: deploy to dev on Heroku
deployment: dev
trigger: manual
script:
- git push --force https://heroku:$HEROKU_API_KEY@git.heroku.com/skillbox-dev.git HEAD:master
- &deploy-academy
name: deploy to iterativ-academy on Heroku
deployment: academy-stage
script:
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/iterativ-academy.git HEAD:master
- &deploy-stage
name: deploy to stage on Heroku
deployment: stage
script:
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/skillbox-stage.git develop:master
- &deploy-preprod-manual
name: deploy to pre-prod environments on heroku
trigger: manual
deployment: preprod
script:
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/skillbox-preprod.git HEAD:master
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/my-kv-preprod.git HEAD:master
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/my-dha-preprod.git HEAD:master
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/my-dhf-preprod.git HEAD:master
- &deploy-prod-manual
name: deploy to prod environments on heroku
trigger: manual
deployment: prod
script:
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/skillbox-prod.git HEAD:master
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/my-kv-prod.git HEAD:master
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/my-dha-prod.git HEAD:master
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/my-dhf-prod.git HEAD:master
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/skillbox-preprod.git HEAD:master
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/my-kv-preprod.git HEAD:master
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/my-dha-preprod.git HEAD:master
- git push https://heroku:$HEROKU_API_KEY@git.heroku.com/my-dhf-preprod.git HEAD:master
pipelines:
default:
- parallel:
- step: *lint
- step: *unittest-python
- step: *cypress-test
- step: *frontend-test
- step: *jest-test
- step: *deploy-dev
- parallel:
- step: *lint
- step: *unittest-python
- step: *cypress-test
- step: *frontend-test
- step: *jest-test
- step: *deploy-dev
branches:
master:
- parallel:
- step: *unittest-python
- step: *cypress-test
- step: *frontend-test
- step: *jest-test
- step: *deploy-preprod-manual
- parallel:
- step: *unittest-python
- step: *cypress-test
- step: *frontend-test
- step: *jest-test
- step: *deploy-preprod-manual
develop:
- parallel:
- step: *lint
- step: *unittest-python
- step: *cypress-test
- step: *frontend-test
- step: *jest-test
- parallel:
- step: *deploy-stage
- step: *deploy-academy
- step: *deploy-preprod-manual
- parallel:
- step: *lint
- step: *unittest-python
- step: *cypress-test
- step: *frontend-test
- step: *jest-test
- parallel:
- step: *deploy-stage
- step: *deploy-academy
- step: *deploy-preprod-manual
tags:
v202*:
- parallel:
- step: *unittest-python
- step: *cypress-test
- step: *jest-test
- step: *unittest-python
- step: *cypress-test
- step: *jest-test
- step: *deploy-prod-manual
custom:
cypress-parallel:
- parallel:
- step: *frontend-test-parallel
- step: *frontend-test-parallel
- step: *frontend-test-parallel
- step: *frontend-test-parallel
- step: *frontend-test-parallel
- step: *frontend-test-parallel
- parallel:
- step: *frontend-test-parallel
- step: *frontend-test-parallel
- step: *frontend-test-parallel
- step: *frontend-test-parallel
- step: *frontend-test-parallel
- step: *frontend-test-parallel

View File

@ -11,6 +11,19 @@ const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const env = require('../config/prod.env');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const SentryWebpackPlugin = require('@sentry/webpack-plugin');
const sentryPlugin = [];
if (process.env.SENTRY_AUTH_TOKEN) {
sentryPlugin.push(
new SentryWebpackPlugin({
org: 'iterativ',
project: 'skillbox-vue',
include: config.build.assetsRoot,
authToken: process.env.SENTRY_AUTH_TOKEN,
})
);
}
const webpackConfig = merge(baseWebpackConfig, {
devtool: config.build.productionSourceMap ? config.build.devtool : false,
@ -24,6 +37,7 @@ const webpackConfig = merge(baseWebpackConfig, {
minimizer: [new CssMinimizerPlugin()],
},
plugins: [
...sentryPlugin,
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env,

View File

@ -8,6 +8,8 @@ const values = {
MATOMO_SITE_ID: JSON.stringify(process.env.MATOMO_SITE_ID),
LOGOUT_REDIRECT_URL: JSON.stringify(process.env.LOGOUT_REDIRECT_URL),
VUE_APP_FLAVOR: JSON.stringify(process.env.APP_FLAVOR),
SENTRY_DSN: JSON.stringify(process.env.SENTRY_JAVASCRIPT_DSN),
SENTRY_ENVIRONMENT: JSON.stringify(process.env.SENTRY_ENV),
/*
* ENV variables used in JS code need to be stringyfied, as they will be replaced (in place) in the code,
* and JS needs quotes around strings

View File

@ -0,0 +1,14 @@
import ContentBlock from '@/components/ContentBlock.vue';
describe('<ContentBlock />', () => {
it('renders', () => {
// see: https://on.cypress.io/mounting-vue
cy.mount(ContentBlock, {
props: {
contentBlock: {},
parent: null,
editMode: false,
},
});
});
});

View File

@ -135,7 +135,7 @@ describe('Instruments Page', () => {
cy.getByDataCy('instrument')
.first()
.within(() => {
cy.getByDataCy('instrument-subheader').should('contain', 'Instrumente - Sprache & Kommunikation');
cy.getByDataCy('instrument-subheader').should('contain', 'Instrumente Sprache & Kommunikation');
});
});
});

View File

@ -68,7 +68,7 @@ describe('Instruments on Module page', () => {
cy.getByDataCy('content-block')
.first()
.within(() => {
cy.getByDataCy('instrument-label').should('contain', 'Instrumente - Sprache & Kommunikation');
cy.getByDataCy('instrument-label').should('contain', 'Instrumente Sprache & Kommunikation');
});
cy.getByDataCy('content-block')
.eq(1)

View File

@ -113,6 +113,26 @@ const mockGraphqlOps = (options) => {
cy.get('@mockGraphqlOps').invoke('setOperations' as any, options);
};
const login = (username: string, password: string, visitLogin = false) => {
if (visitLogin) {
cy.visit('/beta-login');
}
if (username !== '') {
cy.get('[data-cy=email-input]').type(username);
}
if (password !== '') {
cy.get('[data-cy=password-input]').type(password);
}
cy.get('[data-cy=login-button]').click();
};
const fakeLogin = () => {
cy.log('Logging in (fake)');
cy.setCookie('loginStatus', 'true');
};
declare global {
namespace Cypress {
interface Chainable {
@ -136,9 +156,9 @@ declare global {
selectClass(schoolClass: string): void;
login(username: string, password: string, visitLogin?: boolean): void;
login: typeof login;
fakeLogin(username: string, password: string): void;
fakeLogin: typeof fakeLogin;
isSubmissionReadOnly(myText: string): void;
@ -180,20 +200,7 @@ Cypress.Commands.add('apolloLogin', (username, password) => {
});
// todo: replace with apollo call
Cypress.Commands.add('login', (username, password, visitLogin = false) => {
if (visitLogin) {
cy.visit('/beta-login');
}
if (username !== '') {
cy.get('[data-cy=email-input]').type(username);
}
if (password !== '') {
cy.get('[data-cy=password-input]').type(password);
}
cy.get('[data-cy=login-button]').click();
});
Cypress.Commands.add('login', login);
Cypress.Commands.add('getByDataCy', getByDataCy);
@ -203,10 +210,7 @@ Cypress.Commands.add('selectClass', (schoolClass) => {
cy.getByDataCy('class-selection-entry').contains(schoolClass).click();
});
Cypress.Commands.add('fakeLogin', () => {
cy.log('Logging in (fake)');
cy.setCookie('loginStatus', 'true');
});
Cypress.Commands.add('fakeLogin', fakeLogin);
Cypress.Commands.add('isSubmissionReadOnly', (myText) => {
cy.get('.submission-form__textarea--readonly').as('textarea');

311
client/package-lock.json generated
View File

@ -19,6 +19,8 @@
"@graphql-tools/jest-transform": "^1.2.2",
"@graphql-tools/mock": "^8.6.5",
"@graphql-tools/schema": "^8.3.7",
"@sentry/vue": "^7.45.0",
"@sentry/webpack-plugin": "^1.20.0",
"@tiptap/core": "^2.0.0-beta.174",
"@tiptap/extension-bullet-list": "^2.0.0-beta.26",
"@tiptap/extension-document": "^2.0.0-beta.15",
@ -3214,6 +3216,173 @@
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@sentry-internal/tracing": {
"version": "7.45.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.45.0.tgz",
"integrity": "sha512-0aIDY2OvUX7k2XHaimOlWkboXoQvJ9dEKvfpu0Wh0YxfUTGPa+wplUdg3WVdkk018sq1L11MKmj4MPZyYUvXhw==",
"dependencies": {
"@sentry/core": "7.45.0",
"@sentry/types": "7.45.0",
"@sentry/utils": "7.45.0",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry-internal/tracing/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@sentry/browser": {
"version": "7.45.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.45.0.tgz",
"integrity": "sha512-/dUrUwnI34voMj+jSJT7b5Jun+xy1utVyzzwTq3Oc22N+SB17ZOX9svZ4jl1Lu6tVJPVjPyvL6zlcbrbMwqFjg==",
"dependencies": {
"@sentry-internal/tracing": "7.45.0",
"@sentry/core": "7.45.0",
"@sentry/replay": "7.45.0",
"@sentry/types": "7.45.0",
"@sentry/utils": "7.45.0",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/browser/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@sentry/cli": {
"version": "1.75.0",
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.75.0.tgz",
"integrity": "sha512-vT8NurHy00GcN8dNqur4CMIYvFH3PaKdkX3qllVvi4syybKqjwoz+aWRCvprbYv0knweneFkLt1SmBWqazUMfA==",
"hasInstallScript": true,
"dependencies": {
"https-proxy-agent": "^5.0.0",
"mkdirp": "^0.5.5",
"node-fetch": "^2.6.7",
"progress": "^2.0.3",
"proxy-from-env": "^1.1.0",
"which": "^2.0.2"
},
"bin": {
"sentry-cli": "bin/sentry-cli"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/@sentry/cli/node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dependencies": {
"minimist": "^1.2.6"
},
"bin": {
"mkdirp": "bin/cmd.js"
}
},
"node_modules/@sentry/cli/node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/@sentry/core": {
"version": "7.45.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.45.0.tgz",
"integrity": "sha512-xJfdTS4lRmHvZI/A5MazdnKhBJFkisKu6G9EGNLlZLre+6W4PH5sb7QX4+xoBdqG7v10Jvdia112vi762ojO2w==",
"dependencies": {
"@sentry/types": "7.45.0",
"@sentry/utils": "7.45.0",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/core/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@sentry/replay": {
"version": "7.45.0",
"resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.45.0.tgz",
"integrity": "sha512-smM7FIcFIyKu30BqCl8BzLo1gH/z9WwXdGX6V0fNvHab9fJZ09+xjFn+LmIyo6N8H8jjwsup0+yQ12kiF/ZsEw==",
"dependencies": {
"@sentry/core": "7.45.0",
"@sentry/types": "7.45.0",
"@sentry/utils": "7.45.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@sentry/types": {
"version": "7.45.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.45.0.tgz",
"integrity": "sha512-iFt7msfUK8LCodFF3RKUyaxy9tJv/gpWhzxUFyNxtuVwlpmd+q6mtsFGn8Af3pbpm8A+MKyz1ebMwXj0PQqknw==",
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/utils": {
"version": "7.45.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.45.0.tgz",
"integrity": "sha512-aTY7qqtNUudd09SH5DVSKMm3iQ6ZeWufduc0I9bPZe6UMM09BDc4KmjmrzRkdQ+VaOmHo7+v+HZKQk5f+AbuTQ==",
"dependencies": {
"@sentry/types": "7.45.0",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/utils/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@sentry/vue": {
"version": "7.45.0",
"resolved": "https://registry.npmjs.org/@sentry/vue/-/vue-7.45.0.tgz",
"integrity": "sha512-31wn10AjdMnCy5bzrMt3QLmX34ijuoxLOK4xE/E7NbtwAuPxd+mnc67CMy6UVfo8Ng36HFZFeNMewdqF676V5Q==",
"dependencies": {
"@sentry/browser": "7.45.0",
"@sentry/core": "7.45.0",
"@sentry/types": "7.45.0",
"@sentry/utils": "7.45.0",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=8"
},
"peerDependencies": {
"vue": "2.x || 3.x"
}
},
"node_modules/@sentry/vue/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@sentry/webpack-plugin": {
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-1.20.0.tgz",
"integrity": "sha512-Ssj1mJVFsfU6vMCOM2d+h+KQR7QHSfeIP16t4l20Uq/neqWXZUQ2yvQfe4S3BjdbJXz/X4Rw8Hfy1Sd0ocunYw==",
"dependencies": {
"@sentry/cli": "^1.74.6",
"webpack-sources": "^2.0.0 || ^3.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/@sinclair/typebox": {
"version": "0.24.50",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.50.tgz",
@ -20442,6 +20611,148 @@
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz",
"integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw=="
},
"@sentry-internal/tracing": {
"version": "7.45.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.45.0.tgz",
"integrity": "sha512-0aIDY2OvUX7k2XHaimOlWkboXoQvJ9dEKvfpu0Wh0YxfUTGPa+wplUdg3WVdkk018sq1L11MKmj4MPZyYUvXhw==",
"requires": {
"@sentry/core": "7.45.0",
"@sentry/types": "7.45.0",
"@sentry/utils": "7.45.0",
"tslib": "^1.9.3"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@sentry/browser": {
"version": "7.45.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.45.0.tgz",
"integrity": "sha512-/dUrUwnI34voMj+jSJT7b5Jun+xy1utVyzzwTq3Oc22N+SB17ZOX9svZ4jl1Lu6tVJPVjPyvL6zlcbrbMwqFjg==",
"requires": {
"@sentry-internal/tracing": "7.45.0",
"@sentry/core": "7.45.0",
"@sentry/replay": "7.45.0",
"@sentry/types": "7.45.0",
"@sentry/utils": "7.45.0",
"tslib": "^1.9.3"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@sentry/cli": {
"version": "1.75.0",
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.75.0.tgz",
"integrity": "sha512-vT8NurHy00GcN8dNqur4CMIYvFH3PaKdkX3qllVvi4syybKqjwoz+aWRCvprbYv0knweneFkLt1SmBWqazUMfA==",
"requires": {
"https-proxy-agent": "^5.0.0",
"mkdirp": "^0.5.5",
"node-fetch": "^2.6.7",
"progress": "^2.0.3",
"proxy-from-env": "^1.1.0",
"which": "^2.0.2"
},
"dependencies": {
"mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"requires": {
"minimist": "^1.2.6"
}
},
"proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
}
}
},
"@sentry/core": {
"version": "7.45.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.45.0.tgz",
"integrity": "sha512-xJfdTS4lRmHvZI/A5MazdnKhBJFkisKu6G9EGNLlZLre+6W4PH5sb7QX4+xoBdqG7v10Jvdia112vi762ojO2w==",
"requires": {
"@sentry/types": "7.45.0",
"@sentry/utils": "7.45.0",
"tslib": "^1.9.3"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@sentry/replay": {
"version": "7.45.0",
"resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.45.0.tgz",
"integrity": "sha512-smM7FIcFIyKu30BqCl8BzLo1gH/z9WwXdGX6V0fNvHab9fJZ09+xjFn+LmIyo6N8H8jjwsup0+yQ12kiF/ZsEw==",
"requires": {
"@sentry/core": "7.45.0",
"@sentry/types": "7.45.0",
"@sentry/utils": "7.45.0"
}
},
"@sentry/types": {
"version": "7.45.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.45.0.tgz",
"integrity": "sha512-iFt7msfUK8LCodFF3RKUyaxy9tJv/gpWhzxUFyNxtuVwlpmd+q6mtsFGn8Af3pbpm8A+MKyz1ebMwXj0PQqknw=="
},
"@sentry/utils": {
"version": "7.45.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.45.0.tgz",
"integrity": "sha512-aTY7qqtNUudd09SH5DVSKMm3iQ6ZeWufduc0I9bPZe6UMM09BDc4KmjmrzRkdQ+VaOmHo7+v+HZKQk5f+AbuTQ==",
"requires": {
"@sentry/types": "7.45.0",
"tslib": "^1.9.3"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@sentry/vue": {
"version": "7.45.0",
"resolved": "https://registry.npmjs.org/@sentry/vue/-/vue-7.45.0.tgz",
"integrity": "sha512-31wn10AjdMnCy5bzrMt3QLmX34ijuoxLOK4xE/E7NbtwAuPxd+mnc67CMy6UVfo8Ng36HFZFeNMewdqF676V5Q==",
"requires": {
"@sentry/browser": "7.45.0",
"@sentry/core": "7.45.0",
"@sentry/types": "7.45.0",
"@sentry/utils": "7.45.0",
"tslib": "^1.9.3"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@sentry/webpack-plugin": {
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-1.20.0.tgz",
"integrity": "sha512-Ssj1mJVFsfU6vMCOM2d+h+KQR7QHSfeIP16t4l20Uq/neqWXZUQ2yvQfe4S3BjdbJXz/X4Rw8Hfy1Sd0ocunYw==",
"requires": {
"@sentry/cli": "^1.74.6",
"webpack-sources": "^2.0.0 || ^3.0.0"
}
},
"@sinclair/typebox": {
"version": "0.24.50",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.50.tgz",

View File

@ -42,6 +42,8 @@
"@graphql-tools/jest-transform": "^1.2.2",
"@graphql-tools/mock": "^8.6.5",
"@graphql-tools/schema": "^8.3.7",
"@sentry/vue": "^7.45.0",
"@sentry/webpack-plugin": "^1.20.0",
"@tiptap/core": "^2.0.0-beta.174",
"@tiptap/extension-bullet-list": "^2.0.0-beta.26",
"@tiptap/extension-document": "^2.0.0-beta.15",

View File

@ -59,14 +59,14 @@
/>
</div>
<!-- eslint-disable vue/no-v-html -->
<h3
class="content-block__instrument-label"
data-cy="instrument-label"
:style="instrumentLabelStyle"
v-if="instrumentLabel !== ''"
>
{{ instrumentLabel }}
</h3>
v-html="instrumentLabel"
/>
<h4
class="content-block__title"
v-if="!contentBlock.indent"

View File

@ -11,6 +11,7 @@
<script>
import LOGOUT_MUTATION from '@/graphql/gql/mutations/logoutUser.gql';
import { matomoTrackUser } from '@/helpers/matomo-client';
export default {
methods: {
@ -21,6 +22,7 @@ export default {
})
.then(({ data }) => {
if (data.logout.success) {
matomoTrackUser('');
location.replace('/logout');
}
});

View File

@ -66,6 +66,7 @@ import debounce from 'lodash/debounce';
import cloneDeep from 'lodash/cloneDeep';
import { sanitize } from '@/helpers/text';
import { defineAsyncComponent } from 'vue';
import { matomoTrackEvent } from '@/helpers/matomo-client';
const SubmissionForm = defineAsyncComponent(() =>
import(/* webpackChunkName: "content-components" */ '@/components/content-blocks/assignment/SubmissionForm.vue')
@ -189,6 +190,10 @@ export default {
*/
this.assignment.submission.text = answer;
this._save(this.assignment.submission);
if (this.assignment.submission.text.length > 0) {
matomoTrackEvent('Auftrag', 'Text mit Eingabe gespeichert', this.assignment.id);
}
},
changeDocumentUrl(documentUrl) {
this.assignment.submission.document = documentUrl;
@ -210,6 +215,7 @@ export default {
},
},
});
matomoTrackEvent('Auftrag', 'Ergebnis mit Lehrperson geteilt', this.assignment.id);
},
reopen() {
this.$apollo.mutate({
@ -259,6 +265,7 @@ export default {
.then(() => {
this.spellcheckLoading = false;
});
matomoTrackEvent('Auftrag', 'Rechtschreibung geprüft', this.assignment.id);
},
},

View File

@ -6,13 +6,13 @@
}"
class="instrument-entry"
>
<!-- eslint-disable vue/no-v-html -->
<h4
class="instrument-entry__category"
:style="{ color: instrument.type.category.foreground }"
data-cy="instrument-subheader"
>
{{ categoryName }}
</h4>
v-html="categoryName"
/>
<h3 class="instrument-entry__title">
{{ instrument.title }}
</h3>

View File

@ -1,7 +1,7 @@
<template>
<div class="instrument-filter">
<filter-group
title="Alle anzeigen"
:title="$flavor.textInstrumentFilterShowAll"
data-cy="filter-all-instruments"
/>

View File

@ -149,7 +149,7 @@ export default {
&__hero-source {
@include tiny-text;
ling-height: 25px;
line-height: 25px;
}
&__meta-title {

View File

@ -56,6 +56,7 @@ import me from '@/mixins/me';
import APPLY_SNAPSHOT_MUTATION from 'gql/mutations/snapshots/applySnapshot.gql';
import { MODULE_PAGE } from '@/router/module.names';
import { matomoTrackEvent } from '@/helpers/matomo-client';
const _getChange = (snapshot, index) => {
try {
@ -131,6 +132,11 @@ export default {
},
},
}) => {
if (this.snapshot.mine) {
matomoTrackEvent('Modul Snapshot', 'Snapshot angewendet', slug);
} else {
matomoTrackEvent('Modul Snapshot', 'Team Snapshot angewendet', slug);
}
this.$router.push({
name: MODULE_PAGE,
params: {

View File

@ -55,6 +55,7 @@ import gql from 'graphql-tag';
import PenIcon from '@/components/icons/PenIcon';
import TrashIcon from '@/components/icons/TrashIcon';
import { removeAtIndex } from '@/graphql/immutable-operations';
import { matomoTrackEvent } from '@/helpers/matomo-client';
export default {
props: {
@ -134,49 +135,53 @@ export default {
title: 'Snapshot löschen',
})
.then(() => {
this.$apollo.mutate({
mutation: DELETE_SNAPSHOT_MUTATION,
variables: {
input: {
id: this.snapshot.id,
},
},
update: (
store,
{
data: {
deleteSnapshot: { result },
this.$apollo
.mutate({
mutation: DELETE_SNAPSHOT_MUTATION,
variables: {
input: {
id: this.snapshot.id,
},
}
) => {
if (result.__typename === 'Success') {
const slug = this.$route.params.slug;
const query = SNAPSHOTS_QUERY;
const variables = {
slug,
};
const { module } = store.readQuery({
query,
variables,
});
const index = module.snapshots.findIndex((snapshot) => snapshot.id === this.snapshot.id);
const snapshots = removeAtIndex(module.snapshots, index);
const data = {
module: {
...module,
snapshots,
},
update: (
store,
{
data: {
deleteSnapshot: { result },
},
};
}
) => {
if (result.__typename === 'Success') {
const slug = this.$route.params.slug;
const query = SNAPSHOTS_QUERY;
const variables = {
slug,
};
const { module } = store.readQuery({
query,
variables,
});
const index = module.snapshots.findIndex((snapshot) => snapshot.id === this.snapshot.id);
const snapshots = removeAtIndex(module.snapshots, index);
store.writeQuery({
query,
variables,
data,
});
}
},
});
const data = {
module: {
...module,
snapshots,
},
};
store.writeQuery({
query,
variables,
data,
});
}
},
})
.then(() => {
matomoTrackEvent('Modul Snapshot', 'Snapshot gelöscht', this.$route.params.slug);
});
})
.catch(() => {});
},
@ -213,6 +218,13 @@ export default {
});
},
});
const slug = this.$route.params.slug;
if (this.snapshot.shared) {
matomoTrackEvent('Modul Snapshot', 'Snapshot mit Team geteilt', slug);
} else {
matomoTrackEvent('Modul Snapshot', 'Snapshot nicht mehr geteilt', slug);
}
},
},
};

View File

@ -42,6 +42,8 @@ import me from '@/mixins/me';
import CREATE_SNAPSHOT_MUTATION from '@/graphql/gql/mutations/createSnapshot.gql';
import MODULE_SNAPSHOTS_QUERY from '@/graphql/gql/queries/moduleSnapshots.gql';
import log from 'loglevel';
import { matomoTrackEvent } from '@/helpers/matomo-client';
export default {
mixins: [me],
@ -109,12 +111,15 @@ export default {
createSnapshot: { snapshot },
},
}) => {
log.debug('snapshot created', snapshot);
this.showPopover = false;
this.$modal.open('snapshot-created', {
snapshot,
});
}
);
matomoTrackEvent('Modul Snapshot', 'Snapshot erstellt', this.$route.params.slug);
},
},
};

View File

@ -12,6 +12,7 @@ import Toggle from '@/components/ui/Toggle';
// import MODULE_DETAILS_QUERY from '@/graphql/gql/queries/modules/moduleDetailsQuery.gql';
import { gql } from '@apollo/client/core';
import { setModuleEditMode } from '@/graphql/cache-operations';
import { matomoTrackEvent } from '@/helpers/matomo-client';
const QUERY = gql`
query ModuleEditModeQuery($slug: String) {
@ -56,6 +57,11 @@ export default {
methods: {
toggle(newChecked) {
setModuleEditMode(this.slug, newChecked);
if (newChecked) {
matomoTrackEvent('Modul Anpassen', 'Eingeschaltet', this.slug);
} else {
matomoTrackEvent('Modul Anpassen', 'Ausgeschaltet', this.slug);
}
},
},
};

View File

@ -20,10 +20,12 @@
<script>
import me from '@/mixins/me';
import { TYPES, CONTENT_TYPE } from '@/consts/types';
import { CONTENT_TYPE, TYPES } from '@/consts/types';
import { createVisibilityMutation, hidden } from '@/helpers/visibility';
import { defineAsyncComponent } from 'vue';
import { matomoTrackModuleVisibilityEvent } from '@/helpers/matomo-client';
const EyeIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/EyeIcon'));
const ClosedEyeIcon = defineAsyncComponent(() =>
import(/* webpackChunkName: "icons" */ '@/components/icons/ClosedEyeIcon')
@ -76,6 +78,8 @@ export default {
mutation,
variables,
});
matomoTrackModuleVisibilityEvent(this.type, this.block, hidden);
},
},
};

View File

@ -4,7 +4,7 @@ const resizeElement = (el: HTMLElement) => {
};
export default {
update: resizeElement,
updated: resizeElement,
mounted: resizeElement,
created(el: HTMLElement) {
el.classList.add('skillbox-auto-grow');

View File

@ -4,6 +4,7 @@ import { typeDefs } from '@/graphql/typedefs';
import { resolvers } from '@/graphql/resolvers';
import cache from './cache';
import log from 'loglevel';
import * as Sentry from '@sentry/vue';
import { router } from '@/router';
@ -45,9 +46,11 @@ export default function(uri, networkErrorCallback) {
}
if (graphQLErrors) {
graphQLErrors.forEach(({ message, locations, path }) =>
log.warn(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`)
);
graphQLErrors.forEach(({ message, locations, path }) => {
const err = `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`;
log.warn(err);
Sentry.captureMessage(err);
});
}
/*

View File

@ -2,6 +2,9 @@
query RoomEntryQuery($slug: String!) {
roomEntry(slug: $slug) {
...RoomEntryParts
room {
slug
}
comments {
text
owner {

View File

@ -13,6 +13,7 @@ export const defaultFlavorValues: FlavorValues = {
textModules: 'Module',
textInstrument: 'Instrument',
textInstruments: 'Instrumente',
textInstrumentFilterShowAll: 'Alle anzeigen',
// mySkillbox flags
showFooter: true,
@ -36,6 +37,7 @@ export const myKvValues: FlavorValues = {
textModules: 'Lernfelder',
textInstrument: 'Grundlagenwissen',
textInstruments: 'Grundlagenwissen',
textInstrumentFilterShowAll: 'Alles anzeigen',
// myKV flags
showFooter: false,

View File

@ -12,7 +12,7 @@ const instrumentType = (instrument) => {
};
const instrumentCategory = (instrument) => {
return `${flavor.textInstruments} - ${instrumentType(instrument)}`;
return `${flavor.textInstruments} &ndash; ${instrumentType(instrument)}`;
};
export default instrumentType;

View File

@ -0,0 +1,107 @@
import log from 'loglevel';
import {
CHAPTER_DESCRIPTION_TYPE,
CHAPTER_TITLE_TYPE,
CONTENT_TYPE,
OBJECTIVE_GROUP_TYPE,
OBJECTIVE_TYPE,
} from '@/consts/types';
export type matomoUserRole = 'Student' | 'Teacher';
let matomoLastUrl = '';
let matomoLastUserId = '';
let matomoLastEventData = '';
declare global {
interface Window {
_paq: any[];
}
}
window._paq = window._paq || [];
export function matomoTrackPageView(absolutePath: string = '/', title?: string) {
// see https://developer.matomo.org/guides/spa-tracking for details
const url = window.location.origin + absolutePath;
if (matomoLastUrl !== url) {
// do not track the same url twice
log.debug('trackMatomoPageView', { url, matomoLastUrl, title });
window._paq.push(['setCustomUrl', url]);
window._paq.push(['trackPageView', title]);
matomoLastUrl = url;
}
}
export function matomoTrackEvent(category: string, action: string, name?: string, value?: number) {
const data = { category, action, name, value };
const dataJSON = JSON.stringify(data);
if (matomoLastEventData !== dataJSON) {
log.debug('matomoTrackEvent', { category, action, name, value });
// @ts-ignore
window._paq.push(['trackEvent', category, action, name, value]);
matomoLastEventData = dataJSON;
}
}
export function matomoTrackModuleVisibilityEvent(blockType: string, block: any, hidden: boolean) {
let eventAction = '';
let eventName = undefined;
switch (blockType) {
case CONTENT_TYPE:
eventAction = hidden ? 'Inhalt ausgeblendet' : 'Inhalt eingeblendet';
eventName = block.title;
break;
case OBJECTIVE_TYPE:
eventAction = hidden ? 'Lernziel ausgeblendet' : 'Lernziel eingeblendet';
eventName = block.text;
break;
case OBJECTIVE_GROUP_TYPE:
eventAction = hidden ? 'Lernzielgruppe ausgeblendet' : 'Lernzielgruppe eingeblendet';
eventName = block.displayTitle;
break;
case CHAPTER_TITLE_TYPE:
eventAction = hidden ? 'Kapitelüberschrift ausgeblendet' : 'Kapitelüberschrift eingeblendet';
eventName = block.title;
break;
case CHAPTER_DESCRIPTION_TYPE:
eventAction = hidden ? 'Kapitelbeschreibung ausgeblendet' : 'Kapitelbeschreibung eingeblendet';
eventName = block.title;
break;
}
matomoTrackEvent('Modul Anpassen - Sichtbarkeit', eventAction, eventName);
}
export function matomoTrackUser(userId: string, role: matomoUserRole = 'Student') {
// see https://developer.matomo.org/guides/tracking-javascript-guide#user-id for the process
if (matomoLastUserId !== userId) {
log.debug('matomoTrackUser', { userId, matomoLastUserId, role });
if (userId) {
// @ts-ignore
window._paq.push(['setUserId', userId]);
// @ts-ignore
window._paq.push(['setCustomDimension', 1, role]);
} else {
// @ts-ignore
window._paq.push(['deleteCustomDimension', 1]);
// User has just logged out, we reset the User ID
// @ts-ignore
window._paq.push(['resetUserId']);
// we also force a new visit to be created for the pageviews after logout
// @ts-ignore
window._paq.push(['appendToTrackingUrl', 'new_visit=1']);
// @ts-ignore
window._paq.push(['trackPageView']);
// we finally make sure to not again create a new visit afterwards (important for Single Page Applications)
// @ts-ignore
window._paq.push(['appendToTrackingUrl', '']);
}
matomoLastUserId = userId;
}
}

View File

@ -9,6 +9,7 @@ export interface FlavorValues {
textModules: string;
textInstrument: string;
textInstruments: string;
textInstrumentFilterShowAll: string;
showFooter: boolean;
showObjectivesTitle: boolean;
showInstrumentFilterSidebar: boolean;

View File

@ -1,6 +1,7 @@
import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
import { computed } from 'vue';
import { useQuery } from '@vue/apollo-composable';
import { matomoTrackPageView, matomoTrackUser } from '@/helpers/matomo-client';
export interface Me {
selectedClass: {
@ -110,7 +111,6 @@ const meMixin = {
return getTopicRoute(this.$data.me);
},
schoolClass(): any {
console.log('schoolClass legacy');
// @ts-ignore
return getSelectedClass(this.$data.me);
},
@ -137,6 +137,13 @@ const meMixin = {
// @ts-ignore
return this.$getRidOfEdges(data).me;
},
// @ts-ignore
result({ data }) {
const me = data.me || defaultMe.me;
if (me.id) {
matomoTrackUser(me.id ?? '', me.isTeacher ? 'Teacher' : 'Student');
}
},
},
},
};

View File

@ -39,6 +39,7 @@ import ADD_COMMENT_MUTATION from 'gql/mutations/addComment.gql';
import CommentInput from '@/components/rooms/CommentInput';
import Comment from '@/components/rooms/Comment';
import { defineAsyncComponent } from 'vue';
import { matomoTrackPageView } from '@/helpers/matomo-client';
const TextBlock = defineAsyncComponent(() =>
import(/* webpackChunkName: "content-components" */ '@/components/content-blocks/TextBlock')
);
@ -148,6 +149,9 @@ export default {
variables: {
slug: this.$route.params.slug,
},
result() {
matomoTrackPageView(`/room/${this.roomEntry.room.slug}/${this.roomEntry.slug}`);
},
};
},
},

View File

@ -9,6 +9,9 @@
<script>
import ModuleNavigation from '@/components/modules/ModuleNavigation.vue';
import ModuleSubNavigation from '@/components/modules/ModuleSubNavigation.vue';
import MODULE_DETAILS_QUERY from '@/graphql/gql/queries/modules/moduleDetailsQuery.gql';
import { matomoTrackPageView } from '@/helpers/matomo-client';
export default {
components: {
@ -16,6 +19,12 @@ export default {
ModuleSubNavigation,
},
data() {
return {
module: {},
};
},
computed: {
showNavigation() {
return !this.$route.meta.hideNavigation && !this.$route.meta.showSubNavigation;
@ -24,6 +33,21 @@ export default {
return !!this.$route.meta.showSubNavigation;
},
},
apollo: {
module() {
return {
query: MODULE_DETAILS_QUERY,
variables: {
slug: this.$route.params.slug,
},
result() {
matomoTrackPageView(`/topic/${this.module.topic.slug}/${this.module.slug}`);
},
fetchPolicy: 'cache-first',
};
},
},
};
</script>

View File

@ -36,6 +36,7 @@ import { isTeacher } from '@/helpers/is-teacher';
import { meQuery } from '@/graphql/queries';
import { defineAsyncComponent } from 'vue';
import { matomoTrackEvent } from '@/helpers/matomo-client';
const Solution = defineAsyncComponent(() =>
import(
/* webpackChunkName: "content-components"
@ -120,6 +121,7 @@ export default {
mounted() {
if (this.surveyData) {
this.loadSurveyFromServer(this.surveyData);
matomoTrackEvent('Übung', 'Übung angezeigt', this.id);
}
},
@ -172,42 +174,42 @@ export default {
data: JSON.stringify(data),
};
this.$apollo
.mutate({
mutation: UPDATE_ANSWER,
variables: {
input: {
answer,
this.$apollo.mutate({
mutation: UPDATE_ANSWER,
variables: {
input: {
answer,
},
},
update: (
store,
{
data: {
updateAnswer: { answer },
},
},
update: (
store,
{
data: {
updateAnswer: { answer },
},
}
) => {
const query = SURVEY_QUERY;
const variables = { id: this.id };
const { survey } = store.readQuery({ query, variables });
if (survey) {
const newData = {
// data is already in use in parent scope
survey: {
...survey,
answer,
},
};
store.writeQuery({ query, variables, data: newData });
}
},
})
.then(() => {
if (exit) {
this.$router.go(-1);
}
});
) => {
const query = SURVEY_QUERY;
const variables = { id: this.id };
const { survey } = store.readQuery({ query, variables });
if (survey) {
const newData = {
// data is already in use in parent scope
survey: {
...survey,
answer,
},
};
store.writeQuery({ query, variables, data: newData });
}
},
});
if (exit) {
matomoTrackEvent('Übung', 'Übung erfolgreich abgeschlossen', this.id);
this.$router.go(-1);
} else {
matomoTrackEvent('Übung', 'Übungsschritt abgeschlossen', this.id);
}
};
survey.onComplete.add((sender, options) => {

View File

@ -53,6 +53,7 @@ import TopicNavigation from '@/components/book-navigation/TopicNavigation';
import UPDATE_LAST_TOPIC_MUTATION from '@/graphql/gql/mutations/updateLastTopic.gql';
import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
const PlayIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/Play'));
const BulbIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/BulbIcon'));

View File

@ -15,6 +15,7 @@ export default [
public: true,
illustration: 'hello',
illustrationAlign: 'top',
matomoUrl: '/auth/hello',
},
},
{
@ -24,6 +25,7 @@ export default [
meta: {
layout: 'public',
public: true,
matomoUrl: '/auth/beta-login',
},
},
{
@ -45,6 +47,7 @@ export default [
meta: {
public: true,
layout: 'public',
matomoUrl: '/auth/verify-email',
},
},
{
@ -59,6 +62,7 @@ export default [
meta: {
public: true,
layout: 'public',
matomoUrl: '/auth/unknown-auth-error',
},
},
{
@ -67,6 +71,7 @@ export default [
name: LICENSE_ACTIVATION,
meta: {
layout: 'public',
matomoUrl: '/auth/license-activation',
},
},
];

View File

@ -13,6 +13,7 @@ import { EMAIL_NOT_VERIFIED_STATE, NO_VALID_LICENSE_STATE, SUCCESS_STATE } from
import start from '@/pages/start';
import { PAGE_LOAD_TIMEOUT } from '@/consts/navigation.consts';
import { matomoTrackPageView } from '@/helpers/matomo-client';
const instrument = () => import(/* webpackChunkName: "instruments" */ '@/pages/instrument');
const instrumentOverview = () => import(/* webpackChunkName: "instruments" */ '@/pages/instrumentOverview');
@ -36,6 +37,7 @@ const notFoundRoute = {
component: p404,
meta: {
layout: 'blank',
matomoUrl: '/not-found',
},
};
@ -44,6 +46,7 @@ const routes = [
path: '/',
name: 'home',
component: start,
meta: { matomoUrl: '/' },
},
...moduleRoutes,
...authRoutes,
@ -56,11 +59,28 @@ const routes = [
path: '/instruments/',
name: 'instrument-overview',
component: instrumentOverview,
meta: { matomoUrl: '/instrument/' },
},
{
path: '/instrument/:slug',
name: 'instrument',
component: instrument,
meta: { layout: LAYOUT_SIMPLE, matomoUrlCallback: (to) => `/instrument/${to.params.slug}` },
},
{ path: '/instrument/:slug', name: 'instrument', component: instrument, meta: { layout: LAYOUT_SIMPLE } },
{ path: '/submission/:id', name: 'submission', component: submission, meta: { layout: LAYOUT_SIMPLE } },
{ path: '/topic/:topicSlug', name: 'topic', component: topic, alias: '/book/topic/:topicSlug' },
{ path: '/join-class', name: 'join-class', component: joinClass, meta: { layout: LAYOUT_SIMPLE } },
{
path: '/topic/:topicSlug',
name: 'topic',
component: topic,
alias: '/book/topic/:topicSlug',
meta: { matomoUrlCallback: (to) => `/topic/${to.params.topicSlug}/` },
},
{
path: '/join-class',
name: 'join-class',
component: joinClass,
meta: { layout: LAYOUT_SIMPLE, matomoUrl: '/join-class' },
},
{
path: '/survey/:id',
component: surveyPage,
@ -72,6 +92,7 @@ const routes = [
path: '/news',
component: news,
name: 'news',
meta: { matomoUrl: '/news' },
},
{
path: '/oauth-redirect',
@ -126,4 +147,14 @@ router.afterEach(() => {
store.dispatch('showMobileNavigation', false);
});
router.afterEach((to) => {
if (to.meta.matomoUrl) {
matomoTrackPageView(to.meta.matomoUrl);
}
if (to.meta.matomoUrlCallback) {
matomoTrackPageView(to.meta.matomoUrlCallback(to));
}
});
export { router, postLoginRedirectUrlKey };

View File

@ -18,43 +18,48 @@ export default [
path: '/me',
component: profilePage,
children: [
{ path: 'profile', name: 'profile', component: profile, meta: { isProfile: true } },
{ path: 'class', alias: 'my-class', name: 'my-class', component: myClass, meta: { isProfile: true } },
{ path: 'activity', name: 'activity', component: activity, meta: { isProfile: true } },
{ path: '', name: 'profile-activity', component: activity, meta: { isProfile: true } },
{ path: 'profile', name: 'profile', component: profile, meta: { isProfile: true, matomoUrl: '/me/profile' } },
{ path: 'class', alias: 'my-class', name: 'my-class', component: myClass, meta: { isProfile: true, matomoUrl: '/me/class' } },
{ path: 'activity', name: 'activity', component: activity, meta: { isProfile: true, matomoUrl: '/me/activity' } },
{ path: '', name: 'profile-activity', component: activity, meta: { isProfile: true, matomoUrl: '/me/activity'} },
{
path: 'old-classes',
name: 'old-classes',
component: oldClasses,
meta: { isProfile: true },
meta: { isProfile: true, matomoUrl: '/me/old-classes' },
},
{
path: 'class/create',
alias: 'create-class',
name: 'create-class',
component: createClass,
meta: { layout: LAYOUT_SIMPLE },
meta: { layout: LAYOUT_SIMPLE, matomoUrl: '/me/class/create' },
},
{
path: 'class/code',
alias: 'show-code',
name: SHOW_SCHOOL_CLASS_CODE,
component: showSchoolClassCode,
meta: { layout: LAYOUT_SIMPLE },
meta: { layout: LAYOUT_SIMPLE, matomoUrl: '/me/class/code' },
},
{ path: 'team', name: MY_TEAM, component: myTeam, meta: { isProfile: true, matomoUrl: '/me/team' } },
{
path: 'team/join',
name: JOIN_TEAM,
component: joinTeam,
meta: { isProfile: true, layout: LAYOUT_SIMPLE, matomoUrl: '/me/team/join' }
},
{ path: 'team', name: MY_TEAM, component: myTeam, meta: { isProfile: true } },
{ path: 'team/join', name: JOIN_TEAM, component: joinTeam, meta: { isProfile: true, layout: LAYOUT_SIMPLE } },
{
path: 'team/create',
name: CREATE_TEAM,
component: createTeam,
meta: { isProfile: true, layout: LAYOUT_SIMPLE },
meta: { isProfile: true, layout: LAYOUT_SIMPLE, matomoUrl: '/me/team/create' }
},
{
path: 'team/code',
name: SHOW_TEAM_CODE,
component: showTeamCode,
meta: { layout: LAYOUT_SIMPLE },
meta: { layout: LAYOUT_SIMPLE, matomoUrl: '/me/team/code' }
},
],
},

View File

@ -18,6 +18,7 @@ export default [
meta: {
layout: 'split',
next: ONBOARDING_STEP_1,
matomoUrl: '/onboarding/start',
},
},
{
@ -28,6 +29,7 @@ export default [
layout: 'split',
next: ONBOARDING_STEP_2,
illustration: 'contents',
matomoUrl: '/onboarding/learning',
},
},
{
@ -38,6 +40,7 @@ export default [
layout: 'split',
next: ONBOARDING_STEP_3,
illustration: 'rooms',
matomoUrl: '/onboarding/collaboration',
},
},
{
@ -48,6 +51,7 @@ export default [
layout: 'split',
next: 'home',
illustration: 'portfolio',
matomoUrl: '/onboarding/portfolio',
},
},
],

View File

@ -7,10 +7,26 @@ const newProject = () => import(/* webpackChunkName: "portfolio" */ '@/pages/por
const editProject = () => import(/* webpackChunkName: "portfolio" */ '@/pages/portfolio/editProject');
const portfolioRoutes = [
{ path: '/portfolio', name: PROJECTS_PAGE, component: portfolio, meta: { hideFooter: true } },
{ path: '/portfolio/:slug', name: 'project', component: project, props: true },
{ path: '/new-project/', name: NEW_PROJECT_PAGE, component: newProject },
{ path: '/edit-project/:slug', name: 'edit-project', component: editProject, props: true },
{ path: '/portfolio', name: PROJECTS_PAGE, component: portfolio, meta: { hideFooter: true, matomoUrl: '/portfolio/' } },
{
path: '/portfolio/:slug',
name: 'project',
component: project,
props: true ,
meta: {
matomoUrlCallback: (route) => `/portfolio/${route.params.slug}`,
}
},
{ path: '/new-project/', name: NEW_PROJECT_PAGE, component: newProject, meta: { matomoUrl: '/portfolio/' } },
{
path: '/edit-project/:slug',
name: 'edit-project',
component: editProject,
props: true,
meta: {
matomoUrlCallback: (route) => `/portfolio/${route.params.slug}`,
}
},
];
const routes = flavorValues.showPortfolio ? portfolioRoutes : [];

View File

@ -16,24 +16,29 @@ const newRoomEntry = () => import(/* webpackChunkName: "rooms" */ '@/pages/rooms
const editRoomEntry = () => import(/* webpackChunkName: "rooms" */ '@/pages/rooms/editRoomEntry');
const moduleRoom = () => import(/* webpackChunkName: "rooms" */ '@/pages/module/moduleRoom');
function matomoRoomWithSlugCallback(route) {
return `/room/${route.params.slug}/`;
}
export default [
{ path: '/rooms', name: ROOMS_PAGE, component: rooms, meta: { filter: true, hideFooter: true } },
{ path: '/rooms', name: ROOMS_PAGE, component: rooms, meta: { filter: true, hideFooter: true, matomoUrl: '/room/' }},
{ path: '/new-room/', name: NEW_ROOM_PAGE, component: newRoom },
{ path: '/edit-room/:id', name: 'edit-room', component: editRoom, props: true },
{ path: '/room/:slug', name: ROOM_PAGE, component: room, props: true },
{ path: '/room/:slug/add', name: ADD_ROOM_ENTRY_PAGE, component: newRoomEntry, props: true },
{ path: '/room/:slug/edit/:entrySlug', name: UPDATE_ROOM_ENTRY_PAGE, component: editRoomEntry, props: true },
{ path: '/room/:slug', name: ROOM_PAGE, component: room, props: true, meta: { matomoUrlCallback: matomoRoomWithSlugCallback }},
{ path: '/room/:slug/add', name: ADD_ROOM_ENTRY_PAGE, component: newRoomEntry, props: true, meta: { matomoUrlCallback: matomoRoomWithSlugCallback }},
{ path: '/room/:slug/edit/:entrySlug', name: UPDATE_ROOM_ENTRY_PAGE, component: editRoomEntry, meta: { matomoUrlCallback: matomoRoomWithSlugCallback }},
{
path: '/module-room/:slug',
name: MODULE_ROOM_PAGE,
component: moduleRoom,
props: true,
meta: { layout: 'fullScreen' },
meta: { layout: 'fullScreen', matomoUrlCallback: matomoRoomWithSlugCallback },
},
{
path: '/module-room/:slug/add',
name: ADD_MODULE_ROOM_ENTRY_PAGE,
component: newRoomEntry,
props: (route) => ({ slug: route.params.slug, isModule: true }),
meta: { matomoUrlCallback: matomoRoomWithSlugCallback },
},
];

View File

@ -7,6 +7,7 @@ import { createApolloProvider } from '@vue/apollo-option';
import apolloClients from './apollo';
import flavorPlugin from '@/plugins/flavor';
import VueMatomo from 'vue-matomo';
import * as Sentry from '@sentry/vue';
const { publicApolloClient, privateApolloClient } = apolloClients;
@ -17,6 +18,13 @@ const apolloProvider = createApolloProvider({
defaultClient: privateApolloClient,
});
const registerPlugins = (app: any) => {
if (process.env.SENTRY_DSN) {
Sentry.init({
app,
dsn: process.env.SENTRY_DSN,
environment: process.env.SENTRY_ENVIRONMENT || 'localhost',
});
}
app.use(store);
app.use(VueModal);
app.use(VueRemoveEdges);
@ -25,10 +33,14 @@ const registerPlugins = (app: any) => {
app.use(router);
app.use(flavorPlugin);
if (process.env.MATOMO_HOST) {
// MS-628: we use VueMatomo "only" to setup the Matomo tracker
// we will not use any of the provided functions
app.use(VueMatomo, {
host: process.env.MATOMO_HOST,
siteId: process.env.MATOMO_SITE_ID,
router: router,
enableHeartBeatTimer: true,
// we don't want the default vue-matomo router behaviour
router: null,
});
}
};

View File

@ -82,6 +82,20 @@ button {
box-sizing: border-box;
}
sup,
sub {
font-size: toRem(14px);
line-height: normal;
}
sup {
vertical-align: super;
}
sub {
vertical-align: sub;
}
.small-emph {
font-size: toRem($base-font-size-pixels);
margin-bottom: 7.5px;

View File

@ -5,7 +5,7 @@ import logging
from graphene.types import Scalar
from graphene_django.converter import convert_django_field
from graphql_relay import to_global_id
from wagtail.core.fields import StreamField
from wagtail.fields import StreamField
from wagtail.documents.models import Document
from wagtail.images.models import Image

View File

@ -2,24 +2,31 @@ from django.conf import settings
from django.conf.urls import url
from django.urls import include
from django.views.decorators.csrf import csrf_exempt
from graphene_django.views import GraphQLView
from api.schema_public import schema
from core.views import PrivateGraphQLView
from core.views import SentryGraphQLView, PrivateGraphQLView
app_name = 'api'
app_name = "api"
urlpatterns = [
url(r'^graphql-public', csrf_exempt(GraphQLView.as_view(schema=schema))),
url(r'^graphql', csrf_exempt(PrivateGraphQLView.as_view())),
url(r"^graphql-public", csrf_exempt(SentryGraphQLView.as_view(schema=schema))),
url(r"^graphql", csrf_exempt(PrivateGraphQLView.as_view())),
# oauth
url(r'^oauth/', include('oauth.urls', namespace="oauth")),
url(r"^oauth/", include("oauth.urls", namespace="oauth")),
]
if settings.DEBUG:
urlpatterns += [url(r'^graphiql-public', csrf_exempt(GraphQLView.as_view(schema=schema, graphiql=True,
pretty=True)))]
urlpatterns += [url(r'^graphiql', csrf_exempt(PrivateGraphQLView.as_view(graphiql=True, pretty=True)))]
urlpatterns += [
url(
r"^graphiql-public",
csrf_exempt(
SentryGraphQLView.as_view(schema=schema, graphiql=True, pretty=True)
),
)
]
urlpatterns += [
url(
r"^graphiql",
csrf_exempt(PrivateGraphQLView.as_view(graphiql=True, pretty=True)),
)
]

View File

@ -1,7 +1,7 @@
# Generated by Django 2.2.19 on 2021-03-15 21:43
from django.db import migrations
import wagtail.core.fields
import wagtail.fields
class Migration(migrations.Migration):
@ -14,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='assignment',
name='solution',
field=wagtail.core.fields.RichTextField(blank=True, null=True),
field=wagtail.fields.RichTextField(blank=True, null=True),
),
]

View File

@ -1,7 +1,7 @@
# Generated by Django 3.2.13 on 2022-06-15 15:40
from django.db import migrations
import wagtail.core.fields
import wagtail.fields
class Migration(migrations.Migration):
@ -14,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='assignment',
name='assignment',
field=wagtail.core.fields.RichTextField(),
field=wagtail.fields.RichTextField(),
),
]

View File

@ -0,0 +1,21 @@
# Generated by Django 3.2.16 on 2023-04-12 14:01
from django.db import migrations
import django.db.models.deletion
import modelcluster.fields
class Migration(migrations.Migration):
dependencies = [
('books', '0042_alter_contentblock_contents'),
('assignments', '0016_alter_assignment_options'),
]
operations = [
migrations.AlterField(
model_name='assignment',
name='module',
field=modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.PROTECT, related_name='assignments', to='books.module'),
),
]

View File

@ -1,14 +1,15 @@
from django.contrib.auth import get_user_model
from django.db import models
from django_extensions.db.models import TimeStampedModel
from wagtail.admin.edit_handlers import FieldPanel
from wagtail.core.fields import RichTextField
from wagtail.admin.panels import FieldPanel
from wagtail.fields import RichTextField
from wagtail.snippets.models import register_snippet
from wagtailautocomplete.edit_handlers import AutocompletePanel
from wagtail.search import index
from core.constants import DEFAULT_RICH_TEXT_FEATURES
from django.utils.translation import ugettext_lazy as _
from modelcluster.fields import ParentalKey
@register_snippet
@ -17,53 +18,69 @@ class Assignment(index.Indexed, TimeStampedModel):
assignment = RichTextField(features=DEFAULT_RICH_TEXT_FEATURES)
solution = RichTextField(null=True, blank=True, features=DEFAULT_RICH_TEXT_FEATURES)
deleted = models.BooleanField(default=False)
owner = models.ForeignKey(get_user_model(),
on_delete=models.PROTECT, null=True,
blank=True) # probably don't want to delete all assignments if a user gets deleted
module = models.ForeignKey('books.Module', related_name='assignments', on_delete=models.CASCADE)
owner = models.ForeignKey(
get_user_model(), on_delete=models.PROTECT, null=True, blank=True
) # probably don't want to delete all assignments if a user gets deleted
module = ParentalKey(
"books.Module", related_name="assignments", on_delete=models.PROTECT
)
user_created = models.BooleanField(default=False)
taskbase_id = models.CharField(max_length=255, null=True, blank=True)
search_fields = [
index.SearchField('title', partial_match=True),
index.SearchField('assignment', partial_match=True),
index.SearchField("title", partial_match=True),
index.SearchField("assignment", partial_match=True),
]
panels = [
FieldPanel('title'),
FieldPanel('assignment'),
FieldPanel('solution'),
FieldPanel('module'),
AutocompletePanel('owner')
FieldPanel("title"),
FieldPanel("assignment"),
FieldPanel("solution"),
FieldPanel("module"),
AutocompletePanel("owner"),
]
def __str__(self):
return self.title if not self.user_created else '{} (erstellt von {})'.format(self.title, self.owner)
return (
self.title
if not self.user_created
else "{} (erstellt von {})".format(self.title, self.owner)
)
class Meta:
verbose_name = _('assignment')
verbose_name_plural = _('assignments')
ordering = ['-created']
verbose_name = _("assignment")
verbose_name_plural = _("assignments")
ordering = ["-created"]
class StudentSubmission(TimeStampedModel):
text = models.TextField(blank=True)
document = models.URLField(blank=True, default='', max_length=255)
assignment = models.ForeignKey(Assignment, on_delete=models.CASCADE, related_name='submissions')
student = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='submissions')
document = models.URLField(blank=True, default="", max_length=255)
assignment = models.ForeignKey(
Assignment, on_delete=models.CASCADE, related_name="submissions"
)
student = models.ForeignKey(
get_user_model(), on_delete=models.CASCADE, related_name="submissions"
)
final = models.BooleanField(default=False)
def __str__(self):
return '{} - {}'.format(self.student.full_name, self.text)
return "{} - {}".format(self.student.full_name, self.text)
class SubmissionFeedback(TimeStampedModel):
text = models.TextField(blank=True)
# teacher who created the feedback
teacher = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='feedbacks')
student_submission = models.OneToOneField(StudentSubmission, on_delete=models.CASCADE, primary_key=True,
related_name='submission_feedback')
teacher = models.ForeignKey(
get_user_model(), on_delete=models.CASCADE, related_name="feedbacks"
)
student_submission = models.OneToOneField(
StudentSubmission,
on_delete=models.CASCADE,
primary_key=True,
related_name="submission_feedback",
)
final = models.BooleanField(default=False)
def __str__(self):
return '{} - {}'.format(self.student_submission.student.full_name, self.text)
return "{} - {}".format(self.student_submission.student.full_name, self.text)

View File

@ -10,51 +10,54 @@
from graphql_relay import to_global_id
from api.test_utils import create_client, DefaultUserTestCase
from assignments.models import Assignment, StudentSubmission
from api.test_utils import create_client
from core.tests.base_test import SkillboxTestCase
from users.factories import SchoolClassFactory
from users.models import SchoolClassMember
from .queries.mutations import UPDATE_SUBMISSION_FEEDBACK_MUTATION
from ..factories import AssignmentFactory, StudentSubmissionFactory, SubmissionFeedbackFactory
from ..factories import (
AssignmentFactory,
StudentSubmissionFactory,
SubmissionFeedbackFactory,
)
class SubmissionFeedbackTestCase(SkillboxTestCase):
def setUp(self):
self.createDefault()
self.assignment = AssignmentFactory(
owner=self.teacher
)
self.assignment_id = to_global_id('AssignmentNode', self.assignment.pk)
self.assignment = AssignmentFactory(owner=self.teacher)
self.assignment_id = to_global_id("AssignmentNode", self.assignment.pk)
self.student_submission = StudentSubmissionFactory(assignment=self.assignment, student=self.student1,
final=False)
self.student_submission_id = to_global_id('StudentSubmissionNode', self.student_submission.pk)
self.student_submission = StudentSubmissionFactory(
assignment=self.assignment, student=self.student1, final=False
)
self.student_submission_id = to_global_id(
"StudentSubmissionNode", self.student_submission.pk
)
school_class = SchoolClassFactory()
for user in [self.student1, self.teacher]:
SchoolClassMember.objects.create(
user=user,
school_class=school_class
)
SchoolClassMember.objects.create(user=user, school_class=school_class)
user.set_selected_class(school_class)
def _create_submission_feedback(self, user, final, text, student_submission_id):
return self.get_client(user).execute(UPDATE_SUBMISSION_FEEDBACK_MUTATION, variables={
'input': {
"submissionFeedback": {
"studentSubmission": student_submission_id,
"text": text,
"final": final
return self.get_client(user).execute(
UPDATE_SUBMISSION_FEEDBACK_MUTATION,
variables={
"input": {
"submissionFeedback": {
"studentSubmission": student_submission_id,
"text": text,
"final": final,
}
}
}
})
},
)
def _fetch_assignment_student(self, user):
client = self.get_client(user)
query = '''
query = """
query AssignmentWithSubmissions($id: ID!) {
assignment(id: $id) {
title
@ -68,14 +71,12 @@ class SubmissionFeedbackTestCase(SkillboxTestCase):
}
}
}
'''
return client.execute(query, variables={
'id': self.assignment_id
})
"""
return client.execute(query, variables={"id": self.assignment_id})
def _fetch_assignment_teacher(self, user):
client = self.get_client(user)
query = '''
query = """
query AssignmentWithSubmissions($id: ID!) {
assignment(id: $id) {
title
@ -89,14 +90,12 @@ class SubmissionFeedbackTestCase(SkillboxTestCase):
}
}
}
'''
return client.execute(query, variables={
'id': self.assignment_id
})
"""
return client.execute(query, variables={"id": self.assignment_id})
def _fetch_submission_teacher(self, user):
client = self.get_client(user)
query = '''
query = """
query StudentSubmission($id: ID!) {
studentSubmission(id: $id) {
id
@ -107,14 +106,12 @@ class SubmissionFeedbackTestCase(SkillboxTestCase):
}
}
}
'''
return client.execute(query, variables={
'id': self.student_submission_id
})
"""
return client.execute(query, variables={"id": self.student_submission_id})
def _fetch_submission_feedback(self, user):
client = create_client(user)
query = '''
query = """
query AssignmentWithSubmissions($id: ID!) {
assignment(id: $id) {
title
@ -128,96 +125,136 @@ class SubmissionFeedbackTestCase(SkillboxTestCase):
}
}
}
'''
return client.execute(query, variables={
'id': self.assignment_id
})
"""
return client.execute(query, variables={"id": self.assignment_id})
def test_teacher_can_create_feedback(self):
result = self._create_submission_feedback(self.teacher, False, 'Balalal', self.student_submission_id)
result = self._create_submission_feedback(
self.teacher, False, "Balalal", self.student_submission_id
)
self.assertIsNone(result.errors)
self.assertIsNotNone(
result.data.get('updateSubmissionFeedback').get('updatedSubmissionFeedback').get('id'))
def test_student_cannot_create_feedback(self):
result = self._create_submission_feedback(self.student1, False, 'Balalal', self.student_submission_id)
self.assertIsNotNone(result.errors)
self.assertEqual(len(result.errors), 1)
self.assertEqual(result.errors[0].get('message'), 'Missing permissions')
def test_teacher_can_update_feedback(self):
assignment = AssignmentFactory(
owner=self.teacher
result.data.get("updateSubmissionFeedback")
.get("updatedSubmissionFeedback")
.get("id")
)
student_submission = StudentSubmissionFactory(assignment=assignment, student=self.student1, final=False)
submission_feedback = SubmissionFeedbackFactory(teacher=self.teacher, final=False,
student_submission=student_submission)
submission_feedback_id = to_global_id('SubmissionFeedback', submission_feedback.pk)
def test_student_cannot_create_feedback(self):
result = self._create_submission_feedback(
self.student1, False, "Balalal", self.student_submission_id
)
self.assertIsNotNone(result.errors)
self.assertEqual(len(result.errors), 1)
self.assertEqual(result.errors[0].get("message"), "Missing permissions")
result = self._create_submission_feedback(self.teacher, True, 'Some', submission_feedback_id)
def test_teacher_can_update_feedback(self):
assignment = AssignmentFactory(owner=self.teacher)
student_submission = StudentSubmissionFactory(
assignment=assignment, student=self.student1, final=False
)
submission_feedback = SubmissionFeedbackFactory(
teacher=self.teacher, final=False, student_submission=student_submission
)
submission_feedback_id = to_global_id(
"SubmissionFeedback", submission_feedback.pk
)
result = self._create_submission_feedback(
self.teacher, True, "Some", submission_feedback_id
)
self.assertIsNone(result.errors)
submission_feedback_response = result.data.get('updateSubmissionFeedback').get(
'updatedSubmissionFeedback')
self.assertTrue(submission_feedback_response.get('final'))
self.assertEqual(submission_feedback_response.get('text'), 'Some')
def test_external_teacher_cannot_update_feedback(self):
assignment = AssignmentFactory(
owner=self.teacher
submission_feedback_response = result.data.get("updateSubmissionFeedback").get(
"updatedSubmissionFeedback"
)
student_submission = StudentSubmissionFactory(assignment=assignment, student=self.student1, final=False)
submission_feedback = SubmissionFeedbackFactory(teacher=self.teacher, final=False,
student_submission=student_submission)
submission_feedback_id = to_global_id('SubmissionFeedback', submission_feedback.pk)
self.assertTrue(submission_feedback_response.get("final"))
self.assertEqual(submission_feedback_response.get("text"), "Some")
result = self._create_submission_feedback(self.teacher2, True, 'Some', submission_feedback_id)
def test_external_teacher_cannot_update_feedback(self):
assignment = AssignmentFactory(owner=self.teacher)
student_submission = StudentSubmissionFactory(
assignment=assignment, student=self.student1, final=False
)
submission_feedback = SubmissionFeedbackFactory(
teacher=self.teacher, final=False, student_submission=student_submission
)
submission_feedback_id = to_global_id(
"SubmissionFeedback", submission_feedback.pk
)
result = self._create_submission_feedback(
self.teacher2, True, "Some", submission_feedback_id
)
self.assertIsNotNone(result.errors)
def test_student_does_not_see_non_final_feedback(self):
SubmissionFeedbackFactory(teacher=self.teacher, final=False, student_submission=self.student_submission)
SubmissionFeedbackFactory(
teacher=self.teacher,
final=False,
student_submission=self.student_submission,
)
result = self._fetch_assignment_student(self.student1)
self.assertIsNone(result.data.get('submissionFeedback'))
self.assertIsNone(result.data.get("submissionFeedback"))
def test_student_does_see_final_feedback(self):
submission_feedback = SubmissionFeedbackFactory(teacher=self.teacher, final=True,
student_submission=self.student_submission)
submission_feedback = SubmissionFeedbackFactory(
teacher=self.teacher, final=True, student_submission=self.student_submission
)
result = self._fetch_assignment_student(self.student1)
self.assertEqual(result.data.get('assignment').get('submission').get('submissionFeedback')
.get('text'), submission_feedback.text)
self.assertEqual(
result.data.get("assignment")
.get("submission")
.get("submissionFeedback")
.get("text"),
submission_feedback.text,
)
def test_teacher_can_see_feedback_for_submission(self):
submission_feedback = SubmissionFeedbackFactory(teacher=self.teacher, final=False,
student_submission=self.student_submission)
submission_feedback = SubmissionFeedbackFactory(
teacher=self.teacher,
final=False,
student_submission=self.student_submission,
)
self.student_submission.final = True
self.student_submission.save()
result = self._fetch_assignment_teacher(self.teacher)
self.assertEqual(result.data.get('assignment').get('submissions')[0].get('submissionFeedback')
.get('text'), submission_feedback.text)
self.assertEqual(
result.data.get("assignment")
.get("submissions")[0]
.get("submissionFeedback")
.get("text"),
submission_feedback.text,
)
def test_external_teacher_cannot_see_assignment_with_feedback(self):
SubmissionFeedbackFactory(teacher=self.teacher, final=False,
student_submission=self.student_submission)
SubmissionFeedbackFactory(
teacher=self.teacher,
final=False,
student_submission=self.student_submission,
)
self.student_submission.final = True
self.student_submission.save()
result = self._fetch_assignment_teacher(self.teacher2)
self.assertEqual(result.data.get('assignment').get('submissions'), [])
self.assertEqual(result.data.get("assignment").get("submissions"), [])
def test_external_teacher_cannot_see_feedback(self):
SubmissionFeedbackFactory(teacher=self.teacher, final=False,
student_submission=self.student_submission)
SubmissionFeedbackFactory(
teacher=self.teacher,
final=False,
student_submission=self.student_submission,
)
self.student_submission.final = True
self.student_submission.save()
result = self._fetch_submission_teacher(self.teacher2)
self.assertIsNone(result.errors)
self.assertIsNone(result.data.get('studentSubmission'))
self.assertIsNone(result.data.get("studentSubmission"))

View File

@ -1,35 +1,23 @@
from django.conf import settings
import json
from django.test import TestCase, RequestFactory
from graphene.test import Client
from api import schema
from api.schema import schema
from api.test_utils import DefaultUserTestCase, create_client
from assignments.factories import AssignmentFactory, StudentSubmissionFactory
from assignments.models import Assignment
from books.factories import ModuleFactory
from books.models import ContentBlock, Chapter
from core.factories import UserFactory
from users.models import User
from users.services import create_users
class MyAssignmentsTest(DefaultUserTestCase):
def setUp(self):
super(MyAssignmentsTest, self).setUp()
self.assignment = AssignmentFactory(
owner=self.teacher
)
self.assignment = AssignmentFactory(owner=self.teacher)
self.submission1 = StudentSubmissionFactory(student=self.student1, assignment=self.assignment)
self.submission2 = StudentSubmissionFactory(student=self.student2, assignment=self.assignment)
self.submission1 = StudentSubmissionFactory(
student=self.student1, assignment=self.assignment
)
self.submission2 = StudentSubmissionFactory(
student=self.student2, assignment=self.assignment
)
self.client = create_client(self.student1)
def query_my_assignments(self):
query = '''
query = """
query MyActivityQuery {
myActivity {
edges {
@ -138,20 +126,27 @@ class MyAssignmentsTest(DefaultUserTestCase):
}
}
'''
"""
result = self.client.execute(query)
self.assertIsNone(result.get('errors'))
self.assertIsNone(result.get("errors"))
return result
@staticmethod
def get_content(result):
return result.get('data').get('myActivity').get('edges')
return result.get("data").get("myActivity").get("edges")
def test_my_assignment_query(self):
result = self.query_my_assignments()
contents = self.get_content(result)
self.assertEqual(len(contents), 1)
self.assertEquals(contents[0].get('node').get('mySubmissions').get('edges')[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,
)

View File

@ -3,9 +3,11 @@ from datetime import timedelta, date
from graphql_relay import to_global_id
from assignments.factories import AssignmentFactory, StudentSubmissionFactory
from assignments.tests.queries.mutations import UPDATE_ASSIGNMENT_MUTATION, UPDATE_SUBMISSION_FEEDBACK_MUTATION
from assignments.tests.queries.mutations import (
UPDATE_ASSIGNMENT_MUTATION,
UPDATE_SUBMISSION_FEEDBACK_MUTATION,
)
from core.tests.base_test import SkillboxTestCase
from users.models import User
class AssignmentReadOnlyTestCase(SkillboxTestCase):
@ -18,7 +20,7 @@ class AssignmentReadOnlyTestCase(SkillboxTestCase):
self.teacher.license_expiry_date = yesterday
self.teacher.save()
self.assignment = AssignmentFactory()
self.assignment_id = to_global_id('AssignmentNode', self.assignment.id)
self.assignment_id = to_global_id("AssignmentNode", self.assignment.id)
def test_edit_assignment_fails(self):
variables = {
@ -27,11 +29,13 @@ class AssignmentReadOnlyTestCase(SkillboxTestCase):
"answer": "bla",
"document": "",
"final": False,
"id": self.assignment_id
"id": self.assignment_id,
}
}
}
result = self.get_client(self.student1).execute(UPDATE_ASSIGNMENT_MUTATION, variables=variables)
result = self.get_client(self.student1).execute(
UPDATE_ASSIGNMENT_MUTATION, variables=variables
)
self.assertIsNotNone(result.errors)
def test_share_assignment_fails(self):
@ -41,42 +45,55 @@ class AssignmentReadOnlyTestCase(SkillboxTestCase):
"answer": "bla",
"document": "",
"final": True,
"id": self.assignment_id
"id": self.assignment_id,
}
}
}
result = self.get_client(self.student1).execute(UPDATE_ASSIGNMENT_MUTATION, variables=variables)
result = self.get_client(self.student1).execute(
UPDATE_ASSIGNMENT_MUTATION, variables=variables
)
self.assertIsNotNone(result.errors)
def test_edit_feedback_fails(self):
student_submission = StudentSubmissionFactory(assignment=self.assignment, student=self.student1,
final=True)
student_submission_id = to_global_id('StudentSubmissionNode', student_submission.id)
result = self.get_client(self.teacher).execute(UPDATE_SUBMISSION_FEEDBACK_MUTATION, variables={
'input': {
"submissionFeedback": {
"studentSubmission": student_submission_id,
"text": "Feedback",
"final": False
student_submission = StudentSubmissionFactory(
assignment=self.assignment, student=self.student1, final=True
)
student_submission_id = to_global_id(
"StudentSubmissionNode", student_submission.id
)
result = self.get_client(self.teacher).execute(
UPDATE_SUBMISSION_FEEDBACK_MUTATION,
variables={
"input": {
"submissionFeedback": {
"studentSubmission": student_submission_id,
"text": "Feedback",
"final": False,
}
}
}
})
},
)
self.assertIsNotNone(result.errors)
def test_share_feedback_fails(self):
student_submission = StudentSubmissionFactory(assignment=self.assignment, student=self.student1,
final=True)
student_submission_id = to_global_id('StudentSubmissionNode', student_submission.id)
result = self.get_client(self.teacher).execute(UPDATE_SUBMISSION_FEEDBACK_MUTATION, variables={
'input': {
"submissionFeedback": {
"studentSubmission": student_submission_id,
"text": "Feedback",
"final": True
student_submission = StudentSubmissionFactory(
assignment=self.assignment, student=self.student1, final=True
)
student_submission_id = to_global_id(
"StudentSubmissionNode", student_submission.id
)
result = self.get_client(self.teacher).execute(
UPDATE_SUBMISSION_FEEDBACK_MUTATION,
variables={
"input": {
"submissionFeedback": {
"studentSubmission": student_submission_id,
"text": "Feedback",
"final": True,
}
}
}
})
},
)
self.assertIsNotNone(result.errors)

View File

@ -2,8 +2,8 @@
from django.db import migrations, models
import django.db.models.deletion
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
@ -20,7 +20,7 @@ class Migration(migrations.Migration):
name='BasicKnowledge',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')),
('contents', wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock())])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())]))], blank=True, null=True)),
('contents', wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock())])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())]))], blank=True, null=True)),
('type', models.CharField(choices=[('language_communication', 'Sprache & Kommunikation'), ('society', 'Gesellschaft')], max_length=100)),
],
options={

View File

@ -1,8 +1,8 @@
# Generated by Django 2.0.6 on 2019-07-22 09:32
from django.db import migrations
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
@ -16,6 +16,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='basicknowledge',
name='contents',
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['bold', 'ul']))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('section_title', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())]))], blank=True, null=True),
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['bold', 'ul']))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('section_title', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())]))], blank=True, null=True),
),
]

View File

@ -1,8 +1,8 @@
# Generated by Django 2.0.6 on 2019-11-28 16:01
from django.db import migrations
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
@ -16,6 +16,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='basicknowledge',
name='contents',
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['bold', 'ul']))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('section_title', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())]))], blank=True, null=True),
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['bold', 'ul']))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('section_title', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())]))], blank=True, null=True),
),
]

View File

@ -1,8 +1,8 @@
# Generated by Django 2.1.15 on 2020-05-20 09:54
from django.db import migrations
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
@ -16,6 +16,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='basicknowledge',
name='contents',
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['bold', 'ul', 'brand', 'secondary']))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('section_title', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())]))], blank=True, null=True),
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['bold', 'ul', 'brand', 'secondary']))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('section_title', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())]))], blank=True, null=True),
),
]

View File

@ -1,7 +1,7 @@
# Generated by Django 2.2.12 on 2020-09-29 07:54
from django.db import migrations
import wagtail.core.fields
import wagtail.fields
class Migration(migrations.Migration):
@ -14,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='basicknowledge',
name='intro',
field=wagtail.core.fields.RichTextField(blank=True, default=''),
field=wagtail.fields.RichTextField(blank=True, default=''),
),
]

View File

@ -1,8 +1,8 @@
# Generated by Django 3.2.13 on 2022-07-28 08:48
from django.db import migrations
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.blocks
import wagtail.fields
import wagtail.documents.blocks
import wagtail.images.blocks
@ -17,6 +17,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='basicknowledge',
name='contents',
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['bold', 'ul', 'brand', 'secondary']))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('section_title', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('cms_document_block', wagtail.documents.blocks.DocumentChooserBlock())], blank=True, null=True),
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['bold', 'ul', 'brand', 'secondary']))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('section_title', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('cms_document_block', wagtail.documents.blocks.DocumentChooserBlock())], blank=True, null=True),
),
]

View File

@ -2,8 +2,8 @@
import books.blocks
from django.db import migrations
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
@ -29,6 +29,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='basicknowledge',
name='contents',
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['bold', 'ul', 'brand', 'secondary']))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('section_title', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('cms_document_block', books.blocks.CMSDocumentBlock())], blank=True, null=True),
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['bold', 'ul', 'brand', 'secondary']))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('section_title', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('cms_document_block', books.blocks.CMSDocumentBlock())], blank=True, null=True),
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 3.2.16 on 2023-02-21 16:04
import books.blocks
from django.db import migrations
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
class Migration(migrations.Migration):
dependencies = [
('basicknowledge', '0025_auto_20220914_1338'),
]
operations = [
migrations.AlterField(
model_name='basicknowledge',
name='contents',
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['bold', 'ul', 'brand', 'secondary']))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('section_title', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('cms_document_block', books.blocks.CMSDocumentBlock())], blank=True, null=True, use_json_field=True),
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 3.2.16 on 2023-04-12 14:01
import books.blocks
from django.db import migrations
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
class Migration(migrations.Migration):
dependencies = [
('basicknowledge', '0026_alter_basicknowledge_contents'),
]
operations = [
migrations.AlterField(
model_name='basicknowledge',
name='contents',
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold', 'subscript', 'superscript', 'brand', 'secondary']))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('section_title', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('cms_document_block', books.blocks.CMSDocumentBlock())], blank=True, null=True, use_json_field=True),
),
]

View File

@ -1,35 +1,44 @@
from django.db import models
from django.utils.text import slugify
from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel
from wagtail.core.fields import RichTextField, StreamField
from wagtail.admin.panels import FieldPanel
from wagtail.fields import RichTextField, StreamField
from wagtail.images.blocks import ImageChooserBlock
from books.blocks import CMSDocumentBlock, DocumentBlock, GeniallyBlock, InfogramBlock, InstrumentTextBlock, LinkBlock, \
SectionTitleBlock, \
SubtitleBlock, ThinglinkBlock, VideoBlock
from books.blocks import (
CMSDocumentBlock,
DocumentBlock,
GeniallyBlock,
InfogramBlock,
InstrumentTextBlock,
LinkBlock,
SectionTitleBlock,
SubtitleBlock,
ThinglinkBlock,
VideoBlock,
)
from core.constants import DEFAULT_RICH_TEXT_FEATURES
from core.wagtail_utils import StrictHierarchyPage
from django.utils.translation import ugettext_lazy as _
LANGUAGE_COMMUNICATION = 'language_communication'
SOCIETY = 'society'
INTERDISCIPLINARY = 'interdisciplinary'
LANGUAGE_COMMUNICATION_LABEL = 'Sprache & Kommunikation'
SOCIETY_LABEL = 'Gesellschaft'
INTERDISCIPLINARY_LABEL = 'Überfachliche Instrumente'
LANGUAGE_COMMUNICATION = "language_communication"
SOCIETY = "society"
INTERDISCIPLINARY = "interdisciplinary"
LANGUAGE_COMMUNICATION_LABEL = "Sprache & Kommunikation"
SOCIETY_LABEL = "Gesellschaft"
INTERDISCIPLINARY_LABEL = "Überfachliche Instrumente"
class InstrumentCategory(models.Model):
name = models.CharField(max_length=255, unique=True)
background = models.CharField('background color', max_length=7)
foreground = models.CharField('foreground color', max_length=7)
background = models.CharField("background color", max_length=7)
foreground = models.CharField("foreground color", max_length=7)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = _('instrument categories')
verbose_name = _('instrument category')
verbose_name_plural = _("instrument categories")
verbose_name = _("instrument category")
def default_category():
@ -38,8 +47,8 @@ def default_category():
class InstrumentType(models.Model):
class Meta:
verbose_name = _('instrument type')
verbose_name_plural = _('instrument types')
verbose_name = _("instrument type")
verbose_name_plural = _("instrument types")
CATEGORY_CHOICES = (
(LANGUAGE_COMMUNICATION, LANGUAGE_COMMUNICATION_LABEL),
@ -53,7 +62,7 @@ class InstrumentType(models.Model):
on_delete=models.PROTECT,
null=False,
default=default_category,
related_name='instrument_types'
related_name="instrument_types",
)
@property
@ -64,42 +73,46 @@ class InstrumentType(models.Model):
return self.type
class BasicKnowledge(StrictHierarchyPage):
class Meta:
verbose_name = _('instrument')
verbose_name_plural = _('instruments')
verbose_name = _("instrument")
verbose_name_plural = _("instruments")
parent_page_types = ['books.book']
parent_page_types = ["books.book"]
intro = RichTextField(features=DEFAULT_RICH_TEXT_FEATURES, default='', blank=True)
intro = RichTextField(
features=DEFAULT_RICH_TEXT_FEATURES, default="", blank=True)
contents = StreamField([
('text_block', InstrumentTextBlock()),
('image_block', ImageChooserBlock()),
('link_block', LinkBlock()),
('video_block', VideoBlock()),
('document_block', DocumentBlock()),
('section_title', SectionTitleBlock()),
('infogram_block', InfogramBlock()),
('genially_block', GeniallyBlock()),
('thinglink_block', ThinglinkBlock()),
('subtitle', SubtitleBlock()),
('cms_document_block', CMSDocumentBlock()),
], null=True, blank=True)
contents = StreamField(
[
("text_block", InstrumentTextBlock()),
("image_block", ImageChooserBlock()),
("link_block", LinkBlock()),
("video_block", VideoBlock()),
("document_block", DocumentBlock()),
("section_title", SectionTitleBlock()),
("infogram_block", InfogramBlock()),
("genially_block", GeniallyBlock()),
("thinglink_block", ThinglinkBlock()),
("subtitle", SubtitleBlock()),
("cms_document_block", CMSDocumentBlock()),
],
null=True,
blank=True,
use_json_field=True,
)
new_type = models.ForeignKey(InstrumentType, null=True, on_delete=models.PROTECT, related_name='instruments')
new_type = models.ForeignKey(
InstrumentType, null=True, on_delete=models.PROTECT, related_name="instruments"
)
old_type = models.CharField(
max_length=100,
choices=InstrumentType.CATEGORY_CHOICES,
blank=True
max_length=100, choices=InstrumentType.CATEGORY_CHOICES, blank=True
)
content_panels = [
FieldPanel('title', classname="full title"),
FieldPanel('new_type'),
FieldPanel('intro'),
StreamFieldPanel('contents')
FieldPanel("title", classname="full title"),
FieldPanel("new_type"),
FieldPanel("intro"),
FieldPanel("contents"),
]

View File

@ -1,5 +1,9 @@
from wagtail.contrib.modeladmin.options import ModelAdmin, ModelAdminGroup, modeladmin_register
from wagtail.core import hooks
from wagtail.contrib.modeladmin.options import (
ModelAdmin,
ModelAdminGroup,
modeladmin_register,
)
from wagtail import hooks
from .models import BasicKnowledge, InstrumentCategory, InstrumentType
from django.utils.translation import ugettext_lazy as _
@ -7,33 +11,45 @@ from core.logger import get_logger
logger = get_logger(__name__)
class InstrumentAdmin(ModelAdmin):
model = BasicKnowledge
list_display = ('title', 'new_type', 'status_string')
search_fields = ('title', 'new_type__name')
list_display = ("title", "new_type", "status_string")
search_fields = ("title", "new_type__name")
class InstrumentCategoryAdmin(ModelAdmin):
model = InstrumentCategory
list_display = ('name', 'background', 'foreground')
list_display = ("name", "background", "foreground")
class InstrumentTypeAdmin(ModelAdmin):
model = InstrumentType
list_display = ('name', 'category',)
list_display = (
"name",
"category",
)
inspect_view_enabled = True
inspect_view_fields = ('name', 'category', 'instruments',)
inspect_view_fields = (
"name",
"category",
"instruments",
)
class InstrumentGroup(ModelAdminGroup):
menu_label = _('Instruments')
items = (InstrumentAdmin, InstrumentTypeAdmin, InstrumentCategoryAdmin,)
menu_label = _("Instruments")
items = (
InstrumentAdmin,
InstrumentTypeAdmin,
InstrumentCategoryAdmin,
)
modeladmin_register(InstrumentGroup)
@hooks.register('construct_page_chooser_queryset')
@hooks.register("construct_page_chooser_queryset")
def order_by_created(pages, request):
logger.debug('constructing page chooser queryset')
return pages.live().order_by('-latest_revision_created_at')
logger.debug("constructing page chooser queryset")
return pages.all().order_by("-latest_revision_created_at")

View File

@ -1,4 +1,4 @@
from wagtail.core import blocks
from wagtail import blocks
from wagtail.documents.blocks import DocumentChooserBlock
from wagtail.snippets.blocks import SnippetChooserBlock
@ -6,16 +6,29 @@ from assignments.models import Assignment
from core.constants import DEFAULT_RICH_TEXT_FEATURES, INSTRUMENTS_RICH_TEXT_FEATURES
from surveys.models import Survey
"""
Using a StructBlock inside a StreamField (e.g. inside a ContentBlock):
as an illustration
data = {'text': 'This is me'}
self.contents.append(('solution', data))
by itself:
block = SolutionBlock()
data = {'text': 'This is me'}
value = block.to_python(data)
cleaned_value = block.clean(value)
"""
class CMSDocumentBlock(DocumentChooserBlock):
class Meta:
label = 'CMS Document'
label = "CMS Document"
# link_block
class LinkBlock(blocks.StructBlock):
class Meta:
icon = 'link'
icon = "link"
text = blocks.TextBlock()
url = blocks.URLBlock()
@ -24,14 +37,14 @@ class LinkBlock(blocks.StructBlock):
# 'text_block' 'solution'
class TextBlock(blocks.StructBlock):
class Meta:
icon = 'doc-full'
icon = "doc-full"
text = blocks.RichTextBlock(features=DEFAULT_RICH_TEXT_FEATURES)
class SolutionBlock(blocks.StructBlock):
class Meta:
icon = 'tick'
icon = "tick"
text = blocks.RichTextBlock(features=DEFAULT_RICH_TEXT_FEATURES)
document = CMSDocumentBlock(required=False)
@ -40,17 +53,19 @@ class SolutionBlock(blocks.StructBlock):
# 'basic_knowledge'
class BasicKnowledgeBlock(blocks.StructBlock):
class Meta:
icon = 'placeholder'
label = 'Instrument'
icon = "placeholder"
label = "Instrument"
description = blocks.RichTextBlock(required=False)
basic_knowledge = blocks.PageChooserBlock(required=True, page_type='basicknowledge.BasicKnowledge')
basic_knowledge = blocks.PageChooserBlock(
required=True, page_type="basicknowledge.BasicKnowledge"
)
# 'image_url'
class ImageUrlBlock(blocks.StructBlock):
class Meta:
icon = 'image'
icon = "image"
title = blocks.TextBlock()
url = blocks.URLBlock()
@ -59,7 +74,7 @@ class ImageUrlBlock(blocks.StructBlock):
# 'assignment'
class AssignmentBlock(blocks.StructBlock):
class Meta:
icon = 'download'
icon = "download"
assignment_id = SnippetChooserBlock(Assignment)
@ -67,7 +82,7 @@ class AssignmentBlock(blocks.StructBlock):
# 'survey'
class SurveyBlock(blocks.StructBlock):
class Meta:
icon = 'form'
icon = "form"
survey_id = SnippetChooserBlock(Survey)
@ -75,7 +90,7 @@ class SurveyBlock(blocks.StructBlock):
# 'video_block'
class VideoBlock(blocks.StructBlock):
class Meta:
icon = 'media'
icon = "media"
url = blocks.URLBlock()
@ -83,7 +98,7 @@ class VideoBlock(blocks.StructBlock):
# 'document_block'
class DocumentBlock(blocks.StructBlock):
class Meta:
icon = 'doc-full'
icon = "doc-full"
url = blocks.URLBlock()
@ -111,21 +126,21 @@ class SubtitleBlock(blocks.StructBlock):
class InstrumentTextBlock(blocks.StructBlock):
class Meta:
icon = 'doc-full'
icon = "doc-full"
text = blocks.RichTextBlock(features=INSTRUMENTS_RICH_TEXT_FEATURES)
class ModuleRoomSlugBlock(blocks.StructBlock):
class Meta:
icon = 'link'
icon = "link"
title = blocks.TextBlock()
class InstructionBlock(blocks.StructBlock):
class Meta:
icon = 'help'
icon = "help"
url = blocks.URLBlock(required=False)
text = blocks.TextBlock(required=False)

View File

@ -4,17 +4,43 @@ import factory
import wagtail_factories
from django.contrib.auth import get_user_model
from factory import CREATE_STRATEGY
from wagtail.core import blocks
from wagtail.core.models import Page, Site
from wagtail.core.rich_text import RichText
from wagtail import blocks
from wagtail.models import Page, Site
from wagtail.rich_text import RichText
from assignments.models import Assignment
from basicknowledge.models import BasicKnowledge, INTERDISCIPLINARY, INTERDISCIPLINARY_LABEL, InstrumentCategory, \
InstrumentType, \
LANGUAGE_COMMUNICATION, LANGUAGE_COMMUNICATION_LABEL, SOCIETY, SOCIETY_LABEL
from books.blocks import AssignmentBlock, BasicKnowledgeBlock, ImageUrlBlock, LinkBlock, VideoBlock
from basicknowledge.models import (
BasicKnowledge,
INTERDISCIPLINARY,
INTERDISCIPLINARY_LABEL,
InstrumentCategory,
InstrumentType,
LANGUAGE_COMMUNICATION,
LANGUAGE_COMMUNICATION_LABEL,
SOCIETY,
SOCIETY_LABEL,
)
from books.blocks import (
AssignmentBlock,
BasicKnowledgeBlock,
ImageUrlBlock,
LinkBlock,
SurveyBlock,
VideoBlock,
)
from books.models import Book, Chapter, ContentBlock, Module, TextBlock, Topic
from core.factories import BasePageFactory, DummyImageFactory, fake, fake_paragraph, fake_title
from core.factories import (
BasePageFactory,
DummyImageFactory,
fake,
fake_paragraph,
fake_title,
)
from core.logger import get_logger
from surveys.factories import SurveyFactory
from surveys.models import Survey
logger = get_logger(__name__)
class BookFactory(BasePageFactory):
@ -24,18 +50,25 @@ class BookFactory(BasePageFactory):
@staticmethod
def create_default_structure():
site = wagtail_factories.SiteFactory.create(is_default_site=True)
Page.objects.get(title='Root').delete()
Page.objects.get(title="Root").delete()
book = BookFactory.create(parent=site.root_page, title='A book')
topic = TopicFactory.create(parent=book, order=1, title='A topic')
module = ModuleFactory.create(parent=topic,
title="A module",
meta_title="Modul 1",
teaser="Whatever",
intro="<p>Hello</p>")
book = BookFactory.create(parent=site.root_page, title="A book")
topic = TopicFactory.create(parent=book, order=1, title="A topic")
module = ModuleFactory.create(
parent=topic,
title="A module",
meta_title="Modul 1",
teaser="Whatever",
intro="<p>Hello</p>",
)
chapter = ChapterFactory.create(parent=module, title="A chapter")
content_block = ContentBlockFactory.create(parent=chapter, module=module, title="A content block", type="task",
contents=[])
content_block = ContentBlockFactory.create(
parent=chapter,
module=module,
title="A content block",
type="task",
contents=[],
)
return book, topic, module, chapter, content_block
@ -45,7 +78,9 @@ class TopicFactory(BasePageFactory):
model = Topic
order = 0
teaser = factory.LazyAttribute(lambda x: fake.sentence(nb_words=random.randint(8, 12)))
teaser = factory.LazyAttribute(
lambda x: fake.sentence(nb_words=random.randint(8, 12))
)
description = factory.LazyAttribute(lambda x: fake.text(max_nb_chars=200))
@ -54,7 +89,9 @@ class ModuleFactory(BasePageFactory):
model = Module
meta_title = factory.LazyAttribute(lambda x: fake.text(max_nb_chars=20))
teaser = factory.LazyAttribute(lambda x: fake.sentence(nb_words=random.randint(8, 12)))
teaser = factory.LazyAttribute(
lambda x: fake.sentence(nb_words=random.randint(8, 12))
)
intro = factory.LazyAttribute(lambda x: fake.text(max_nb_chars=200))
hero_image = factory.SubFactory(DummyImageFactory)
@ -75,11 +112,14 @@ class TextBlockFactory(wagtail_factories.StructBlockFactory):
class InstrumentCategoryFactory(factory.DjangoModelFactory):
class Meta:
model = InstrumentCategory
django_get_or_create = ('name',)
django_get_or_create = ("name",)
name = factory.Iterator(
[LANGUAGE_COMMUNICATION_LABEL, SOCIETY_LABEL, INTERDISCIPLINARY_LABEL]
)
foreground = factory.Iterator(["FF0000", "FFFFFF", "000000"])
background = factory.Iterator(["FF0000", "FFFFFF", "000000"])
name = factory.Iterator([LANGUAGE_COMMUNICATION_LABEL, SOCIETY_LABEL, INTERDISCIPLINARY_LABEL])
foreground = factory.Iterator(['FF0000', 'FFFFFF', '000000'])
background = factory.Iterator(['FF0000', 'FFFFFF', '000000'])
class InstrumentTypeFactory(factory.DjangoModelFactory):
class Meta:
@ -99,7 +139,7 @@ class InstrumentFactory(BasePageFactory):
@classmethod
def _create(cls, model_class, *args, **kwargs):
kwargs['parent'] = Site.objects.get(is_default_site=True).root_page
kwargs["parent"] = Site.objects.get(is_default_site=True).root_page
return super()._create(model_class, *args, **kwargs)
@ -113,7 +153,7 @@ class BasicKnowledgeBlockFactory(wagtail_factories.StructBlockFactory):
class ImageUrlBlockFactory(wagtail_factories.StructBlockFactory):
title = fake_title()
url = factory.LazyAttribute(lambda x: 'https://picsum.photos/600/400/?random')
url = factory.LazyAttribute(lambda x: "https://picsum.photos/600/400/?random")
class Meta:
model = ImageUrlBlock
@ -121,127 +161,202 @@ class ImageUrlBlockFactory(wagtail_factories.StructBlockFactory):
class LinkBlockFactory(wagtail_factories.StructBlockFactory):
text = fake_title()
url = factory.LazyAttribute(lambda x: 'https://picsum.photos/600/400/?random')
url = factory.LazyAttribute(lambda x: "https://picsum.photos/600/400/?random")
class Meta:
model = LinkBlock
class AssignmentBlockFactory(wagtail_factories.StructBlockFactory):
class Meta:
model = AssignmentBlock
class EntityBlockFactory(wagtail_factories.StructBlockFactory):
@classmethod
def _build(cls, model_class, *args, **kwargs):
block = model_class()
return blocks.StructValue(
block,
# todo: build in a more generic fashion
[
(name, kwargs['assignment']) for name, child_block in block.child_blocks.items()
],
)
logger.debug(cls.id_key)
logger.debug(cls.entity_key)
logger.debug(kwargs)
value = block.to_python({cls.id_key: kwargs.get(cls.entity_key).id})
clean_value = block.clean(value)
return clean_value
class AssignmentBlockFactory(EntityBlockFactory):
class Meta:
model = AssignmentBlock
id_key = "assignment_id"
entity_key = "assignment"
class SurveyBlockFactory(EntityBlockFactory):
class Meta:
model = SurveyBlock
id_key = "survey_id"
entity_key = "survey"
class VideoBlockFactory(wagtail_factories.StructBlockFactory):
url = factory.LazyAttribute(lambda x: 'https://www.youtube.com/watch?v=lO9d-AJai8Q')
url = factory.LazyAttribute(lambda x: "https://www.youtube.com/watch?v=lO9d-AJai8Q")
class Meta:
model = VideoBlock
block_types = ['text_block', 'basic_knowledge', 'student_entry', 'image_url_block', 'solution']
block_types = [
"text_block",
"basic_knowledge",
"student_entry",
"image_url_block",
"solution",
]
class ContentBlockFactory(BasePageFactory):
class Meta:
model = ContentBlock
type = factory.LazyAttribute(lambda x: random.choice(['normal', 'instrument', 'task',]))
type = factory.LazyAttribute(
lambda x: random.choice(
[
"normal",
"instrument",
"task",
]
)
)
contents = wagtail_factories.StreamFieldFactory({
'text_block': TextBlockFactory,
'basic_knowledge': BasicKnowledgeBlockFactory,
'assignment': AssignmentBlockFactory,
'image_block': wagtail_factories.ImageChooserBlockFactory,
'image_url_block': ImageUrlBlockFactory,
'link_block': LinkBlockFactory,
'video_block': VideoBlockFactory,
'solution': TextBlockFactory
})
contents = wagtail_factories.StreamFieldFactory(
{
"text_block": TextBlockFactory,
"basic_knowledge": BasicKnowledgeBlockFactory,
"assignment": AssignmentBlockFactory,
"image_block": wagtail_factories.ImageChooserBlockFactory,
"image_url_block": ImageUrlBlockFactory,
"link_block": LinkBlockFactory,
"video_block": VideoBlockFactory,
"solution": TextBlockFactory,
"survey": SurveyBlockFactory,
}
)
@classmethod
def stream_field_magic(cls, module, kwargs, stream_field_name):
if stream_field_name in kwargs:
"""
"""
stream_field_name is most likely 'contents'
this means: if there is a property named contents, use the defined ones in this block.
otherwise, go into the other block and randomize the contents
"""
for idx, resource in enumerate(kwargs[stream_field_name]):
value = resource['value']
block_type = resource['type']
value = resource["value"]
block_type = resource["type"]
if block_type == 'assignment':
if block_type == "assignment":
user = get_user_model().objects.first()
assignment = Assignment.objects.create(
title=value['title'],
assignment=value['assignment'],
title=value["title"],
assignment=value["assignment"],
owner=user,
module=module
module=module,
)
kwargs['{}__{}__{}__{}'.format(stream_field_name, idx, block_type, 'assignment')] = assignment
kwargs[
"{}__{}__{}__{}".format(
stream_field_name, idx, block_type, "assignment"
)
] = assignment
elif block_type == "survey":
survey = Survey.objects.create(
title=value["title"], data=value["data"], module=module
)
kwargs[
"{}__{}__{}__{}".format(
stream_field_name, idx, block_type, "survey"
)
] = survey
else:
for jdx, field in enumerate(value):
if block_type == 'text_block':
kwargs['{}__{}__{}__{}'.format(stream_field_name, idx, block_type, field)] = RichText(
value[field])
elif block_type == 'solution':
kwargs['{}__{}__{}__{}'.format(stream_field_name, idx, block_type, field)] = RichText(
value[field])
elif block_type == 'basic_knowledge':
if field == 'description':
if block_type == "text_block":
kwargs[
"{}__{}__{}__{}".format(
stream_field_name, idx, block_type, field
)
] = RichText(value[field])
elif block_type == "solution":
kwargs[
"{}__{}__{}__{}".format(
stream_field_name, idx, block_type, field
)
] = RichText(value[field])
elif block_type == "basic_knowledge":
if field == "description":
kwargs[
'{}__{}__{}__{}'.format(stream_field_name, idx, block_type, field)] = RichText(
value[field])
"{}__{}__{}__{}".format(
stream_field_name, idx, block_type, field
)
] = RichText(value[field])
else:
kwargs[
'{}__{}__{}__{}'.format(stream_field_name, idx, block_type,
field)] = 'https://google.ch'
elif block_type == 'image_url_block':
"{}__{}__{}__{}".format(
stream_field_name, idx, block_type, field
)
] = "https://google.ch"
elif block_type == "image_url_block":
kwargs[
'{}__{}__{}__{}'.format(stream_field_name, idx, block_type, field)] = value[field]
"{}__{}__{}__{}".format(
stream_field_name, idx, block_type, field
)
] = value[field]
else:
kwargs[
'{}__{}__{}__{}'.format(stream_field_name, idx, block_type, field)] = value[field]
"{}__{}__{}__{}".format(
stream_field_name, idx, block_type, field
)
] = value[field]
del kwargs[stream_field_name]
else: # random contents from generator
for i in range(0, random.randint(3, 7)):
block_type = random.choice(block_types)
if block_type == 'text_block':
kwargs['{}__{}__{}__{}'.format(stream_field_name, i, 'text_block', 'text')] = RichText(
fake_paragraph())
elif block_type == 'basic_knowledge':
kwargs['{}__{}__{}__{}'.format(stream_field_name, i, 'basic_knowledge', 'description')] = RichText(
fake_paragraph())
elif block_type == 'assignment':
kwargs['{}__{}__{}__{}'.format(stream_field_name, i, 'assignment', 'task_text')] = RichText(
fake_paragraph())
elif block_type == 'image_url_block':
if block_type == "text_block":
kwargs[
'{}__{}__{}__{}'.format(stream_field_name, i, 'image_url_block', 'title')] = fake_paragraph()
"{}__{}__{}__{}".format(
stream_field_name, i, "text_block", "text"
)
] = RichText(fake_paragraph())
elif block_type == "basic_knowledge":
kwargs[
'{}__{}__{}__{}'.format(stream_field_name, i, 'image_url_block',
'url')] = 'https://picsum.photos/400/?random={}'.format(
''.join(random.choice('abcdefghiklmn') for _ in range(6)))
elif block_type == 'solution':
kwargs['{}__{}__{}__{}'.format(stream_field_name, i, 'solution', 'text')] = RichText(
fake_paragraph())
"{}__{}__{}__{}".format(
stream_field_name, i, "basic_knowledge", "description"
)
] = RichText(fake_paragraph())
elif block_type == "assignment":
kwargs[
"{}__{}__{}__{}".format(
stream_field_name, i, "assignment", "task_text"
)
] = RichText(fake_paragraph())
elif block_type == "image_url_block":
kwargs[
"{}__{}__{}__{}".format(
stream_field_name, i, "image_url_block", "title"
)
] = fake_paragraph()
kwargs[
"{}__{}__{}__{}".format(
stream_field_name, i, "image_url_block", "url"
)
] = "https://picsum.photos/400/?random={}".format(
"".join(random.choice("abcdefghiklmn") for _ in range(6))
)
elif block_type == "solution":
kwargs[
"{}__{}__{}__{}".format(
stream_field_name, i, "solution", "text"
)
] = RichText(fake_paragraph())
@classmethod
def create(cls, module, **kwargs):
cls.stream_field_magic(module, kwargs, 'contents')
cls.stream_field_magic(module, kwargs, "contents")
return cls._generate(CREATE_STRATEGY, kwargs)

View File

@ -1,4 +1,4 @@
from wagtail.core.models import PageManager
from wagtail.models import PageManager
from typing import TYPE_CHECKING
from core.logger import get_logger

View File

@ -2,8 +2,8 @@
from django.db import migrations, models
import django.db.models.deletion
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
@ -45,7 +45,7 @@ class Migration(migrations.Migration):
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')),
('user_created', models.BooleanField(default=False)),
('contents', wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock())])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.core.blocks.IntegerBlock())])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('task', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock())], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())]))], blank=True, null=True)),
('contents', wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock())])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock()), ('url', wagtail.blocks.URLBlock())])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.blocks.IntegerBlock())])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('task', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock())], icon='tick')), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())]))], blank=True, null=True)),
('type', models.CharField(choices=[('plain', 'Normal'), ('yellow', 'Gelb'), ('green', 'Grün'), ('blue', 'Blau')], default='plain', max_length=100)),
],
options={
@ -61,7 +61,7 @@ class Migration(migrations.Migration):
('order', models.PositiveIntegerField(help_text='Order of the module')),
('meta_title', models.CharField(help_text="e.g. 'Intro' or 'Modul 1'", max_length=255)),
('teaser', models.TextField()),
('intro', wagtail.core.fields.RichTextField()),
('intro', wagtail.fields.RichTextField()),
('hero_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.Image')),
],
options={
@ -76,7 +76,7 @@ class Migration(migrations.Migration):
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')),
('order', models.PositiveIntegerField(help_text='Order of the topic')),
('teaser', models.TextField()),
('description', wagtail.core.fields.RichTextField()),
('description', wagtail.fields.RichTextField()),
],
options={
'verbose_name': 'Thema',

View File

@ -1,8 +1,8 @@
# Generated by Django 2.0.6 on 2018-10-25 11:55
from django.db import migrations
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
@ -16,6 +16,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='contentblock',
name='contents',
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock())])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock()), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.core.blocks.IntegerBlock())])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('task', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock())], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())]))], blank=True, null=True),
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock())])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock()), ('basic_knowledge', wagtail.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.blocks.IntegerBlock())])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('task', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock())], icon='tick')), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())]))], blank=True, null=True),
),
]

View File

@ -2,8 +2,8 @@
import assignments.models
from django.db import migrations
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
import wagtail.snippets.blocks
@ -18,6 +18,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='contentblock',
name='contents',
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock())])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock()), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('task', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock())], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())]))], blank=True, null=True),
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock())])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock()), ('basic_knowledge', wagtail.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('task', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock())], icon='tick')), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())]))], blank=True, null=True),
),
]

View File

@ -3,8 +3,8 @@
import assignments.models
from django.conf import settings
from django.db import migrations, models
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
import wagtail.snippets.blocks
@ -25,6 +25,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='contentblock',
name='contents',
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock())])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock()), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock())], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())]))], blank=True, null=True),
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock())])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock()), ('basic_knowledge', wagtail.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock())], icon='tick')), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())]))], blank=True, null=True),
),
]

View File

@ -2,8 +2,8 @@
import assignments.models
from django.db import migrations
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
import wagtail.snippets.blocks
@ -18,6 +18,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='contentblock',
name='contents',
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock())])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock()), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock())], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())]))], blank=True, null=True),
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock())])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock()), ('basic_knowledge', wagtail.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock())], icon='tick')), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())]))], blank=True, null=True),
),
]

View File

@ -2,8 +2,8 @@
import assignments.models
from django.db import migrations, models
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
import wagtail.snippets.blocks
@ -18,7 +18,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='contentblock',
name='contents',
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock())])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock())], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())]))], blank=True, null=True),
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock())])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock())], icon='tick')), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())]))], blank=True, null=True),
),
migrations.AlterField(
model_name='contentblock',

View File

@ -2,8 +2,8 @@
import assignments.models
from django.db import migrations
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
import wagtail.snippets.blocks
@ -18,6 +18,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='contentblock',
name='contents',
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('content_list_item', wagtail.core.blocks.StreamBlock([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())]))]))], blank=True, null=True),
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul']))])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul']))], icon='tick')), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('content_list_item', wagtail.blocks.StreamBlock([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul']))])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul']))], icon='tick')), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())]))]))], blank=True, null=True),
),
]

View File

@ -3,8 +3,8 @@
import assignments.models
from django.db import migrations
import surveys.models
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
import wagtail.snippets.blocks
@ -19,6 +19,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='contentblock',
name='contents',
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.core.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('module_room_slug', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock())])), ('content_list_item', wagtail.core.blocks.StreamBlock([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.core.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('module_room_slug', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock())]))]))], blank=True, null=True),
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul']))])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul']))], icon='tick')), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('module_room_slug', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock())])), ('content_list_item', wagtail.blocks.StreamBlock([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul']))])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul']))], icon='tick')), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('module_room_slug', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock())]))]))], blank=True, null=True),
),
]

View File

@ -3,8 +3,8 @@
import assignments.models
from django.db import migrations
import surveys.models
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
import wagtail.snippets.blocks
@ -19,6 +19,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='contentblock',
name='contents',
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.core.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('module_room_slug', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock())])), ('content_list_item', wagtail.core.blocks.StreamBlock([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.core.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('module_room_slug', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock())]))]))], blank=True, null=True),
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul']))])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul']))], icon='tick')), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('module_room_slug', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock())])), ('content_list_item', wagtail.blocks.StreamBlock([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul']))])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul']))], icon='tick')), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('module_room_slug', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock())]))]))], blank=True, null=True),
),
]

View File

@ -3,8 +3,8 @@
import assignments.models
from django.db import migrations
import surveys.models
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
import wagtail.snippets.blocks
@ -19,6 +19,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='contentblock',
name='contents',
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.core.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('instruction', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock()), ('text', wagtail.core.blocks.TextBlock(required=False))])), ('module_room_slug', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock())])), ('content_list_item', wagtail.core.blocks.StreamBlock([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.core.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('instruction', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock()), ('text', wagtail.core.blocks.TextBlock(required=False))])), ('module_room_slug', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock())]))]))], blank=True, null=True),
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul']))])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul']))], icon='tick')), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('instruction', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock()), ('text', wagtail.blocks.TextBlock(required=False))])), ('module_room_slug', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock())])), ('content_list_item', wagtail.blocks.StreamBlock([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul']))])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul']))], icon='tick')), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('instruction', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock()), ('text', wagtail.blocks.TextBlock(required=False))])), ('module_room_slug', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock())]))]))], blank=True, null=True),
),
]

View File

@ -3,8 +3,8 @@
import assignments.models
from django.db import migrations
import surveys.models
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
import wagtail.snippets.blocks
@ -19,6 +19,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='contentblock',
name='contents',
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul', 'bold']))])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(page_type=['basicknowledge.BasicKnowledge'], required=True))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.core.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul', 'bold']))], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('instruction', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock()), ('text', wagtail.core.blocks.TextBlock(required=False))])), ('module_room_slug', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock())])), ('content_list_item', wagtail.core.blocks.StreamBlock([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul', 'bold']))])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(page_type=['basicknowledge.BasicKnowledge'], required=True))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.core.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul', 'bold']))], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('instruction', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock()), ('text', wagtail.core.blocks.TextBlock(required=False))])), ('module_room_slug', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock())]))]))], blank=True, null=True),
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold']))])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.blocks.PageChooserBlock(page_type=['basicknowledge.BasicKnowledge'], required=True))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold']))], icon='tick')), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('instruction', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock()), ('text', wagtail.blocks.TextBlock(required=False))])), ('module_room_slug', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock())])), ('content_list_item', wagtail.blocks.StreamBlock([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold']))])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.blocks.PageChooserBlock(page_type=['basicknowledge.BasicKnowledge'], required=True))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold']))], icon='tick')), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('instruction', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock()), ('text', wagtail.blocks.TextBlock(required=False))])), ('module_room_slug', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock())]))]))], blank=True, null=True),
),
]

View File

@ -6,9 +6,9 @@ from django.db import migrations, models
import django.db.models.deletion
import surveys.models
import taggit.managers
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.core.models.collections
import wagtail.blocks
import wagtail.fields
import wagtail.models.collections
import wagtail.documents.blocks
import wagtail.images.blocks
import wagtail.search.index
@ -28,7 +28,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='contentblock',
name='contents',
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul', 'bold']))])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(page_type=['basicknowledge.BasicKnowledge'], required=True))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.core.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul', 'bold']))], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('instruction', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock()), ('text', wagtail.core.blocks.TextBlock(required=False))])), ('module_room_slug', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock())])), ('cms_document_block', wagtail.documents.blocks.DocumentChooserBlock()), ('content_list_item', wagtail.core.blocks.StreamBlock([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul', 'bold']))])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(page_type=['basicknowledge.BasicKnowledge'], required=True))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.core.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul', 'bold']))], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('instruction', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock()), ('text', wagtail.core.blocks.TextBlock(required=False))])), ('module_room_slug', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock())])), ('cms_document_block', wagtail.documents.blocks.DocumentChooserBlock())]))], blank=True, null=True),
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold']))])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.blocks.PageChooserBlock(page_type=['basicknowledge.BasicKnowledge'], required=True))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold']))], icon='tick')), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('instruction', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock()), ('text', wagtail.blocks.TextBlock(required=False))])), ('module_room_slug', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock())])), ('cms_document_block', wagtail.documents.blocks.DocumentChooserBlock()), ('content_list_item', wagtail.blocks.StreamBlock([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold']))])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.blocks.PageChooserBlock(page_type=['basicknowledge.BasicKnowledge'], required=True))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold']))], icon='tick')), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('instruction', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock()), ('text', wagtail.blocks.TextBlock(required=False))])), ('module_room_slug', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock())])), ('cms_document_block', wagtail.documents.blocks.DocumentChooserBlock())]))], blank=True, null=True),
),
migrations.CreateModel(
name='CustomDocument',
@ -40,7 +40,7 @@ class Migration(migrations.Migration):
('file_size', models.PositiveIntegerField(editable=False, null=True)),
('file_hash', models.CharField(blank=True, editable=False, max_length=40)),
('display_text', models.CharField(default='', max_length=1024)),
('collection', models.ForeignKey(default=wagtail.core.models.collections.get_root_collection_id, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='wagtailcore.collection', verbose_name='collection')),
('collection', models.ForeignKey(default=wagtail.models.collections.get_root_collection_id, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='wagtailcore.collection', verbose_name='collection')),
('tags', taggit.managers.TaggableManager(blank=True, help_text=None, through='taggit.TaggedItem', to='taggit.Tag', verbose_name='tags')),
('uploaded_by_user', models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='uploaded by user')),
],

View File

@ -4,8 +4,8 @@ import assignments.models
import books.blocks
from django.db import migrations
import surveys.models
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.blocks
import wagtail.fields
import wagtail.documents.blocks
import wagtail.images.blocks
import wagtail.snippets.blocks
@ -21,6 +21,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='contentblock',
name='contents',
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul', 'bold']))])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(page_type=['basicknowledge.BasicKnowledge'], required=True))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.core.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul', 'bold'])), ('document', books.blocks.CMSDocumentBlock(required=False))])), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('instruction', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock(required=False)), ('text', wagtail.core.blocks.TextBlock(required=False)), ('document', wagtail.documents.blocks.DocumentChooserBlock(required=False))])), ('module_room_slug', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock())])), ('cms_document_block', books.blocks.CMSDocumentBlock()), ('content_list_item', wagtail.core.blocks.StreamBlock([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul', 'bold']))])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(page_type=['basicknowledge.BasicKnowledge'], required=True))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.core.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul', 'bold'])), ('document', books.blocks.CMSDocumentBlock(required=False))])), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('instruction', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock(required=False)), ('text', wagtail.core.blocks.TextBlock(required=False)), ('document', wagtail.documents.blocks.DocumentChooserBlock(required=False))])), ('module_room_slug', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock())])), ('cms_document_block', books.blocks.CMSDocumentBlock())]))], blank=True, null=True),
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold']))])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.blocks.PageChooserBlock(page_type=['basicknowledge.BasicKnowledge'], required=True))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold'])), ('document', books.blocks.CMSDocumentBlock(required=False))])), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('instruction', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock(required=False)), ('text', wagtail.blocks.TextBlock(required=False)), ('document', wagtail.documents.blocks.DocumentChooserBlock(required=False))])), ('module_room_slug', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock())])), ('cms_document_block', books.blocks.CMSDocumentBlock()), ('content_list_item', wagtail.blocks.StreamBlock([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold']))])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.blocks.PageChooserBlock(page_type=['basicknowledge.BasicKnowledge'], required=True))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold'])), ('document', books.blocks.CMSDocumentBlock(required=False))])), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('instruction', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock(required=False)), ('text', wagtail.blocks.TextBlock(required=False)), ('document', wagtail.documents.blocks.DocumentChooserBlock(required=False))])), ('module_room_slug', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock())])), ('cms_document_block', books.blocks.CMSDocumentBlock())]))], blank=True, null=True),
),
]

View File

@ -0,0 +1,26 @@
# Generated by Django 3.2.16 on 2023-02-21 16:04
import assignments.models
import books.blocks
from django.db import migrations
import surveys.models
import wagtail.blocks
import wagtail.documents.blocks
import wagtail.fields
import wagtail.images.blocks
import wagtail.snippets.blocks
class Migration(migrations.Migration):
dependencies = [
('books', '0040_module_hero_source'),
]
operations = [
migrations.AlterField(
model_name='contentblock',
name='contents',
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold']))])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.blocks.PageChooserBlock(page_type=['basicknowledge.BasicKnowledge'], required=True))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold'])), ('document', books.blocks.CMSDocumentBlock(required=False))])), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('instruction', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock(required=False)), ('text', wagtail.blocks.TextBlock(required=False)), ('document', wagtail.documents.blocks.DocumentChooserBlock(required=False))])), ('module_room_slug', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock())])), ('cms_document_block', books.blocks.CMSDocumentBlock()), ('content_list_item', wagtail.blocks.StreamBlock([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold']))])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.blocks.PageChooserBlock(page_type=['basicknowledge.BasicKnowledge'], required=True))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold'])), ('document', books.blocks.CMSDocumentBlock(required=False))])), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('instruction', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock(required=False)), ('text', wagtail.blocks.TextBlock(required=False)), ('document', wagtail.documents.blocks.DocumentChooserBlock(required=False))])), ('module_room_slug', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock())])), ('cms_document_block', books.blocks.CMSDocumentBlock())]))], blank=True, null=True, use_json_field=True),
),
]

View File

@ -0,0 +1,26 @@
# Generated by Django 3.2.16 on 2023-04-12 14:01
import assignments.models
import books.blocks
from django.db import migrations
import surveys.models
import wagtail.blocks
import wagtail.documents.blocks
import wagtail.fields
import wagtail.images.blocks
import wagtail.snippets.blocks
class Migration(migrations.Migration):
dependencies = [
('books', '0041_alter_contentblock_contents'),
]
operations = [
migrations.AlterField(
model_name='contentblock',
name='contents',
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold', 'subscript', 'superscript']))])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.blocks.PageChooserBlock(page_type=['basicknowledge.BasicKnowledge'], required=True))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold', 'subscript', 'superscript'])), ('document', books.blocks.CMSDocumentBlock(required=False))])), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('instruction', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock(required=False)), ('text', wagtail.blocks.TextBlock(required=False)), ('document', wagtail.documents.blocks.DocumentChooserBlock(required=False))])), ('module_room_slug', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock())])), ('cms_document_block', books.blocks.CMSDocumentBlock()), ('content_list_item', wagtail.blocks.StreamBlock([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold', 'subscript', 'superscript']))])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.blocks.PageChooserBlock(page_type=['basicknowledge.BasicKnowledge'], required=True))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold', 'subscript', 'superscript'])), ('document', books.blocks.CMSDocumentBlock(required=False))])), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('instruction', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock(required=False)), ('text', wagtail.blocks.TextBlock(required=False)), ('document', wagtail.documents.blocks.DocumentChooserBlock(required=False))])), ('module_room_slug', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock())])), ('cms_document_block', books.blocks.CMSDocumentBlock())]))], blank=True, null=True, use_json_field=True),
),
]

View File

@ -1,6 +1,6 @@
import logging
from wagtail.admin.edit_handlers import FieldPanel, TabbedInterface, ObjectList
from wagtail.admin.panels import FieldPanel, TabbedInterface, ObjectList
from core.wagtail_utils import StrictHierarchyPage, get_default_settings

View File

@ -1,7 +1,7 @@
import logging
from django.db import models
from wagtail.admin.edit_handlers import FieldPanel, TabbedInterface, ObjectList
from wagtail.admin.panels import FieldPanel, TabbedInterface, ObjectList
from core.wagtail_utils import StrictHierarchyPage, get_default_settings
from users.models import SchoolClass
@ -53,8 +53,7 @@ class Chapter(StrictHierarchyPage, GraphqlNodeMixin):
def sync_description_visibility(self, school_class_template, school_class_to_sync):
if (
self.description_hidden_for.filter(
id=school_class_template.id).exists()
self.description_hidden_for.filter(id=school_class_template.id).exists()
and not self.description_hidden_for.filter(
id=school_class_to_sync.id
).exists()
@ -62,8 +61,7 @@ class Chapter(StrictHierarchyPage, GraphqlNodeMixin):
self.description_hidden_for.add(school_class_to_sync)
if (
self.description_hidden_for.filter(
id=school_class_to_sync.id).exists()
self.description_hidden_for.filter(id=school_class_to_sync.id).exists()
and not self.description_hidden_for.filter(
id=school_class_template.id
).exists()

View File

@ -1,17 +1,15 @@
import logging
from django.db import models
from wagtail.admin.edit_handlers import (
from wagtail.admin.panels import (
FieldPanel,
TabbedInterface,
ObjectList,
StreamFieldPanel,
)
from wagtail.core.blocks import StreamBlock
from wagtail.core.fields import StreamField
from wagtail.blocks import StreamBlock
from wagtail.fields import StreamField
from wagtail.images.blocks import ImageChooserBlock
from books.managers import ContentBlockManager
from core.logger import get_logger
from core.wagtail_utils import get_default_settings
from books.blocks import (
CMSDocumentBlock,
@ -31,14 +29,50 @@ from books.blocks import (
ThinglinkBlock,
InstructionBlock,
)
from books.utils import get_type_and_value
from core.wagtail_utils import StrictHierarchyPage
from notes.models import ContentBlockBookmark
from surveys.models import Survey
from users.models import SchoolClass, User
from core.mixins import GraphqlNodeMixin
logger = logging.getLogger(__name__)
logger = get_logger(__name__)
def duplicate_entities_generator(new_module):
def duplicate_entities(block):
content_type = block.block_type
value = block.value
# logger.debug(block)
if content_type == "assignment":
assignment = value.get("assignment_id")
if assignment is None:
return None
# copy the assignment
assignment.pk = None
assignment.title = f"{assignment.title} (Kopie)"
assignment.module = new_module
# logger.debug(f"setting new module {new_module}, {assignment.module}")
assignment.save()
data = {"assignment_id": assignment}
new_block = ("assignment", data)
return new_block
if content_type == "survey":
# logger.debug(value)
survey = value.get("survey_id")
if survey is None:
return None
# copy the survey
survey.pk = None
survey.title = f"{survey.title} (Kopie)"
survey.module = new_module
# logger.debug(f"setting new module {new_module}, {survey.module}")
survey.save()
data = {"survey_id": survey}
new_block = ("survey", data)
# logger.debug(new_block)
return new_block
return block
return duplicate_entities
class ContentBlock(StrictHierarchyPage, GraphqlNodeMixin):
@ -100,15 +134,15 @@ class ContentBlock(StrictHierarchyPage, GraphqlNodeMixin):
content_blocks + [("content_list_item", content_list_item)],
null=True,
blank=True,
use_json_field=True,
)
type = models.CharField(
max_length=100, choices=TYPE_CHOICES, default=NORMAL)
type = models.CharField(max_length=100, choices=TYPE_CHOICES, default=NORMAL)
content_panels = [
FieldPanel("title", classname="full title"),
FieldPanel("type"),
StreamFieldPanel("contents"),
FieldPanel("contents"),
]
#
@ -127,6 +161,23 @@ class ContentBlock(StrictHierarchyPage, GraphqlNodeMixin):
def module(self):
return self.get_parent().get_parent().specific
# duplicate all attached Surveys and Assignments
def duplicate_attached_entities(self):
duplicate_entities_func = duplicate_entities_generator(self.module)
new_contents = [duplicate_entities_func(content) for content in self.contents]
cleaned_contents = [item for item in new_contents if item is not None]
# we can't just insert a list here, we need a StreamValue data type
# so we need to clear the list, then add each element in turn
self.contents.clear()
# like this, the internal methods of the SteamValue data type can work on the single elements
for content in cleaned_contents:
self.contents.append(content)
# as an illustration
# data = {'text': 'This is me'}
# self.contents.append(('solution', data))
self.save()
def is_hidden_for_class(self, school_class):
return (
not self.user_created
@ -136,19 +187,38 @@ class ContentBlock(StrictHierarchyPage, GraphqlNodeMixin):
and not self.visible_for.filter(id=school_class.id).exists()
)
def save(self, *args, **kwargs):
for data in self.contents.raw_data:
block_type, value = get_type_and_value(data)
if block_type == "survey":
module = self.module
survey = value["survey_id"]
if isinstance(survey, int):
survey = Survey.objects.get(pk=survey)
def reassign_entities(self):
module = self.module
logger.debug("reassigning entities")
for content in self.contents:
if content.block_type == "assignment":
assignment = content.value.get("assignment_id")
logger.debug(assignment.module)
if assignment.module != module:
assignment.module = module
assignment.save()
if content.block_type == "survey":
survey = content.value.get("survey_id")
logger.debug(survey.module)
if survey.module != module:
survey.module = module
survey.save()
super().save(*args, **kwargs)
# def save(self, *args, **kwargs):
# todo: move this to the after_create_page and after_edit_page hooks, and remove from here.
# for data in self.contents.raw_data:
# block_type, value = get_type_and_value(data)
# todo: also do the same for assignments
# if block_type == "survey":
# module = self.module
# survey = value["survey_id"]
# if isinstance(survey, int):
# survey = Survey.objects.get(pk=survey)
# if survey.module != module:
# survey.module = module
# survey.save()
# super().save(*args, **kwargs)
class ContentBlockSnapshot(ContentBlock):

View File

@ -1,8 +1,8 @@
from django.db import models
from django.utils import timezone
from wagtail.admin.edit_handlers import FieldPanel, TabbedInterface, ObjectList
from wagtail.core.fields import RichTextField
from wagtail.images.edit_handlers import ImageChooserPanel
from wagtail.admin.panels import FieldPanel, InlinePanel, TabbedInterface, ObjectList
from wagtail.fields import RichTextField
from django.utils.translation import ugettext_lazy as _
from core.constants import DEFAULT_RICH_TEXT_FEATURES
from core.wagtail_utils import StrictHierarchyPage, get_default_settings
@ -14,8 +14,7 @@ class Module(StrictHierarchyPage):
verbose_name = "Modul"
verbose_name_plural = "Module"
meta_title = models.CharField(
max_length=255, help_text="e.g. 'Intro' or 'Modul 1'")
meta_title = models.CharField(max_length=255, help_text="e.g. 'Intro' or 'Modul 1'")
hero_image = models.ForeignKey(
"wagtailimages.Image",
null=True,
@ -35,10 +34,30 @@ class Module(StrictHierarchyPage):
content_panels = [
FieldPanel("title", classname="full title"),
FieldPanel("meta_title", classname="full title"),
ImageChooserPanel("hero_image"),
FieldPanel("hero_image"),
FieldPanel("hero_source"),
FieldPanel("teaser"),
FieldPanel("intro"),
InlinePanel(
"assignments",
label=_("Assignment"),
classname="collapsed",
heading=_("linked assignments"),
help_text=_(
"These %s are automatically linked, they are shown here only to provide an overview. Please don't change anything here."
)
% _("assignments"),
),
InlinePanel(
"surveys",
heading=_("linked surveys"),
label=_("Survey"),
classname="collapsed",
help_text=_(
"These %s are automatically linked, they are shown here only to provide an overview. Please don't change anything here."
)
% _("surveys"),
),
]
edit_handler = TabbedInterface(
@ -93,8 +112,7 @@ class Module(StrictHierarchyPage):
content_block.visible_for.add(school_class_to_sync)
for chapter in chapters:
chapter.sync_title_visibility(
school_class_template, school_class_to_sync)
chapter.sync_title_visibility(school_class_template, school_class_to_sync)
chapter.sync_description_visibility(
school_class_template, school_class_to_sync
)
@ -102,8 +120,7 @@ class Module(StrictHierarchyPage):
objective_groups = self.objective_groups.all()
for objective_group in objective_groups:
objective_group.sync_visibility(
school_class_template, school_class_to_sync)
objective_group.sync_visibility(school_class_template, school_class_to_sync)
def get_admin_display_title(self):
return f"{self.meta_title} - {self.title}"

View File

@ -1,8 +1,8 @@
import logging
from django.db import models
from wagtail.admin.edit_handlers import FieldPanel, TabbedInterface, ObjectList
from wagtail.core.fields import RichTextField
from wagtail.admin.panels import FieldPanel, TabbedInterface, ObjectList
from wagtail.fields import RichTextField
from core.constants import DEFAULT_RICH_TEXT_FEATURES
from core.wagtail_utils import StrictHierarchyPage, get_default_settings

View File

@ -13,7 +13,7 @@ import re
from typing import List, Union
from wagtail.core.blocks import StreamValue
from wagtail.blocks import StreamValue
from api.utils import get_object
from assignments.models import Assignment

View File

@ -1,6 +1,6 @@
from graphql_relay import to_global_id
from wagtail.core.fields import StreamField
from wagtail.tests.utils.form_data import streamfield, nested_form_data, rich_text
from wagtail.fields import StreamField
from wagtail.test.utils.form_data import streamfield, nested_form_data, rich_text
from books.factories import ContentBlockFactory, ModuleFactory, ChapterFactory
from books.models import ContentBlock

View File

@ -0,0 +1,51 @@
from django.test import TestCase
from assignments.models import Assignment
from books.factories import BookFactory, ContentBlockFactory
from books.models.contentblock import ContentBlock
from core.logger import get_logger
from surveys.models import Survey
from users.services import create_users
logger = get_logger(__name__)
class DuplicateContentsTestCase(TestCase):
def setUp(self) -> None:
create_users()
_, _, self.module, chapter, _ = BookFactory.create_default_structure()
text = {"type": "text_block", "value": {"text": "Hallo"}}
assignment = {
"type": "assignment",
"value": {"title": "Hello", "assignment": "Assignment"},
}
survey = {"type": "survey", "value": {"title": "Survey Title", "data": "null"}}
self.content_block = ContentBlockFactory.create(
parent=chapter,
module=self.module,
title="Another content block",
type="task",
contents=[text, assignment, survey],
)
self.assignment = Assignment.objects.first()
def test_duplicate_entities(self):
self.assertEqual(
self.content_block.contents[1].value["assignment_id"], self.assignment
)
self.assertEqual(
self.content_block.contents[1].value["assignment_id"].title, "Hello"
)
self.assertEqual(Assignment.objects.count(), 1)
self.assertEqual(Survey.objects.count(), 1)
self.content_block.duplicate_attached_entities()
self.assertEqual(Assignment.objects.count(), 2)
self.assertEqual(Survey.objects.count(), 2)
new_assignment = Assignment.objects.latest("id")
new_survey = Survey.objects.latest("id")
content_block = ContentBlock.objects.get(id=self.content_block.id)
self.assertEqual(len(content_block.contents), 3)
self.assertEqual(
content_block.contents[1].value["assignment_id"], new_assignment
)
self.assertEqual(content_block.contents[2].value["survey_id"], new_survey)

View File

@ -1,2 +1,2 @@
DEFAULT_RICH_TEXT_FEATURES = ['ul', 'bold']
INSTRUMENTS_RICH_TEXT_FEATURES = ['bold', 'ul', 'brand', 'secondary']
DEFAULT_RICH_TEXT_FEATURES = ["ul", "bold", "subscript", "superscript"]
INSTRUMENTS_RICH_TEXT_FEATURES = DEFAULT_RICH_TEXT_FEATURES + ["brand", "secondary"]

View File

@ -8,7 +8,7 @@ from django.conf import settings
from django.core import management
from django.core.management import BaseCommand
from django.db import connection
from wagtail.core.models import Page
from wagtail.models import Page
from books.factories import BookFactory, TopicFactory, ModuleFactory, ChapterFactory, ContentBlockFactory
from core.factories import UserFactory

View File

@ -4,6 +4,8 @@ from django.conf import settings
from django.http import Http404, HttpResponsePermanentRedirect
from django.shortcuts import redirect
from django.utils.deprecation import MiddlewareMixin
from graphene import ResolveInfo
from sentry_sdk import capture_exception
try:
@ -15,25 +17,25 @@ _thread_locals = local()
def get_current_request():
""" returns the request object for this thread """
"""returns the request object for this thread"""
return getattr(_thread_locals, "request", None)
def get_current_user():
""" returns the current user, if exist, otherwise returns None """
"""returns the current user, if exist, otherwise returns None"""
request = get_current_request()
if request:
return getattr(request, "user", None)
class ThreadLocalMiddleware(MiddlewareMixin):
""" Simple middleware that adds the request object in thread local storage."""
"""Simple middleware that adds the request object in thread local storage."""
def process_request(self, request):
_thread_locals.request = request
def process_response(self, request, response):
if hasattr(_thread_locals, 'request'):
if hasattr(_thread_locals, "request"):
del _thread_locals.request
return response
@ -42,9 +44,14 @@ class CommonRedirectMiddleware(MiddlewareMixin):
"""
redirects common bad requests or missing images
"""
DEFAULT_REDIRECTS = [
(re.compile("(?i)(.+\.php|.+wp-admin.+|.+\.htm|\/wordpress\/|\/wp\/|\/mm5\/|\/wp-content\/)"),
'http://example.org/'),
(
re.compile(
"(?i)(.+\.php|.+wp-admin.+|.+\.htm|\/wordpress\/|\/wp\/|\/mm5\/|\/wp-content\/)"
),
"http://example.org/",
),
]
def process_exception(self, request, exception):
@ -72,10 +79,17 @@ class CommonRedirectMiddleware(MiddlewareMixin):
# some static image is missing show a placeholder (use full for local dev)
m = re.match(r".*(max|fill)-(?P<dimensions>\d+x\d+)\.(jpg|png|svg)", path)
if m:
return 'https://picsum.photos/{}'.format(m.group('dimensions').replace('x', '/'))
return "https://picsum.photos/{}".format(
m.group("dimensions").replace("x", "/")
)
# or dummy image: return 'http://via.placeholder.com/{}'.format(m.group('dimensions'))
if '.png' in path or '.jpg' in path or '.svg' in path or 'not-found' in path:
return 'https://picsum.photos/400/400'
if (
".png" in path
or ".jpg" in path
or ".svg" in path
or "not-found" in path
):
return "https://picsum.photos/400/400"
# https://stackoverflow.com/questions/4898408/how-to-set-a-login-cookie-in-django
@ -86,14 +100,24 @@ class UserLoggedInCookieMiddleWare(MiddlewareMixin):
If the user is not authenticated and the cookie remains, delete it
"""
cookie_name = 'loginStatus'
cookie_name = "loginStatus"
def process_response(self, request, response):
#if user and no cookie, set cookie
# if user and no cookie, set cookie
if request.user.is_authenticated and not request.COOKIES.get(self.cookie_name):
response.set_cookie(self.cookie_name, 'true')
elif not request.user.is_authenticated and request.COOKIES.get(self.cookie_name):
#else if if no user and cookie remove user cookie, logout
response.set_cookie(self.cookie_name, "true")
elif not request.user.is_authenticated and request.COOKIES.get(
self.cookie_name
):
# else if if no user and cookie remove user cookie, logout
response.delete_cookie(self.cookie_name)
return response
class SentryMiddleware(object):
def on_error(self, error):
capture_exception(error)
raise error
def resolve(self, next, root, info: ResolveInfo, **args):
return next(root, info, **args).catch(self.on_error)

View File

@ -0,0 +1,57 @@
# Generated by Django 3.2.16 on 2023-03-09 17:14
from django.contrib.auth.models import Group, Permission
from django.db import migrations
"""
CMS-Editors:
Wagtail: Alle Rechte
Django: Bereich «Lernziele»
Support:
Django: Bereich «User»
News:
Django: Bereich «News»
"""
news = ["news"]
support = ["users", "auth"]
cms_editors = [
"objectives",
"books",
"assignments",
"basicknowledge",
"surveys",
"wagtailadmin",
"wagtailcore",
"wagtailimages",
"wagtailembeds",
"wagtailredirects",
"wagtailsearch",
"wagtailusers",
"wagtaildocs",
]
groups = [("News", news), ("CMS-Editors", cms_editors), ("Support", support)]
def add_permissions(apps, schema_editor):
for name, app_labels in groups:
group, _ = Group.objects.get_or_create(name=name)
for app_label in app_labels:
for permission in Permission.objects.filter(
content_type__app_label=app_label
):
group.permissions.add(permission)
class Migration(migrations.Migration):
dependencies = [
("core", "0002_delete_admindata"),
]
operations = [
migrations.RunPython(add_permissions, migrations.RunPython.noop),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 3.2.16 on 2023-03-13 15:19
from django.db import migrations
from django.contrib.auth.models import Group
def delete_old_group(apps, schema_editor):
try:
group = Group.objects.get(name="Altes CMS")
group.delete()
except Group.DoesNotExist:
pass
class Migration(migrations.Migration):
dependencies = [
("core", "0003_auto_20230309_1714"),
]
operations = [
migrations.RunPython(delete_old_group, migrations.RunPython.noop),
]

View File

@ -0,0 +1,35 @@
# Generated by Django 3.2.16 on 2023-03-16 10:28
from django.db import migrations
from core.logger import get_logger
logger = get_logger(__name__)
def add_group_page_permissions(apps, schema_editor):
try:
types = ["lock", "bulk_delete", "edit", "publish", "unlock", "add"]
Group = apps.get_model("auth", "Group")
Site = apps.get_model("wagtailcore", "Site")
GroupPagePermission = apps.get_model("wagtailcore", "GroupPagePermission")
group = Group.objects.get(name="CMS-Editors")
site = Site.objects.get(is_default_site=True)
page = site.root_page
for tp in types:
GroupPagePermission.objects.get_or_create(
group=group, page=page, permission_type=tp
)
except Exception as e:
logger.error(e)
class Migration(migrations.Migration):
dependencies = [
("core", "0004_auto_20230313_1519"),
("wagtailcore", "0083_workflowcontenttype"),
]
operations = [
migrations.RunPython(add_group_page_permissions, migrations.RunPython.noop)
]

View File

@ -16,142 +16,138 @@ load_dotenv(find_dotenv())
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get('SECRET_KEY')
SIGNING_SECRET = os.environ.get('SIGNING_SECRET')
SECRET_KEY = os.environ.get("SECRET_KEY")
SIGNING_SECRET = os.environ.get("SIGNING_SECRET")
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = bool_value(os.environ.get('DEBUG', ''))
TEST = 'test' in sys.argv
ENABLE_SILKY = bool_value(os.environ.get('ENABLE_SILKY', ''))
SERVE_VIA_WEBPACK = bool_value(os.environ.get('SERVE_VIA_WEBPACK', DEBUG))
ENABLE_SENTRY = not DEBUG or bool_value(os.environ.get('ENABLE_SENTRY_DEBUG', ''))
DEBUG = bool_value(os.environ.get("DEBUG", ""))
TEST = "test" in sys.argv
ENABLE_SILKY = bool_value(os.environ.get("ENABLE_SILKY", ""))
SERVE_VIA_WEBPACK = bool_value(os.environ.get("SERVE_VIA_WEBPACK", DEBUG))
ENABLE_SENTRY = not DEBUG or bool_value(os.environ.get("ENABLE_SENTRY_DEBUG", ""))
ALLOWED_HOSTS = ['*']
ALLOWED_HOSTS = ["*"]
if not DEBUG:
SECURE_SSL_REDIRECT = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
# Application definition
INSTALLED_APPS = [
'core',
'api',
'users',
'books',
'objectives',
'rooms',
'assignments',
'basicknowledge',
'portfolio',
'statistics',
'surveys',
'notes',
'news',
'oauth',
'wagtail.contrib.redirects',
'wagtail.contrib.modeladmin',
'wagtail.embeds',
'wagtail.sites',
'wagtail.users',
'wagtail.snippets',
'wagtail.documents',
'wagtail.images',
'wagtail.search',
'wagtail.admin',
'wagtail.core',
'wagtail.api.v2',
'wagtailautocomplete',
'taggit',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
"core",
"api",
"users",
"books",
"objectives",
"rooms",
"assignments",
"basicknowledge",
"portfolio",
"statistics",
"surveys",
"notes",
"news",
"oauth",
"wagtail.contrib.redirects",
"wagtail.contrib.modeladmin",
"wagtail.embeds",
"wagtail.sites",
"wagtail.users",
"wagtail.snippets",
"wagtail.documents",
"wagtail.images",
"wagtail.search",
"wagtail.admin",
"wagtail",
"wagtail.api.v2",
"wagtailautocomplete",
"taggit",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
# 'raven.contrib.django.raven_compat',
'whitenoise.runserver_nostatic',
'django.contrib.staticfiles',
'django_filters',
'graphene_django',
'django_extensions',
'compressor',
"whitenoise.runserver_nostatic",
"django.contrib.staticfiles",
"django_filters",
"graphene_django",
"django_extensions",
"compressor",
]
if DEBUG:
INSTALLED_APPS += ['wagtail.contrib.styleguide']
INSTALLED_APPS += ["wagtail.contrib.styleguide"]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.middleware.gzip.GZipMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware'
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"django.middleware.gzip.GZipMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",
]
# Enable CORS for local development
if DEBUG:
INSTALLED_APPS += ['corsheaders']
MIDDLEWARE += ['corsheaders.middleware.CorsMiddleware']
CORS_ORIGIN_WHITELIST = (
'http://localhost:8080',
)
INSTALLED_APPS += ["corsheaders"]
MIDDLEWARE += ["corsheaders.middleware.CorsMiddleware"]
CORS_ORIGIN_WHITELIST = ("http://localhost:8080",)
CORS_ALLOW_CREDENTIALS = True
# enable silk for performance measuring
if ENABLE_SILKY:
INSTALLED_APPS += ['silk']
MIDDLEWARE += ['silk.middleware.SilkyMiddleware', ]
INSTALLED_APPS += ["silk"]
MIDDLEWARE += [
"silk.middleware.SilkyMiddleware",
]
MIDDLEWARE += [
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'wagtail.contrib.redirects.middleware.RedirectMiddleware',
'core.middleware.ThreadLocalMiddleware',
'core.middleware.CommonRedirectMiddleware',
'core.middleware.UserLoggedInCookieMiddleWare',
'oauth.middleware.user_has_license_middleware',
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"wagtail.contrib.redirects.middleware.RedirectMiddleware",
"core.middleware.ThreadLocalMiddleware",
"core.middleware.CommonRedirectMiddleware",
"core.middleware.UserLoggedInCookieMiddleWare",
"oauth.middleware.user_has_license_middleware",
]
ROOT_URLCONF = 'core.urls'
ROOT_URLCONF = "core.urls"
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, '..', 'client/dist'), os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'core.context_processors.settings_context',
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [
os.path.join(BASE_DIR, "..", "client/dist"),
os.path.join(BASE_DIR, "templates"),
],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
"core.context_processors.settings_context",
],
},
},
]
WSGI_APPLICATION = 'core.wsgi.application'
WSGI_APPLICATION = "core.wsgi.application"
# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
# Database
DATABASES = {
'default': dj_database_url.config(conn_max_age=600)
}
DATABASES = {"default": dj_database_url.config(conn_max_age=600)}
# Django custom user
AUTH_USER_MODEL = 'users.User'
AUTH_USER_MODEL = "users.User"
# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
@ -162,30 +158,30 @@ if WEAK_PASSWORDS:
else:
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
LOGOUT_REDIRECT_URL = '/login'
LOGIN_REDIRECT_URL = '/login'
LOGOUT_REDIRECT_URL = "/login"
LOGIN_REDIRECT_URL = "/login"
LOGIN_URL = LOGIN_REDIRECT_URL
# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/
LANGUAGE_CODE = 'de'
LANGUAGE_CODE = "de"
TIME_ZONE = 'UTC'
TIME_ZONE = "UTC"
USE_I18N = True
@ -194,167 +190,161 @@ USE_L10N = True
USE_TZ = True
LANGUAGES = [
('de', _('German')),
('en', _('English')),
("de", _("German")),
("en", _("English")),
]
LOCALE_PATHS = [
os.path.join(BASE_DIR, 'locale')
]
LOCALE_PATHS = [os.path.join(BASE_DIR, "locale")]
# Honor the 'X-Forwarded-Proto' header for request.is_secure()
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static")
STATIC_URL = "/static/"
STATICFILES_DIRS = (
os.path.join(BASE_DIR, '..', 'client/dist/static'),
os.path.join(BASE_DIR, '..', 'client/src/assets'),
os.path.join(BASE_DIR, "..", "client/dist/static"),
os.path.join(BASE_DIR, "..", "client/src/assets"),
)
if not TEST:
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
COMPRESS_CSS_FILTERS = [
# 'django_compressor_autoprefixer.AutoprefixerFilter',
'compressor.filters.cssmin.CSSMinFilter',
"compressor.filters.cssmin.CSSMinFilter",
]
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'compressor.finders.CompressorFinder',
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
"compressor.finders.CompressorFinder",
)
COMPRESS_PRECOMPILERS = (
('text/x-scss', 'django_libsass.SassCompiler'),
)
COMPRESS_PRECOMPILERS = (("text/x-scss", "django_libsass.SassCompiler"),)
COMPRESS_ENABLED = True
if not DEBUG:
COMPRESS_STORAGE = 'compressor.storage.GzipCompressorFileStorage'
COMPRESS_STORAGE = "compressor.storage.GzipCompressorFileStorage"
COMPRESS_OFFLINE = True
# AWS S3
# http://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html
USE_AWS = bool_value(os.environ.get('USE_AWS'))
USE_AWS = bool_value(os.environ.get("USE_AWS"))
AWS_QUERYSTRING_AUTH = False
AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY')
AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME')
AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID")
AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY")
AWS_STORAGE_BUCKET_NAME = os.environ.get("AWS_STORAGE_BUCKET_NAME")
AWS_S3_FILE_OVERWRITE = False
# use with cloudfront
AWS_S3_CUSTOM_DOMAIN = '{}.s3-{}.amazonaws.com'.format(AWS_STORAGE_BUCKET_NAME,
os.environ.get('AWS_REGION', 'eu-west-1'))
AWS_S3_CUSTOM_DOMAIN = "{}.s3-{}.amazonaws.com".format(
AWS_STORAGE_BUCKET_NAME, os.environ.get("AWS_REGION", "eu-west-1")
)
if USE_AWS:
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
# use with cloudfront
MEDIA_URL = "https://%s/" % AWS_S3_CUSTOM_DOMAIN
else:
MEDIA_URL = '/media/'
MEDIA_ROOT = os.environ.get('DJANGO_MEDIAFILES', os.path.join(BASE_DIR, 'media'))
MEDIA_URL = "/media/"
MEDIA_ROOT = os.environ.get("DJANGO_MEDIAFILES", os.path.join(BASE_DIR, "media"))
AWS_S3_OBJECT_PARAMETERS = {
'CacheControl': 'max-age=86400',
"CacheControl": "max-age=86400",
}
# Media Files
USE_404_FALLBACK_IMAGE = bool_value(os.environ.get('USE_404_FALLBACK_IMAGE', 'True'))
USE_404_FALLBACK_IMAGE = bool_value(os.environ.get("USE_404_FALLBACK_IMAGE", "True"))
# Logging Conf
LOGGING = {
'version': 1,
'formatters': {
'verbose_format': {
'format': '%(levelname)s %(asctime)s %(module)s %(name)s (%(process)d): %(message)s'
},
'simple_format': {
'format': '%(levelname)s %(name)s: %(message)s'
"version": 1,
"formatters": {
"verbose_format": {
"format": "%(levelname)s %(asctime)s %(module)s %(name)s (%(process)d): %(message)s"
},
"simple_format": {"format": "%(levelname)s %(name)s: %(message)s"},
},
'disable_existing_loggers': not DEBUG,
'handlers': {
'mail_admins': {
'level': 'CRITICAL',
'class': 'django.utils.log.AdminEmailHandler'
"disable_existing_loggers": not DEBUG,
"handlers": {
"mail_admins": {
"level": "CRITICAL",
"class": "django.utils.log.AdminEmailHandler",
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'stream': sys.stdout,
'formatter': 'simple_format'
"console": {
"level": "DEBUG",
"class": "logging.StreamHandler",
"stream": sys.stdout,
"formatter": "simple_format",
},
# for automatic papertrail logging
'SysLog': {
'level': 'INFO',
'class': 'logging.handlers.SysLogHandler',
'formatter': 'simple_format',
"SysLog": {
"level": "INFO",
"class": "logging.handlers.SysLogHandler",
"formatter": "simple_format",
},
},
'loggers': {
'': {
'handlers': ['console'],
'level': 'WARNING'
"loggers": {
"": {"handlers": ["console"], "level": "WARNING"},
"skillbox": {
"handlers": ["console", "SysLog"],
"level": os.environ.get("DJANGO_LOG_LEVEL", "INFO"),
"propagate": False,
},
'skillbox': {
'handlers': ['console', 'SysLog'],
'level': os.environ.get('DJANGO_LOG_LEVEL', 'INFO'),
'propagate': False
"graphql": {"handlers": ["console"], "level": "WARNING", "propagate": False},
"django": {
"handlers": ["console"],
"level": "INFO",
"propagate": False,
},
'graphql': {
'handlers': ['console'],
'level': 'WARNING',
'propagate': False
"django.server": {
"handlers": ["console"],
"level": "WARNING",
"propagate": False,
},
'django': {
'handlers': ['console'],
'level': 'INFO',
'propagate': False,
},
'django.server': {
'handlers': ['console'],
'level': 'WARNING',
'propagate': False,
},
}
},
}
if ENABLE_SENTRY and os.environ.get('SENTRY_DSN'):
if ENABLE_SENTRY and os.environ.get("SENTRY_DSN"):
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.logging import ignore_logger
def before_send(event, hint):
user = event['user']
id = user['id']
event['user'] = {'id': id}
user = event["user"]
id = user["id"]
event["user"] = {"id": id}
return event
environment = os.environ.get('SENTRY_ENV', 'localhost')
environment = os.environ.get("SENTRY_ENV", "localhost")
sample_rate = os.environ.get("SENTRY_SAMPLE_RATE", 0.01)
sentry_sdk.init(
dsn=os.environ.get('SENTRY_DSN'),
dsn=os.environ.get("SENTRY_DSN"),
integrations=[DjangoIntegration()],
send_default_pii=True,
before_send=before_send,
environment=environment
before_send_transaction=before_send,
environment=environment,
traces_sample_rate=sample_rate,
)
RAVEN_DSN_JS = os.environ.get('RAVEN_DSN_JS', '')
# ignore 'Traceback' error messages, they don't give enough information
# from https://jerrynsh.com/how-to-monitor-python-graphql-api-with-sentry/
ignore_logger("graphql.execution.utils")
RAVEN_DSN_JS = os.environ.get("RAVEN_DSN_JS", "")
GRAPHENE = {
'SCHEMA': 'api.schema.schema',
'SCHEMA_OUTPUT': 'schema.graphql'
"SCHEMA": "api.schema.schema",
"SCHEMA_OUTPUT": "schema.graphql",
"MIDDLEWARE": ["core.middleware.SentryMiddleware"],
}
# if DEBUG:
@ -363,25 +353,27 @@ GRAPHENE = {
# ]
# http://docs.wagtail.io/en/v2.1/advanced_topics/settings.html?highlight=urls
WAGTAIL_SITE_NAME = 'skillbox'
WAGTAIL_SITE_NAME = "skillbox"
WAGTAILSEARCH_BACKENDS = {
'default': {
'BACKEND': 'wagtail.search.backends.database',
"default": {
"BACKEND": "wagtail.search.backends.database",
}
}
WAGTAILDOCS_DOCUMENT_MODEL = 'books.CustomDocument'
WAGTAILDOCS_DOCUMENT_MODEL = "books.CustomDocument"
GRAPHQL_QUERIES_DIR = os.path.join(BASE_DIR, '..', 'client', 'src', 'graphql', 'gql', 'queries')
GRAPHQL_MUTATIONS_DIR = os.path.join(GRAPHQL_QUERIES_DIR, '../mutations')
GRAPHQL_QUERIES_DIR = os.path.join(
BASE_DIR, "..", "client", "src", "graphql", "gql", "queries"
)
GRAPHQL_MUTATIONS_DIR = os.path.join(GRAPHQL_QUERIES_DIR, "../mutations")
DEFAULT_FROM_EMAIL = 'myskillbox <noreply@myskillbox.ch>'
DEFAULT_FROM_EMAIL = "myskillbox <noreply@myskillbox.ch>"
# Metanet Config
if DEBUG:
EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'
EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
else:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
ALLOW_BETA_LOGIN = True
@ -390,25 +382,25 @@ HEP_URL = os.environ.get("HEP_URL")
# HEP Oauth
AUTHLIB_OAUTH_CLIENTS = {
'hep': {
'client_id': os.environ.get("OAUTH_CLIENT_ID"),
'client_secret': os.environ.get("OAUTH_CLIENT_SECRET"),
'request_token_url': None,
'request_token_params': None,
'access_token_url': os.environ.get("OAUTH_ACCESS_TOKEN_URL"),
'access_token_params': None,
'refresh_token_url': None,
'authorize_url': os.environ.get("OAUTH_AUTHORIZE_URL"),
'api_base_url': os.environ.get("OAUTH_API_BASE_URL"),
'client_kwargs': {
'scope': 'orders',
'token_endpoint_auth_method': 'client_secret_post',
'token_placement': 'header',
}
"hep": {
"client_id": os.environ.get("OAUTH_CLIENT_ID"),
"client_secret": os.environ.get("OAUTH_CLIENT_SECRET"),
"request_token_url": None,
"request_token_params": None,
"access_token_url": os.environ.get("OAUTH_ACCESS_TOKEN_URL"),
"access_token_params": None,
"refresh_token_url": None,
"authorize_url": os.environ.get("OAUTH_AUTHORIZE_URL"),
"api_base_url": os.environ.get("OAUTH_API_BASE_URL"),
"client_kwargs": {
"scope": "orders",
"token_endpoint_auth_method": "client_secret_post",
"token_placement": "header",
},
}
}
PLATFORM = os.environ.get('APP_FLAVOR', 'myskillbox')
PLATFORM = os.environ.get("APP_FLAVOR", "myskillbox")
OAUTH_LOCAL_REDIRECT_URI = os.environ.get("OAUTH_LOCAL_REDIRECT_URI")
@ -419,12 +411,12 @@ TASKBASE_SUPERPASSWORD = os.environ.get("TASKBASE_SUPERPASSWORD")
TASKBASE_BASEURL = os.environ.get("TASKBASE_BASEURL")
ENABLE_SPELLCHECK = True if TASKBASE_BASEURL else False
TEST_RUNNER = 'xmlrunner.extra.djangotestrunner.XMLTestRunner'
TEST_OUTPUT_DIR = './test-reports/'
TEST_RUNNER = "xmlrunner.extra.djangotestrunner.XMLTestRunner"
TEST_OUTPUT_DIR = "./test-reports/"
TEST_OUTPUT_VERBOSE = 1
# new default in Django 3.0, making it explicit to facilitate bug hunting
X_FRAME_OPTIONS = 'DENY'
X_FRAME_OPTIONS = "DENY"
SECURE_CONTENT_TYPE_NOSNIFF = True
# Django 3.2 uses BitAutoField by default, we keep things the old way
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"

View File

@ -0,0 +1,264 @@
import json
from django.test import TestCase
from django.urls import reverse
from wagtail.models import Page
from wagtail.test.utils import WagtailTestUtils
from assignments.factories import AssignmentFactory
from assignments.models import Assignment
from books.blocks import AssignmentBlock, SurveyBlock
from books.factories import BookFactory, ChapterFactory, ModuleFactory, TopicFactory
from books.models.contentblock import ContentBlock
from books.models.module import Module
from books.models.topic import Topic
from core.logger import get_logger
from surveys.factories import SurveyFactory
from surveys.models import Survey
logger = get_logger(__name__)
def get_copy_payload(title, slug, parent):
return {
"new_title": title,
"new_slug": slug,
"new_parent_page": parent.id,
"copy_subpages": True,
"publish_copies": False,
"alias": False,
}
def get_copy_url(page):
return reverse("wagtailadmin_pages:copy", args=(page.id,))
class CoreHooksTestCase(WagtailTestUtils, TestCase):
def setUp(self) -> None:
(
self.book,
self.topic,
self.module,
self.chapter,
self.content_block,
) = BookFactory.create_default_structure()
self.user = self.login()
# create content blocks
assignment = AssignmentFactory()
self.assignment_id = assignment.id
assignment_block = AssignmentBlock()
assignment_value = assignment_block.to_python({"assignment_id": assignment.id})
cleaned_assignment_value = assignment_block.clean(assignment_value)
assignment_content = ("assignment", cleaned_assignment_value)
survey = SurveyFactory()
survey_block = SurveyBlock()
survey_value = survey_block.to_python({"survey_id": survey.id})
cleaned_survey_value = survey_block.clean(survey_value)
survey_content = ("survey", cleaned_survey_value)
self.content_block.contents.append(assignment_content)
self.content_block.contents.append(survey_content)
self.content_block.save()
self.assertEqual(
self.content_block.contents[0].value["assignment_id"], assignment
)
self.assertEqual(self.content_block.contents[1].value["survey_id"], survey)
self.assertEqual(Assignment.objects.count(), 1)
self.assertEqual(Survey.objects.count(), 1)
# logger.debug(f"assignment: {assignment.id}")
self.new_topic = TopicFactory.create(
parent=self.book, title="A second Topic", order=2
)
self.new_module = ModuleFactory.create(
parent=self.new_topic,
title="A second module",
meta_title="Modul 1",
teaser="Whatever",
intro="<p>Hello</p>",
)
self.new_chapter = ChapterFactory.create(
parent=self.new_module, title="A second chapter"
)
def test_after_create_hook(self):
# description = rich_text('<p>hello</p>')
self.assertEqual(Topic.objects.count(), 2)
description = {
"blocks": [
{
"key": "thfb4",
"text": "asd",
"type": "unstyled",
"depth": 0,
"inlineStyleRanges": [],
"entityRanges": [],
"data": {},
}
],
"entityMap": {},
}
post_data = {
"title": "New Page",
"order": 1,
"teaser": "Tease",
"description": json.dumps(description),
"slug": "hello-world",
}
url = reverse(
"wagtailadmin_pages:add",
args=("books", "topic", self.book.id),
)
# logger.debug(url)
self.client.post(
url,
post_data,
)
self.assertEqual(Topic.objects.count(), 3)
def _check_copied_content_block(self, module=None):
if module is None:
module = self.new_module
self.assertEqual(ContentBlock.objects.count(), 2)
self.assertEqual(Assignment.objects.count(), 2)
self.assertEqual(Survey.objects.count(), 2)
new_content_block = ContentBlock.objects.latest("-latest_revision_created_at")
new_assignment = Assignment.objects.latest("pk")
new_survey = Survey.objects.latest("pk")
self.assertEqual(
new_content_block.contents[0].value["assignment_id"], new_assignment
)
self.assertEqual(new_assignment.module, module)
self.assertEqual(new_content_block.contents[1].value["survey_id"], new_survey)
self.assertEqual(new_survey.module, module)
def test_content_block_after_copy_hook(self):
"""
should copy the content block, and set all entities' module to the new parent's parent module
"""
# inspired by wagtail.admin.tests.pages.test_copy_page
url = get_copy_url(self.content_block)
post_data = get_copy_payload(
"Neuer Titel (Kopie)", "new-slug", self.new_chapter
)
self.client.post(url, post_data)
self._check_copied_content_block()
def test_chapter_after_copy_hook(self):
"""
should copy the chapter, and set all entities' module to the new parent module
"""
url = get_copy_url(self.chapter)
post_data = get_copy_payload("Neuer Titel (Kopie)", "new-slug", self.new_module)
self.client.post(url, post_data)
self._check_copied_content_block()
def test_module_after_copy_hook(self):
"""
should copy the module, and set all entities' module to the newly created module
"""
url = get_copy_url(self.module)
post_data = get_copy_payload("Neuer Titel (Kopie)", "new-slug", self.new_topic)
self.client.post(url, post_data)
module = Module.objects.latest("-latest_revision_created_at")
self._check_copied_content_block(module)
def test_topic_after_copy_hook(self):
"""
should copy the whole topic, and set all entities to the newly created child module
"""
url = get_copy_url(self.topic)
post_data = get_copy_payload("Neuer Titel (Kopie)", "new-slug", self.book)
self.client.post(url, post_data)
topic = Topic.objects.latest("-latest_revision_created_at")
module = topic.get_children().first().specific
self._check_copied_content_block(module)
# def test_chapter_after_move_hook(self):
# """
# should move the chapter, and set all entities' module to the new parent's parent module
# """
#
# assignment = self.content_block.contents[0].value["assignment_id"]
# survey = self.content_block.contents[1].value["survey_id"]
# self.assertEqual(self.chapter.pk, self.content_block.get_parent().pk)
#
# logger.debug("content blocks")
# logger.debug(self.chapter.get_content_blocks())
#
# url = reverse(
# "wagtailadmin_pages:move_confirm",
# args=(self.chapter.id, self.new_module.id),
# )
# logger.debug(url)
# response = self.client.post(url)
# logger.debug(response)
# chapter = Page.objects.get(id=self.chapter.id)
# logger.debug("new content blocks")
# logger.debug(chapter.specific.get_content_blocks())
# self.assertEqual(chapter.get_parent().id, self.new_module.id)
# # reload the assignment from the DB
# assignment = Assignment.objects.get(id=assignment.id)
# self.assertEqual(assignment.module, self.new_module)
# survey = Survey.objects.get(id=survey.id)
# self.assertEqual(survey.module, self.new_module)
# def test_content_block_after_move_hook(self):
# """
# should move the content block, and set all entities' module to the new parent's parent module
# """
#
# url = reverse(
# "wagtailadmin_pages:move_confirm",
# args=(self.content_block.id, self.new_chapter.id),
# )
# self.client.post(url)
# assignment = self.content_block.contents[0].value["assignment_id"]
# self.assertEqual(assignment.module, self.new_module)
def test_content_block_after_create_hook(self):
"""
should save the content block and set all new entites' module to the correct module
"""
assignment = AssignmentFactory(module=self.module)
survey = SurveyFactory(module=None)
self.assertEqual(assignment.module, assignment.module)
self.assertIsNone(survey.module)
self.assertEqual(ContentBlock.objects.count(), 1)
url = reverse(
"wagtailadmin_pages:add",
args=("books", "contentblock", self.new_chapter.id),
)
post_data = {
"title": "New Content Block",
"order": 1,
"slug": "hello-world",
"contents-count": 2,
"contents-0-deleted": "",
"contents-0-order": "0",
"contents-0-type": "assignment",
"contents-0-id": "",
"contents-0-value-assignment_id": f"{assignment.id}",
"contents-1-deleted": "",
"contents-1-order": "1",
"contents-1-type": "survey",
"contents-1-id": "",
"contents-1-value-survey_id": f"{survey.id}",
"type": "normal",
}
response = self.client.post(
url,
post_data,
)
self.assertEqual(ContentBlock.objects.count(), 2)
# reload assignment
assignment = Assignment.objects.get(id=assignment.id)
self.assertEqual(assignment.module, self.new_module)
survey = Survey.objects.get(id=survey.id)
self.assertEqual(survey.module, self.new_module)

View File

@ -5,7 +5,7 @@ from django.contrib import admin
from django.urls import re_path
from django.views.generic import RedirectView
from wagtail.admin import urls as wagtailadmin_urls
from wagtail.core import urls as wagtail_urls
from wagtail import urls as wagtail_urls
from wagtail.documents import urls as wagtaildocs_urls
from wagtailautocomplete.urls.admin import urlpatterns as autocomplete_admin_urls
@ -15,21 +15,20 @@ from core.views import override_wagtailadmin_explore_default_ordering
urlpatterns = [
# django admin
url(r'^guru/', admin.site.urls),
url(r'^statistics/', include('statistics.urls', namespace='statistics')),
url(r"^guru/", admin.site.urls),
url(r"^statistics/", include("statistics.urls", namespace="statistics")),
# wagtail
url(r'^admin/autocomplete/', include(autocomplete_admin_urls)),
re_path(r'^cms/pages/(\d+)/$', override_wagtailadmin_explore_default_ordering),
url(r'^cms/', include(wagtailadmin_urls)),
url(r'^documents/', include(wagtaildocs_urls)),
url(r"^cms/autocomplete/", include(autocomplete_admin_urls)),
re_path(r"^cms/pages/(\d+)/$", override_wagtailadmin_explore_default_ordering),
url(r"^cms/", include(wagtailadmin_urls)),
url(r"^documents/", include(wagtaildocs_urls)),
# graphql backend
url(r'^api/', include('api.urls', namespace="api")),
#favicon
url(r'^favicon\.ico$', RedirectView.as_view(url='/static/favicon@2x.png', permanent=True)),
url(r"^api/", include("api.urls", namespace="api")),
# favicon
url(
r"^favicon\.ico$",
RedirectView.as_view(url="/static/favicon@2x.png", permanent=True),
),
]
if settings.DEBUG and not settings.USE_AWS:
@ -39,11 +38,13 @@ if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
if settings.ENABLE_SILKY:
urlpatterns += [url(r'^silk/', include('silk.urls', namespace='silk'))]
urlpatterns += [url(r"^silk/", include("silk.urls", namespace="silk"))]
# actually we use the cms in headless mode but need the url pattern to get the wagtail_serve function
urlpatterns += [url(r'pages/', include(wagtail_urls)), ]
urlpatterns += [
url(r"pages/", include(wagtail_urls)),
]
urlpatterns += [re_path(r'^.*$', views.home, name='home')]
urlpatterns += [re_path(r"^.*$", views.home, name="home")]
admin.site.site_header = 'Myskillbox Admin'
admin.site.site_header = "Myskillbox Admin"

View File

@ -1,3 +1,4 @@
from django.http.request import HttpRequest
import requests
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
@ -5,10 +6,34 @@ from django.http.response import HttpResponse, HttpResponseRedirect
from django.shortcuts import render
from django.views.decorators.csrf import ensure_csrf_cookie
from graphene_django.views import GraphQLView
from sentry_sdk.api import start_transaction
from wagtail.admin.views.pages.listing import index as wagtailadmin_explore
class PrivateGraphQLView(LoginRequiredMixin, GraphQLView):
# For sentry perfomance monitoring
# taken from https://jerrynsh.com/how-to-monitor-python-graphql-api-with-sentry/
class SentryGraphQLView(GraphQLView):
def execute_graphql_request(
self,
request: HttpRequest,
data,
query,
variables,
operation_name,
show_graphiql,
):
operation_type = (
self.get_backend(request)
.document_from_string(self.schema, query)
.get_operation_type(operation_name)
)
with start_transaction(op=operation_type, name=operation_name):
return super().execute_graphql_request(
request, data, query, variables, operation_name, show_graphiql
)
class PrivateGraphQLView(LoginRequiredMixin, SentryGraphQLView):
pass
@ -16,22 +41,24 @@ class PrivateGraphQLView(LoginRequiredMixin, GraphQLView):
def home(request):
if settings.SERVE_VIA_WEBPACK:
try:
res = requests.get('http://localhost:8080{}'.format(request.get_full_path()))
res = requests.get(
"http://localhost:8080{}".format(request.get_full_path())
)
headers = res.headers
content_type = headers.get('content-type', 'text/html')
content_type = headers.get("content-type", "text/html")
return HttpResponse(res.text, content_type=content_type)
except Exception as e:
print('Can not connect to dev server at http://localhost:8080:', e)
print("Can not connect to dev server at http://localhost:8080:", e)
return render(request, 'index.html', {})
return render(request, "index.html", {})
def override_wagtailadmin_explore_default_ordering(request, parent_page_id):
"""
Wrap Wagtail's explore view to change the default ordering
"""
if request.method == 'GET' and 'ordering' not in request.GET:
if request.method == "GET" and "ordering" not in request.GET:
# Display reordering handles by default for children of all Page types.
return HttpResponseRedirect(request.path_info + '?ordering=ord')
return HttpResponseRedirect(request.path_info + "?ordering=ord")
return wagtailadmin_explore(request, parent_page_id=parent_page_id)

View File

@ -1,90 +1,200 @@
from django.db.models import ProtectedError
from django.shortcuts import redirect
from wagtail.admin import messages
import wagtail.admin.rich_text.editors.draftail.features as draftail_features
from wagtail.admin.rich_text.converters.html_to_contentstate import InlineStyleElementHandler
from wagtail.core import hooks
from wagtail.admin.rich_text.converters.html_to_contentstate import (
InlineStyleElementHandler,
)
from wagtail import hooks
from wagtail.admin.utils import get_valid_next_url_from_request
from basicknowledge.models import BasicKnowledge
from books.models import ContentBlockSnapshot
from books.models.chapter import Chapter
from books.models.contentblock import ContentBlock
from core.logger import get_logger
logger = get_logger(__name__)
# 1. Use the register_rich_text_features hook.
@hooks.register('register_rich_text_features')
@hooks.register("register_rich_text_features")
def register_brand_feature(features):
"""
Registering the feature, which uses the `BRAND` Draft.js inline style type,
and is stored as HTML with a `<span class="brand">` tag.
"""
feature_name = 'brand'
type_ = 'BRAND'
feature_name = "brand"
type_ = "BRAND"
# 2. Configure how Draftail handles the feature in its toolbar.
control = {
'type': type_,
'label': 'Grün',
'description': 'Grün',
'style': {
'color': '#17A887',
'font-weight': '600'
},
"type": type_,
"label": "Grün",
"description": "Grün",
"style": {"color": "#17A887", "font-weight": "600"},
}
# 3. Call register_editor_plugin to register the configuration for Draftail.
features.register_editor_plugin(
'draftail', feature_name, draftail_features.InlineStyleFeature(control)
"draftail", feature_name, draftail_features.InlineStyleFeature(control)
)
# 4.configure the content transform from the DB to the editor and back.
db_conversion = {
'from_database_format': {'span[class="brand"]': InlineStyleElementHandler(type_)},
'to_database_format': {'style_map': {type_: 'span class="brand""'}},
"from_database_format": {
'span[class="brand"]': InlineStyleElementHandler(type_)
},
"to_database_format": {"style_map": {type_: 'span class="brand""'}},
}
# 5. Call register_converter_rule to register the content transformation conversion.
features.register_converter_rule('contentstate', feature_name, db_conversion)
features.register_converter_rule("contentstate", feature_name, db_conversion)
# 6. (optional) Add the feature to the default features list to make it available
# on rich text fields that do not specify an explicit 'features' list
features.default_features.append(feature_name)
@hooks.register('register_rich_text_features')
@hooks.register("register_rich_text_features")
def register_secondary_feature(features):
"""
Registering the feature, which uses the `SECONDARY` Draft.js inline style type,
and is stored as HTML with a `<span class="secondary">` tag.
"""
feature_name = 'secondary'
type_ = 'SECONDARY'
feature_name = "secondary"
type_ = "SECONDARY"
# 2. Configure how Draftail handles the feature in its toolbar.
control = {
'type': type_,
'label': 'Blau',
'description': 'Blau',
'style': {
'color': '#078CC6',
'font-weight': '600'
},
"type": type_,
"label": "Blau",
"description": "Blau",
"style": {"color": "#078CC6", "font-weight": "600"},
}
# 3. Call register_editor_plugin to register the configuration for Draftail.
features.register_editor_plugin(
'draftail', feature_name, draftail_features.InlineStyleFeature(control)
"draftail", feature_name, draftail_features.InlineStyleFeature(control)
)
# 4.configure the content transform from the DB to the editor and back.
db_conversion = {
'from_database_format': {'span[class="secondary"]': InlineStyleElementHandler(type_)},
'to_database_format': {'style_map': {type_: 'span class="secondary"'}},
"from_database_format": {
'span[class="secondary"]': InlineStyleElementHandler(type_)
},
"to_database_format": {"style_map": {type_: 'span class="secondary"'}},
}
# 5. Call register_converter_rule to register the content transformation conversion.
features.register_converter_rule('contentstate', feature_name, db_conversion)
features.register_converter_rule("contentstate", feature_name, db_conversion)
# 6. (optional) Add the feature to the default features list to make it available
# on rich text fields that do not specify an explicit 'features' list
features.default_features.append(feature_name)
@hooks.register('construct_explorer_page_queryset')
@hooks.register("construct_explorer_page_queryset")
def remove_page_types_from_menu(parent_page, pages, request):
return pages.not_type(ContentBlockSnapshot).not_type(BasicKnowledge).exclude(contentblock__user_created=True)
return (
pages.not_type(ContentBlockSnapshot)
.not_type(BasicKnowledge)
.exclude(contentblock__user_created=True)
)
@hooks.register("after_copy_page")
def after_copy_hook(request, page, new_page):
# todo: find every ContentBlock further down in the tree, see if there are any Surveys or Assignments and copy them and reassign them
if type(page.specific) == ContentBlock:
# logger.debug("It's a content block")
content_block: ContentBlock = new_page.specific
# logger.debug(f"duplicatin {content_block.title, content_block.pk}")
content_block.duplicate_attached_entities()
else:
# logger.debug(f"It's something else {type(page.specific)}, {ContentBlock}")
content_blocks = new_page.specific.get_content_blocks()
for content_block in content_blocks:
content_block.duplicate_attached_entities()
@hooks.register("after_move_page")
def after_move_hook(request, page):
logger.debug(f"after moving the page {page.title}")
if type(page.specific) == ContentBlock:
logger.debug("it's a content block")
page.specific.reassign_entities()
if type(page.specific) == Chapter:
logger.debug("it's a chapter")
content_blocks = page.specific.get_content_blocks()
logger.debug(page.id)
logger.debug(page.specific.get_content_blocks())
logger.debug(page.get_children())
for content_block in content_blocks:
content_block.reassign_entities()
@hooks.register("after_edit_page")
def after_edit_hook(request, page):
logger.debug(f"After edit page {page.title}, {type(page).__name__}")
@hooks.register("after_create_page")
def after_create_hook(request, page):
# reassign assignment and survey module
if type(page.specific) == ContentBlock:
content_block = page.specific
content_block.reassign_entities()
@hooks.register("before_delete_page")
def on_page_delete(request, page):
if request.method != "POST":
return
try:
next_url = get_valid_next_url_from_request(request)
parent_id = page.get_parent().id
page.delete()
messages.success(
request, ("Page '{0}' deleted.").format(page.get_admin_display_title())
)
for fn in hooks.get_hooks("after_delete_page"):
result = fn(request, page)
if hasattr(result, "status_code"):
return result
if next_url:
return redirect(next_url)
return redirect("wagtailadmin_explore", parent_id)
except ProtectedError as exc:
protected_objects = {}
for obj in exc.protected_objects:
model_name = obj._meta.verbose_name_plural
if model_name in protected_objects:
protected_objects[model_name].append(str(obj))
else:
protected_objects[model_name] = [str(obj)]
dependency_summary = []
for model_name, items in protected_objects.items():
if len(items) == 1:
items_summary = ("{0} ({1})").format(model_name, items[0])
else:
items_summary = ("{0} ({1} and {2} more...)").format(
model_name, items[0], len(items) - 1
)
dependency_summary.append(items_summary)
messages.error(
request,
("Page '{0}' cannot be deleted while it's used by {1}").format(
page.get_admin_display_title(), ", ".join(dependency_summary)
),
)
return redirect("wagtailadmin_pages:delete", page.id)

View File

@ -1,7 +1,7 @@
from django.contrib import admin
from wagtail.admin.edit_handlers import CommentPanel
from wagtail.admin.edit_handlers import FieldPanel, ObjectList
from wagtail.core.models import Page
from wagtail.admin.panels import CommentPanel
from wagtail.admin.panels import FieldPanel, ObjectList
from wagtail.models import Page
class StrictHierarchyPage(Page):
@ -9,20 +9,27 @@ class StrictHierarchyPage(Page):
abstract = True
def get_child_ids(self):
return self.get_children().values_list('id', flat=True)
return self.get_children().values_list("id", flat=True)
@classmethod
def get_by_parent(cls, parent):
return cls.objects.filter(id__in=parent.get_child_ids()).live()
def get_content_blocks(self):
from books.models.contentblock import ContentBlock
return ContentBlock.objects.all().descendant_of(self)
def wagtail_parent_filter(parent_cls, child_cls):
class ParentValueFilter(admin.SimpleListFilter):
title = 'parent'
parameter_name = 'parent'
title = "parent"
parameter_name = "parent"
def lookups(self, request, model_admin):
return list((parent.slug, parent.title) for parent in parent_cls.objects.all())
return list(
(parent.slug, parent.title) for parent in parent_cls.objects.all()
)
def queryset(self, request, queryset):
filter_value = self.value()
@ -34,7 +41,4 @@ def wagtail_parent_filter(parent_cls, child_cls):
def get_default_settings():
return ObjectList([
FieldPanel('slug'),
CommentPanel()
], heading='Settings')
return ObjectList([FieldPanel("slug"), CommentPanel()], heading="Settings")

Some files were not shown because too many files have changed in this diff Show More