commit
9dca627465
|
|
@ -0,0 +1,34 @@
|
|||
describe('Survey', () => {
|
||||
beforeEach(() => {
|
||||
cy.exec("python ../server/manage.py prepare_surveys_for_cypress");
|
||||
|
||||
cy.viewport('macbook-15');
|
||||
cy.startGraphQLCapture();
|
||||
cy.login('rahel.cueni', 'test');
|
||||
});
|
||||
|
||||
it('should display and fill out the survey', () => {
|
||||
cy.visit('/survey/U3VydmV5Tm9kZTox');
|
||||
|
||||
cy.get('.survey__panel-title').should('contain', 'Fall 1')
|
||||
|
||||
cy.get('#sq_100i').type('Wohlwollen');
|
||||
cy.get('#sq_101i').type('Demut');
|
||||
|
||||
cy.get('[value=Next]').click();
|
||||
//cy.get('.button--primary').click()
|
||||
|
||||
cy.get('#sq_102i').type('Keuschheit');
|
||||
cy.get('#sq_103i').type('Geduld');
|
||||
|
||||
cy.get('[value=Complete]').click();
|
||||
|
||||
cy.waitFor('UpdateAnswer');
|
||||
|
||||
cy.visit('/survey/U3VydmV5Tm9kZTox');
|
||||
|
||||
cy.get('#sq_100i').should('have.value', 'Wohlwollen')
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -8,6 +8,9 @@
|
|||
<link href='https://fonts.googleapis.com/css?family=Material+Icons' rel="stylesheet" type="text/css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,600,700" rel="stylesheet">
|
||||
<link href="https://use.typekit.net/tck7ptw.css" rel="stylesheet">
|
||||
<!-- FIXME: replace with own css -->
|
||||
<link href="https://surveyjs.azureedge.net/1.0.87/survey.css" type="text/css" rel="stylesheet"/>
|
||||
|
||||
|
||||
<script>
|
||||
window.UPLOADCARE_PUBLIC_KEY = '78212ff39934a59775ac';
|
||||
|
|
|
|||
|
|
@ -4656,8 +4656,7 @@
|
|||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
|
|
@ -5022,8 +5021,7 @@
|
|||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
|
|
@ -5071,7 +5069,6 @@
|
|||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
|
|
@ -5110,13 +5107,11 @@
|
|||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.2",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -11236,6 +11231,14 @@
|
|||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"survey-vue": {
|
||||
"version": "1.0.87",
|
||||
"resolved": "https://registry.npmjs.org/survey-vue/-/survey-vue-1.0.87.tgz",
|
||||
"integrity": "sha512-NKxrv6KHtGr3P5gr+nx+cu3OkvBgZUgV+cdb0mzvqObex8PJD+IEsbE4NpE6yeoIN6h6NLxaX+ns9Y2fV+xv0A==",
|
||||
"requires": {
|
||||
"vue": "^2.1.10"
|
||||
}
|
||||
},
|
||||
"svgo": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz",
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@
|
|||
"sass-loader": "^7.1.0",
|
||||
"semver": "^5.3.0",
|
||||
"shelljs": "^0.7.6",
|
||||
"survey-vue": "^1.0.87",
|
||||
"uglifyjs-webpack-plugin": "^1.1.1",
|
||||
"unfetch": "^3.1.1",
|
||||
"uploadcare-widget": "^3.6.0",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
mutation UpdateAnswer($input:UpdateAnswerInput!) {
|
||||
updateAnswer(input:$input){
|
||||
answer {
|
||||
id
|
||||
data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# input
|
||||
|
||||
#{
|
||||
# "input": {
|
||||
# "answer": {
|
||||
# "surveyId": "U3VydmV5Tm9kZTox",
|
||||
# "data": "{\"some\": \"json\"}"
|
||||
# },
|
||||
# }
|
||||
#}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
query SurveyQuery($id: ID!) {
|
||||
survey(id: $id) {
|
||||
id
|
||||
title
|
||||
data
|
||||
answer {
|
||||
data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,10 +10,10 @@ import router from './router'
|
|||
import store from '@/store/index'
|
||||
import VueScrollTo from 'vue-scrollto';
|
||||
import VueAnalytics from 'vue-analytics';
|
||||
import { Validator, install as VeeValidate } from 'vee-validate/dist/vee-validate.minimal.esm.js';
|
||||
import { required, min } from 'vee-validate/dist/rules.esm.js';
|
||||
import {Validator, install as VeeValidate} from 'vee-validate/dist/vee-validate.minimal.esm.js';
|
||||
import {required, min} from 'vee-validate/dist/rules.esm.js';
|
||||
import veeDe from 'vee-validate/dist/locale/de';
|
||||
import {dateFilter} from './filters/date-filter'
|
||||
import {dateFilter} from './filters/date-filter';
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
<template>
|
||||
<div class="style-guide">
|
||||
<h1>Styleguide</h1>
|
||||
<h1 class="style-guide__main-title">Main Title - 64px Bold - 700</h1>
|
||||
<h2 class="style-guide__heading-1">Title 1 - 44px Semibold - 600</h2>
|
||||
<h3 class="style-guide__heading-2">Title 2 - 34px Semibold- 600</h3>
|
||||
<h4 class="style-guide__heading-3">Title 3 - 22px Semibold - 600</h4>
|
||||
<h5 class="style-guide__heading-4">Title 4 - 18px Semibold - 600</h5>
|
||||
<p class="style-guide__regular-text">Text - 18px Regular - 400</p>
|
||||
<p class="style-guide__small-text">Text Small - 16px Regular - 400</p>
|
||||
<h2 class="style-guide__meta-title">Meta Title - 42px Book</h2>
|
||||
<p class="style-guide__lead-paragraph">Lead paragraph - 26px<br>
|
||||
Vor wenigen Monaten haben Sie Ihren ersten Lohn bekommen – ein befriedigendes Gefühl, für seine Arbeit Geld zu erhalten.</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
|
||||
.style-guide {
|
||||
&__main-title {
|
||||
@include main-title;
|
||||
}
|
||||
|
||||
&__heading-1 {
|
||||
@include heading-1;
|
||||
}
|
||||
&__heading-2 {
|
||||
@include heading-2;
|
||||
}
|
||||
&__heading-3 {
|
||||
@include heading-3;
|
||||
}
|
||||
&__heading-4 {
|
||||
@include heading-4;
|
||||
}
|
||||
&__regular-text {
|
||||
@include regular-text;
|
||||
}
|
||||
&__small-text {
|
||||
@include small-text;
|
||||
}
|
||||
&__meta-title {
|
||||
@include meta-title;
|
||||
}
|
||||
&__lead-paragraph {
|
||||
@include lead-paragraph;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
<template>
|
||||
<div class="survey-page">
|
||||
<h1 class="survey-page__title">{{title}}</h1>
|
||||
<survey :survey='survey'></survey>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as SurveyVue from 'survey-vue';
|
||||
import {css} from '@/survey.config'
|
||||
|
||||
import SURVEY_QUERY from '@/graphql/gql/surveyQuery.gql';
|
||||
import UPDATE_ANSWER from '@/graphql/gql/mutations/updateAnswer.gql';
|
||||
|
||||
const Survey = SurveyVue.Survey;
|
||||
|
||||
export default {
|
||||
props: ['id'],
|
||||
|
||||
components: {
|
||||
Survey
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
survey: this.initSurvey(),
|
||||
title: ''
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
initSurvey(data, answer) {
|
||||
let survey = new SurveyVue.Model(data);
|
||||
survey.data = answer;
|
||||
|
||||
survey.onComplete.add((sender, options) => {
|
||||
sender.clear(false);
|
||||
|
||||
sender.mode = 'display';
|
||||
|
||||
this.$apollo.mutate({
|
||||
mutation: UPDATE_ANSWER,
|
||||
variables: {
|
||||
input: {
|
||||
answer: {
|
||||
surveyId: this.id,
|
||||
data: JSON.stringify(survey.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
survey.css = css;
|
||||
|
||||
return survey;
|
||||
}
|
||||
},
|
||||
|
||||
apollo: {
|
||||
survey: {
|
||||
query: SURVEY_QUERY,
|
||||
variables() {
|
||||
return {
|
||||
id: this.id
|
||||
}
|
||||
},
|
||||
manual: true,
|
||||
result({data, loading, networkStatus}) {
|
||||
if (!loading) {
|
||||
let json = JSON.parse(data.survey.data);
|
||||
let answer = {};
|
||||
if (data.survey.answer && data.survey.answer.data) {
|
||||
answer = JSON.parse(data.survey.answer.data);
|
||||
}
|
||||
|
||||
this.survey = this.initSurvey(json, answer);
|
||||
this.title = data.survey.title;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
|
||||
.survey-page {
|
||||
max-width: 800px;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
grid-row-gap: $large-spacing;
|
||||
justify-self: center;
|
||||
padding: 100px 0;
|
||||
|
||||
&__title {
|
||||
@include heading-2;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -24,6 +24,8 @@ import activity from '@/pages/activity'
|
|||
import Router from 'vue-router'
|
||||
import editProject from '@/pages/editProject'
|
||||
import newProject from '@/pages/newProject'
|
||||
import surveyPage from '@/pages/survey'
|
||||
import styleGuidePage from '@/pages/styleguide'
|
||||
|
||||
import store from '@/store/index';
|
||||
|
||||
|
|
@ -45,9 +47,7 @@ const routes = [
|
|||
component: submissions,
|
||||
meta: {filter: true}
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
{path: '/rooms', name: 'rooms', component: rooms, meta: {filter: true}},
|
||||
{path: '/new-room/', name: 'new-room', component: newRoom},
|
||||
|
|
@ -87,6 +87,12 @@ const routes = [
|
|||
{path: '', name: 'profile-activity', component: activity, meta: {isProfile: true}},
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/survey/:id',
|
||||
component: surveyPage,
|
||||
props: true
|
||||
},
|
||||
{path: '/styleguide', component: styleGuidePage},
|
||||
{path: '*', component: p404}
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
.survey {
|
||||
&__panel-title {
|
||||
@include main-title;
|
||||
margin-bottom: $large-spacing*2;
|
||||
span {
|
||||
@include main-title;
|
||||
}
|
||||
}
|
||||
|
||||
&__panel-description {
|
||||
@include regular-paragraph;
|
||||
line-height: 1.5;
|
||||
margin-bottom: $large-spacing;
|
||||
}
|
||||
|
||||
&__question-title {
|
||||
@include heading-4;
|
||||
margin-bottom: $medium-spacing;
|
||||
span {
|
||||
@include heading-4;
|
||||
}
|
||||
}
|
||||
|
||||
&__input {
|
||||
width: 100%;
|
||||
margin-bottom: $medium-spacing;
|
||||
}
|
||||
}
|
||||
|
|
@ -14,3 +14,4 @@
|
|||
@import "article";
|
||||
@import "actions";
|
||||
@import "top-navigation";
|
||||
@import "survey";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,161 @@
|
|||
export const css = {
|
||||
'root': 'survey',
|
||||
'header': '',
|
||||
'body': '',
|
||||
'bodyEmpty': '',
|
||||
'footer': '',
|
||||
'navigationButton': 'button button--primary',
|
||||
'completedPage': '',
|
||||
'navigation': {
|
||||
'complete': 'button button--primary',
|
||||
'prev': 'button button--primary',
|
||||
'next': 'button button--primary',
|
||||
'start': 'button button--primary'
|
||||
},
|
||||
'progress': 'progress center-block mx-auto mb-4',
|
||||
'progressBar': 'progress-bar',
|
||||
'page': {
|
||||
'root': '',
|
||||
'title': '',
|
||||
'survey__page-description': ''
|
||||
},
|
||||
'pageTitle': '',
|
||||
'pageDescription': 'small',
|
||||
'row': 'sv_row',
|
||||
'question': {
|
||||
'mainRoot': 'survey__question question',
|
||||
'flowRoot': 'sv_q_flow sv_qstn',
|
||||
'titleLeftRoot': 'sv_qstn_left',
|
||||
'title': 'survey__question-title',
|
||||
'number': 'sv_q_num',
|
||||
'survey__question-description': 'small',
|
||||
'comment': 'survey__question-input skillbox-input',
|
||||
'required': '',
|
||||
'titleRequired': '',
|
||||
'hasError': 'has-error',
|
||||
'indent': 20
|
||||
},
|
||||
'panel': {
|
||||
'title': 'survey__panel-title',
|
||||
'description': 'small survey__panel-description',
|
||||
'container': 'sv_p_container'
|
||||
},
|
||||
'error': {
|
||||
'root': 'alert alert-danger',
|
||||
'icon': 'glyphicon glyphicon-exclamation-sign',
|
||||
'item': '',
|
||||
'locationTop': 'sv_qstn_error_top',
|
||||
'locationBottom': 'sv_qstn_error_bottom'
|
||||
},
|
||||
'boolean': {
|
||||
'root': 'sv_qbln form-inline checkbox',
|
||||
'item': '',
|
||||
'label': '',
|
||||
'materialDecorator': 'checkbox-material'
|
||||
},
|
||||
'checkbox': {
|
||||
'root': 'sv_qcbc sv_qcbx form-inline',
|
||||
'item': 'checkbox',
|
||||
'itemControl': '',
|
||||
'controlLabel': '',
|
||||
'materialDecorator': 'checkbox-material',
|
||||
'other': 'sv_q_checkbox_other skillbox-input',
|
||||
'column': 'sv_q_select_column'
|
||||
},
|
||||
'comment': 'survey__input skillbox-input',
|
||||
'dropdown': {
|
||||
'root': '',
|
||||
'control': 'skillbox-input',
|
||||
'other': 'sv_q_dd_other skillbox-input'
|
||||
},
|
||||
'html': {
|
||||
'root': ''
|
||||
},
|
||||
'matrix': {
|
||||
'root': 'table table-striped',
|
||||
'label': 'sv_q_m_label',
|
||||
'cellText': 'sv_q_m_cell_text',
|
||||
'cellTextSelected': 'sv_q_m_cell_selected bg-primary',
|
||||
'cellLabel': 'sv_q_m_cell_label'
|
||||
},
|
||||
'matrixdropdown': {
|
||||
'root': 'table'
|
||||
},
|
||||
'matrixdynamic': {
|
||||
'root': 'table',
|
||||
'button': 'button',
|
||||
'buttonAdd': '',
|
||||
'buttonRemove': '',
|
||||
'iconAdd': '',
|
||||
'iconRemove': ''
|
||||
},
|
||||
'paneldynamic': {
|
||||
'root': '',
|
||||
'button': 'button',
|
||||
'buttonPrev': '',
|
||||
'buttonNext': '',
|
||||
'buttonAdd': '',
|
||||
'buttonRemove': ''
|
||||
},
|
||||
'multipletext': {
|
||||
'root': 'table',
|
||||
'itemTitle': '',
|
||||
'itemValue': 'sv_q_mt_item_value skillbox-input'
|
||||
},
|
||||
'radiogroup': {
|
||||
'root': 'sv_qcbc form-inline',
|
||||
'item': 'radio',
|
||||
'label': '',
|
||||
'itemControl': '',
|
||||
'controlLabel': '',
|
||||
'materialDecorator': 'circle',
|
||||
'other': 'sv_q_radiogroup_other skillbox-input',
|
||||
'clearButton': 'sv_q_radiogroup_clear button',
|
||||
'column': 'sv_q_select_column'
|
||||
},
|
||||
'imagepicker': {
|
||||
'root': 'sv_imgsel',
|
||||
'item': 'sv_q_imgsel',
|
||||
'label': 'sv_q_imgsel_label',
|
||||
'itemControl': 'sv_q_imgsel_control_item',
|
||||
'image': 'sv_q_imgsel_image',
|
||||
'itemText': 'sv_q_imgsel_text',
|
||||
'clearButton': 'sv_q_radiogroup_clear'
|
||||
},
|
||||
'rating': {
|
||||
'root': 'btn-group',
|
||||
'item': 'btn btn-default btn-secondary',
|
||||
'selected': 'active',
|
||||
'minText': 'sv_q_rating_min_text',
|
||||
'itemText': 'sv_q_rating_item_text',
|
||||
'maxText': 'sv_q_rating_max_text'
|
||||
},
|
||||
'text': 'survey__input skillbox-input',
|
||||
'expression': 'survey__input skillbox-input',
|
||||
'file': {
|
||||
'root': 'sv_q_file',
|
||||
'placeholderInput': 'sv_q_file_placeholder',
|
||||
'preview': 'sv_q_file_preview',
|
||||
'removeButton': 'sv_q_file_remove_button',
|
||||
'fileInput': 'sv_q_file_input',
|
||||
'removeFile': 'sv_q_file_remove'
|
||||
},
|
||||
'saveData': {
|
||||
'root': '',
|
||||
'saving': 'alert alert-info',
|
||||
'error': 'alert alert-danger',
|
||||
'success': 'alert alert-success',
|
||||
'saveAgainButton': ''
|
||||
},
|
||||
'window': {
|
||||
'root': 'modal-content',
|
||||
'body': 'modal-body',
|
||||
'header': {
|
||||
'root': 'modal-header panel-title',
|
||||
'title': 'pull-left',
|
||||
'button': 'glyphicon pull-right',
|
||||
'buttonExpanded': 'glyphicon pull-right glyphicon-chevron-up',
|
||||
'buttonCollapsed': 'glyphicon pull-right glyphicon-chevron-down'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -15,6 +15,8 @@ from objectives.mutations import ObjectiveMutations
|
|||
from objectives.schema import ObjectivesQuery
|
||||
from portfolio.mutations import PortfolioMutations
|
||||
from portfolio.schema import PortfolioQuery
|
||||
from surveys.schema import SurveysQuery
|
||||
from surveys.mutations import SurveysMutations
|
||||
from rooms.mutations import RoomMutations
|
||||
from rooms.schema import RoomsQuery
|
||||
from users.schema import UsersQuery
|
||||
|
|
@ -22,7 +24,7 @@ from users.mutations import ProfileMutations
|
|||
|
||||
|
||||
class Query(UsersQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery, StudentSubmissionQuery,
|
||||
BasicKnowledgeQuery, PortfolioQuery, MyActivityQuery, graphene.ObjectType):
|
||||
BasicKnowledgeQuery, PortfolioQuery, MyActivityQuery, SurveysQuery, graphene.ObjectType):
|
||||
node = relay.Node.Field()
|
||||
|
||||
if settings.DEBUG:
|
||||
|
|
@ -30,7 +32,7 @@ class Query(UsersQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery
|
|||
|
||||
|
||||
class Mutation(BookMutations, RoomMutations, AssignmentMutations, ObjectiveMutations, CoreMutations, PortfolioMutations,
|
||||
ProfileMutations, graphene.ObjectType):
|
||||
ProfileMutations, SurveysMutations, graphene.ObjectType):
|
||||
|
||||
if settings.DEBUG:
|
||||
debug = graphene.Field(DjangoDebug, name='__debug')
|
||||
|
|
|
|||
|
|
@ -56,6 +56,12 @@ def get_graphql_mutation(filename):
|
|||
return mutation
|
||||
|
||||
|
||||
def get_by_id(model, **kwargs):
|
||||
id = kwargs.get('id')
|
||||
|
||||
return get_object(model, id) if id is not None else None
|
||||
|
||||
|
||||
def get_by_id_or_slug(model, **kwargs):
|
||||
slug = kwargs.get('slug')
|
||||
id = kwargs.get('id')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
from django.core.management import BaseCommand
|
||||
|
||||
from portfolio.factories import ProjectFactory
|
||||
from portfolio.models import ProjectEntry
|
||||
from surveys.models import Survey
|
||||
from users.models import User
|
||||
|
||||
survey_data = {
|
||||
"pages": [
|
||||
{
|
||||
"elements": [
|
||||
{
|
||||
"description": "Max hat Ende Monat noch Fr. 20.\u2013 \u00fcbrig, die er gespart hat, um mit seinem besten Kumpel, der ein halbes Jahr im Ausland verweilte, Billard spielen zu gehen. Doch dann bittet ihn seine j\u00fcngere Schwester um Geld. Sie hat ein unverhofftes Date mit einem jungen Mann, in den sie sich bereits vor Monaten unsterblich verliebt hat. Leider ist ihr Kontostand aber bereits auf Null.",
|
||||
"elements": [
|
||||
{
|
||||
"name": "A: Max gibt ihr das Geld und muss das Billardspiel absagen.",
|
||||
"placeHolder": "Passende Tugenden erfassen...",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "question2",
|
||||
"placeHolder": "Passende Tugenden erfassen...",
|
||||
"title": "B: Max gibt ihr das Geld nicht und geht Billard spielen.",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"name": "Fall 1",
|
||||
"title": "Fall 1",
|
||||
"type": "panel"
|
||||
}
|
||||
],
|
||||
"name": "Seite 1"
|
||||
},
|
||||
{
|
||||
"elements": [
|
||||
{
|
||||
"description": "Auf der Autobahn brennt ein Lastwagen, der jederzeit explodieren kann. Silvio, dem Fahrer, bleiben nur noch wenige Minuten: Entweder bringt er seinen ohnm\u00e4chtig gewordenen Mitfahrer in Sicherheit oder er sperrt die Strasse ab, die nach wie vor dicht befahren wird.",
|
||||
"elements": [
|
||||
{
|
||||
"name": "question1",
|
||||
"placeHolder": "Passende Tugenden erfassen...",
|
||||
"title": "A: Silvio bringt seinen Mitfahrer in Sicherheit.",
|
||||
"type": "text",
|
||||
"useDisplayValuesInTitle": False
|
||||
},
|
||||
{
|
||||
"name": "question3",
|
||||
"placeHolder": "Passende Tugenden erfassen...",
|
||||
"title": "B: Silvio sperrt die Strasse ab.",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"name": "panel1",
|
||||
"title": "Fall 2",
|
||||
"type": "panel"
|
||||
}
|
||||
],
|
||||
"name": "Seite 2"
|
||||
}
|
||||
],
|
||||
"showQuestionNumbers": "off"
|
||||
}
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
def handle(self, *args, **options):
|
||||
self.stdout.write("Clearing surveys")
|
||||
Survey.objects.all().delete()
|
||||
self.stdout.write("Creating survey")
|
||||
|
||||
Survey.objects.create(
|
||||
title='Test',
|
||||
data=survey_data,
|
||||
pk=1
|
||||
)
|
||||
|
|
@ -53,6 +53,7 @@ INSTALLED_APPS = [
|
|||
'basicknowledge',
|
||||
'portfolio',
|
||||
'statistics',
|
||||
'surveys',
|
||||
|
||||
'wagtail.contrib.forms',
|
||||
'wagtail.contrib.redirects',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
# Generated by Django 2.0.6 on 2019-06-17 11:15
|
||||
|
||||
from django.db import migrations
|
||||
import wagtail.core.blocks
|
||||
import wagtail.core.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('rooms', '0004_auto_20190210_2125'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='roomentry',
|
||||
name='contents',
|
||||
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock())])), ('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())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())]))], blank=True, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import json
|
||||
import logging
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.postgres.fields import JSONField
|
||||
from django.forms import widgets
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Register your models here.
|
||||
from surveys.models import Survey, Answer
|
||||
|
||||
|
||||
class PrettyJSONWidget(widgets.Textarea):
|
||||
|
||||
def format_value(self, value):
|
||||
try:
|
||||
value = json.dumps(json.loads(value), indent=2, sort_keys=True)
|
||||
# these lines will try to adjust size of TextArea to fit to content
|
||||
row_lengths = [len(r) for r in value.split('\n')]
|
||||
self.attrs['rows'] = min(max(len(row_lengths) + 2, 10), 30)
|
||||
self.attrs['cols'] = min(max(max(row_lengths) + 2, 40), 120)
|
||||
return value
|
||||
except Exception as e:
|
||||
logger.warning("Error while formatting JSON: {}".format(e))
|
||||
return super(PrettyJSONWidget, self).format_value(value)
|
||||
|
||||
|
||||
class JSONAdmin(admin.ModelAdmin):
|
||||
formfield_overrides = {
|
||||
JSONField: {'widget': PrettyJSONWidget}
|
||||
}
|
||||
|
||||
@admin.register(Survey)
|
||||
class SurveyAdmin(JSONAdmin):
|
||||
pass
|
||||
|
||||
@admin.register(Answer)
|
||||
class AnswerAdmin(JSONAdmin):
|
||||
pass
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SurveysConfig(AppConfig):
|
||||
name = 'surveys'
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import graphene
|
||||
from graphene import InputObjectType
|
||||
|
||||
class UpdateAnswerArgument(InputObjectType):
|
||||
survey_id = graphene.ID(required=True)
|
||||
data = graphene.String(required=True)
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 2.0.6 on 2019-06-17 11:15
|
||||
|
||||
import django.contrib.postgres.fields.jsonb
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Survey',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=255)),
|
||||
('data', django.contrib.postgres.fields.jsonb.JSONField()),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# Generated by Django 2.0.6 on 2019-06-27 14:35
|
||||
|
||||
from django.conf import settings
|
||||
import django.contrib.postgres.fields.jsonb
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('surveys', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Answer',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('data', django.contrib.postgres.fields.jsonb.JSONField()),
|
||||
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
('survey', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='surveys.Survey')),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.db import models
|
||||
from django.contrib.postgres.fields import JSONField
|
||||
|
||||
|
||||
class Survey(models.Model):
|
||||
title = models.CharField(max_length=255)
|
||||
data = JSONField()
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
class Answer(models.Model):
|
||||
owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='answers')
|
||||
data = JSONField()
|
||||
survey = models.ForeignKey(Survey, on_delete=models.CASCADE, related_name='answers')
|
||||
|
||||
def __str__(self):
|
||||
return '{} - {}'.format(self.owner.username, self.survey.title)
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import graphene
|
||||
import json
|
||||
from graphene import relay
|
||||
|
||||
from api.utils import get_object
|
||||
from surveys.inputs import UpdateAnswerArgument
|
||||
from surveys.models import Survey, Answer
|
||||
from surveys.schema import AnswerNode
|
||||
|
||||
|
||||
class UpdateAnswer(relay.ClientIDMutation):
|
||||
class Input:
|
||||
answer = graphene.Argument(UpdateAnswerArgument)
|
||||
|
||||
answer = graphene.Field(AnswerNode)
|
||||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||
user = info.context.user
|
||||
|
||||
answer = kwargs.get('answer')
|
||||
survey_id = answer.get('survey_id')
|
||||
data = json.loads(answer.get('data'))
|
||||
survey = get_object(Survey, survey_id)
|
||||
|
||||
try:
|
||||
answer = survey.answers.get(owner=user)
|
||||
answer.data = data
|
||||
answer.save()
|
||||
except Answer.DoesNotExist:
|
||||
answer = Answer.objects.create(owner=user, survey=survey, data=data)
|
||||
|
||||
return cls(answer=answer)
|
||||
|
||||
|
||||
class SurveysMutations:
|
||||
update_answer = UpdateAnswer.Field()
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import graphene
|
||||
from graphene import relay
|
||||
from graphene_django import DjangoObjectType
|
||||
from graphene_django.filter import DjangoFilterConnectionField
|
||||
|
||||
from api.utils import get_by_id
|
||||
from surveys.models import Answer
|
||||
from .models import Survey
|
||||
|
||||
|
||||
class AnswerNode(DjangoObjectType):
|
||||
pk = graphene.Int()
|
||||
|
||||
class Meta:
|
||||
model = Answer
|
||||
interfaces = (relay.Node,)
|
||||
|
||||
def resolve_pk(self, *args, **kwargs):
|
||||
return self.id
|
||||
|
||||
|
||||
class SurveyNode(DjangoObjectType):
|
||||
pk = graphene.Int()
|
||||
answer = graphene.Field(AnswerNode)
|
||||
|
||||
class Meta:
|
||||
model = Survey
|
||||
filter_fields = []
|
||||
interfaces = (relay.Node,)
|
||||
|
||||
def resolve_pk(self, *args, **kwargs):
|
||||
return self.id
|
||||
|
||||
def resolve_answer(self, info, **kwargs):
|
||||
user = info.context.user
|
||||
try:
|
||||
return Answer.objects.get(owner=user, survey=self)
|
||||
except Answer.DoesNotExist:
|
||||
return None
|
||||
|
||||
class SurveysQuery(object):
|
||||
survey = graphene.Field(SurveyNode, id=graphene.ID())
|
||||
surveys = DjangoFilterConnectionField(SurveyNode)
|
||||
|
||||
def resolve_surveys(self, info, **kwargs):
|
||||
return Survey.objects.all()
|
||||
|
||||
def resolve_survey(self, info, **kwargs):
|
||||
return get_by_id(Survey, **kwargs)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
Loading…
Reference in New Issue