Merged in feature/active-and-inactive-users-in-class (pull request #49)
Feature/active and inactive users in class Approved-by: Christian Cueni
This commit is contained in:
commit
1da6a00d40
|
|
@ -7,6 +7,7 @@
|
||||||
"firstName": "Rahel",
|
"firstName": "Rahel",
|
||||||
"lastName": "Cueni",
|
"lastName": "Cueni",
|
||||||
"avatarUrl": "",
|
"avatarUrl": "",
|
||||||
|
"isTeacher": false,
|
||||||
"lastModule": {
|
"lastModule": {
|
||||||
"id": "TW9kdWxlTm9kZToxNw==",
|
"id": "TW9kdWxlTm9kZToxNw==",
|
||||||
"slug": "lohn-und-budget",
|
"slug": "lohn-und-budget",
|
||||||
|
|
|
||||||
|
|
@ -408,6 +408,33 @@
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": null
|
"deprecationReason": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "node",
|
||||||
|
"description": "The ID of the object",
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "ID",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "INTERFACE",
|
||||||
|
"name": "Node",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "book",
|
"name": "book",
|
||||||
"description": "The ID of the object",
|
"description": "The ID of the object",
|
||||||
|
|
@ -701,71 +728,11 @@
|
||||||
"ofType": null
|
"ofType": null
|
||||||
},
|
},
|
||||||
"defaultValue": null
|
"defaultValue": null
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "slug",
|
|
||||||
"description": null,
|
|
||||||
"type": {
|
|
||||||
"kind": "SCALAR",
|
|
||||||
"name": "String",
|
|
||||||
"ofType": null
|
|
||||||
},
|
|
||||||
"defaultValue": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "slug_Icontains",
|
|
||||||
"description": null,
|
|
||||||
"type": {
|
|
||||||
"kind": "SCALAR",
|
|
||||||
"name": "String",
|
|
||||||
"ofType": null
|
|
||||||
},
|
|
||||||
"defaultValue": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "slug_In",
|
|
||||||
"description": null,
|
|
||||||
"type": {
|
|
||||||
"kind": "SCALAR",
|
|
||||||
"name": "String",
|
|
||||||
"ofType": null
|
|
||||||
},
|
|
||||||
"defaultValue": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "title",
|
|
||||||
"description": null,
|
|
||||||
"type": {
|
|
||||||
"kind": "SCALAR",
|
|
||||||
"name": "String",
|
|
||||||
"ofType": null
|
|
||||||
},
|
|
||||||
"defaultValue": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "title_Icontains",
|
|
||||||
"description": null,
|
|
||||||
"type": {
|
|
||||||
"kind": "SCALAR",
|
|
||||||
"name": "String",
|
|
||||||
"ofType": null
|
|
||||||
},
|
|
||||||
"defaultValue": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "title_In",
|
|
||||||
"description": null,
|
|
||||||
"type": {
|
|
||||||
"kind": "SCALAR",
|
|
||||||
"name": "String",
|
|
||||||
"ofType": null
|
|
||||||
},
|
|
||||||
"defaultValue": null
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"type": {
|
"type": {
|
||||||
"kind": "OBJECT",
|
"kind": "OBJECT",
|
||||||
"name": "TopicNodeConnection",
|
"name": "TopicConnection",
|
||||||
"ofType": null
|
"ofType": null
|
||||||
},
|
},
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
|
|
@ -1573,33 +1540,6 @@
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": null
|
"deprecationReason": null
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "node",
|
|
||||||
"description": "The ID of the object",
|
|
||||||
"args": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"description": null,
|
|
||||||
"type": {
|
|
||||||
"kind": "NON_NULL",
|
|
||||||
"name": null,
|
|
||||||
"ofType": {
|
|
||||||
"kind": "SCALAR",
|
|
||||||
"name": "ID",
|
|
||||||
"ofType": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"defaultValue": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"type": {
|
|
||||||
"kind": "INTERFACE",
|
|
||||||
"name": "Node",
|
|
||||||
"ofType": null
|
|
||||||
},
|
|
||||||
"isDeprecated": false,
|
|
||||||
"deprecationReason": null
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "_debug",
|
"name": "_debug",
|
||||||
"description": null,
|
"description": null,
|
||||||
|
|
@ -3125,6 +3065,81 @@
|
||||||
},
|
},
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": null
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "isTeacher",
|
||||||
|
"description": null,
|
||||||
|
"args": [],
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Boolean",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "oldClasses",
|
||||||
|
"description": null,
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "before",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "after",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "first",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Int",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "last",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Int",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "SchoolClassNodeConnection",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"inputFields": null,
|
"inputFields": null,
|
||||||
|
|
@ -3362,6 +3377,119 @@
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": null
|
"deprecationReason": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "moduleSet",
|
||||||
|
"description": null,
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "before",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "after",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "first",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Int",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "last",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Int",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "slug",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "slug_Icontains",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "slug_In",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "title",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "title_Icontains",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "title_In",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "ModuleNodeConnection",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "hiddenContentBlocks",
|
"name": "hiddenContentBlocks",
|
||||||
"description": null,
|
"description": null,
|
||||||
|
|
@ -3864,6 +3992,22 @@
|
||||||
},
|
},
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": null
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "members",
|
||||||
|
"description": null,
|
||||||
|
"args": [],
|
||||||
|
"type": {
|
||||||
|
"kind": "LIST",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "ClassMemberNode",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"inputFields": null,
|
"inputFields": null,
|
||||||
|
|
@ -3973,6 +4117,92 @@
|
||||||
"enumValues": null,
|
"enumValues": null,
|
||||||
"possibleTypes": null
|
"possibleTypes": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "ModuleNodeConnection",
|
||||||
|
"description": null,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "pageInfo",
|
||||||
|
"description": "Pagination data for this connection.",
|
||||||
|
"args": [],
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "PageInfo",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "edges",
|
||||||
|
"description": "Contains the nodes in this connection.",
|
||||||
|
"args": [],
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "LIST",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "ModuleNodeEdge",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inputFields": null,
|
||||||
|
"interfaces": [],
|
||||||
|
"enumValues": null,
|
||||||
|
"possibleTypes": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "ModuleNodeEdge",
|
||||||
|
"description": "A Relay edge containing a `ModuleNode` and its cursor.",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "node",
|
||||||
|
"description": "The item at the end of the edge",
|
||||||
|
"args": [],
|
||||||
|
"type": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "ModuleNode",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cursor",
|
||||||
|
"description": "A cursor for use in pagination",
|
||||||
|
"args": [],
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inputFields": null,
|
||||||
|
"interfaces": [],
|
||||||
|
"enumValues": null,
|
||||||
|
"possibleTypes": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"kind": "OBJECT",
|
"kind": "OBJECT",
|
||||||
"name": "ContentBlockNodeConnection",
|
"name": "ContentBlockNodeConnection",
|
||||||
|
|
@ -6617,6 +6847,89 @@
|
||||||
"enumValues": null,
|
"enumValues": null,
|
||||||
"possibleTypes": null
|
"possibleTypes": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "ClassMemberNode",
|
||||||
|
"description": "We need to build this ourselves, because we want the active property on the node, because providing it on the\nConnection or Edge for a UserNodeConnection is difficult.",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "user",
|
||||||
|
"description": null,
|
||||||
|
"args": [],
|
||||||
|
"type": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "UserNode",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "active",
|
||||||
|
"description": null,
|
||||||
|
"args": [],
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Boolean",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "firstName",
|
||||||
|
"description": null,
|
||||||
|
"args": [],
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lastName",
|
||||||
|
"description": null,
|
||||||
|
"args": [],
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "isTeacher",
|
||||||
|
"description": null,
|
||||||
|
"args": [],
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Boolean",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"description": null,
|
||||||
|
"args": [],
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "ID",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inputFields": null,
|
||||||
|
"interfaces": [],
|
||||||
|
"enumValues": null,
|
||||||
|
"possibleTypes": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"kind": "OBJECT",
|
"kind": "OBJECT",
|
||||||
"name": "StudentSubmissionNode",
|
"name": "StudentSubmissionNode",
|
||||||
|
|
@ -7239,92 +7552,6 @@
|
||||||
"enumValues": null,
|
"enumValues": null,
|
||||||
"possibleTypes": null
|
"possibleTypes": null
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"kind": "OBJECT",
|
|
||||||
"name": "ModuleNodeConnection",
|
|
||||||
"description": null,
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"name": "pageInfo",
|
|
||||||
"description": "Pagination data for this connection.",
|
|
||||||
"args": [],
|
|
||||||
"type": {
|
|
||||||
"kind": "NON_NULL",
|
|
||||||
"name": null,
|
|
||||||
"ofType": {
|
|
||||||
"kind": "OBJECT",
|
|
||||||
"name": "PageInfo",
|
|
||||||
"ofType": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"isDeprecated": false,
|
|
||||||
"deprecationReason": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "edges",
|
|
||||||
"description": "Contains the nodes in this connection.",
|
|
||||||
"args": [],
|
|
||||||
"type": {
|
|
||||||
"kind": "NON_NULL",
|
|
||||||
"name": null,
|
|
||||||
"ofType": {
|
|
||||||
"kind": "LIST",
|
|
||||||
"name": null,
|
|
||||||
"ofType": {
|
|
||||||
"kind": "OBJECT",
|
|
||||||
"name": "ModuleNodeEdge",
|
|
||||||
"ofType": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"isDeprecated": false,
|
|
||||||
"deprecationReason": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"inputFields": null,
|
|
||||||
"interfaces": [],
|
|
||||||
"enumValues": null,
|
|
||||||
"possibleTypes": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"kind": "OBJECT",
|
|
||||||
"name": "ModuleNodeEdge",
|
|
||||||
"description": "A Relay edge containing a `ModuleNode` and its cursor.",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"name": "node",
|
|
||||||
"description": "The item at the end of the edge",
|
|
||||||
"args": [],
|
|
||||||
"type": {
|
|
||||||
"kind": "OBJECT",
|
|
||||||
"name": "ModuleNode",
|
|
||||||
"ofType": null
|
|
||||||
},
|
|
||||||
"isDeprecated": false,
|
|
||||||
"deprecationReason": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "cursor",
|
|
||||||
"description": "A cursor for use in pagination",
|
|
||||||
"args": [],
|
|
||||||
"type": {
|
|
||||||
"kind": "NON_NULL",
|
|
||||||
"name": null,
|
|
||||||
"ofType": {
|
|
||||||
"kind": "SCALAR",
|
|
||||||
"name": "String",
|
|
||||||
"ofType": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"isDeprecated": false,
|
|
||||||
"deprecationReason": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"inputFields": null,
|
|
||||||
"interfaces": [],
|
|
||||||
"enumValues": null,
|
|
||||||
"possibleTypes": null
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"kind": "OBJECT",
|
"kind": "OBJECT",
|
||||||
"name": "StudentSubmissionNodeConnection",
|
"name": "StudentSubmissionNodeConnection",
|
||||||
|
|
@ -8825,6 +9052,92 @@
|
||||||
"enumValues": null,
|
"enumValues": null,
|
||||||
"possibleTypes": null
|
"possibleTypes": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "TopicConnection",
|
||||||
|
"description": null,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "pageInfo",
|
||||||
|
"description": "Pagination data for this connection.",
|
||||||
|
"args": [],
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "PageInfo",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "edges",
|
||||||
|
"description": "Contains the nodes in this connection.",
|
||||||
|
"args": [],
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "LIST",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "TopicEdge",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inputFields": null,
|
||||||
|
"interfaces": [],
|
||||||
|
"enumValues": null,
|
||||||
|
"possibleTypes": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "TopicEdge",
|
||||||
|
"description": "A Relay edge containing a `Topic` and its cursor.",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "node",
|
||||||
|
"description": "The item at the end of the edge",
|
||||||
|
"args": [],
|
||||||
|
"type": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "TopicNode",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cursor",
|
||||||
|
"description": "A cursor for use in pagination",
|
||||||
|
"args": [],
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inputFields": null,
|
||||||
|
"interfaces": [],
|
||||||
|
"enumValues": null,
|
||||||
|
"possibleTypes": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"kind": "OBJECT",
|
"kind": "OBJECT",
|
||||||
"name": "DjangoDebug",
|
"name": "DjangoDebug",
|
||||||
|
|
@ -9397,6 +9710,33 @@
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": null
|
"deprecationReason": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "addRemoveMember",
|
||||||
|
"description": null,
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "input",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "INPUT_OBJECT",
|
||||||
|
"name": "AddRemoveMemberInput",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "AddRemoveMemberPayload",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "addProject",
|
"name": "addProject",
|
||||||
"description": null,
|
"description": null,
|
||||||
|
|
@ -11628,6 +11968,104 @@
|
||||||
"enumValues": null,
|
"enumValues": null,
|
||||||
"possibleTypes": null
|
"possibleTypes": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "AddRemoveMemberPayload",
|
||||||
|
"description": null,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "success",
|
||||||
|
"description": null,
|
||||||
|
"args": [],
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Boolean",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "clientMutationId",
|
||||||
|
"description": null,
|
||||||
|
"args": [],
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inputFields": null,
|
||||||
|
"interfaces": [],
|
||||||
|
"enumValues": null,
|
||||||
|
"possibleTypes": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "INPUT_OBJECT",
|
||||||
|
"name": "AddRemoveMemberInput",
|
||||||
|
"description": null,
|
||||||
|
"fields": null,
|
||||||
|
"inputFields": [
|
||||||
|
{
|
||||||
|
"name": "member",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "ID",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "schoolClass",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "ID",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "active",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Boolean",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "clientMutationId",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"interfaces": null,
|
||||||
|
"enumValues": null,
|
||||||
|
"possibleTypes": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"kind": "OBJECT",
|
"kind": "OBJECT",
|
||||||
"name": "AddProjectPayload",
|
"name": "AddProjectPayload",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"me": {
|
||||||
|
"id": "VXNlck5vZGU6Mg==",
|
||||||
|
"isTeacher": false,
|
||||||
|
"selectedClass": {
|
||||||
|
"id": "U2Nob29sQ2xhc3NOb2RlOjE=",
|
||||||
|
"name": "Moordale",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"id": "VXNlck5vZGU6Mw==",
|
||||||
|
"firstName": "Otis",
|
||||||
|
"lastName": "Milburn",
|
||||||
|
"isTeacher": false,
|
||||||
|
"active": true,
|
||||||
|
"__typename": "ClassMemberNode"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "VXNlck5vZGU6NA==",
|
||||||
|
"firstName": "Maeve",
|
||||||
|
"lastName": "Wiley",
|
||||||
|
"isTeacher": false,
|
||||||
|
"active": true,
|
||||||
|
"__typename": "ClassMemberNode"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"__typename": "SchoolClassNode"
|
||||||
|
},
|
||||||
|
"__typename": "UserNode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
const schema = require('../fixtures/schema.json');
|
const schema = require('../fixtures/schema.json');
|
||||||
const me = require('../fixtures/me.join-class.json');
|
const me = require('../fixtures/me.join-class.json');
|
||||||
|
const selectedClass = require('../fixtures/selected-school-class.json');
|
||||||
|
|
||||||
describe('Join Class', () => {
|
describe('Class Management', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.server();
|
cy.server();
|
||||||
|
|
||||||
|
|
@ -31,14 +32,15 @@ describe('Join Class', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
cy.visit('/me/profile');
|
cy.visit('/me/profile');
|
||||||
|
|
||||||
cy.get('[data-cy=header-user-widget]').within(() => {
|
cy.get('[data-cy=header-user-widget]').within(() => {
|
||||||
cy.get('[data-cy=user-widget-avatar]').click();
|
cy.get('[data-cy=user-widget-avatar]').click();
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.get('[data-cy=class-selection]').click();
|
cy.get('[data-cy=class-selection]').click();
|
||||||
cy.get('[data-cy=class-selection-entry]').should('have.length', 1);
|
cy.get('[data-cy=class-selection-entry]').should('have.length', 1);
|
||||||
|
cy.get('[data-cy=class-selection]').click();
|
||||||
|
|
||||||
cy.get('[data-cy=join-class-link]').click();
|
cy.get('[data-cy=join-class-link]').click();
|
||||||
|
|
||||||
|
|
@ -51,5 +53,94 @@ describe('Join Class', () => {
|
||||||
|
|
||||||
cy.get('[data-cy=class-selection]').click();
|
cy.get('[data-cy=class-selection]').click();
|
||||||
cy.get('[data-cy=class-selection-entry]').should('have.length', 2);
|
cy.get('[data-cy=class-selection-entry]').should('have.length', 2);
|
||||||
})
|
});
|
||||||
})
|
|
||||||
|
it('should not be able to leave class', () => {
|
||||||
|
cy.mockGraphqlOps({
|
||||||
|
operations: {
|
||||||
|
MeQuery: me,
|
||||||
|
MySchoolClassQuery: selectedClass
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.visit('/me/my-class');
|
||||||
|
cy.get('[data-cy=school-class-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);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should leave and re-join class', () => {
|
||||||
|
const teacher = {
|
||||||
|
me: {
|
||||||
|
...me.me,
|
||||||
|
isTeacher: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const teacherSelectedClass = {
|
||||||
|
me: {
|
||||||
|
...selectedClass.me,
|
||||||
|
isTeacher: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
cy.mockGraphqlOps({
|
||||||
|
operations: {
|
||||||
|
MeQuery: teacher,
|
||||||
|
AddRemoveMember: {
|
||||||
|
addRemoveMember: {
|
||||||
|
success: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MySchoolClassQuery: teacherSelectedClass
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.visit('/me/my-class');
|
||||||
|
cy.get('[data-cy=active-class-members-list]').within(() => {
|
||||||
|
cy.get('[data-cy=school-class-member]').should('have.length', 2)
|
||||||
|
});
|
||||||
|
cy.get('[data-cy=inactive-class-members-list]').within(() => {
|
||||||
|
cy.get('[data-cy=school-class-member]').should('have.length', 0)
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.get('[data-cy=remove-from-class]').first().click();
|
||||||
|
|
||||||
|
cy.get('[data-cy=active-class-members-list]').within(() => {
|
||||||
|
cy.get('[data-cy=school-class-member]').should('have.length', 1)
|
||||||
|
});
|
||||||
|
cy.get('[data-cy=inactive-class-members-list]').within(() => {
|
||||||
|
cy.get('[data-cy=school-class-member]').should('have.length', 1)
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.get('[data-cy=add-to-class]').first().click();
|
||||||
|
|
||||||
|
cy.get('[data-cy=active-class-members-list]').within(() => {
|
||||||
|
cy.get('[data-cy=school-class-member]').should('have.length', 2)
|
||||||
|
});
|
||||||
|
cy.get('[data-cy=inactive-class-members-list]').within(() => {
|
||||||
|
cy.get('[data-cy=school-class-member]').should('have.length', 0)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it.only('should display old classes', () => {
|
||||||
|
let oldClasses = me.me.schoolClasses;
|
||||||
|
let OldClassesQuery = {
|
||||||
|
me: {
|
||||||
|
...me.me,
|
||||||
|
oldClasses
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
debugger;
|
||||||
|
cy.mockGraphqlOps({
|
||||||
|
operations: {
|
||||||
|
MeQuery: me,
|
||||||
|
OldClassesQuery
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.visit('/me/old-classes');
|
||||||
|
|
||||||
|
cy.get('[data-cy=old-class-item]').should('have.length', 1);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: $color-white;
|
background-color: $color-white;
|
||||||
padding: 20px;
|
padding: 0;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
@include widget-shadow;
|
@include widget-shadow;
|
||||||
|
|
||||||
|
|
@ -43,6 +43,9 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
||||||
&__link {
|
&__link {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 $medium-spacing;
|
||||||
|
|
||||||
& > a {
|
& > a {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
color: $color-silver-dark;
|
color: $color-silver-dark;
|
||||||
|
|
@ -55,6 +58,7 @@
|
||||||
|
|
||||||
&--large {
|
&--large {
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
|
padding: $small-spacing $medium-spacing;
|
||||||
& > a, & {
|
& > a, & {
|
||||||
@include small-text;
|
@include small-text;
|
||||||
}
|
}
|
||||||
|
|
@ -64,5 +68,9 @@
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__divider {
|
||||||
|
border-top: 1px solid $color-silver-dark;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
<template>
|
||||||
|
<div class="school-class">
|
||||||
|
<h2 class="school-class__name">{{name}}</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
|
||||||
|
class="simple-list__item member-item"
|
||||||
|
data-cy="school-class-member"
|
||||||
|
v-for="member in activeMembers"
|
||||||
|
: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="remove-from-class"
|
||||||
|
v-if="teacher"
|
||||||
|
@click="$emit('remove', member)">Deaktivieren</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h3 class="school-class__inactive-heading">Deaktivierte Benutzer</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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: ['members', 'name', 'teacher'],
|
||||||
|
methods: {
|
||||||
|
fullName(member) {
|
||||||
|
return `${member.firstName} ${member.lastName}`;
|
||||||
|
},
|
||||||
|
role({isTeacher}) {
|
||||||
|
return isTeacher ? 'Lehrperson' : 'Schüler';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
activeMembers() {
|
||||||
|
return this.members.filter(member => member.active)
|
||||||
|
},
|
||||||
|
inactiveMembers() {
|
||||||
|
return this.members.filter(member => !member.active)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/styles/_variables.scss";
|
||||||
|
@import "@/styles/_mixins.scss";
|
||||||
|
|
||||||
|
.school-class {
|
||||||
|
&__inactive-heading {
|
||||||
|
@include heading-4;
|
||||||
|
margin-bottom: $small-spacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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,59 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="schoolclass">
|
|
||||||
<h2 class="schoolclass__name">{{name}}</h2>
|
|
||||||
<div class="schoolclass__members schoolclass-members">
|
|
||||||
<ul class="schoolclass-members__list members-list">
|
|
||||||
<li v-for="user in users" :key="user.id" class="members-list__item">
|
|
||||||
<p class="member-item"><span class="member-item__name">{{fullName(user)}}</span> <span class="member-item__role">{{role(user)}}</span></p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['users', 'name'],
|
|
||||||
methods: {
|
|
||||||
fullName (user) {
|
|
||||||
return `${user.firstName} ${user.lastName}`;
|
|
||||||
},
|
|
||||||
role ({permissions}) {
|
|
||||||
return permissions.indexOf('users.can_manage_school_class_content') > -1 ? 'Lehrperson' : '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
@import "@/styles/_variables.scss";
|
|
||||||
|
|
||||||
$height: 52px;
|
|
||||||
|
|
||||||
.members-list {
|
|
||||||
&__item {
|
|
||||||
line-height: $height;
|
|
||||||
height: $height;
|
|
||||||
border-bottom: 1px solid $color-silver-dark;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.member-item {
|
|
||||||
|
|
||||||
line-height: $height;
|
|
||||||
height: $height;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
&__name {
|
|
||||||
font-family: $sans-serif-font-family;
|
|
||||||
font-weight: $font-weight-bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__role {
|
|
||||||
padding-right: $medium-spacing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
<div class="class-selection" v-if="currentClassSelection">
|
<div class="class-selection" v-if="currentClassSelection">
|
||||||
<div data-cy="class-selection" class="class-selection__selected-class selected-class"
|
<div data-cy="class-selection" class="class-selection__selected-class selected-class"
|
||||||
@click="showPopover = !showPopover">
|
@click="showPopover = !showPopover">
|
||||||
<current-class class="selected-class__text"></current-class> <chevron-down class="selected-class__dropdown-icon"></chevron-down>
|
<current-class class="selected-class__text"></current-class>
|
||||||
|
<chevron-down class="selected-class__dropdown-icon"></chevron-down>
|
||||||
</div>
|
</div>
|
||||||
<widget-popover v-if="showPopover"
|
<widget-popover v-if="showPopover"
|
||||||
@hide-me="showPopover = false"
|
@hide-me="showPopover = false"
|
||||||
|
|
@ -13,9 +14,13 @@
|
||||||
:key="schoolClass.id"
|
:key="schoolClass.id"
|
||||||
:label="schoolClass.name"
|
:label="schoolClass.name"
|
||||||
:item="schoolClass"
|
:item="schoolClass"
|
||||||
@click="updateFilter(schoolClass)">
|
@click="updateSelectedClass(schoolClass)">
|
||||||
{{schoolClass.name}}
|
{{schoolClass.name}}
|
||||||
</li>
|
</li>
|
||||||
|
<li class="popover-links__link popover-links__link--large popover-links__divider">Klasse erfassen</li>
|
||||||
|
<li class="popover-links__link popover-links__link--large popover-links__divider">
|
||||||
|
<router-link :to="{name: 'old-classes'}">Alte Klassen anzeigen</router-link>
|
||||||
|
</li>
|
||||||
</widget-popover>
|
</widget-popover>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -24,8 +29,10 @@
|
||||||
import WidgetPopover from '@/components/WidgetPopover';
|
import WidgetPopover from '@/components/WidgetPopover';
|
||||||
import ChevronDown from '@/components/icons/ChevronDown';
|
import ChevronDown from '@/components/icons/ChevronDown';
|
||||||
import CurrentClass from '@/components/school-class/CurrentClass';
|
import CurrentClass from '@/components/school-class/CurrentClass';
|
||||||
|
|
||||||
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
||||||
import UPDATE_USER_SETTING from '@/graphql/gql/mutations/updateUserSetting.gql';
|
|
||||||
|
import updateSelectedClassMixin from '@/mixins/updateSelectedClass';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -41,6 +48,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mixins: [updateSelectedClassMixin],
|
||||||
|
|
||||||
apollo: {
|
apollo: {
|
||||||
me: {
|
me: {
|
||||||
query: ME_QUERY,
|
query: ME_QUERY,
|
||||||
|
|
@ -67,22 +76,8 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
updateFilter(selectedClass) {
|
updateSelectedClassAndHidePopover(selectedClass) {
|
||||||
this.$apollo.mutate({
|
this.updateSelectedClass(selectedClass);
|
||||||
mutation: UPDATE_USER_SETTING,
|
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
id: selectedClass.id
|
|
||||||
}
|
|
||||||
},
|
|
||||||
update(store, data) {
|
|
||||||
let meData = store.readQuery({query: ME_QUERY});
|
|
||||||
meData.me.selectedClass = selectedClass;
|
|
||||||
store.writeQuery({query: ME_QUERY, data: meData});
|
|
||||||
}
|
|
||||||
}).catch((error) => {
|
|
||||||
console.log('fail', error)
|
|
||||||
});
|
|
||||||
this.showPopover = false;
|
this.showPopover = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -139,9 +134,4 @@
|
||||||
fill: $color-brand;
|
fill: $color-brand;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.popover-links__link {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
mutation AddRemoveMember($input: AddRemoveMemberInput!) {
|
||||||
|
addRemoveMember(input: $input) {
|
||||||
|
success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,18 +1,16 @@
|
||||||
query {
|
query MySchoolClassQuery {
|
||||||
me {
|
me {
|
||||||
id
|
id
|
||||||
|
isTeacher
|
||||||
selectedClass {
|
selectedClass {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
users {
|
members {
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
id
|
||||||
firstName
|
firstName
|
||||||
lastName
|
lastName
|
||||||
permissions
|
isTeacher
|
||||||
}
|
active
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
query OldClassesQuery {
|
||||||
|
me {
|
||||||
|
id
|
||||||
|
oldClasses {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
||||||
|
import UPDATE_USER_SETTING from '@/graphql/gql/mutations/updateUserSetting.gql';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
updateSelectedClass(selectedClass) {
|
||||||
|
return this.$apollo.mutate({
|
||||||
|
mutation: UPDATE_USER_SETTING,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
id: selectedClass.id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update(store, data) {
|
||||||
|
let meData = store.readQuery({query: ME_QUERY});
|
||||||
|
meData.me.selectedClass = selectedClass;
|
||||||
|
store.writeQuery({query: ME_QUERY, data: meData});
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
console.warn('failed to update selected class', error)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -1,33 +1,79 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="my-class">
|
<div class="my-class">
|
||||||
<h1 class="my-class__header" data-cy="class-list-title">Klassenliste</h1>
|
<h1 class="my-class__header" data-cy="class-list-title">Klassenliste</h1>
|
||||||
<classlist v-bind="selectedClass" class="my-class__class"></classlist>
|
<class-list
|
||||||
|
class="my-class__class"
|
||||||
|
:name="me.selectedClass.name"
|
||||||
|
:members="me.selectedClass.members"
|
||||||
|
:teacher="me.isTeacher"
|
||||||
|
@remove="remove"
|
||||||
|
@add="add"
|
||||||
|
></class-list>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import ClassList from '@/components/profile/ClassList';
|
||||||
|
|
||||||
import MY_SCHOOL_CLASS_QUERY from '@/graphql/gql/mySchoolClass.gql';
|
import MY_SCHOOL_CLASS_QUERY from '@/graphql/gql/mySchoolClass.gql';
|
||||||
import Classlist from '@/components/profile/Classlist';
|
import ADD_REMOVE_MEMBER_MUTATION from '@/graphql/gql/mutations/addRemoveMember.gql';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Classlist
|
ClassList
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
changeMember(member, active) {
|
||||||
|
this.$apollo.mutate({
|
||||||
|
mutation: ADD_REMOVE_MEMBER_MUTATION,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
member: member.id,
|
||||||
|
schoolClass: this.me.selectedClass.id,
|
||||||
|
active
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update(store, {data: {addRemoveMember: {success}}}) {
|
||||||
|
if (success) {
|
||||||
|
const query = MY_SCHOOL_CLASS_QUERY;
|
||||||
|
const data = store.readQuery({query});
|
||||||
|
let memberIndex = data.me.selectedClass.members.findIndex(m => m.id === member.id);
|
||||||
|
data.me.selectedClass.members = [
|
||||||
|
...data.me.selectedClass.members.slice(0, memberIndex),
|
||||||
|
{...member, active},
|
||||||
|
...data.me.selectedClass.members.slice(memberIndex),
|
||||||
|
];
|
||||||
|
store.writeQuery({query, data});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
add(member) {
|
||||||
|
this.changeMember(member, true);
|
||||||
|
},
|
||||||
|
remove(member) {
|
||||||
|
this.changeMember(member, false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
apollo: {
|
apollo: {
|
||||||
selectedClass: {
|
me: {
|
||||||
query: MY_SCHOOL_CLASS_QUERY,
|
query: MY_SCHOOL_CLASS_QUERY,
|
||||||
update(data) {
|
update(data) {
|
||||||
return this.$getRidOfEdges(data).me.selectedClass
|
return this.$getRidOfEdges(data).me
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
me: {
|
||||||
|
isTeacher: false,
|
||||||
selectedClass: {
|
selectedClass: {
|
||||||
name: ''
|
name: '',
|
||||||
|
members: []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
<template>
|
||||||
|
<div class="old-classes">
|
||||||
|
<h1 class="old-classes__title">Alte Klassen</h1>
|
||||||
|
<ul class="old-classes__list simple-list">
|
||||||
|
<li class="simple-list__item" v-for="schoolClass in me.oldClasses" :key="schoolClass.id" data-cy="old-class-item"><span
|
||||||
|
class="old-classes__class-name">{{schoolClass.name}}</span> <a
|
||||||
|
class="simple-list__action" @click="updateSelectedClassAndGoToClassList(schoolClass)">Anzeigen</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import OLD_CLASSES_QUERY from '@/graphql/gql/oldClasses.gql';
|
||||||
|
|
||||||
|
import updateSelectedClassMixin from '@/mixins/updateSelectedClass';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [updateSelectedClassMixin],
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
updateSelectedClassAndGoToClassList(selectedClass) {
|
||||||
|
this.updateSelectedClass(selectedClass).then(() => {
|
||||||
|
this.$router.push({name: 'my-class'});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// todo: test this in front- and backend
|
||||||
|
apollo: {
|
||||||
|
me: {
|
||||||
|
query: OLD_CLASSES_QUERY,
|
||||||
|
update(data) {
|
||||||
|
return this.$getRidOfEdges(data).me;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data: () => ({
|
||||||
|
me: {
|
||||||
|
oldClasses: []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/styles/_variables.scss";
|
||||||
|
|
||||||
|
.old-classes {
|
||||||
|
&__class-name {
|
||||||
|
font-family: $sans-serif-font-family;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -31,6 +31,7 @@ import login from '@/pages/login'
|
||||||
import registration from '@/pages/registration'
|
import registration from '@/pages/registration'
|
||||||
import waitForClass from '@/pages/waitForClass'
|
import waitForClass from '@/pages/waitForClass'
|
||||||
import joinClass from '@/pages/joinClass'
|
import joinClass from '@/pages/joinClass'
|
||||||
|
import oldClasses from '@/pages/oldClasses';
|
||||||
|
|
||||||
import store from '@/store/index';
|
import store from '@/store/index';
|
||||||
|
|
||||||
|
|
@ -111,6 +112,13 @@ const routes = [
|
||||||
{path: 'my-class', name: 'my-class', component: myClass, meta: {isProfile: true}},
|
{path: 'my-class', name: 'my-class', component: myClass, meta: {isProfile: true}},
|
||||||
{path: 'activity', name: 'activity', component: activity, meta: {isProfile: true}},
|
{path: 'activity', name: 'activity', component: activity, meta: {isProfile: true}},
|
||||||
{path: '', name: 'profile-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: 'join-class', name: 'join-class', component: joinClass, meta: {layout: 'simple'}},
|
{path: 'join-class', name: 'join-class', component: joinClass, meta: {layout: 'simple'}},
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,7 @@
|
||||||
font-family: $sans-serif-font-family;
|
font-family: $sans-serif-font-family;
|
||||||
font-weight: $font-weight-regular;
|
font-weight: $font-weight-regular;
|
||||||
color: $color-silver-dark;
|
color: $color-silver-dark;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&--active {
|
&--active {
|
||||||
color: $color-brand;
|
color: $color-brand;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
@import "variables";
|
||||||
|
|
||||||
|
.simple-list {
|
||||||
|
border-top: 1px solid $color-silver-dark;
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
margin-bottom: 2*$large-spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
line-height: $list-height;
|
||||||
|
height: $list-height;
|
||||||
|
border-bottom: 1px solid $color-silver-dark;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__action {
|
||||||
|
@include default-link;
|
||||||
|
color: $color-brand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -56,6 +56,9 @@ $red: #FA5F5F;
|
||||||
$green: #6DD79A;
|
$green: #6DD79A;
|
||||||
$brown: #EB9E77;
|
$brown: #EB9E77;
|
||||||
|
|
||||||
|
$list-height: 52px;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$default-border-radius: 13px;
|
$default-border-radius: 13px;
|
||||||
$input-border-radius: 3px;
|
$input-border-radius: 3px;
|
||||||
|
|
|
||||||
|
|
@ -23,3 +23,4 @@
|
||||||
@import "student-submission";
|
@import "student-submission";
|
||||||
@import "module-activity";
|
@import "module-activity";
|
||||||
@import "book-subnavigation";
|
@import "book-subnavigation";
|
||||||
|
@import "simple-list";
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ from graphql_relay import to_global_id
|
||||||
from api.test_utils import create_client, DefaultUserTestCase
|
from api.test_utils import create_client, DefaultUserTestCase
|
||||||
from assignments.models import Assignment, StudentSubmission
|
from assignments.models import Assignment, StudentSubmission
|
||||||
from users.factories import SchoolClassFactory
|
from users.factories import SchoolClassFactory
|
||||||
|
from users.models import SchoolClassMember
|
||||||
from ..factories import AssignmentFactory, StudentSubmissionFactory, SubmissionFeedbackFactory
|
from ..factories import AssignmentFactory, StudentSubmissionFactory, SubmissionFeedbackFactory
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -25,13 +26,16 @@ class SubmissionFeedbackTestCase(DefaultUserTestCase):
|
||||||
)
|
)
|
||||||
self.assignment_id = to_global_id('AssignmentNode', self.assignment.pk)
|
self.assignment_id = to_global_id('AssignmentNode', self.assignment.pk)
|
||||||
|
|
||||||
self.student_submission = StudentSubmissionFactory(assignment=self.assignment, student=self.student1, final=False)
|
self.student_submission = StudentSubmissionFactory(assignment=self.assignment, student=self.student1,
|
||||||
|
final=False)
|
||||||
self.student_submission_id = to_global_id('StudentSubmissionNode', self.student_submission.pk)
|
self.student_submission_id = to_global_id('StudentSubmissionNode', self.student_submission.pk)
|
||||||
|
|
||||||
school_class = SchoolClassFactory()
|
school_class = SchoolClassFactory()
|
||||||
school_class.users.add(self.student1)
|
for user in [self.student1, self.teacher, self.teacher2]:
|
||||||
school_class.users.add(self.teacher)
|
SchoolClassMember.objects.create(
|
||||||
school_class.users.add(self.teacher2)
|
user=user,
|
||||||
|
school_class=school_class
|
||||||
|
)
|
||||||
|
|
||||||
def _create_submission_feedback(self, user, final, text, student_submission_id):
|
def _create_submission_feedback(self, user, final, text, student_submission_id):
|
||||||
mutation = '''
|
mutation = '''
|
||||||
|
|
@ -122,19 +126,17 @@ class SubmissionFeedbackTestCase(DefaultUserTestCase):
|
||||||
})
|
})
|
||||||
|
|
||||||
def test_teacher_can_create_feedback(self):
|
def test_teacher_can_create_feedback(self):
|
||||||
|
|
||||||
result = self._create_submission_feedback(self.teacher, False, 'Balalal', self.student_submission_id)
|
result = self._create_submission_feedback(self.teacher, False, 'Balalal', self.student_submission_id)
|
||||||
|
|
||||||
self.assertIsNone(result.get('errors'))
|
self.assertIsNone(result.get('errors'))
|
||||||
self.assertIsNotNone(result.get('data').get('updateSubmissionFeedback').get('updatedSubmissionFeedback').get('id'))
|
self.assertIsNotNone(
|
||||||
|
result.get('data').get('updateSubmissionFeedback').get('updatedSubmissionFeedback').get('id'))
|
||||||
|
|
||||||
def test_student_cannot_create_feedback(self):
|
def test_student_cannot_create_feedback(self):
|
||||||
|
|
||||||
result = self._create_submission_feedback(self.student1, False, 'Balalal', self.student_submission_id)
|
result = self._create_submission_feedback(self.student1, False, 'Balalal', self.student_submission_id)
|
||||||
self.assertIsNotNone(result.get('errors'))
|
self.assertIsNotNone(result.get('errors'))
|
||||||
|
|
||||||
def test_teacher_can_update_feedback(self):
|
def test_teacher_can_update_feedback(self):
|
||||||
|
|
||||||
assignment = AssignmentFactory(
|
assignment = AssignmentFactory(
|
||||||
owner=self.teacher
|
owner=self.teacher
|
||||||
)
|
)
|
||||||
|
|
@ -148,13 +150,13 @@ class SubmissionFeedbackTestCase(DefaultUserTestCase):
|
||||||
|
|
||||||
self.assertIsNone(result.get('errors'))
|
self.assertIsNone(result.get('errors'))
|
||||||
|
|
||||||
submission_feedback_response = result.get('data').get('updateSubmissionFeedback').get('updatedSubmissionFeedback')
|
submission_feedback_response = result.get('data').get('updateSubmissionFeedback').get(
|
||||||
|
'updatedSubmissionFeedback')
|
||||||
|
|
||||||
self.assertTrue(submission_feedback_response.get('final'))
|
self.assertTrue(submission_feedback_response.get('final'))
|
||||||
self.assertEqual(submission_feedback_response.get('text'), 'Some')
|
self.assertEqual(submission_feedback_response.get('text'), 'Some')
|
||||||
|
|
||||||
def test_rogue_teacher_cannot_update_feedback(self):
|
def test_rogue_teacher_cannot_update_feedback(self):
|
||||||
|
|
||||||
assignment = AssignmentFactory(
|
assignment = AssignmentFactory(
|
||||||
owner=self.teacher
|
owner=self.teacher
|
||||||
)
|
)
|
||||||
|
|
@ -169,14 +171,12 @@ class SubmissionFeedbackTestCase(DefaultUserTestCase):
|
||||||
self.assertIsNotNone(result.get('errors'))
|
self.assertIsNotNone(result.get('errors'))
|
||||||
|
|
||||||
def test_student_does_not_see_non_final_feedback(self):
|
def test_student_does_not_see_non_final_feedback(self):
|
||||||
|
|
||||||
SubmissionFeedbackFactory(teacher=self.teacher, final=False, student_submission=self.student_submission)
|
SubmissionFeedbackFactory(teacher=self.teacher, final=False, student_submission=self.student_submission)
|
||||||
result = self._fetch_assignment_student(self.student1)
|
result = self._fetch_assignment_student(self.student1)
|
||||||
|
|
||||||
self.assertIsNone(result.get('data').get('submissionFeedback'))
|
self.assertIsNone(result.get('data').get('submissionFeedback'))
|
||||||
|
|
||||||
def test_student_does_see_final_feedback(self):
|
def test_student_does_see_final_feedback(self):
|
||||||
|
|
||||||
submission_feedback = SubmissionFeedbackFactory(teacher=self.teacher, final=True,
|
submission_feedback = SubmissionFeedbackFactory(teacher=self.teacher, final=True,
|
||||||
student_submission=self.student_submission)
|
student_submission=self.student_submission)
|
||||||
result = self._fetch_assignment_student(self.student1)
|
result = self._fetch_assignment_student(self.student1)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import os
|
||||||
import requests
|
import requests
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from users.models import User, SchoolClass, Role, UserRole
|
from users.models import User, SchoolClass, Role, UserRole, SchoolClassMember
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|
@ -40,7 +40,10 @@ class Command(BaseCommand):
|
||||||
self.stdout.write("Adding to class(es) {}".format(', '.join(school_class_names)))
|
self.stdout.write("Adding to class(es) {}".format(', '.join(school_class_names)))
|
||||||
for school_class_name in school_class_names:
|
for school_class_name in school_class_names:
|
||||||
school, _ = SchoolClass.objects.get_or_create(name=school_class_name)
|
school, _ = SchoolClass.objects.get_or_create(name=school_class_name)
|
||||||
user.school_classes.add(school)
|
SchoolClassMember.objects.create(
|
||||||
|
school_class=school,
|
||||||
|
user=user
|
||||||
|
)
|
||||||
|
|
||||||
self.stdout.write("")
|
self.stdout.write("")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ from graphene import relay
|
||||||
from core.views import SetPasswordView
|
from core.views import SetPasswordView
|
||||||
from registration.models import License
|
from registration.models import License
|
||||||
from registration.serializers import RegistrationSerializer
|
from registration.serializers import RegistrationSerializer
|
||||||
from users.models import User, Role, UserRole, SchoolClass
|
from users.models import User, Role, UserRole, SchoolClass, SchoolClassMember
|
||||||
|
|
||||||
|
|
||||||
class PublicFieldError(graphene.ObjectType):
|
class PublicFieldError(graphene.ObjectType):
|
||||||
|
|
@ -61,7 +61,10 @@ class Registration(relay.ClientIDMutation):
|
||||||
UserRole.objects.get_or_create(user=user, role=teacher_role)
|
UserRole.objects.get_or_create(user=user, role=teacher_role)
|
||||||
default_class_name = SchoolClass.generate_default_group_name()
|
default_class_name = SchoolClass.generate_default_group_name()
|
||||||
default_class = SchoolClass.objects.create(name=default_class_name)
|
default_class = SchoolClass.objects.create(name=default_class_name)
|
||||||
user.school_classes.add(default_class)
|
SchoolClassMember.objects.create(
|
||||||
|
user=user,
|
||||||
|
school_class=default_class
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
student_role = Role.objects.get(key=Role.objects.STUDENT_KEY)
|
student_role = Role.objects.get(key=Role.objects.STUDENT_KEY)
|
||||||
UserRole.objects.get_or_create(user=user, role=student_role)
|
UserRole.objects.get_or_create(user=user, role=student_role)
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,24 @@ from .models import User, SchoolClass, Role, UserRole, UserSetting
|
||||||
|
|
||||||
class SchoolClassInline(admin.TabularInline):
|
class SchoolClassInline(admin.TabularInline):
|
||||||
model = SchoolClass.users.through
|
model = SchoolClass.users.through
|
||||||
|
extra = 1
|
||||||
|
|
||||||
|
|
||||||
class RoleInline(admin.TabularInline):
|
class RoleInline(admin.TabularInline):
|
||||||
model = UserRole
|
model = UserRole
|
||||||
|
extra = 1
|
||||||
|
|
||||||
|
|
||||||
@admin.register(SchoolClass)
|
@admin.register(SchoolClass)
|
||||||
class SchoolClassAdmin(admin.ModelAdmin):
|
class SchoolClassAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'code', 'is_deleted')
|
list_display = ('name', 'code', 'user_list', 'is_deleted')
|
||||||
|
|
||||||
|
inlines = [
|
||||||
|
SchoolClassInline
|
||||||
|
]
|
||||||
|
|
||||||
|
def user_list(self, obj):
|
||||||
|
return ', '.join([s.username for s in obj.users.all()])
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Role)
|
@admin.register(Role)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import random
|
||||||
|
|
||||||
import factory
|
import factory
|
||||||
|
|
||||||
from users.models import SchoolClass
|
from users.models import SchoolClass, SchoolClassMember
|
||||||
|
|
||||||
class_types = ['DA', 'KV', 'INF', 'EE']
|
class_types = ['DA', 'KV', 'INF', 'EE']
|
||||||
class_suffix = ['A', 'B', 'C', 'D', 'E']
|
class_suffix = ['A', 'B', 'C', 'D', 'E']
|
||||||
|
|
@ -16,7 +16,8 @@ class SchoolClassFactory(factory.django.DjangoModelFactory):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SchoolClass
|
model = SchoolClass
|
||||||
|
|
||||||
name = factory.Sequence(lambda n: '{}{}{}'.format(random.choice(class_types), '18', class_suffix[n % len(class_suffix)]))
|
name = factory.Sequence(
|
||||||
|
lambda n: '{}{}{}'.format(random.choice(class_types), '18', class_suffix[n % len(class_suffix)]))
|
||||||
is_deleted = False
|
is_deleted = False
|
||||||
|
|
||||||
@factory.post_generation
|
@factory.post_generation
|
||||||
|
|
@ -28,4 +29,4 @@ class SchoolClassFactory(factory.django.DjangoModelFactory):
|
||||||
if extracted:
|
if extracted:
|
||||||
# A list of groups were passed in, use them
|
# A list of groups were passed in, use them
|
||||||
for user in extracted:
|
for user in extracted:
|
||||||
self.users.add(user)
|
SchoolClassMember.objects.create(user=user, school_class=self, active=True)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Generated by Django 2.1.15 on 2020-03-03 12:58
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0011_auto_20200302_1613'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserSchoolClassConnection',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('active', models.BooleanField(default=True)),
|
||||||
|
('school_class', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.SchoolClass')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='schoolclass',
|
||||||
|
name='users_with_active',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='school_classes_with_active', through='users.UserSchoolClassConnection', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Generated by Django 2.1.15 on 2020-03-03 12:59
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
def forwards(apps, schema_editor):
|
||||||
|
SchoolClass = apps.get_model('users', 'SchoolClass')
|
||||||
|
UserSchoolClassConnection = apps.get_model('users', 'UserSchoolClassConnection')
|
||||||
|
|
||||||
|
for school_class in SchoolClass.objects.all():
|
||||||
|
for user in school_class.users.all():
|
||||||
|
UserSchoolClassConnection.objects.create(user=user, school_class=school_class, active=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('users', '0012_auto_20200303_1258'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(forwards)
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 2.1.15 on 2020-03-03 13:06
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0013_auto_20200303_1259'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='schoolclass',
|
||||||
|
name='users',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 2.1.15 on 2020-03-03 13:06
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0014_remove_schoolclass_users'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='schoolclass',
|
||||||
|
name='users_with_active',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='schoolclass',
|
||||||
|
name='users',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='school_classes', through='users.UserSchoolClassConnection', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 2.1.15 on 2020-03-04 12:50
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0015_auto_20200303_1306'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name='UserSchoolClassConnection',
|
||||||
|
new_name='SchoolClassMember',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -42,13 +42,16 @@ class User(AbstractUser):
|
||||||
return User.objects.filter(school_classes__users=self.pk)
|
return User.objects.filter(school_classes__users=self.pk)
|
||||||
|
|
||||||
def get_teacher(self):
|
def get_teacher(self):
|
||||||
if self.user_roles.filter(role__key='teacher').exists():
|
if self.is_teacher():
|
||||||
return self
|
return self
|
||||||
elif self.school_classes.count() > 0:
|
elif self.school_classes.count() > 0:
|
||||||
return self.school_classes.first().get_teacher()
|
return self.school_classes.first().get_teacher()
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def is_teacher(self):
|
||||||
|
return self.user_roles.filter(role__key='teacher').exists()
|
||||||
|
|
||||||
def selected_class(self):
|
def selected_class(self):
|
||||||
try:
|
try:
|
||||||
settings = UserSetting.objects.get(user=self)
|
settings = UserSetting.objects.get(user=self)
|
||||||
|
|
@ -69,7 +72,8 @@ class User(AbstractUser):
|
||||||
class SchoolClass(models.Model):
|
class SchoolClass(models.Model):
|
||||||
name = models.CharField(max_length=100, blank=False, null=False, unique=True)
|
name = models.CharField(max_length=100, blank=False, null=False, unique=True)
|
||||||
is_deleted = models.BooleanField(blank=False, null=False, default=False)
|
is_deleted = models.BooleanField(blank=False, null=False, default=False)
|
||||||
users = models.ManyToManyField(get_user_model(), related_name='school_classes', blank=True)
|
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)
|
code = models.CharField('Code zum Beitreten', blank=True, null=True, max_length=10, unique=True, default=None)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -175,3 +179,9 @@ class UserRole(models.Model):
|
||||||
class UserSetting(models.Model):
|
class UserSetting(models.Model):
|
||||||
user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE, related_name='user_setting')
|
user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE, related_name='user_setting')
|
||||||
selected_class = models.ForeignKey(SchoolClass, blank=True, null=True, on_delete=models.CASCADE)
|
selected_class = models.ForeignKey(SchoolClass, blank=True, null=True, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
|
||||||
|
class SchoolClassMember(models.Model):
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
school_class = models.ForeignKey(SchoolClass, on_delete=models.CASCADE)
|
||||||
|
active = models.BooleanField(default=True)
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,11 @@ from django.contrib.auth import update_session_auth_hash
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from graphene import relay
|
from graphene import relay
|
||||||
|
from graphql_relay import from_global_id
|
||||||
|
|
||||||
from api.utils import get_object
|
from api.utils import get_object
|
||||||
from users.inputs import PasswordUpdateInput
|
from users.inputs import PasswordUpdateInput
|
||||||
from users.models import SchoolClass, UserSetting
|
from users.models import SchoolClass, UserSetting, User, SchoolClassMember
|
||||||
from users.schema import SchoolClassNode
|
from users.schema import SchoolClassNode
|
||||||
from users.serializers import PasswordSerialzer, AvatarUrlSerializer
|
from users.serializers import PasswordSerialzer, AvatarUrlSerializer
|
||||||
|
|
||||||
|
|
@ -122,9 +123,11 @@ class JoinClass(relay.ClientIDMutation):
|
||||||
try:
|
try:
|
||||||
school_class = SchoolClass.objects.get(Q(code__iexact=code))
|
school_class = SchoolClass.objects.get(Q(code__iexact=code))
|
||||||
|
|
||||||
|
|
||||||
if user not in list(school_class.users.all()):
|
if user not in list(school_class.users.all()):
|
||||||
school_class.users.add(user)
|
SchoolClassMember.objects.create(
|
||||||
|
user=user,
|
||||||
|
school_class=school_class
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise CodeNotFoundException('[CAJ] Schüler ist bereits in Klasse') # CAJ = Class Already Joined
|
raise CodeNotFoundException('[CAJ] Schüler ist bereits in Klasse') # CAJ = Class Already Joined
|
||||||
|
|
||||||
|
|
@ -133,8 +136,37 @@ class JoinClass(relay.ClientIDMutation):
|
||||||
raise CodeNotFoundException('[CNV] Code ist nicht gültig') # CAV = Code Not Valid
|
raise CodeNotFoundException('[CNV] Code ist nicht gültig') # CAV = Code Not Valid
|
||||||
|
|
||||||
|
|
||||||
|
class AddRemoveMember(relay.ClientIDMutation):
|
||||||
|
class Input:
|
||||||
|
member = graphene.ID(required=True)
|
||||||
|
school_class = graphene.ID(required=True)
|
||||||
|
active = graphene.Boolean(required=True)
|
||||||
|
|
||||||
|
success = graphene.Boolean()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||||
|
member_id = kwargs.get('member')
|
||||||
|
school_class_id = kwargs.get('school_class')
|
||||||
|
active = kwargs.get('active')
|
||||||
|
user = info.context.user
|
||||||
|
|
||||||
|
member_pk = from_global_id(member_id)[1]
|
||||||
|
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')
|
||||||
|
|
||||||
|
school_class_member = SchoolClassMember.objects.get(user__pk=member_pk, school_class=school_class)
|
||||||
|
school_class_member.active = active
|
||||||
|
school_class_member.save()
|
||||||
|
|
||||||
|
return cls(success=True)
|
||||||
|
|
||||||
|
|
||||||
class ProfileMutations:
|
class ProfileMutations:
|
||||||
update_password = UpdatePassword.Field()
|
update_password = UpdatePassword.Field()
|
||||||
update_avatar = UpdateAvatar.Field()
|
update_avatar = UpdateAvatar.Field()
|
||||||
update_setting = UpdateSetting.Field()
|
update_setting = UpdateSetting.Field()
|
||||||
join_class = JoinClass.Field()
|
join_class = JoinClass.Field()
|
||||||
|
add_remove_member = AddRemoveMember.Field()
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,21 @@
|
||||||
import graphene
|
import graphene
|
||||||
from graphene import relay
|
from django.db.models import Prefetch, Value, CharField, Q
|
||||||
|
from django.db.models.functions import Concat
|
||||||
|
from graphene import relay, ObjectType
|
||||||
from graphene_django import DjangoObjectType
|
from graphene_django import DjangoObjectType
|
||||||
from graphene_django.filter import DjangoFilterConnectionField
|
from graphene_django.filter import DjangoFilterConnectionField
|
||||||
|
from graphql_relay import to_global_id
|
||||||
|
|
||||||
from assignments.models import StudentSubmission
|
|
||||||
from assignments.schema.types import StudentSubmissionNode
|
|
||||||
from basicknowledge.models import BasicKnowledge
|
from basicknowledge.models import BasicKnowledge
|
||||||
from basicknowledge.queries import InstrumentNode
|
from basicknowledge.queries import InstrumentNode
|
||||||
from books.models import Module
|
from books.models import Module
|
||||||
from books.schema.queries import ModuleNode
|
from books.schema.queries import ModuleNode
|
||||||
from users.models import User, SchoolClass
|
from users.models import User, SchoolClass, SchoolClassMember
|
||||||
|
|
||||||
|
|
||||||
class SchoolClassNode(DjangoObjectType):
|
class SchoolClassNode(DjangoObjectType):
|
||||||
pk = graphene.Int()
|
pk = graphene.Int()
|
||||||
|
members = graphene.List('users.schema.ClassMemberNode')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SchoolClass
|
model = SchoolClass
|
||||||
|
|
@ -23,11 +25,16 @@ class SchoolClassNode(DjangoObjectType):
|
||||||
def resolve_pk(self, *args, **kwargs):
|
def resolve_pk(self, *args, **kwargs):
|
||||||
return self.id
|
return self.id
|
||||||
|
|
||||||
|
def resolve_members(self, info, **kwargs):
|
||||||
|
return SchoolClassMember.objects.filter(school_class=self)
|
||||||
|
|
||||||
|
|
||||||
class UserNode(DjangoObjectType):
|
class UserNode(DjangoObjectType):
|
||||||
pk = graphene.Int()
|
pk = graphene.Int()
|
||||||
permissions = graphene.List(graphene.String)
|
permissions = graphene.List(graphene.String)
|
||||||
selected_class = graphene.Field(SchoolClassNode)
|
selected_class = graphene.Field(SchoolClassNode)
|
||||||
|
is_teacher = graphene.Boolean()
|
||||||
|
old_classes = DjangoFilterConnectionField(SchoolClassNode)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
|
|
@ -45,6 +52,49 @@ class UserNode(DjangoObjectType):
|
||||||
def resolve_selected_class(self, info):
|
def resolve_selected_class(self, info):
|
||||||
return self.selected_class()
|
return self.selected_class()
|
||||||
|
|
||||||
|
def resolve_is_teacher(self, info):
|
||||||
|
return self.is_teacher()
|
||||||
|
|
||||||
|
def resolve_school_classes(self, info):
|
||||||
|
if self.selected_class() is None: # then we don't have any class to return
|
||||||
|
return []
|
||||||
|
return SchoolClass.objects.filter(
|
||||||
|
Q(schoolclassmember__active=True, schoolclassmember__user=self) | Q(pk=self.selected_class().pk)).distinct()
|
||||||
|
|
||||||
|
def resolve_old_classes(self, info):
|
||||||
|
return SchoolClass.objects.filter(schoolclassmember__active=False, schoolclassmember__user=self)
|
||||||
|
|
||||||
|
|
||||||
|
class ClassMemberNode(ObjectType):
|
||||||
|
"""
|
||||||
|
We need to build this ourselves, because we want the active property on the node, because providing it on the
|
||||||
|
Connection or Edge for a UserNodeConnection is difficult.
|
||||||
|
"""
|
||||||
|
user = graphene.Field(UserNode)
|
||||||
|
active = graphene.Boolean()
|
||||||
|
first_name = graphene.String()
|
||||||
|
last_name = graphene.String()
|
||||||
|
is_teacher = graphene.Boolean()
|
||||||
|
id = graphene.ID()
|
||||||
|
|
||||||
|
def resolve_id(self, *args):
|
||||||
|
return to_global_id('UserNode', self.user.pk)
|
||||||
|
|
||||||
|
def resolve_active(self, *args):
|
||||||
|
return self.active
|
||||||
|
|
||||||
|
def resolve_first_name(self, *args):
|
||||||
|
return self.user.first_name
|
||||||
|
|
||||||
|
def resolve_last_name(self, *args):
|
||||||
|
return self.user.last_name
|
||||||
|
|
||||||
|
def resolve_user(self, info, **kwargs):
|
||||||
|
return self.user
|
||||||
|
|
||||||
|
def resolve_is_teacher(self, *args):
|
||||||
|
return self.user.is_teacher()
|
||||||
|
|
||||||
|
|
||||||
class UsersQuery(object):
|
class UsersQuery(object):
|
||||||
me = graphene.Field(UserNode)
|
me = graphene.Field(UserNode)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ from graphene.test import Client
|
||||||
|
|
||||||
from core.factories import UserFactory
|
from core.factories import UserFactory
|
||||||
from users.factories import SchoolClassFactory
|
from users.factories import SchoolClassFactory
|
||||||
from users.models import SchoolClass
|
from users.models import SchoolClass, SchoolClassMember
|
||||||
from api.schema import schema
|
from api.schema import schema
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -45,7 +45,10 @@ class JoinSchoolClassTest(TestCase):
|
||||||
def test_class_already_joined(self):
|
def test_class_already_joined(self):
|
||||||
code = 'YYYY'
|
code = 'YYYY'
|
||||||
school_class = SchoolClass.objects.get(code=code)
|
school_class = SchoolClass.objects.get(code=code)
|
||||||
school_class.users.add(self.user)
|
SchoolClassMember.objects.create(
|
||||||
|
user=self.user,
|
||||||
|
school_class=school_class
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(self.user.school_classes.count(), 2)
|
self.assertEqual(self.user.school_classes.count(), 2)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
from graphene import Context
|
||||||
|
from graphene.test import Client
|
||||||
|
from graphql_relay import to_global_id
|
||||||
|
|
||||||
|
from api.utils import get_graphql_mutation
|
||||||
|
from core.factories import UserFactory
|
||||||
|
from users.factories import SchoolClassFactory
|
||||||
|
from users.models import SchoolClass, User, SchoolClassMember
|
||||||
|
from api.schema import schema
|
||||||
|
from users.services import create_users
|
||||||
|
|
||||||
|
|
||||||
|
class JoinSchoolClassTest(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client(schema=schema)
|
||||||
|
self.school_class_name = 'Moordale'
|
||||||
|
|
||||||
|
user_data = [
|
||||||
|
{
|
||||||
|
'teacher': ('Emily', 'Sands',),
|
||||||
|
'class': self.school_class_name,
|
||||||
|
'code': 'SEXED',
|
||||||
|
'students': [
|
||||||
|
('Otis', 'Milburn'),
|
||||||
|
('Maeve', 'Wiley'),
|
||||||
|
('Adam', 'Groff'),
|
||||||
|
('Eric', 'Effiong'),
|
||||||
|
('Jackson', 'Marchetti'),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'teacher': ('Colin', 'Hendricks'),
|
||||||
|
'class': 'Swing Band',
|
||||||
|
'students': [
|
||||||
|
('Ola', 'Nyman'),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
create_users(user_data)
|
||||||
|
teacher = User.objects.get(username='emily.sands')
|
||||||
|
self.teacher_id = to_global_id('UserNode', teacher.pk)
|
||||||
|
student = User.objects.get(username='adam.groff')
|
||||||
|
self.student_id = to_global_id('UserNode', student.pk)
|
||||||
|
other_student = User.objects.get(username='eric.effiong')
|
||||||
|
self.other_student_id = to_global_id('UserNode', other_student.pk)
|
||||||
|
|
||||||
|
school_class = SchoolClass.objects.get(name=self.school_class_name)
|
||||||
|
self.school_class_id = to_global_id('SchoolClassNode', school_class.pk)
|
||||||
|
self.teacher_context = Context(user=teacher)
|
||||||
|
self.student_context = Context(user=student)
|
||||||
|
|
||||||
|
self.mutation = get_graphql_mutation('addRemoveMember.gql')
|
||||||
|
|
||||||
|
def test_leave_and_join_class(self):
|
||||||
|
self.assertEqual(
|
||||||
|
SchoolClassMember.objects.filter(school_class__name=self.school_class_name, active=True).count(), 6)
|
||||||
|
self.assertEqual(
|
||||||
|
SchoolClassMember.objects.filter(school_class__name=self.school_class_name, active=False).count(),
|
||||||
|
0)
|
||||||
|
result = self.client.execute(self.mutation, variables={
|
||||||
|
'input': {
|
||||||
|
'schoolClass': self.school_class_id,
|
||||||
|
'member': self.student_id,
|
||||||
|
'active': False
|
||||||
|
}
|
||||||
|
}, context=self.teacher_context)
|
||||||
|
self.assertIsNone(result.get('errors'))
|
||||||
|
self.assertEqual(
|
||||||
|
SchoolClassMember.objects.filter(school_class__name=self.school_class_name, active=True).count(), 5)
|
||||||
|
self.assertEqual(
|
||||||
|
SchoolClassMember.objects.filter(school_class__name=self.school_class_name, active=False).count(),
|
||||||
|
1)
|
||||||
|
|
||||||
|
result = self.client.execute(self.mutation, variables={
|
||||||
|
'input': {
|
||||||
|
'schoolClass': self.school_class_id,
|
||||||
|
'member': self.student_id,
|
||||||
|
'active': True
|
||||||
|
}
|
||||||
|
}, context=self.teacher_context)
|
||||||
|
|
||||||
|
self.assertIsNone(result.get('errors'))
|
||||||
|
self.assertEqual(
|
||||||
|
SchoolClassMember.objects.filter(school_class__name=self.school_class_name, active=True).count(), 6)
|
||||||
|
self.assertEqual(
|
||||||
|
SchoolClassMember.objects.filter(school_class__name=self.school_class_name, active=False).count(),
|
||||||
|
0)
|
||||||
|
|
||||||
|
def test_leave_class_student_raises_error(self):
|
||||||
|
self.assertEqual(
|
||||||
|
SchoolClassMember.objects.filter(school_class__name=self.school_class_name, active=True).count(), 6)
|
||||||
|
self.assertEqual(
|
||||||
|
SchoolClassMember.objects.filter(school_class__name=self.school_class_name, active=False).count(), 0)
|
||||||
|
result = self.client.execute(self.mutation, variables={
|
||||||
|
'input': {
|
||||||
|
'schoolClass': self.school_class_id,
|
||||||
|
'member': self.other_student_id,
|
||||||
|
'active': False
|
||||||
|
}
|
||||||
|
}, context=self.student_context)
|
||||||
|
self.assertIsNotNone(result['errors'])
|
||||||
|
self.assertEqual(
|
||||||
|
SchoolClassMember.objects.filter(school_class__name=self.school_class_name, active=True).count(), 6)
|
||||||
|
self.assertEqual(
|
||||||
|
SchoolClassMember.objects.filter(school_class__name=self.school_class_name, active=False).count(), 0)
|
||||||
|
|
||||||
|
def test_leave_class_other_school_class_raises_error(self):
|
||||||
|
self.assertEqual(
|
||||||
|
SchoolClassMember.objects.filter(school_class__name=self.school_class_name, active=True).count(), 6)
|
||||||
|
self.assertEqual(
|
||||||
|
SchoolClassMember.objects.filter(school_class__name=self.school_class_name, active=False).count(), 0)
|
||||||
|
student = User.objects.get(username='ola.nyman')
|
||||||
|
school_class = SchoolClass.objects.get(name='Swing Band')
|
||||||
|
result = self.client.execute(self.mutation, variables={
|
||||||
|
'input': {
|
||||||
|
'schoolClass': to_global_id('SchoolClassNode', school_class.id),
|
||||||
|
'member': to_global_id('UserNode', student.id),
|
||||||
|
'active': False
|
||||||
|
}
|
||||||
|
}, context=self.teacher_context)
|
||||||
|
self.assertIsNotNone(result['errors'])
|
||||||
|
self.assertEqual(
|
||||||
|
SchoolClassMember.objects.filter(school_class__name=self.school_class_name, active=True).count(), 6)
|
||||||
|
self.assertEqual(
|
||||||
|
SchoolClassMember.objects.filter(school_class__name=self.school_class_name, active=False).count(), 0)
|
||||||
|
|
@ -11,10 +11,12 @@ from django.conf import settings
|
||||||
from django.test import TestCase, RequestFactory
|
from django.test import TestCase, RequestFactory
|
||||||
from graphene.test import Client
|
from graphene.test import Client
|
||||||
from api.schema import schema
|
from api.schema import schema
|
||||||
|
from api.utils import get_graphql_query
|
||||||
from core.factories import UserFactory
|
from core.factories import UserFactory
|
||||||
from django.contrib.auth import authenticate
|
from django.contrib.auth import authenticate
|
||||||
|
|
||||||
from users.factories import SchoolClassFactory
|
from users.factories import SchoolClassFactory
|
||||||
|
from users.models import SchoolClassMember
|
||||||
|
|
||||||
|
|
||||||
class MySchoolClasses(TestCase):
|
class MySchoolClasses(TestCase):
|
||||||
|
|
@ -24,13 +26,17 @@ class MySchoolClasses(TestCase):
|
||||||
self.class1 = SchoolClassFactory(users=[self.user, self.another_user])
|
self.class1 = SchoolClassFactory(users=[self.user, self.another_user])
|
||||||
self.class2 = SchoolClassFactory(users=[self.user])
|
self.class2 = SchoolClassFactory(users=[self.user])
|
||||||
self.class3 = SchoolClassFactory(users=[self.another_user])
|
self.class3 = SchoolClassFactory(users=[self.another_user])
|
||||||
|
self.inactive_class = SchoolClassFactory(users=[self.user])
|
||||||
|
|
||||||
|
inactive_member = SchoolClassMember.objects.get(school_class=self.inactive_class)
|
||||||
|
inactive_member.active = False
|
||||||
|
inactive_member.save()
|
||||||
|
|
||||||
request = RequestFactory().get('/')
|
request = RequestFactory().get('/')
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
self.client = Client(schema=schema, context_value=request)
|
self.client = Client(schema=schema, context_value=request)
|
||||||
|
|
||||||
def make_query(self):
|
def test_user_sees_her_classes(self):
|
||||||
|
|
||||||
query = '''
|
query = '''
|
||||||
query {
|
query {
|
||||||
me {
|
me {
|
||||||
|
|
@ -55,11 +61,8 @@ class MySchoolClasses(TestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
return self.client.execute(query)
|
|
||||||
|
|
||||||
def test_user_sees_her_classes(self):
|
result = self.client.execute(query)
|
||||||
|
|
||||||
result = self.make_query()
|
|
||||||
|
|
||||||
classes = result.get('data').get('me').get('schoolClasses').get('edges')
|
classes = result.get('data').get('me').get('schoolClasses').get('edges')
|
||||||
self.assertEqual(len(classes), 2)
|
self.assertEqual(len(classes), 2)
|
||||||
|
|
@ -72,5 +75,12 @@ class MySchoolClasses(TestCase):
|
||||||
elif school_class.get('name') == self.class2:
|
elif school_class.get('name') == self.class2:
|
||||||
self.fail('MySchoolClassTest:test_user_sees_her_classes: Class should not be in response')
|
self.fail('MySchoolClassTest:test_user_sees_her_classes: Class should not be in response')
|
||||||
|
|
||||||
|
def test_old_classes(self):
|
||||||
|
query = get_graphql_query('oldClasses.gql')
|
||||||
|
|
||||||
|
result = self.client.execute(query)
|
||||||
|
|
||||||
|
old_classes = result.get('data').get('me').get('oldClasses').get('edges')
|
||||||
|
self.assertEqual(len(old_classes), 1)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue