Merged in feature/surveyjs (pull request #17)

Feature/surveyjs
This commit is contained in:
Ramon Wenger 2019-07-04 11:31:12 +00:00 committed by Christian Cueni
commit 9dca627465
30 changed files with 760 additions and 24 deletions

View File

@ -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')
});
});

View File

@ -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';

View File

@ -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",

View File

@ -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",

View File

@ -0,0 +1,20 @@
mutation UpdateAnswer($input:UpdateAnswerInput!) {
updateAnswer(input:$input){
answer {
id
data
}
}
}
# input
#{
# "input": {
# "answer": {
# "surveyId": "U3VydmV5Tm9kZTox",
# "data": "{\"some\": \"json\"}"
# },
# }
#}

View File

@ -0,0 +1,10 @@
query SurveyQuery($id: ID!) {
survey(id: $id) {
id
title
data
answer {
data
}
}
}

View File

@ -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;

View File

@ -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>

103
client/src/pages/survey.vue Normal file
View File

@ -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>

View File

@ -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}
];

View File

@ -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;
}
}

View File

@ -14,3 +14,4 @@
@import "article";
@import "actions";
@import "top-navigation";
@import "survey";

161
client/src/survey.config.js Normal file
View File

@ -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'
}
}
};

View File

@ -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')

View File

@ -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')

View File

@ -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
)

View File

@ -53,6 +53,7 @@ INSTALLED_APPS = [
'basicknowledge',
'portfolio',
'statistics',
'surveys',
'wagtail.contrib.forms',
'wagtail.contrib.redirects',

View File

@ -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),
),
]

View File

40
server/surveys/admin.py Normal file
View File

@ -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

5
server/surveys/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class SurveysConfig(AppConfig):
name = 'surveys'

6
server/surveys/inputs.py Normal file
View File

@ -0,0 +1,6 @@
import graphene
from graphene import InputObjectType
class UpdateAnswerArgument(InputObjectType):
survey_id = graphene.ID(required=True)
data = graphene.String(required=True)

View File

@ -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()),
],
),
]

View File

@ -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')),
],
),
]

View File

19
server/surveys/models.py Normal file
View File

@ -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)

View File

@ -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()

49
server/surveys/schema.py Normal file
View File

@ -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)

3
server/surveys/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
server/surveys/views.py Normal file
View File

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.