Merged in feature/teams (pull request #81)
Feature/teams Approved-by: Christian Cueni
This commit is contained in:
commit
da2253a73d
|
|
@ -15,6 +15,7 @@
|
|||
},
|
||||
"onboardingVisited": false,
|
||||
"__typename": "UserNode",
|
||||
"permissions": []
|
||||
"permissions": [],
|
||||
"team": null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,16 @@ const schema = require('../../fixtures/schema.json');
|
|||
const me = require('../../fixtures/me.new-student.json');
|
||||
|
||||
describe('New student', () => {
|
||||
it('shows "Enter Code" page and adds the user to a class', () => {
|
||||
before(() => {
|
||||
cy.server();
|
||||
|
||||
cy.mockGraphql({
|
||||
schema: schema,
|
||||
cy.task('getSchema').then(schema => {
|
||||
cy.mockGraphql({
|
||||
schema,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('shows "Enter Code" page and adds the user to a class', () => {
|
||||
cy.apolloLogin('hansli', 'test');
|
||||
|
||||
const __typename = 'SchoolClassNode';
|
||||
|
|
@ -26,9 +29,9 @@ describe('New student', () => {
|
|||
schoolClass: {
|
||||
id,
|
||||
name,
|
||||
__typename
|
||||
}
|
||||
}
|
||||
__typename,
|
||||
},
|
||||
},
|
||||
},
|
||||
MySchoolClassQuery: {
|
||||
me: {
|
||||
|
|
@ -37,21 +40,21 @@ describe('New student', () => {
|
|||
__typename,
|
||||
name,
|
||||
id,
|
||||
members: []
|
||||
}
|
||||
}
|
||||
members: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
...mockUpdateOnboardingProgress()
|
||||
}
|
||||
...mockUpdateOnboardingProgress(),
|
||||
},
|
||||
});
|
||||
|
||||
cy.visit('/');
|
||||
cy.get('[data-cy=join-class-title]').should('contain', 'Einer Klasse beitreten');
|
||||
cy.get('[data-cy=input-class-code]').type('XXXX');
|
||||
cy.get('[data-cy=join-class]').click();
|
||||
cy.get('[data-cy=join-form-title]').should('contain', 'Einer Klasse beitreten');
|
||||
cy.get('[data-cy=input-form-code]').type('XXXX');
|
||||
cy.get('[data-cy=join-form-confirm]').click();
|
||||
cy.skipOnboarding();
|
||||
cy.get('[data-cy=user-widget-avatar]').click();
|
||||
cy.get('[data-cy=class-list-link]').click();
|
||||
cy.get('[data-cy=class-list-title]').should('contain', 'Klassenliste');
|
||||
cy.get('[data-cy=group-list-title]').should('contain', 'Klassenliste');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
import {mockUpdateOnboardingProgress} from '../../support/helpers';
|
||||
|
||||
const schema = require('../../fixtures/schema.json');
|
||||
const me = require('../../fixtures/me.join-class.json');
|
||||
|
||||
describe('Onboarding', () => {
|
||||
beforeEach(() => {
|
||||
cy.server();
|
||||
|
||||
cy.mockGraphql({
|
||||
schema: schema,
|
||||
cy.task('getSchema').then(schema => {
|
||||
cy.mockGraphql({
|
||||
schema,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,96 @@
|
|||
const schema = require('../../fixtures/schema.json');
|
||||
const me = require('../../fixtures/me.new-student.json');
|
||||
|
||||
const operations = {
|
||||
MeQuery: {
|
||||
me: {
|
||||
id: 'VXNlck5vZGU6NQ==',
|
||||
onboardingVisited: true,
|
||||
permissions: [],
|
||||
},
|
||||
},
|
||||
ProjectsQuery: {
|
||||
projects: {
|
||||
edges: [{
|
||||
node: {
|
||||
id: 'UHJvamVjdE5vZGU6MzM=',
|
||||
title: 'Groot',
|
||||
appearance: 'red',
|
||||
'description': 'I am Groot',
|
||||
'slug': 'groot',
|
||||
'objectives': 'Be Groot\nBe awesome',
|
||||
'final': false,
|
||||
'student': {
|
||||
'firstName': 'Rahel',
|
||||
'lastName': 'Cueni',
|
||||
'id': 'VXNlck5vZGU6NQ==',
|
||||
'avatarUrl': '',
|
||||
'__typename': 'UserNode',
|
||||
},
|
||||
'entriesCount': 2,
|
||||
'__typename': 'ProjectNode',
|
||||
},
|
||||
'__typename': 'ProjectNodeEdge',
|
||||
}],
|
||||
'__typename': 'ProjectNodeConnection',
|
||||
},
|
||||
},
|
||||
ProjectQuery: {
|
||||
'project': {
|
||||
'id': 'UHJvamVjdE5vZGU6MzY=',
|
||||
'title': 'Groot',
|
||||
'appearance': 'yellow',
|
||||
'description': 'I am Groot',
|
||||
'slug': 'groot',
|
||||
'objectives': 'Be Groot\nBe awesome',
|
||||
'final': false,
|
||||
'student': {
|
||||
'firstName': 'Rahel',
|
||||
'lastName': 'Cueni',
|
||||
'id': 'VXNlck5vZGU6NQ==',
|
||||
'avatarUrl': '',
|
||||
'__typename': 'UserNode',
|
||||
},
|
||||
'entriesCount': 1,
|
||||
'__typename': 'ProjectNode',
|
||||
'entries': {
|
||||
'edges': [{
|
||||
'node': {
|
||||
'id': 'UHJvamVjdEVudHJ5Tm9kZTo2NQ==',
|
||||
'activity': 'Kill Thanos',
|
||||
'reflection': 'He sucks',
|
||||
'nextSteps': 'Go for the head',
|
||||
'documentUrl': '',
|
||||
'__typename': 'ProjectEntryNode',
|
||||
'created': '2020-01-20T15:20:31.262510+00:00',
|
||||
},
|
||||
'__typename': 'ProjectEntryNodeEdge',
|
||||
}],
|
||||
'__typename': 'ProjectEntryNodeConnection',
|
||||
},
|
||||
},
|
||||
},
|
||||
AddProjectEntry: variables => ({
|
||||
addProjectEntry: {
|
||||
projectEntry: Object.assign({}, variables.input.projectEntry, {
|
||||
created: '2020-01-20T15:26:58.722773+00:00',
|
||||
}),
|
||||
errors: null,
|
||||
__typename: 'AddProjectEntryPayload',
|
||||
},
|
||||
}),
|
||||
UpdateProjectEntry: variables => ({
|
||||
updateProjectEntry: {
|
||||
projectEntry: variables.input.projectEntry,
|
||||
errors: null,
|
||||
__typename: 'UpdateProjectEntryPayload',
|
||||
},
|
||||
}),
|
||||
DeleteProjectEntry: {
|
||||
deleteProjectEntry: {
|
||||
success: true,
|
||||
__typename: 'DeleteProjectEntryPayload',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('Project Entry', () => {
|
||||
beforeEach(() => {
|
||||
|
|
@ -7,100 +98,11 @@ describe('Project Entry', () => {
|
|||
cy.fakeLogin('rahel.cueni', 'test');
|
||||
cy.server();
|
||||
|
||||
cy.mockGraphql({
|
||||
schema: schema,
|
||||
operations: {
|
||||
MeQuery: {
|
||||
me: {
|
||||
id: 'VXNlck5vZGU6NQ==',
|
||||
onboardingVisited: true,
|
||||
permissions: []
|
||||
}
|
||||
},
|
||||
ProjectsQuery: {
|
||||
projects: {
|
||||
edges: [{
|
||||
node: {
|
||||
id: 'UHJvamVjdE5vZGU6MzM=',
|
||||
title: 'Groot',
|
||||
appearance: 'red',
|
||||
'description': 'I am Groot',
|
||||
'slug': 'groot',
|
||||
'objectives': 'Be Groot\nBe awesome',
|
||||
'final': false,
|
||||
'student': {
|
||||
'firstName': 'Rahel',
|
||||
'lastName': 'Cueni',
|
||||
'id': 'VXNlck5vZGU6NQ==',
|
||||
'avatarUrl': '',
|
||||
'__typename': 'UserNode'
|
||||
},
|
||||
'entriesCount': 2,
|
||||
'__typename': 'ProjectNode'
|
||||
},
|
||||
'__typename': 'ProjectNodeEdge'
|
||||
}],
|
||||
'__typename': 'ProjectNodeConnection'
|
||||
}
|
||||
},
|
||||
ProjectQuery: {
|
||||
'project': {
|
||||
'id': 'UHJvamVjdE5vZGU6MzY=',
|
||||
'title': 'Groot',
|
||||
'appearance': 'yellow',
|
||||
'description': 'I am Groot',
|
||||
'slug': 'groot',
|
||||
'objectives': 'Be Groot\nBe awesome',
|
||||
'final': false,
|
||||
'student': {
|
||||
'firstName': 'Rahel',
|
||||
'lastName': 'Cueni',
|
||||
'id': 'VXNlck5vZGU6NQ==',
|
||||
'avatarUrl': '',
|
||||
'__typename': 'UserNode'
|
||||
},
|
||||
'entriesCount': 1,
|
||||
'__typename': 'ProjectNode',
|
||||
'entries': {
|
||||
'edges': [{
|
||||
'node': {
|
||||
'id': 'UHJvamVjdEVudHJ5Tm9kZTo2NQ==',
|
||||
'activity': 'Kill Thanos',
|
||||
'reflection': 'He sucks',
|
||||
'nextSteps': 'Go for the head',
|
||||
'documentUrl': '',
|
||||
'__typename': 'ProjectEntryNode',
|
||||
'created': '2020-01-20T15:20:31.262510+00:00'
|
||||
},
|
||||
'__typename': 'ProjectEntryNodeEdge'
|
||||
}],
|
||||
'__typename': 'ProjectEntryNodeConnection'
|
||||
}
|
||||
}
|
||||
},
|
||||
AddProjectEntry: variables => ({
|
||||
addProjectEntry: {
|
||||
projectEntry: Object.assign({}, variables.input.projectEntry, {
|
||||
created: '2020-01-20T15:26:58.722773+00:00'
|
||||
}),
|
||||
errors: null,
|
||||
__typename: 'AddProjectEntryPayload'
|
||||
}
|
||||
}),
|
||||
UpdateProjectEntry: variables => ({
|
||||
updateProjectEntry: {
|
||||
projectEntry: variables.input.projectEntry,
|
||||
errors: null,
|
||||
__typename: 'UpdateProjectEntryPayload'
|
||||
}
|
||||
}),
|
||||
DeleteProjectEntry: {
|
||||
deleteProjectEntry: {
|
||||
success: true,
|
||||
__typename: 'DeleteProjectEntryPayload'
|
||||
}
|
||||
}
|
||||
}
|
||||
cy.task('getSchema').then(schema => {
|
||||
cy.mockGraphql({
|
||||
schema,
|
||||
operations,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
const schema = require('../../fixtures/schema.json');
|
||||
const me = require('../../fixtures/me.join-class.json');
|
||||
const selectedClass = require('../../fixtures/selected-school-class.json');
|
||||
|
||||
|
|
@ -6,8 +5,10 @@ describe('Class Management', () => {
|
|||
beforeEach(() => {
|
||||
cy.server();
|
||||
|
||||
cy.mockGraphql({
|
||||
schema: schema,
|
||||
cy.task('getSchema').then(schema => {
|
||||
cy.mockGraphql({
|
||||
schema,
|
||||
});
|
||||
});
|
||||
|
||||
cy.viewport('macbook-15');
|
||||
|
|
@ -104,7 +105,7 @@ describe('Class Management', () => {
|
|||
});
|
||||
|
||||
cy.visit('/me/my-class');
|
||||
cy.get('[data-cy=school-class-member]').should('have.length', 2);
|
||||
cy.get('[data-cy=group-list-member]').should('have.length', 2);
|
||||
cy.get('[data-cy=remove-from-class]').should('have.length', 0);
|
||||
cy.get('[data-cy=add-to-class]').should('have.length', 0);
|
||||
});
|
||||
|
|
@ -187,8 +188,10 @@ describe('Teacher Class Management', () => {
|
|||
beforeEach(() => {
|
||||
cy.server();
|
||||
|
||||
cy.mockGraphql({
|
||||
schema: schema,
|
||||
cy.task('getSchema').then(schema => {
|
||||
cy.mockGraphql({
|
||||
schema,
|
||||
});
|
||||
});
|
||||
|
||||
cy.viewport('macbook-15');
|
||||
|
|
@ -229,10 +232,10 @@ describe('Teacher Class Management', () => {
|
|||
|
||||
cy.visit('/me/my-class');
|
||||
|
||||
cy.get('[data-cy=edit-class-name-link]').click();
|
||||
cy.get('[data-cy=edit-class-name-input] input').type('{selectall}{backspace}').type(className);
|
||||
cy.get('[data-cy=edit-group-name-link]').click();
|
||||
cy.get('[data-cy=edit-name-input] input').type('{selectall}{backspace}').type(className);
|
||||
cy.get('[data-cy=modal-save-button]').click();
|
||||
cy.get('[data-cy=school-class-name]').should('contain', className);
|
||||
cy.get('[data-cy=group-list-name]').should('contain', className);
|
||||
});
|
||||
|
||||
// // fixme: cache misbehaves with mequery, but only for test
|
||||
|
|
|
|||
|
|
@ -1,33 +1,37 @@
|
|||
import {mockUpdateLastModule} from '../../support/helpers';
|
||||
|
||||
const schema = require('../../fixtures/schema.json');
|
||||
const assignments = require('../../fixtures/assignments.json');
|
||||
const module = require('../../fixtures/module.json');
|
||||
const spellCheck = require('../../fixtures/spell-check.json');
|
||||
|
||||
const operations = {
|
||||
MeQuery: {
|
||||
me: {
|
||||
permissions: [],
|
||||
onboardingVisited: true,
|
||||
},
|
||||
},
|
||||
AssignmentsQuery: {
|
||||
assignments,
|
||||
},
|
||||
ModulesQuery: {
|
||||
module,
|
||||
},
|
||||
SpellCheck: {
|
||||
spellCheck,
|
||||
},
|
||||
...mockUpdateLastModule(),
|
||||
};
|
||||
|
||||
describe('Spellcheck', () => {
|
||||
before(() => {
|
||||
cy.server();
|
||||
cy.mockGraphql({
|
||||
schema: schema,
|
||||
operations: {
|
||||
MeQuery: {
|
||||
me: {
|
||||
permissions: [],
|
||||
onboardingVisited: true
|
||||
}
|
||||
},
|
||||
AssignmentsQuery: {
|
||||
assignments
|
||||
},
|
||||
ModulesQuery: {
|
||||
module
|
||||
},
|
||||
SpellCheck: {
|
||||
spellCheck
|
||||
},
|
||||
...mockUpdateLastModule()
|
||||
}
|
||||
|
||||
cy.task('getSchema').then(schema => {
|
||||
cy.mockGraphql({
|
||||
schema,
|
||||
operations,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
const schema = require('../../fixtures/schema.json');
|
||||
const module = require('../../fixtures/module.json');
|
||||
|
||||
describe('Survey', () => {
|
||||
beforeEach(() => {
|
||||
cy.server();
|
||||
|
||||
cy.mockGraphql({
|
||||
schema: schema,
|
||||
cy.task('getSchema').then(schema => {
|
||||
cy.mockGraphql({
|
||||
schema,
|
||||
});
|
||||
});
|
||||
|
||||
cy.viewport('macbook-15');
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
:is="showModal"
|
||||
v-if="showModal"/>
|
||||
<component :is="layout"/>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -32,6 +31,7 @@
|
|||
import NewNoteWizard from '@/components/notes/NewNoteWizard';
|
||||
import EditNoteWizard from '@/components/notes/EditNoteWizard';
|
||||
import EditClassNameWizard from '@/components/school-class/EditClassNameWizard';
|
||||
import EditTeamNameWizard from '@/components/profile/EditTeamNameWizard';
|
||||
import FullscreenImage from '@/components/FullscreenImage';
|
||||
import FullscreenInfographic from '@/components/FullscreenInfographic';
|
||||
import FullscreenVideo from '@/components/FullscreenVideo';
|
||||
|
|
@ -60,6 +60,7 @@
|
|||
NewNoteWizard,
|
||||
EditNoteWizard,
|
||||
EditClassNameWizard,
|
||||
EditTeamNameWizard,
|
||||
FullscreenImage,
|
||||
FullscreenInfographic,
|
||||
FullscreenVideo,
|
||||
|
|
|
|||
|
|
@ -1,115 +0,0 @@
|
|||
<template>
|
||||
<div class="school-class">
|
||||
<h2 class="school-class__heading"><span
|
||||
class="school-class__name"
|
||||
data-cy="school-class-name">{{ name }}</span>
|
||||
<edit-class-name
|
||||
v-if="teacher"
|
||||
@edit="editClassName"/>
|
||||
</h2>
|
||||
<div class="school-class__members school-class-members">
|
||||
<ul
|
||||
class="school-class-members__list simple-list simple-list--active"
|
||||
data-cy="active-class-members-list">
|
||||
<li
|
||||
:key="member.id"
|
||||
class="simple-list__item member-item"
|
||||
data-cy="school-class-member"
|
||||
v-for="member in activeMembers">
|
||||
<span class="member-item__name">{{ fullName(member) }}</span>
|
||||
<span class="member-item__role">{{ role(member) }}</span>
|
||||
<!-- <a-->
|
||||
<!-- class="member-item__action simple-list__action"-->
|
||||
<!-- data-cy="remove-from-class"-->
|
||||
<!-- v-if="teacher"-->
|
||||
<!-- @click="$emit('remove', member)">Deaktivieren</a>-->
|
||||
</li>
|
||||
</ul>
|
||||
<!-- <template v-if="inactiveMembers.length">-->
|
||||
<!-- <h3 class="school-class__inactive-heading">Deaktivierte Personen</h3>-->
|
||||
<!-- <ul data-cy="inactive-class-members-list" class="simple-list simple-list--inactive">-->
|
||||
<!-- <li-->
|
||||
<!-- class="simple-list__item member-item"-->
|
||||
<!-- data-cy="school-class-member"-->
|
||||
<!-- v-for="member in inactiveMembers"-->
|
||||
<!-- :key="member.id">-->
|
||||
<!-- <span class="member-item__name">{{fullName(member)}}</span>-->
|
||||
<!-- <span class="member-item__role">{{role(member)}}</span>-->
|
||||
<!-- <a-->
|
||||
<!-- class="member-item__action simple-list__action"-->
|
||||
<!-- data-cy="add-to-class"-->
|
||||
<!-- v-if="teacher"-->
|
||||
<!-- @click="$emit('add', member)">Aktivieren</a>-->
|
||||
<!-- </li>-->
|
||||
<!-- </ul>-->
|
||||
<!-- </template>-->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EditClassName from '@/components/school-class/EditClassName';
|
||||
|
||||
export default {
|
||||
props: ['members', 'name', 'teacher', 'id'],
|
||||
|
||||
components: {
|
||||
EditClassName
|
||||
},
|
||||
computed: {
|
||||
activeMembers() {
|
||||
return this.members.filter(member => member.active);
|
||||
},
|
||||
inactiveMembers() {
|
||||
return this.members.filter(member => !member.active);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
fullName(member) {
|
||||
return `${member.firstName} ${member.lastName}`;
|
||||
},
|
||||
role({isTeacher}) {
|
||||
return isTeacher ? 'Lehrperson' : 'Schüler';
|
||||
},
|
||||
editClassName() {
|
||||
this.$store.dispatch('editClassName');
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
|
||||
.school-class {
|
||||
&__inactive-heading {
|
||||
@include heading-4;
|
||||
margin-bottom: $small-spacing;
|
||||
}
|
||||
|
||||
&__name {
|
||||
@include heading-2;
|
||||
}
|
||||
}
|
||||
|
||||
.member-item {
|
||||
&__name {
|
||||
font-family: $sans-serif-font-family;
|
||||
font-weight: $font-weight-bold;
|
||||
flex: 2 1 auto;
|
||||
}
|
||||
|
||||
&__role {
|
||||
flex: 0 1 110px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&__action {
|
||||
flex: 0 1 110px;
|
||||
padding-left: $large-spacing;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<a
|
||||
class="edit-class-name"
|
||||
data-cy="edit-class-name-link"
|
||||
class="edit-group-name"
|
||||
data-cy="edit-group-name-link"
|
||||
@click="$emit('edit')">
|
||||
<pen-icon class="edit-class-name__icon"/>
|
||||
<pen-icon class="edit-group-name__icon"/>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
|
|
@ -18,9 +18,9 @@
|
|||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "~styles/_variables.scss";
|
||||
|
||||
.edit-class-name {
|
||||
.edit-group-name {
|
||||
&__icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<template>
|
||||
<modal
|
||||
:hide-header="false"
|
||||
:small="true">
|
||||
<h4 slot="header">{{ type }} bearbeiten</h4>
|
||||
<modal-input
|
||||
:value="name"
|
||||
placeholder="Klassenname"
|
||||
data-cy="edit-name-input"
|
||||
@input="$emit('input', $event)"
|
||||
/>
|
||||
<div slot="footer">
|
||||
<a
|
||||
class="button button--primary"
|
||||
data-cy="modal-save-button"
|
||||
@click="$emit('save')">Speichern</a>
|
||||
<a
|
||||
class="button"
|
||||
@click="$emit('cancel')">Abbrechen</a>
|
||||
</div>
|
||||
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Modal from '@/components/Modal';
|
||||
import ModalInput from '@/components/ModalInput';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Modal,
|
||||
ModalInput
|
||||
},
|
||||
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<template>
|
||||
<edit-name-wizard
|
||||
:name="name"
|
||||
type="Team"
|
||||
@input="name = $event"
|
||||
@cancel="hideModal"
|
||||
@save="save"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import me from '@/mixins/me';
|
||||
import EditNameWizard from '@/components/profile/EditNameWizard';
|
||||
import UPDATE_TEAM_MUTATION from '@/graphql/gql/mutations/updateTeam.gql';
|
||||
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
||||
|
||||
export default {
|
||||
mixins: [me],
|
||||
|
||||
components: {
|
||||
EditNameWizard,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
name: ''
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
team() {
|
||||
return this.me.team;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.name = this.team ? this.team.name : '';
|
||||
},
|
||||
|
||||
methods: {
|
||||
save() {
|
||||
this.$apollo.mutate({
|
||||
mutation: UPDATE_TEAM_MUTATION,
|
||||
variables: {
|
||||
input: {
|
||||
name: this.name,
|
||||
id: this.team.id
|
||||
}
|
||||
},
|
||||
update(store, {data: {updateTeam: {team: {name}}}}) {
|
||||
const query = ME_QUERY;
|
||||
const data = store.readQuery({query});
|
||||
data.me.team.name = name;
|
||||
store.writeQuery({query, data});
|
||||
}
|
||||
});
|
||||
this.hideModal();
|
||||
},
|
||||
hideModal() {
|
||||
this.$store.dispatch('hideModal');
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~styles/helpers';
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
<template>
|
||||
<div class="group-list">
|
||||
<h1
|
||||
class="group-list__header"
|
||||
data-cy="group-list-title">{{ title }}</h1>
|
||||
<router-link
|
||||
:to="showCodeRoute"
|
||||
class="group-list__code-link button button--primary"
|
||||
v-if="showCode">Zugangscode anzeigen
|
||||
</router-link>
|
||||
|
||||
<div class="group-list__content">
|
||||
<h2 class="group-list__heading"><span
|
||||
class="group-list__name"
|
||||
data-cy="group-list-name">{{ name }}</span>
|
||||
<edit-group-name
|
||||
v-if="canEdit"
|
||||
@edit="$emit('edit')"/>
|
||||
</h2>
|
||||
<div class="group-list__members group-list-members">
|
||||
<ul
|
||||
class="group-list-members__list simple-list simple-list--active"
|
||||
data-cy="active-class-members-list">
|
||||
<li
|
||||
:key="member.id"
|
||||
class="simple-list__item member-item"
|
||||
data-cy="group-list-member"
|
||||
v-for="member in activeMembers">
|
||||
<span class="member-item__name">{{ fullName(member) }}</span>
|
||||
<span class="member-item__role">{{ role(member) }}</span>
|
||||
<!-- <a-->
|
||||
<!-- class="member-item__action simple-list__action"-->
|
||||
<!-- data-cy="remove-from-class"-->
|
||||
<!-- v-if="teacher"-->
|
||||
<!-- @click="$emit('remove', member)">Deaktivieren</a>-->
|
||||
</li>
|
||||
</ul>
|
||||
<!-- <template v-if="inactiveMembers.length">-->
|
||||
<!-- <h3 class="group-list__inactive-heading">Deaktivierte Personen</h3>-->
|
||||
<!-- <ul data-cy="inactive-class-members-list" class="simple-list simple-list--inactive">-->
|
||||
<!-- <li-->
|
||||
<!-- class="simple-list__item member-item"-->
|
||||
<!-- data-cy="group-list-member"-->
|
||||
<!-- v-for="member in inactiveMembers"-->
|
||||
<!-- :key="member.id">-->
|
||||
<!-- <span class="member-item__name">{{fullName(member)}}</span>-->
|
||||
<!-- <span class="member-item__role">{{role(member)}}</span>-->
|
||||
<!-- <a-->
|
||||
<!-- class="member-item__action simple-list__action"-->
|
||||
<!-- data-cy="add-to-class"-->
|
||||
<!-- v-if="teacher"-->
|
||||
<!-- @click="$emit('add', member)">Aktivieren</a>-->
|
||||
<!-- </li>-->
|
||||
<!-- </ul>-->
|
||||
<!-- </template>-->
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EditGroupName from '@/components/profile/EditGroupName';
|
||||
|
||||
export default {
|
||||
// props: ['active-members', 'inactive-members', 'name', 'canEdit'],
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
showCode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showCodeRoute: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
activeMembers: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
inactiveMembers: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
canEdit: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
EditGroupName,
|
||||
},
|
||||
|
||||
methods: {
|
||||
fullName(member) {
|
||||
return `${member.firstName} ${member.lastName}`;
|
||||
},
|
||||
role({isTeacher}) {
|
||||
if (isTeacher === undefined) {
|
||||
return '';
|
||||
}
|
||||
return isTeacher ? 'Lehrperson' : 'Schüler';
|
||||
},
|
||||
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "~styles/helpers";
|
||||
|
||||
.group-list {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
grid-template-rows: auto auto;
|
||||
grid-template-areas: "h b" "c c";
|
||||
|
||||
&__header {
|
||||
grid-area: h;
|
||||
}
|
||||
|
||||
&__code-link {
|
||||
grid-area: b;
|
||||
justify-self: end;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
&__content {
|
||||
grid-area: c;
|
||||
width: 100%;
|
||||
margin-bottom: $large-spacing;
|
||||
}
|
||||
|
||||
&__inactive-heading {
|
||||
@include heading-4;
|
||||
margin-bottom: $small-spacing;
|
||||
}
|
||||
|
||||
&__name {
|
||||
@include heading-2;
|
||||
}
|
||||
}
|
||||
|
||||
.member-item {
|
||||
&__name {
|
||||
font-family: $sans-serif-font-family;
|
||||
font-weight: $font-weight-bold;
|
||||
flex: 2 1 auto;
|
||||
}
|
||||
|
||||
&__role {
|
||||
flex: 0 1 110px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&__action {
|
||||
flex: 0 1 110px;
|
||||
padding-left: $large-spacing;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
<template>
|
||||
<div>
|
||||
<h1 data-cy="join-form-title">{{ title }}</h1>
|
||||
<div>
|
||||
<div class="skillboxform-input">
|
||||
<label
|
||||
for="join-code"
|
||||
class="skillboxform-input__label">{{ labelText }}</label>
|
||||
<input
|
||||
:class="{'skillboxform-input__input--error': error}"
|
||||
:value="value"
|
||||
class="skillbox-input skillboxform-input__input"
|
||||
data-cy="input-form-code"
|
||||
id="join-code"
|
||||
@input="$emit('input', $event)">
|
||||
<small
|
||||
class="skillboxform-input__error"
|
||||
v-if="error"
|
||||
>{{ error }}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a
|
||||
class="button button--primary button--big"
|
||||
data-cy="join-form-confirm"
|
||||
@click="$emit('confirm', value)">{{ okText }}</a>
|
||||
<button
|
||||
class="button button--big"
|
||||
data-cy="join-form-cancel"
|
||||
@click="$emit('cancel')">{{ cancelText }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
error: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
okText: {
|
||||
type: String,
|
||||
default: 'Klasse beitreten'
|
||||
},
|
||||
labelText: {
|
||||
type: String,
|
||||
default: 'Zugangscode eingeben'
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: 'Abmelden'
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~styles/helpers';
|
||||
|
||||
</style>
|
||||
|
|
@ -19,6 +19,15 @@
|
|||
class="profile-sidebar__link">Meine Aktivitäten
|
||||
</router-link>
|
||||
</div>
|
||||
<div
|
||||
class="profile-sidebar__item"
|
||||
v-if="me.isTeacher"
|
||||
@click="close">
|
||||
<router-link
|
||||
:to="myTeamPage"
|
||||
class="profile-sidebar__link">Mein Team
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-sidebar__section">
|
||||
<div class="profile-sidebar__item">
|
||||
|
|
@ -56,31 +65,40 @@
|
|||
|
||||
import ClassSelectionWidget from '@/components/school-class/ClassSelectionWidget';
|
||||
|
||||
import sidebarMixin from '@/mixins/sidebar';
|
||||
import sidebar from '@/mixins/sidebar';
|
||||
import me from '@/mixins/me';
|
||||
import LogoutWidget from '@/components/LogoutWidget';
|
||||
import {MY_TEAM} from '@/router/me.names';
|
||||
|
||||
export default {
|
||||
|
||||
mixins: [sidebarMixin],
|
||||
mixins: [sidebar, me],
|
||||
|
||||
components: {
|
||||
LogoutWidget,
|
||||
ClassSelectionWidget,
|
||||
ProfileWidget,
|
||||
Cross
|
||||
Cross,
|
||||
},
|
||||
|
||||
computed: {
|
||||
myTeamPage() {
|
||||
return {
|
||||
name: MY_TEAM,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
close() {
|
||||
this.closeSidebar('profile');
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
@import "~styles/helpers";
|
||||
|
||||
$desktop-width: 333px;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<div class="show-code">
|
||||
<h2 class="show-code__title">Zugangscode {{ type }} {{ name }}</h2>
|
||||
<h1 class="show-code__code">{{ code }}</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
code: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~styles/helpers';
|
||||
|
||||
.show-code {
|
||||
&__title {
|
||||
@include regular-text;
|
||||
margin-bottom: 2*$large-spacing;
|
||||
}
|
||||
|
||||
&__code {
|
||||
font-size: toRem(120px);
|
||||
letter-spacing: toRem(20px);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,39 +1,20 @@
|
|||
<template>
|
||||
<modal
|
||||
:hide-header="false"
|
||||
:small="true"
|
||||
title="Hello">
|
||||
<h4 slot="header">Klasse bearbeiten</h4>
|
||||
<modal-input
|
||||
:value="name"
|
||||
placeholder="Klassenname"
|
||||
data-cy="edit-class-name-input"
|
||||
@input="name = $event"
|
||||
/>
|
||||
<div slot="footer">
|
||||
<a
|
||||
class="button button--primary"
|
||||
data-cy="modal-save-button"
|
||||
@click="save">Speichern</a>
|
||||
<a
|
||||
class="button"
|
||||
@click="hide">Abbrechen</a>
|
||||
</div>
|
||||
|
||||
</modal>
|
||||
<edit-name-wizard
|
||||
:name="name"
|
||||
type="Klasse"
|
||||
@input="name = $event"
|
||||
@cancel="hideModal"
|
||||
@save="save" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Modal from '@/components/Modal';
|
||||
import ModalInput from '@/components/ModalInput';
|
||||
|
||||
import MY_SCHOOL_CLASS_QUERY from '@/graphql/gql/mySchoolClass.gql';
|
||||
import UPDATE_SCHOOL_CLASS_MUTATION from '@/graphql/gql/mutations/updateSchoolClass.gql';
|
||||
import EditNameWizard from '@/components/profile/EditNameWizard';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Modal,
|
||||
ModalInput
|
||||
EditNameWizard,
|
||||
},
|
||||
|
||||
data() {
|
||||
|
|
@ -63,9 +44,9 @@
|
|||
store.writeQuery({query, data});
|
||||
}
|
||||
});
|
||||
this.hide();
|
||||
this.hideModal();
|
||||
},
|
||||
hide() {
|
||||
hideModal() {
|
||||
this.$store.dispatch('hideModal');
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,6 +2,16 @@
|
|||
query MeQuery {
|
||||
me {
|
||||
...UserParts
|
||||
team {
|
||||
name
|
||||
code
|
||||
id
|
||||
members {
|
||||
firstName
|
||||
lastName
|
||||
id
|
||||
}
|
||||
}
|
||||
isTeacher
|
||||
permissions
|
||||
onboardingVisited
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
mutation CreateTeamMutation($input: CreateTeamInput!) {
|
||||
createTeam(input: $input) {
|
||||
success
|
||||
team {
|
||||
name
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
mutation JoinTeamMutation($input: JoinTeamInput!) {
|
||||
joinTeam(input: $input) {
|
||||
success
|
||||
team {
|
||||
name
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
mutation UpdateTeam($input: UpdateTeamInput!) {
|
||||
updateTeam(input: $input) {
|
||||
success
|
||||
team {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
export default {
|
||||
methods: {
|
||||
addTeam(store, team) {
|
||||
console.log('add team');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -10,6 +10,7 @@ export default {
|
|||
permissions: [],
|
||||
schoolClasses: [],
|
||||
isTeacher: false,
|
||||
team: null
|
||||
},
|
||||
showPopover: false,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,41 +1,15 @@
|
|||
<template>
|
||||
<div class="create-class">
|
||||
<h1 class="create-class__title">Klasse erfassen</h1>
|
||||
|
||||
<div>
|
||||
<div class="skillboxform-input">
|
||||
<label
|
||||
for="class-name"
|
||||
class="skillboxform-input__label">Name</label>
|
||||
<input
|
||||
:class="{'skillboxform-input__input--error': error}"
|
||||
:value="name"
|
||||
class="skillbox-input skillboxform-input__input"
|
||||
data-cy="input-class-name"
|
||||
id="class-name"
|
||||
@input="updateName">
|
||||
<small
|
||||
class="skillboxform-input__error"
|
||||
data-cy="email-local-errors"
|
||||
v-if="error"
|
||||
>{{ error }}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a
|
||||
class="button button--primary button--big"
|
||||
data-cy="create-class"
|
||||
@click="createClass(name)">Klasse
|
||||
erfassen</a>
|
||||
<a
|
||||
class="button button--big"
|
||||
data-cy="create-class-cancel"
|
||||
@click="cancel">Abbrechen</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<join-form
|
||||
:value="name"
|
||||
class="create-class"
|
||||
title="Klasse erfassen"
|
||||
ok-text="Klasse erfassen"
|
||||
label-text="Name"
|
||||
cancel-text="Abbrechen"
|
||||
@input="updateName"
|
||||
@cancel="cancel"
|
||||
@confirm="createClass"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -43,12 +17,18 @@
|
|||
|
||||
import CREATE_CLASS_MUTATION from '@/graphql/gql/mutations/createClass.gql';
|
||||
import MY_SCHOOL_CLASS_QUERY from '@/graphql/gql/mySchoolClass';
|
||||
import JoinForm from '@/components/profile/JoinForm';
|
||||
|
||||
export default {
|
||||
mixins: [addSchoolClassMixin],
|
||||
|
||||
components: {
|
||||
JoinForm
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
name: '',
|
||||
error: ''
|
||||
error: '',
|
||||
}),
|
||||
|
||||
methods: {
|
||||
|
|
@ -62,24 +42,24 @@
|
|||
mutation: CREATE_CLASS_MUTATION,
|
||||
variables: {
|
||||
input: {
|
||||
name
|
||||
}
|
||||
name,
|
||||
},
|
||||
},
|
||||
update(store, {data: {createSchoolClass: {schoolClass}}}) {
|
||||
self.addSchoolClass(store, schoolClass);
|
||||
self.$router.push({
|
||||
name: 'my-class'
|
||||
name: 'my-class',
|
||||
});
|
||||
},
|
||||
refetchQueries: [{
|
||||
query: MY_SCHOOL_CLASS_QUERY
|
||||
}]
|
||||
query: MY_SCHOOL_CLASS_QUERY,
|
||||
}],
|
||||
});
|
||||
},
|
||||
cancel() {
|
||||
this.$router.go(-1);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,40 +1,13 @@
|
|||
<template>
|
||||
<div>
|
||||
<h1 data-cy="join-class-title">Einer Klasse beitreten</h1>
|
||||
<div>
|
||||
<div class="skillboxform-input">
|
||||
<label
|
||||
for="join-code"
|
||||
class="skillboxform-input__label">Zugangscode eingeben</label>
|
||||
<input
|
||||
:class="{'skillboxform-input__input--error': error}"
|
||||
:value="code"
|
||||
class="skillbox-input skillboxform-input__input"
|
||||
data-cy="input-class-code"
|
||||
id="join-code"
|
||||
@input="updateCode">
|
||||
<small
|
||||
class="skillboxform-input__error"
|
||||
data-cy="email-local-errors"
|
||||
v-if="error"
|
||||
>{{ error }}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a
|
||||
class="button button--primary button--big"
|
||||
data-cy="join-class"
|
||||
@click="joinClass(code)">Klasse beitreten</a>
|
||||
<button
|
||||
class="button button--big"
|
||||
data-cy="join-class-cancel"
|
||||
@click="logout">Abmelden
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<join-form
|
||||
:value="code"
|
||||
:error="error"
|
||||
title="Einer Klasse beitreten"
|
||||
ok-text="Klasse beitreten"
|
||||
cancel-text="Abmelden"
|
||||
@input="updateCode"
|
||||
@cancel="logout"
|
||||
@confirm="joinClass"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -44,12 +17,15 @@
|
|||
import addSchoolClass from '@/mixins/add-school-class';
|
||||
import logout from '@/mixins/logout';
|
||||
|
||||
import JoinForm from '@/components/profile/JoinForm';
|
||||
|
||||
export default {
|
||||
mixins: [addSchoolClass, logout],
|
||||
components: {JoinForm},
|
||||
|
||||
data: () => ({
|
||||
code: '',
|
||||
error: ''
|
||||
error: '',
|
||||
}),
|
||||
|
||||
methods: {
|
||||
|
|
@ -60,18 +36,18 @@
|
|||
joinClass(code) {
|
||||
let self = this;
|
||||
this.$apollo.mutate({
|
||||
mutation: JOIN_CLASS_MUTATION,
|
||||
variables: {
|
||||
input: {
|
||||
code
|
||||
}
|
||||
},
|
||||
update(store, {data: {joinClass: {schoolClass}}}) {
|
||||
self.addSchoolClass(store, schoolClass);
|
||||
self.$router.push({name: 'my-class'});
|
||||
},
|
||||
refetchQueries: [{query: MY_SCHOOL_CLASS_QUERY}]
|
||||
})
|
||||
mutation: JOIN_CLASS_MUTATION,
|
||||
variables: {
|
||||
input: {
|
||||
code,
|
||||
},
|
||||
},
|
||||
update(store, {data: {joinClass: {schoolClass}}}) {
|
||||
self.addSchoolClass(store, schoolClass);
|
||||
self.$router.push({name: 'my-class'});
|
||||
},
|
||||
refetchQueries: [{query: MY_SCHOOL_CLASS_QUERY}],
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
})
|
||||
|
|
@ -83,7 +59,7 @@
|
|||
this.error = 'Dieser Zugangscode ist nicht gültig.';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<join-form
|
||||
:value="name"
|
||||
class="create-team"
|
||||
title="Team erfassen"
|
||||
ok-text="Team erfassen"
|
||||
label-text="Name"
|
||||
cancel-text="Abbrechen"
|
||||
@input="updateName"
|
||||
@cancel="cancel"
|
||||
@confirm="createTeam"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import addTeamMixin from '@/mixins/add-team';
|
||||
|
||||
import JoinForm from '@/components/profile/JoinForm';
|
||||
|
||||
import CREATE_TEAM_MUTATION from '@/graphql/gql/mutations/createTeam.gql';
|
||||
import {MY_TEAM} from '@/router/me.names';
|
||||
|
||||
export default {
|
||||
mixins: [addTeamMixin],
|
||||
|
||||
components: {
|
||||
JoinForm
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
name: '',
|
||||
error: '',
|
||||
}),
|
||||
|
||||
methods: {
|
||||
updateName(event) {
|
||||
this.name = event.target.value;
|
||||
this.error = '';
|
||||
},
|
||||
createTeam(name) {
|
||||
this.$apollo.mutate({
|
||||
mutation: CREATE_TEAM_MUTATION,
|
||||
variables: {
|
||||
input: {
|
||||
name,
|
||||
},
|
||||
},
|
||||
update: (store, {data: {createTeam: {team}}}) => {
|
||||
this.addTeam(store, team);
|
||||
this.$router.push({
|
||||
name: MY_TEAM,
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
cancel() {
|
||||
this.$router.go(-1);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
<template>
|
||||
<join-form
|
||||
:value="code"
|
||||
:error="error"
|
||||
title="Einem Team beitreten"
|
||||
ok-text="Team beitreten"
|
||||
cancel-text="Abbrechen"
|
||||
@input="updateCode"
|
||||
@cancel="cancel"
|
||||
@confirm="joinTeam"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JOIN_TEAM_MUTATION from '@/graphql/gql/mutations/joinTeam.gql';
|
||||
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
||||
|
||||
import JoinForm from '@/components/profile/JoinForm';
|
||||
import {MY_TEAM} from '@/router/me.names';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
JoinForm,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
code: '',
|
||||
error: '',
|
||||
teamRoute: {
|
||||
name: MY_TEAM,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateCode(event) {
|
||||
this.code = event.target.value;
|
||||
this.error = '';
|
||||
},
|
||||
cancel() {
|
||||
this.$router.push(this.teamRoute);
|
||||
},
|
||||
joinTeam(code) {
|
||||
this.$apollo.mutate({
|
||||
mutation: JOIN_TEAM_MUTATION,
|
||||
variables: {
|
||||
'input': {
|
||||
'code': code,
|
||||
},
|
||||
},
|
||||
update: (store, {data: {joinTeam: {team}}}) => {
|
||||
const query = ME_QUERY;
|
||||
const data = store.readQuery({query});
|
||||
store.writeQuery({
|
||||
query,
|
||||
data: {
|
||||
...data,
|
||||
me: {
|
||||
...data.me,
|
||||
team: team,
|
||||
},
|
||||
},
|
||||
});
|
||||
this.$router.push({name: MY_TEAM});
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~styles/helpers';
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
<template>
|
||||
<div class="my-team">
|
||||
<template v-if="me.team">
|
||||
<group-list
|
||||
:active-members="me.team.members"
|
||||
:can-edit="true"
|
||||
:show-code-route="showCodeRoute"
|
||||
:show-code="true"
|
||||
:name="me.team.name"
|
||||
title="Mein Team"
|
||||
@edit="editTeamName"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<h1 class="my-team__heading">Mein Team {{ me.team }}</h1>
|
||||
<div class="my-team__section">
|
||||
<h2 class="my-team__subheading">Willst du einem bestehenden Team beitreten?</h2>
|
||||
<router-link
|
||||
:to="joinTeamRoute"
|
||||
class="button button--primary">Zugangscode eingeben
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="my-team__section">
|
||||
<h2 class="my-team__subheading">Willst du ein neues Team erfassen?</h2>
|
||||
<router-link
|
||||
:to="createTeamRoute"
|
||||
class="button button--primary">Team erfassen
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {CREATE_TEAM, JOIN_TEAM, SHOW_TEAM_CODE} from '@/router/me.names';
|
||||
import me from '@/mixins/me';
|
||||
import GroupList from '@/components/profile/GroupList';
|
||||
|
||||
export default {
|
||||
mixins: [me],
|
||||
components: {GroupList},
|
||||
|
||||
data() {
|
||||
return {
|
||||
joinTeamRoute: {
|
||||
name: JOIN_TEAM,
|
||||
},
|
||||
createTeamRoute: {
|
||||
name: CREATE_TEAM,
|
||||
},
|
||||
showCodeRoute: {
|
||||
name: SHOW_TEAM_CODE
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
editTeamName() {
|
||||
this.$store.dispatch('editTeamName');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~styles/helpers';
|
||||
|
||||
.my-team {
|
||||
&__heading {
|
||||
margin-bottom: $section-spacing;
|
||||
}
|
||||
|
||||
&__section {
|
||||
margin-bottom: $section-spacing;
|
||||
}
|
||||
|
||||
&__subheading {
|
||||
@include heading-3;
|
||||
margin-bottom: $large-spacing;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<template>
|
||||
<show-code
|
||||
:name="me.selectedClass.name"
|
||||
:code="me.selectedClass.code"
|
||||
class="show-school-class-code"
|
||||
type="Klasse"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import selectedClassMixin from '@/mixins/selected-class';
|
||||
import ShowCode from '@/components/profile/ShowCode';
|
||||
|
||||
export default {
|
||||
mixins: [selectedClassMixin],
|
||||
components: {ShowCode},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<template>
|
||||
<show-code
|
||||
:name="me.team.name"
|
||||
:code="me.team.code"
|
||||
class="show-school-class-code"
|
||||
type="Team"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import me from '@/mixins/me';
|
||||
import ShowCode from '@/components/profile/ShowCode';
|
||||
|
||||
export default {
|
||||
mixins: [me],
|
||||
components: {ShowCode},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -1,37 +1,41 @@
|
|||
<template>
|
||||
<div class="my-class">
|
||||
<h1
|
||||
class="my-class__header"
|
||||
data-cy="class-list-title">Klassenliste</h1>
|
||||
<router-link
|
||||
:to="{name: 'show-code'}"
|
||||
class="my-class__code-link button button--primary"
|
||||
v-if="me.isTeacher">Zugangscode anzeigen
|
||||
</router-link>
|
||||
<class-list
|
||||
<group-list
|
||||
:name="me.selectedClass.name"
|
||||
:members="me.selectedClass.members"
|
||||
:teacher="me.isTeacher"
|
||||
:id="me.selectedClass.id"
|
||||
:active-members="me.selectedClass.members.filter(member => member.active)"
|
||||
:inactive-members="me.selectedClass.members.filter(member => !member.active)"
|
||||
:show-code="me.isTeacher"
|
||||
:show-code-route="showCodeRoute"
|
||||
:can-edit="me.isTeacher"
|
||||
title="Klassenliste"
|
||||
class="my-class__class"
|
||||
@remove="remove"
|
||||
@add="add"
|
||||
@edit="editClassName"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ClassList from '@/components/profile/ClassList';
|
||||
import GroupList from '@/components/profile/GroupList';
|
||||
|
||||
import MY_SCHOOL_CLASS_QUERY from '@/graphql/gql/mySchoolClass.gql';
|
||||
import ADD_REMOVE_MEMBER_MUTATION from '@/graphql/gql/mutations/addRemoveMember.gql';
|
||||
import selectedClassMixin from '@/mixins/selected-class';
|
||||
import {SHOW_SCHOOL_CLASS_CODE} from '@/router/me.names';
|
||||
|
||||
export default {
|
||||
mixins: [selectedClassMixin],
|
||||
|
||||
components: {
|
||||
ClassList
|
||||
GroupList,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
showCodeRoute: {
|
||||
name: SHOW_SCHOOL_CLASS_CODE,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
|
@ -42,8 +46,8 @@
|
|||
input: {
|
||||
member: member.id,
|
||||
schoolClass: this.me.selectedClass.id,
|
||||
active
|
||||
}
|
||||
active,
|
||||
},
|
||||
},
|
||||
update(store, {data: {addRemoveMember: {success}}}) {
|
||||
if (success) {
|
||||
|
|
@ -57,7 +61,7 @@
|
|||
];
|
||||
store.writeQuery({query, data});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
add(member) {
|
||||
|
|
@ -65,22 +69,25 @@
|
|||
},
|
||||
remove(member) {
|
||||
this.$modal.open('deactivate-person', {
|
||||
myself: member.id === this.me.id,
|
||||
name: `${member.firstName} ${member.lastName}`,
|
||||
className: this.me.selectedClass.name,
|
||||
})
|
||||
.then(() => {
|
||||
this.changeMember(member, false);
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
myself: member.id === this.me.id,
|
||||
name: `${member.firstName} ${member.lastName}`,
|
||||
className: this.me.selectedClass.name,
|
||||
})
|
||||
.then(() => {
|
||||
this.changeMember(member, false);
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
},
|
||||
editClassName() {
|
||||
this.$store.dispatch('editClassName');
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "~styles/helpers";
|
||||
|
||||
.my-class {
|
||||
display: grid;
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@
|
|||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_functions.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
@import "~styles/helpers";
|
||||
|
||||
.profile {
|
||||
padding: $large-spacing;
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
<template>
|
||||
<div class="show-code">
|
||||
<h2 class="show-code__title">Zugangscode Klasse {{ me.selectedClass.name }}</h2>
|
||||
<h1 class="show-code__code">{{ me.selectedClass.code }}</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import selectedClassMixin from '@/mixins/selected-class';
|
||||
|
||||
export default {
|
||||
mixins: [selectedClassMixin],
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
|
||||
.show-code {
|
||||
&__title {
|
||||
@include regular-text;
|
||||
margin-bottom: 2*$large-spacing;
|
||||
}
|
||||
|
||||
&__code {
|
||||
font-size: toRem(120px);
|
||||
letter-spacing: toRem(20px);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -6,10 +6,6 @@ import instrumentOverview from '@/pages/instrumentOverview';
|
|||
import p404 from '@/pages/p404';
|
||||
import start from '@/pages/start';
|
||||
import submission from '@/pages/studentSubmission';
|
||||
import profilePage from '@/pages/profile';
|
||||
import profile from '@/components/profile/Profile';
|
||||
import myClass from '@/pages/myClass';
|
||||
import activity from '@/pages/activity';
|
||||
import Router from 'vue-router';
|
||||
import surveyPage from '@/pages/survey';
|
||||
import styleGuidePage from '@/pages/styleguide';
|
||||
|
|
@ -18,14 +14,12 @@ import emailVerification from '@/pages/email-verification';
|
|||
import licenseActivation from '@/pages/license-activation';
|
||||
import forgotPassword from '@/pages/forgot-password';
|
||||
import joinClass from '@/pages/joinClass';
|
||||
import oldClasses from '@/pages/oldClasses';
|
||||
import createClass from '@/pages/createClass';
|
||||
import showCode from '@/pages/showCode';
|
||||
import news from '@/pages/news';
|
||||
|
||||
import moduleRoutes from './module.routes';
|
||||
import portfolioRoutes from './portfolio.routes';
|
||||
import onboardingRoutes from './onboarding.routes';
|
||||
import meRoutes from './me.routes';
|
||||
import authRoutes from './auth.routes';
|
||||
import roomRoutes from './room.routes';
|
||||
|
||||
|
|
@ -50,25 +44,8 @@ const routes = [
|
|||
{path: '/submission/:id', name: 'submission', component: submission, meta: {layout: 'simple'}},
|
||||
...portfolioRoutes,
|
||||
{path: '/topic/:topicSlug', name: 'topic', component: topic, alias: '/book/topic/:topicSlug'},
|
||||
{
|
||||
path: '/me',
|
||||
component: profilePage,
|
||||
children: [
|
||||
{path: 'profile', name: 'profile', component: profile, meta: {isProfile: true}},
|
||||
{path: '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: 'old-classes',
|
||||
name: 'old-classes',
|
||||
component: oldClasses,
|
||||
meta: {isProfile: true},
|
||||
},
|
||||
{path: 'create-class', name: 'create-class', component: createClass, meta: {layout: 'simple'}},
|
||||
{path: 'show-code', name: 'show-code', component: showCode, meta: {layout: 'simple'}},
|
||||
],
|
||||
},
|
||||
{path: 'join-class', name: 'join-class', component: joinClass, meta: {layout: 'public'}},
|
||||
...meRoutes,
|
||||
{path: '/join-class', name: 'join-class', component: joinClass, meta: {layout: 'simple'}},
|
||||
{
|
||||
path: '/survey/:id',
|
||||
component: surveyPage,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
export const MY_TEAM = 'my-team';
|
||||
export const JOIN_TEAM = 'join-team';
|
||||
export const CREATE_TEAM = 'create-team';
|
||||
export const SHOW_SCHOOL_CLASS_CODE = 'show-school-class-code';
|
||||
export const SHOW_TEAM_CODE = 'show-teams-code';
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import profilePage from '@/pages/profile';
|
||||
import profile from '@/components/profile/Profile';
|
||||
import myClass from '@/pages/myClass';
|
||||
import activity from '@/pages/activity';
|
||||
import oldClasses from '@/pages/oldClasses';
|
||||
import createClass from '@/pages/createClass';
|
||||
import showSchoolClassCode from '@/pages/me/showSchoolClassCode';
|
||||
import showTeamCode from '@/pages/me/showTeamCode';
|
||||
import myTeam from '@/pages/me/myTeam';
|
||||
import joinTeam from '@/pages/me/joinTeam';
|
||||
import createTeam from '@/pages/me/createTeam';
|
||||
|
||||
import {CREATE_TEAM, JOIN_TEAM, MY_TEAM, SHOW_SCHOOL_CLASS_CODE, SHOW_TEAM_CODE} from './me.names';
|
||||
|
||||
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: 'old-classes',
|
||||
name: 'old-classes',
|
||||
component: oldClasses,
|
||||
meta: {isProfile: true},
|
||||
},
|
||||
{
|
||||
path: 'class/create',
|
||||
alias: 'create-class',
|
||||
name: 'create-class',
|
||||
component: createClass,
|
||||
meta: {layout: 'simple'},
|
||||
},
|
||||
{
|
||||
path: 'class/code',
|
||||
alias: 'show-code',
|
||||
name: SHOW_SCHOOL_CLASS_CODE,
|
||||
component: showSchoolClassCode,
|
||||
meta: {layout: 'simple'},
|
||||
},
|
||||
{path: 'team', name: MY_TEAM, component: myTeam, meta: {isProfile: true}},
|
||||
{path: 'team/join', name: JOIN_TEAM, component: joinTeam, meta: {isProfile: true, layout: 'simple'}},
|
||||
{path: 'team/create', name: CREATE_TEAM, component: createTeam, meta: {isProfile: true, layout: 'simple'}},
|
||||
{
|
||||
path: 'team/code',
|
||||
name: SHOW_TEAM_CODE,
|
||||
component: showTeamCode,
|
||||
meta: {layout: 'simple'},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
@ -185,9 +185,12 @@ export default new Vuex.Store({
|
|||
editModule({commit}, payload) {
|
||||
commit('setEditModule', payload);
|
||||
},
|
||||
editClassName({dispatch}, payload) {
|
||||
editClassName({dispatch}) {
|
||||
dispatch('showModal', 'edit-class-name-wizard');
|
||||
},
|
||||
editTeamName({dispatch}) {
|
||||
dispatch('showModal', 'edit-team-name-wizard');
|
||||
},
|
||||
deactivateUser({commit, dispatch}, payload) {
|
||||
commit('setModulePayload', payload);
|
||||
return dispatch('showModal', 'deactivate-person');
|
||||
|
|
|
|||
|
|
@ -347,6 +347,17 @@ type CreateSchoolClassPayload {
|
|||
clientMutationId: String
|
||||
}
|
||||
|
||||
input CreateTeamInput {
|
||||
name: String!
|
||||
clientMutationId: String
|
||||
}
|
||||
|
||||
type CreateTeamPayload {
|
||||
success: Boolean
|
||||
team: TeamNode
|
||||
clientMutationId: String
|
||||
}
|
||||
|
||||
type CustomMutation {
|
||||
redeemCoupon(input: CouponInput!): CouponPayload
|
||||
spellCheck(input: SpellCheckInput!): SpellCheckPayload
|
||||
|
|
@ -365,6 +376,9 @@ type CustomMutation {
|
|||
updateSchoolClass(input: UpdateSchoolClassInput!): UpdateSchoolClassPayload
|
||||
createSchoolClass(input: CreateSchoolClassInput!): CreateSchoolClassPayload
|
||||
updateOnboardingProgress: UpdateOnboardingProgress
|
||||
createTeam(input: CreateTeamInput!): CreateTeamPayload
|
||||
joinTeam(input: JoinTeamInput!): JoinTeamPayload
|
||||
updateTeam(input: UpdateTeamInput!): UpdateTeamPayload
|
||||
addProject(input: AddProjectInput!): AddProjectPayload
|
||||
updateProject(input: UpdateProjectInput!): UpdateProjectPayload
|
||||
deleteProject(input: DeleteProjectInput!): DeleteProjectPayload
|
||||
|
|
@ -578,6 +592,17 @@ type JoinClassPayload {
|
|||
clientMutationId: String
|
||||
}
|
||||
|
||||
input JoinTeamInput {
|
||||
code: String!
|
||||
clientMutationId: String
|
||||
}
|
||||
|
||||
type JoinTeamPayload {
|
||||
success: Boolean
|
||||
team: TeamNode
|
||||
clientMutationId: String
|
||||
}
|
||||
|
||||
type Logout {
|
||||
success: Boolean
|
||||
}
|
||||
|
|
@ -803,8 +828,8 @@ type SchoolClassNode implements Node {
|
|||
id: ID!
|
||||
name: String!
|
||||
isDeleted: Boolean!
|
||||
users(offset: Int, before: String, after: String, first: Int, last: Int, username: String, email: String): UserNodeConnection!
|
||||
code: String
|
||||
users(offset: Int, before: String, after: String, first: Int, last: Int, username: String, email: String): UserNodeConnection!
|
||||
moduleSet(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, slug_Icontains: String, slug_In: [String], title: String, title_Icontains: String, title_In: [String]): ModuleNodeConnection!
|
||||
hiddenChapterTitles(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, title: String): ChapterNodeConnection!
|
||||
hiddenChapterDescriptions(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, title: String): ChapterNodeConnection!
|
||||
|
|
@ -920,6 +945,16 @@ type SyncModuleVisibilityPayload {
|
|||
clientMutationId: String
|
||||
}
|
||||
|
||||
type TeamNode implements Node {
|
||||
name: String!
|
||||
isDeleted: Boolean!
|
||||
code: String
|
||||
id: ID!
|
||||
creator: UserNode
|
||||
members: [UserNode]
|
||||
pk: Int
|
||||
}
|
||||
|
||||
type TopicConnection {
|
||||
pageInfo: PageInfo!
|
||||
edges: [TopicEdge]!
|
||||
|
|
@ -1266,6 +1301,18 @@ type UpdateSubmissionFeedbackPayload {
|
|||
clientMutationId: String
|
||||
}
|
||||
|
||||
input UpdateTeamInput {
|
||||
id: ID!
|
||||
name: String
|
||||
clientMutationId: String
|
||||
}
|
||||
|
||||
type UpdateTeamPayload {
|
||||
success: Boolean
|
||||
team: TeamNode
|
||||
clientMutationId: String
|
||||
}
|
||||
|
||||
input UserGroupBlockVisibility {
|
||||
schoolClassId: ID!
|
||||
hidden: Boolean!
|
||||
|
|
@ -1280,6 +1327,7 @@ type UserNode implements Node {
|
|||
avatarUrl: String!
|
||||
email: String!
|
||||
onboardingVisited: Boolean!
|
||||
team: TeamNode
|
||||
schoolClasses(offset: Int, before: String, after: String, first: Int, last: Int, name: String): SchoolClassNodeConnection!
|
||||
id: ID!
|
||||
pk: Int
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ from faker import Faker
|
|||
from wagtail.documents.models import get_document_model
|
||||
from wagtail.images import get_image_model
|
||||
|
||||
from users.models import Role, UserRole
|
||||
|
||||
fake = Faker('de_CH')
|
||||
|
||||
|
||||
|
|
@ -48,7 +50,7 @@ class DummyImageFactory(factory.DjangoModelFactory):
|
|||
class UserFactory(factory.django.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = get_user_model()
|
||||
django_get_or_create = ('username', )
|
||||
django_get_or_create = ('username',)
|
||||
|
||||
first_name = factory.LazyAttribute(lambda x: fake.first_name())
|
||||
last_name = factory.LazyAttribute(lambda x: fake.last_name())
|
||||
|
|
@ -58,3 +60,11 @@ class UserFactory(factory.django.DjangoModelFactory):
|
|||
def post(self, create, extracted, **kwargs):
|
||||
self.set_password('test')
|
||||
self.save()
|
||||
|
||||
|
||||
class TeacherFactory(UserFactory):
|
||||
@factory.post_generation
|
||||
def post(self, create, extracted, **kwargs):
|
||||
Role.objects.create_default_roles()
|
||||
teacher_role = Role.objects.get_default_teacher_role()
|
||||
UserRole.objects.create(user=self, role=teacher_role)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,6 @@ class Command(BaseCommand):
|
|||
with open(schema_path, 'w') as o:
|
||||
o.write(str(schema))
|
||||
|
||||
with open(public_schema_path, 'w') as o:
|
||||
o.write(str(schema_public))
|
||||
# with open(public_schema_path, 'w') as o:
|
||||
# o.write(str(schema_public))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import random
|
||||
|
||||
import factory
|
||||
from users.models import SchoolClass, SchoolClassMember, License
|
||||
|
||||
from users.models import SchoolClass, SchoolClassMember, License, Team
|
||||
|
||||
class_types = ['DA', 'KV', 'INF', 'EE']
|
||||
class_suffix = ['A', 'B', 'C', 'D', 'E']
|
||||
|
|
@ -31,6 +32,15 @@ class SchoolClassFactory(factory.django.DjangoModelFactory):
|
|||
SchoolClassMember.objects.create(user=user, school_class=self, active=True)
|
||||
|
||||
|
||||
class TeamFactory(factory.django.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = Team
|
||||
|
||||
name = factory.Faker('name')
|
||||
code = factory.Sequence(lambda n: "CODE{}".format(n))
|
||||
is_deleted = False
|
||||
|
||||
|
||||
class LicenseFactory(factory.django.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = License
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
# Generated by Django 2.2.19 on 2021-03-24 21:26
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0025_auto_20210126_1343'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Team',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
('is_deleted', models.BooleanField(default=False)),
|
||||
('code', models.CharField(blank=True, default=None, max_length=10, null=True, unique=True, verbose_name='Code zum Beitreten')),
|
||||
('creator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='team',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='members', to='users.Team'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import re
|
||||
from datetime import datetime
|
||||
import string
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
from datetime import datetime
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import AbstractUser, Permission
|
||||
|
|
@ -25,6 +25,7 @@ class User(AbstractUser):
|
|||
hep_group_id = models.PositiveIntegerField(null=True, blank=False)
|
||||
license_expiry_date = models.DateField(blank=False, null=True, default=None)
|
||||
onboarding_visited = models.BooleanField(default=False)
|
||||
team = models.ForeignKey('users.Team', on_delete=models.SET_NULL, blank=True, null=True, related_name='members')
|
||||
|
||||
# for wagtail autocomplete
|
||||
autocomplete_search_field = 'username'
|
||||
|
|
@ -110,14 +111,43 @@ class User(AbstractUser):
|
|||
return self.get_full_name()
|
||||
|
||||
class Meta:
|
||||
ordering = ['pk',]
|
||||
ordering = ['pk', ]
|
||||
|
||||
|
||||
class GroupWithCode(models.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
class SchoolClass(models.Model):
|
||||
name = models.CharField(max_length=100, blank=False, null=False, unique=True)
|
||||
is_deleted = models.BooleanField(blank=False, null=False, default=False)
|
||||
code = models.CharField('Code zum Beitreten', blank=True, null=True, max_length=10, unique=True, default=None)
|
||||
|
||||
def generate_code(self):
|
||||
letters = string.ascii_lowercase
|
||||
digits = string.digits
|
||||
code = ''.join(random.choice(letters) for i in range(4)) + ''.join(random.choice(digits) for i in range(2))
|
||||
try:
|
||||
self.__class__.objects.get(code=code)
|
||||
self.generate_code()
|
||||
except self.__class__.DoesNotExist:
|
||||
self.code = code.upper()
|
||||
self.save()
|
||||
|
||||
|
||||
class Team(GroupWithCode):
|
||||
creator = models.ForeignKey(get_user_model(), null=True, on_delete=models.SET_NULL, blank=True, related_name='+')
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Team'
|
||||
verbose_name_plural = 'Teams'
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class SchoolClass(GroupWithCode):
|
||||
users = models.ManyToManyField(get_user_model(), related_name='school_classes', blank=True,
|
||||
through='users.SchoolClassMember')
|
||||
code = models.CharField('Code zum Beitreten', blank=True, null=True, max_length=10, unique=True, default=None)
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Schulklasse'
|
||||
|
|
@ -162,17 +192,6 @@ class SchoolClass(models.Model):
|
|||
def get_teacher(self):
|
||||
return self.users.filter(user_roles__role__key='teacher').first()
|
||||
|
||||
def generate_code(self):
|
||||
letters = string.ascii_lowercase
|
||||
digits = string.digits
|
||||
code = ''.join(random.choice(letters) for i in range(4)) + ''.join(random.choice(digits) for i in range(2))
|
||||
try:
|
||||
SchoolClass.objects.get(code=code)
|
||||
self.generate_code()
|
||||
except SchoolClass.DoesNotExist:
|
||||
self.code = code.upper()
|
||||
self.save()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.code == '': # '' can't be unique, so we null it
|
||||
self.code = None
|
||||
|
|
|
|||
|
|
@ -6,11 +6,13 @@ from graphene import relay
|
|||
from graphql_relay import from_global_id
|
||||
|
||||
from api.utils import get_object
|
||||
from core.logger import get_logger
|
||||
from users.inputs import PasswordUpdateInput
|
||||
from users.models import SchoolClass, UserSetting, User, SchoolClassMember
|
||||
from users.schema import SchoolClassNode
|
||||
from users.models import SchoolClass, SchoolClassMember, Team
|
||||
from users.schema import SchoolClassNode, TeamNode
|
||||
from users.serializers import PasswordSerialzer, AvatarUrlSerializer
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
class CodeNotFoundException(Exception):
|
||||
pass
|
||||
|
|
@ -25,6 +27,18 @@ class UpdateError(graphene.ObjectType):
|
|||
errors = graphene.List(FieldError)
|
||||
|
||||
|
||||
class TeacherOnlyMutation(relay.ClientIDMutation):
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@classmethod
|
||||
def mutate(cls, root, info, input):
|
||||
user = info.context.user
|
||||
if 'users.can_manage_school_class_content' not in user.get_role_permissions():
|
||||
raise PermissionError('Permission denied')
|
||||
return super().mutate(root, info, input)
|
||||
|
||||
|
||||
class UpdatePassword(relay.ClientIDMutation):
|
||||
class Input:
|
||||
password_input = graphene.Argument(PasswordUpdateInput)
|
||||
|
|
@ -132,7 +146,7 @@ class JoinClass(relay.ClientIDMutation):
|
|||
|
||||
return cls(success=True, school_class=school_class)
|
||||
except SchoolClass.DoesNotExist:
|
||||
raise CodeNotFoundException('[CNV] Code ist nicht gültig') # CAV = Code Not Valid
|
||||
raise CodeNotFoundException('[CNV] Code ist nicht gültig') # CNV = Code Not Valid
|
||||
|
||||
|
||||
class AddRemoveMember(relay.ClientIDMutation):
|
||||
|
|
@ -154,7 +168,7 @@ class AddRemoveMember(relay.ClientIDMutation):
|
|||
school_class = get_object(SchoolClass, school_class_id)
|
||||
|
||||
if not user.is_teacher() or not school_class.users.filter(pk=user.pk).exists():
|
||||
raise PermissionError('Fehlende Berechtigung')
|
||||
raise PermissionError('Permission denied')
|
||||
|
||||
school_class_member = SchoolClassMember.objects.get(user__pk=member_pk, school_class=school_class)
|
||||
school_class_member.active = active
|
||||
|
|
@ -163,7 +177,7 @@ class AddRemoveMember(relay.ClientIDMutation):
|
|||
return cls(success=True)
|
||||
|
||||
|
||||
class UpdateSchoolClass(relay.ClientIDMutation):
|
||||
class UpdateSchoolClass(TeacherOnlyMutation):
|
||||
class Input:
|
||||
id = graphene.ID(required=True)
|
||||
name = graphene.String()
|
||||
|
|
@ -177,9 +191,7 @@ class UpdateSchoolClass(relay.ClientIDMutation):
|
|||
name = kwargs.get('name')
|
||||
user = info.context.user
|
||||
|
||||
if 'users.can_manage_school_class_content' not in user.get_role_permissions():
|
||||
raise PermissionError()
|
||||
|
||||
# todo: only allow to edit your own school class
|
||||
school_class = get_object(SchoolClass, id)
|
||||
school_class.name = name
|
||||
school_class.save()
|
||||
|
|
@ -187,7 +199,7 @@ class UpdateSchoolClass(relay.ClientIDMutation):
|
|||
return cls(success=True, school_class=school_class)
|
||||
|
||||
|
||||
class CreateSchoolClass(relay.ClientIDMutation):
|
||||
class CreateSchoolClass(TeacherOnlyMutation):
|
||||
class Input:
|
||||
name = graphene.String()
|
||||
|
||||
|
|
@ -197,18 +209,78 @@ class CreateSchoolClass(relay.ClientIDMutation):
|
|||
@classmethod
|
||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||
name = kwargs.get('name')
|
||||
|
||||
user = info.context.user
|
||||
|
||||
if 'users.can_manage_school_class_content' not in user.get_role_permissions():
|
||||
raise PermissionError()
|
||||
|
||||
school_class = SchoolClass.objects.create(name=name)
|
||||
SchoolClassMember.objects.create(school_class=school_class, user=user)
|
||||
user.set_selected_class(school_class)
|
||||
return cls(success=True, school_class=school_class)
|
||||
|
||||
|
||||
class CreateTeam(TeacherOnlyMutation):
|
||||
class Input:
|
||||
name = graphene.String(required=True)
|
||||
|
||||
success = graphene.Boolean()
|
||||
team = graphene.Field(TeamNode)
|
||||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||
name = kwargs.get('name')
|
||||
user = info.context.user
|
||||
|
||||
team = Team.objects.create(name=name, creator=user)
|
||||
team.generate_code()
|
||||
user.team = team
|
||||
user.save()
|
||||
return cls(success=True, team=team)
|
||||
|
||||
|
||||
class UpdateTeam(TeacherOnlyMutation):
|
||||
class Input:
|
||||
id = graphene.ID(required=True)
|
||||
name = graphene.String()
|
||||
|
||||
success = graphene.Boolean()
|
||||
team = graphene.Field(TeamNode)
|
||||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||
id = kwargs.get('id')
|
||||
name = kwargs.get('name')
|
||||
user = info.context.user
|
||||
|
||||
team = get_object(Team, id)
|
||||
if user not in team.members.all():
|
||||
logger.info('User not part of this team')
|
||||
raise PermissionError('Permission denied')
|
||||
team.name = name
|
||||
team.save()
|
||||
|
||||
return cls(success=True, team=team)
|
||||
|
||||
|
||||
class JoinTeam(TeacherOnlyMutation):
|
||||
class Input:
|
||||
code = graphene.String(required=True)
|
||||
|
||||
success = graphene.Boolean()
|
||||
team = graphene.Field(TeamNode)
|
||||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||
user = info.context.user
|
||||
code = kwargs.get('code')
|
||||
try:
|
||||
team = Team.objects.get(Q(code__iexact=code))
|
||||
user.team = team
|
||||
user.save()
|
||||
|
||||
return cls(success=True, team=team)
|
||||
except Team.DoesNotExist:
|
||||
raise CodeNotFoundException('[CNV] Code ist nicht gültig') # CNV = Code Not Valid
|
||||
|
||||
|
||||
class UpdateOnboardingProgress(graphene.Mutation):
|
||||
success = graphene.Boolean()
|
||||
|
||||
|
|
@ -231,3 +303,6 @@ class ProfileMutations:
|
|||
update_school_class = UpdateSchoolClass.Field()
|
||||
create_school_class = CreateSchoolClass.Field()
|
||||
update_onboarding_progress = UpdateOnboardingProgress.Field()
|
||||
create_team = CreateTeam.Field()
|
||||
join_team = JoinTeam.Field()
|
||||
update_team = UpdateTeam.Field()
|
||||
|
|
|
|||
|
|
@ -2,18 +2,18 @@ from datetime import datetime
|
|||
|
||||
import graphene
|
||||
from django.db.models import Q
|
||||
from django.utils.dateformat import format
|
||||
from django_filters import FilterSet, OrderingFilter
|
||||
from graphene import relay, ObjectType
|
||||
from graphene_django import DjangoObjectType
|
||||
from graphene_django.filter import DjangoFilterConnectionField
|
||||
from django.utils.dateformat import format
|
||||
from graphql_relay import to_global_id
|
||||
|
||||
from basicknowledge.models import BasicKnowledge
|
||||
from basicknowledge.queries import InstrumentNode
|
||||
from books.models import Module, RecentModule
|
||||
from books.models import Module
|
||||
from books.schema.queries import ModuleNode
|
||||
from users.models import User, SchoolClass, SchoolClassMember
|
||||
from users.models import User, SchoolClass, SchoolClassMember, Team
|
||||
|
||||
|
||||
class SchoolClassNode(DjangoObjectType):
|
||||
|
|
@ -40,6 +40,22 @@ class SchoolClassNode(DjangoObjectType):
|
|||
return self.code
|
||||
|
||||
|
||||
class TeamNode(DjangoObjectType):
|
||||
class Meta:
|
||||
model = Team
|
||||
filter_fields = ['name']
|
||||
interfaces = (relay.Node,)
|
||||
|
||||
pk = graphene.Int()
|
||||
members = graphene.List('users.schema.UserNode')
|
||||
|
||||
def resolve_pk(self, *args, **kwargs):
|
||||
return self.id
|
||||
|
||||
def resolve_members(self, *args, **kwargs):
|
||||
return self.members.all()
|
||||
|
||||
|
||||
class RecentModuleFilter(FilterSet):
|
||||
class Meta:
|
||||
model = Module
|
||||
|
|
@ -60,13 +76,14 @@ class UserNode(DjangoObjectType):
|
|||
is_teacher = graphene.Boolean()
|
||||
old_classes = DjangoFilterConnectionField(SchoolClassNode)
|
||||
recent_modules = DjangoFilterConnectionField(ModuleNode, filterset_class=RecentModuleFilter)
|
||||
team = graphene.Field(TeamNode)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
filter_fields = ['username', 'email']
|
||||
only_fields = ['username', 'email', 'first_name', 'last_name', 'school_classes', 'last_module',
|
||||
'last_topic', 'avatar_url',
|
||||
'selected_class', 'expiry_date', 'onboarding_visited']
|
||||
'selected_class', 'expiry_date', 'onboarding_visited', 'team']
|
||||
interfaces = (relay.Node,)
|
||||
|
||||
def resolve_pk(self, info, **kwargs):
|
||||
|
|
@ -100,6 +117,9 @@ class UserNode(DjangoObjectType):
|
|||
# see https://docs.graphene-python.org/projects/django/en/latest/filtering/
|
||||
return RecentModuleFilter(kwargs).qs.filter(recent_modules__user=self)
|
||||
|
||||
def resolve_team(self, info, **kwargs):
|
||||
return self.team
|
||||
|
||||
|
||||
class ClassMemberNode(ObjectType):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,32 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-10-10
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
from django.conf import settings
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-04-09
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
from django.test import TestCase, RequestFactory
|
||||
from graphene import Context
|
||||
from graphene.test import Client
|
||||
from graphql_relay import to_global_id
|
||||
|
||||
from api.utils import get_graphql_mutation, get_object
|
||||
from core.factories import UserFactory
|
||||
from users.models import SchoolClass, User
|
||||
from api.schema import schema
|
||||
from api.utils import get_graphql_mutation, get_object
|
||||
from core.factories import UserFactory, TeacherFactory
|
||||
from users.models import SchoolClass, User
|
||||
from users.services import create_users
|
||||
|
||||
UPDATE_SCHOOL_CLASS_MUTATION = get_graphql_mutation('updateSchoolClass.gql')
|
||||
|
||||
|
||||
class SchoolClassesTest(TestCase):
|
||||
|
||||
|
|
@ -53,6 +37,7 @@ class SchoolClassesTest(TestCase):
|
|||
class_name = SchoolClass.generate_default_group_name(user=user)
|
||||
self.assertEqual(f'{self.prefix} {user.pk}', class_name)
|
||||
|
||||
|
||||
class ModifySchoolClassTest(TestCase):
|
||||
def setUp(self):
|
||||
create_users()
|
||||
|
|
@ -72,9 +57,8 @@ class ModifySchoolClassTest(TestCase):
|
|||
school_class = SchoolClass.objects.get(name='skillbox')
|
||||
self.assertEqual(school_class.name, 'skillbox')
|
||||
id = to_global_id('SchoolClassNode', school_class.id)
|
||||
mutation = get_graphql_mutation('updateSchoolClass.gql')
|
||||
|
||||
result = self.client.execute(mutation, variables={
|
||||
result = self.client.execute(UPDATE_SCHOOL_CLASS_MUTATION, variables={
|
||||
'input': {
|
||||
'id': id,
|
||||
'name': class_name
|
||||
|
|
@ -85,15 +69,30 @@ class ModifySchoolClassTest(TestCase):
|
|||
school_class = get_object(SchoolClass, id)
|
||||
self.assertEqual(school_class.name, class_name)
|
||||
|
||||
def test_update_school_class_not_in_class_fails(self):
|
||||
client = Client(schema=schema)
|
||||
teacher = TeacherFactory(username='conan')
|
||||
context = Context(user=teacher)
|
||||
school_class = SchoolClass.objects.get(name='skillbox')
|
||||
self.assertEqual(school_class.name, 'skillbox')
|
||||
id = to_global_id('SchoolClassNode', school_class.id)
|
||||
variables = {
|
||||
'input': {
|
||||
'id': id,
|
||||
'name': 'Nein'
|
||||
}
|
||||
}
|
||||
result = client.execute(UPDATE_SCHOOL_CLASS_MUTATION, variables=variables, context=context)
|
||||
self.assertIsNone(result.get('errors'))
|
||||
|
||||
def test_update_school_class_fail(self):
|
||||
class_name = 'Nanana'
|
||||
|
||||
school_class = SchoolClass.objects.get(name='skillbox')
|
||||
self.assertEqual(school_class.name, 'skillbox')
|
||||
id = to_global_id('SchoolClassNode', school_class.id)
|
||||
mutation = get_graphql_mutation('updateSchoolClass.gql')
|
||||
|
||||
result = self.student_client.execute(mutation, variables={
|
||||
result = self.student_client.execute(UPDATE_SCHOOL_CLASS_MUTATION, variables={
|
||||
'input': {
|
||||
'id': id,
|
||||
'name': class_name
|
||||
|
|
|
|||
|
|
@ -0,0 +1,171 @@
|
|||
from django.test import TestCase
|
||||
from graphene import Context
|
||||
from graphene.test import Client
|
||||
from graphql_relay import to_global_id
|
||||
|
||||
from api.schema import schema
|
||||
from api.utils import get_graphql_mutation
|
||||
from core.factories import TeacherFactory, UserFactory
|
||||
from users.factories import TeamFactory
|
||||
from users.models import Team
|
||||
|
||||
ME_QUERY = """
|
||||
query MeQuery {
|
||||
me {
|
||||
team {
|
||||
name
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
CREATE_TEAM_MUTATION = get_graphql_mutation('createTeam.gql')
|
||||
JOIN_TEAM_MUTATION = get_graphql_mutation('joinTeam.gql')
|
||||
UPDATE_TEAM_MUTATION = get_graphql_mutation('updateTeam.gql')
|
||||
|
||||
|
||||
class TeamTest(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client(schema=schema)
|
||||
self.team_name = 'Fiterativ'
|
||||
self.code = 'AAAA'
|
||||
self.team = TeamFactory(name=self.team_name, code=self.code)
|
||||
self.team_id = to_global_id('TeamNode', self.team.id)
|
||||
self.user = TeacherFactory(username='ueli', team=self.team)
|
||||
self.student = UserFactory(username='fritzli')
|
||||
self.context = Context(user=self.user)
|
||||
|
||||
@staticmethod
|
||||
def get_team(result):
|
||||
return result.get('data').get('me').get('team')
|
||||
|
||||
def no_error(self, result):
|
||||
self.assertIsNone(result.get('errors'))
|
||||
|
||||
def permission_error(self, result):
|
||||
errors = result.get('errors')
|
||||
self.assertIsNotNone(errors)
|
||||
self.assertIn('Permission denied', errors[0]['message'])
|
||||
|
||||
def check_me(self, context, name, code):
|
||||
result = self.client.execute(ME_QUERY, context=context)
|
||||
self.no_error(result)
|
||||
team = self.get_team(result)
|
||||
|
||||
self.assertEqual(team.get('name'), name)
|
||||
self.assertEqual(team.get('code'), code)
|
||||
|
||||
def join_team(self, code, context):
|
||||
variables = {
|
||||
"input": {
|
||||
"code": code
|
||||
}
|
||||
}
|
||||
result = self.client.execute(JOIN_TEAM_MUTATION, variables=variables, context=context)
|
||||
self.no_error(result)
|
||||
success = result['data']['joinTeam']['success']
|
||||
self.assertTrue(success)
|
||||
|
||||
def test_team_query(self):
|
||||
self.check_me(self.context, self.team_name, self.code)
|
||||
|
||||
def test_join_team_mutation(self):
|
||||
teacher = TeacherFactory(username='hansli')
|
||||
context = Context(user=teacher)
|
||||
self.join_team(self.code, context)
|
||||
|
||||
self.check_me(context, self.team_name, self.code)
|
||||
|
||||
def test_join_second_team_mutation(self):
|
||||
teacher = TeacherFactory(username='peterli')
|
||||
context = Context(user=teacher)
|
||||
second_team = TeamFactory()
|
||||
self.join_team(self.code, context)
|
||||
self.check_me(context, self.team_name, self.code)
|
||||
self.join_team(second_team.code, context)
|
||||
self.check_me(context, second_team.name, second_team.code)
|
||||
|
||||
def test_create_team_mutation(self):
|
||||
team_name = "Dunder Mifflin"
|
||||
variables = {
|
||||
"input": {
|
||||
"name": team_name
|
||||
}
|
||||
}
|
||||
result = self.client.execute(CREATE_TEAM_MUTATION, context=self.context, variables=variables)
|
||||
self.no_error(result)
|
||||
create_team = result.get('data').get('createTeam')
|
||||
team = create_team.get('team')
|
||||
success = create_team.get('success')
|
||||
self.assertTrue(success)
|
||||
self.assertEqual(team.get('name'), team_name)
|
||||
self.assertIsNotNone(team.get('code'))
|
||||
|
||||
def test_update_team_name(self):
|
||||
new_name = 'Team Böhmermann'
|
||||
variables = {
|
||||
"input": {
|
||||
"name": new_name,
|
||||
"id": self.team_id
|
||||
}
|
||||
}
|
||||
result = self.client.execute(UPDATE_TEAM_MUTATION, context=self.context, variables=variables)
|
||||
self.no_error(result)
|
||||
update_team = result.get('data').get('updateTeam')
|
||||
team = update_team.get('team')
|
||||
success = update_team.get('success')
|
||||
self.assertTrue(success)
|
||||
self.assertEqual(team.get('name'), new_name)
|
||||
|
||||
def test_update_team_name_as_student_fails(self):
|
||||
context = Context(user=self.student)
|
||||
variables = {
|
||||
"input": {
|
||||
"name": 'Not gonna happen',
|
||||
"id": self.team_id
|
||||
}
|
||||
}
|
||||
result = self.client.execute(UPDATE_TEAM_MUTATION, context=context, variables=variables)
|
||||
self.permission_error(result)
|
||||
|
||||
def test_update_team_name_not_in_team_fails(self):
|
||||
schelm = TeacherFactory(username='schelm')
|
||||
context = Context(user=schelm)
|
||||
team = Team.objects.get(pk=self.team.pk)
|
||||
old_name = team.name
|
||||
self.assertFalse(self.team.members.filter(pk=schelm.pk).exists())
|
||||
variables = {
|
||||
"input": {
|
||||
"name": 'Not gonna happen',
|
||||
"id": self.team_id
|
||||
}
|
||||
}
|
||||
result = self.client.execute(UPDATE_TEAM_MUTATION, context=context, variables=variables)
|
||||
self.permission_error(result)
|
||||
team = Team.objects.get(pk=self.team.pk)
|
||||
self.assertEqual(team.name, old_name)
|
||||
|
||||
def test_create_team_mutation_as_student_fails(self):
|
||||
self.assertEqual(Team.objects.count(), 1)
|
||||
variables = {
|
||||
"input": {
|
||||
"name": 'Nope'
|
||||
}
|
||||
}
|
||||
context = Context(user=self.student)
|
||||
result = self.client.execute(CREATE_TEAM_MUTATION, context=context, variables=variables)
|
||||
self.permission_error(result)
|
||||
self.assertEqual(Team.objects.count(), 1)
|
||||
|
||||
def test_join_create_team_mutation_as_student_fails(self):
|
||||
self.assertIsNone(self.student.team)
|
||||
variables = {
|
||||
"input": {
|
||||
"code": 'ZZZZ'
|
||||
}
|
||||
}
|
||||
context = Context(user=self.student)
|
||||
result = self.client.execute(JOIN_TEAM_MUTATION, context=context, variables=variables)
|
||||
self.permission_error(result)
|
||||
self.assertIsNone(self.student.team)
|
||||
Loading…
Reference in New Issue