Merge branch 'develop' into feature/licensing
# Conflicts: # Pipfile.lock # client/cypress/fixtures/schema.json # client/src/graphql/client.js # client/src/graphql/resolvers.js # client/src/graphql/typedefs.js # client/src/main.js # client/src/router/index.js # server/users/models.py
This commit is contained in:
commit
d95e8ca492
1
Pipfile
1
Pipfile
|
|
@ -10,6 +10,7 @@ python_version = '3.6'
|
||||||
awscli = "*"
|
awscli = "*"
|
||||||
ipdb = "*"
|
ipdb = "*"
|
||||||
coverage = "*"
|
coverage = "*"
|
||||||
|
django-silk = "*"
|
||||||
|
|
||||||
[packages]
|
[packages]
|
||||||
factory-boy = "==2.11.0"
|
factory-boy = "==2.11.0"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "e7807798e8b5c39d7c1bc57d61520f2c888da08d2b6061f07758e00b490fdbd6"
|
"sha256": "ca4f635dc983134e4569df2af8f0d73f488211bc2a97d11c194f76279f942977"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
|
@ -48,18 +48,18 @@
|
||||||
},
|
},
|
||||||
"boto3": {
|
"boto3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:27e221d3868f35687807e5c920f7e8d4872f722f64196a7fd274a06ad65beec0",
|
"sha256:629ce3be236b6e0aed52358146eea9ffa7679d6cd1cc9b3e12332226270d6499",
|
||||||
"sha256:8ff4e3d9e5d6a26dd7494afc68dc96afe6b7bda88130cca84cd58702d888ed27"
|
"sha256:b1351e62136fae29be8fcbb1c4890f1d72017d57e33051d435a8bf9f71212fde"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.11.10"
|
"version": "==1.11.14"
|
||||||
},
|
},
|
||||||
"botocore": {
|
"botocore": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:cf3144994191847e30ef76781af867009bdc233b3f1f4736615e5330687a891e",
|
"sha256:34ad4d73e6bef5c8ad956c66354611628cdebd431fe2927261ed9c068b9cfb7a",
|
||||||
"sha256:f11ff8616f46ca04697df031e622c9ed50931b9d649d4e719f961e9d80771e8d"
|
"sha256:6570f2ba046956d5b548ab2346d32f713ecf8b8cb098a839d73fcf832ccfa223"
|
||||||
],
|
],
|
||||||
"version": "==1.14.10"
|
"version": "==1.14.14"
|
||||||
},
|
},
|
||||||
"certifi": {
|
"certifi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -535,10 +535,10 @@
|
||||||
},
|
},
|
||||||
"s3transfer": {
|
"s3transfer": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2525bae2a530195576da53671bae8ca8c55ee8e33bc2225a65e804476611ea5a",
|
"sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13",
|
||||||
"sha256:4924e10451cc37901945806423d16c2c2040a6530645a614ed87e995ccec764c"
|
"sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db"
|
||||||
],
|
],
|
||||||
"version": "==0.3.2"
|
"version": "==0.3.3"
|
||||||
},
|
},
|
||||||
"sendgrid": {
|
"sendgrid": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -603,6 +603,7 @@
|
||||||
"sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
|
"sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
|
||||||
"sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
|
"sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version != '3.4'",
|
||||||
"version": "==1.25.8"
|
"version": "==1.25.8"
|
||||||
},
|
},
|
||||||
"wagtail": {
|
"wagtail": {
|
||||||
|
|
@ -652,21 +653,26 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {
|
"develop": {
|
||||||
|
"asgiref": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0",
|
||||||
|
"sha256:ea448f92fc35a0ef4b1508f53a04c4670255a3f33d22a81c8fc9c872036adbe5"
|
||||||
|
],
|
||||||
|
"version": "==3.2.3"
|
||||||
|
},
|
||||||
|
"autopep8": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0f592a0447acea0c2b0a9602be1e4e3d86db52badd2e3c84f0193bfd89fd3a43"
|
||||||
|
],
|
||||||
|
"version": "==1.5"
|
||||||
|
},
|
||||||
"awscli": {
|
"awscli": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
<<<<<<< HEAD
|
"sha256:2748af4e77728ced50d7d5bda0fa980449bd71eedff90ee643bee86ed4283d2f",
|
||||||
"sha256:4c49f085fb827ca1aeba5e6e5e39f6005110a0059b5c772aeb1d51c4f33c4028",
|
"sha256:9118015f4bbab1c671d9c9927d07b6f7eadb7e1e8bbb2b06dc849c3de578d692"
|
||||||
"sha256:9459ac705c2a5d8724057492800c52084df714b624853eb3331087ecf8726a22"
|
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.17.9"
|
"version": "==1.17.14"
|
||||||
=======
|
|
||||||
"sha256:7ddb43a5423725adfabb752e21ac7d47c0b440a10128e9884f578848c2369555",
|
|
||||||
"sha256:e5617cb8244863566df1cb12564e439b224e88ea2270f27b28da82df093eba0a"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==1.17.11"
|
|
||||||
>>>>>>> Add hello page, add local mutation
|
|
||||||
},
|
},
|
||||||
"backcall": {
|
"backcall": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -677,24 +683,32 @@
|
||||||
},
|
},
|
||||||
"botocore": {
|
"botocore": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
<<<<<<< HEAD
|
"sha256:34ad4d73e6bef5c8ad956c66354611628cdebd431fe2927261ed9c068b9cfb7a",
|
||||||
"sha256:cf3144994191847e30ef76781af867009bdc233b3f1f4736615e5330687a891e",
|
"sha256:6570f2ba046956d5b548ab2346d32f713ecf8b8cb098a839d73fcf832ccfa223"
|
||||||
"sha256:f11ff8616f46ca04697df031e622c9ed50931b9d649d4e719f961e9d80771e8d"
|
|
||||||
],
|
],
|
||||||
"version": "==1.14.10"
|
"version": "==1.14.14"
|
||||||
=======
|
},
|
||||||
"sha256:5ad6f4b80f3151fc5aa940f89fb6bf2db3064bf8d3f8919f5b60f5c741054ba5",
|
"certifi": {
|
||||||
"sha256:ac783a87bd90be8a4d08101bfc0d29a4b35fe0ced387f5c8bc91d01cdaa7a168"
|
"hashes": [
|
||||||
|
"sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3",
|
||||||
|
"sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"
|
||||||
],
|
],
|
||||||
"version": "==1.14.11"
|
"version": "==2019.11.28"
|
||||||
>>>>>>> Add hello page, add local mutation
|
},
|
||||||
|
"chardet": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||||
|
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||||
|
],
|
||||||
|
"version": "==3.0.4"
|
||||||
},
|
},
|
||||||
"colorama": {
|
"colorama": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d",
|
"sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff",
|
||||||
"sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"
|
"sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"
|
||||||
],
|
],
|
||||||
"version": "==0.4.1"
|
"markers": "python_version != '3.4'",
|
||||||
|
"version": "==0.4.3"
|
||||||
},
|
},
|
||||||
"coverage": {
|
"coverage": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -740,6 +754,22 @@
|
||||||
],
|
],
|
||||||
"version": "==4.4.1"
|
"version": "==4.4.1"
|
||||||
},
|
},
|
||||||
|
"django": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:48522428f4a285cf265af969f4744c5ebb027c7f41958ba48b639ace2068ffe7",
|
||||||
|
"sha256:a794f7a2f4b7c928eecfbc4ebad03712ff27fb545abe269bf01aa8500781eb1c"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2.1.15"
|
||||||
|
},
|
||||||
|
"django-silk": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:9d5d66628689230288d1020de186b86e6f38583f611b5dd796ec988bb6a6333e",
|
||||||
|
"sha256:b0033ec3713882a5abb8b3db2b4392a746b83ce164ab7be568f0eeec4ba78a98"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==4.0.0"
|
||||||
|
},
|
||||||
"docutils": {
|
"docutils": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0",
|
"sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0",
|
||||||
|
|
@ -748,6 +778,19 @@
|
||||||
],
|
],
|
||||||
"version": "==0.15.2"
|
"version": "==0.15.2"
|
||||||
},
|
},
|
||||||
|
"gprof2dot": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:b43fe04ebb3dfe181a612bbfc69e90555b8957022ad6a466f0308ed9c7f22e99"
|
||||||
|
],
|
||||||
|
"version": "==2019.11.30"
|
||||||
|
},
|
||||||
|
"idna": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
|
||||||
|
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
|
||||||
|
],
|
||||||
|
"version": "==2.8"
|
||||||
|
},
|
||||||
"ipdb": {
|
"ipdb": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:5d9a4a0e3b7027a158fc6f2929934341045b9c3b0b86ed5d7e84e409653f72fd"
|
"sha256:5d9a4a0e3b7027a158fc6f2929934341045b9c3b0b86ed5d7e84e409653f72fd"
|
||||||
|
|
@ -777,6 +820,13 @@
|
||||||
],
|
],
|
||||||
"version": "==0.16.0"
|
"version": "==0.16.0"
|
||||||
},
|
},
|
||||||
|
"jinja2": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250",
|
||||||
|
"sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49"
|
||||||
|
],
|
||||||
|
"version": "==2.11.1"
|
||||||
|
},
|
||||||
"jmespath": {
|
"jmespath": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3720a4b1bd659dd2eecad0666459b9788813e032b83e7ba58578e48254e0a0e6",
|
"sha256:3720a4b1bd659dd2eecad0666459b9788813e032b83e7ba58578e48254e0a0e6",
|
||||||
|
|
@ -784,6 +834,44 @@
|
||||||
],
|
],
|
||||||
"version": "==0.9.4"
|
"version": "==0.9.4"
|
||||||
},
|
},
|
||||||
|
"markupsafe": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
|
||||||
|
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
|
||||||
|
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
|
||||||
|
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
|
||||||
|
"sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
|
||||||
|
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
|
||||||
|
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
|
||||||
|
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
|
||||||
|
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
|
||||||
|
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
|
||||||
|
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
|
||||||
|
"sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
|
||||||
|
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
|
||||||
|
"sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
|
||||||
|
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
|
||||||
|
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
|
||||||
|
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
|
||||||
|
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
|
||||||
|
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
|
||||||
|
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
|
||||||
|
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
|
||||||
|
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
|
||||||
|
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
|
||||||
|
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
|
||||||
|
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
|
||||||
|
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
|
||||||
|
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
|
||||||
|
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
|
||||||
|
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
|
||||||
|
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
|
||||||
|
"sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
|
||||||
|
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
|
||||||
|
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
|
||||||
|
],
|
||||||
|
"version": "==1.1.1"
|
||||||
|
},
|
||||||
"parso": {
|
"parso": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:56b2105a80e9c4df49de85e125feb6be69f49920e121406f15e7acde6c9dfc57",
|
"sha256:56b2105a80e9c4df49de85e125feb6be69f49920e121406f15e7acde6c9dfc57",
|
||||||
|
|
@ -827,6 +915,13 @@
|
||||||
],
|
],
|
||||||
"version": "==0.4.8"
|
"version": "==0.4.8"
|
||||||
},
|
},
|
||||||
|
"pycodestyle": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56",
|
||||||
|
"sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"
|
||||||
|
],
|
||||||
|
"version": "==2.5.0"
|
||||||
|
},
|
||||||
"pygments": {
|
"pygments": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b",
|
"sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b",
|
||||||
|
|
@ -841,6 +936,13 @@
|
||||||
],
|
],
|
||||||
"version": "==2.8.1"
|
"version": "==2.8.1"
|
||||||
},
|
},
|
||||||
|
"pytz": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
|
||||||
|
"sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
|
||||||
|
],
|
||||||
|
"version": "==2019.3"
|
||||||
|
},
|
||||||
"pyyaml": {
|
"pyyaml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc",
|
"sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc",
|
||||||
|
|
@ -857,6 +959,14 @@
|
||||||
],
|
],
|
||||||
"version": "==5.2"
|
"version": "==5.2"
|
||||||
},
|
},
|
||||||
|
"requests": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
|
||||||
|
"sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2.22.0"
|
||||||
|
},
|
||||||
"rsa": {
|
"rsa": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:25df4e10c263fb88b5ace923dd84bf9aa7f5019687b5e55382ffcdb8bede9db5",
|
"sha256:25df4e10c263fb88b5ace923dd84bf9aa7f5019687b5e55382ffcdb8bede9db5",
|
||||||
|
|
@ -866,10 +976,10 @@
|
||||||
},
|
},
|
||||||
"s3transfer": {
|
"s3transfer": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2525bae2a530195576da53671bae8ca8c55ee8e33bc2225a65e804476611ea5a",
|
"sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13",
|
||||||
"sha256:4924e10451cc37901945806423d16c2c2040a6530645a614ed87e995ccec764c"
|
"sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db"
|
||||||
],
|
],
|
||||||
"version": "==0.3.2"
|
"version": "==0.3.3"
|
||||||
},
|
},
|
||||||
"six": {
|
"six": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -878,6 +988,13 @@
|
||||||
],
|
],
|
||||||
"version": "==1.14.0"
|
"version": "==1.14.0"
|
||||||
},
|
},
|
||||||
|
"sqlparse": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177",
|
||||||
|
"sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873"
|
||||||
|
],
|
||||||
|
"version": "==0.3.0"
|
||||||
|
},
|
||||||
"traitlets": {
|
"traitlets": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44",
|
"sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44",
|
||||||
|
|
@ -890,6 +1007,7 @@
|
||||||
"sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
|
"sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
|
||||||
"sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
|
"sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version != '3.4'",
|
||||||
"version": "==1.25.8"
|
"version": "==1.25.8"
|
||||||
},
|
},
|
||||||
"wcwidth": {
|
"wcwidth": {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"me": {
|
||||||
|
"id": "VXNlck5vZGU6NQ==",
|
||||||
|
"pk": 5,
|
||||||
|
"username": "rahel.cueni",
|
||||||
|
"email": "rahel.cueni@skillbox.example",
|
||||||
|
"firstName": "Rahel",
|
||||||
|
"lastName": "Cueni",
|
||||||
|
"avatarUrl": "",
|
||||||
|
"lastModule": {
|
||||||
|
"id": "TW9kdWxlTm9kZToxNw==",
|
||||||
|
"slug": "lohn-und-budget",
|
||||||
|
"__typename": "ModuleNode"
|
||||||
|
},
|
||||||
|
"selectedClass": {
|
||||||
|
"id": "U2Nob29sQ2xhc3NOb2RlOjI=",
|
||||||
|
"__typename": "SchoolClassNode"
|
||||||
|
},
|
||||||
|
"schoolClasses": {
|
||||||
|
"edges": [
|
||||||
|
{
|
||||||
|
"node": {
|
||||||
|
"id": "U2Nob29sQ2xhc3NOb2RlOjE=",
|
||||||
|
"name": "FLID2018a",
|
||||||
|
"__typename": "SchoolClassNode"
|
||||||
|
},
|
||||||
|
"__typename": "SchoolClassNodeEdge"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"__typename": "SchoolClassNodeConnection"
|
||||||
|
},
|
||||||
|
"__typename": "UserNode",
|
||||||
|
"permissions": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"me": {
|
||||||
|
"id": "VXNlck5vZGU6NQ==",
|
||||||
|
"pk": 5,
|
||||||
|
"username": "hansli",
|
||||||
|
"email": "hansli@skillbox.example",
|
||||||
|
"firstName": "Hansli",
|
||||||
|
"lastName": "Alleini",
|
||||||
|
"avatarUrl": "",
|
||||||
|
"lastModule": null,
|
||||||
|
"selectedClass": null,
|
||||||
|
"schoolClasses": {
|
||||||
|
"edges": [],
|
||||||
|
"__typename": "SchoolClassNodeConnection"
|
||||||
|
},
|
||||||
|
"__typename": "UserNode",
|
||||||
|
"permissions": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"data": {
|
||||||
"__schema": {
|
"__schema": {
|
||||||
"queryType": {
|
"queryType": {
|
||||||
"name": "Query"
|
"name": "Query"
|
||||||
|
|
@ -3125,6 +3126,18 @@
|
||||||
},
|
},
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": null
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "expiryDate",
|
||||||
|
"description": null,
|
||||||
|
"args": [],
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"inputFields": null,
|
"inputFields": null,
|
||||||
|
|
@ -3350,6 +3363,18 @@
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": null
|
"deprecationReason": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "code",
|
||||||
|
"description": "",
|
||||||
|
"args": [],
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "hiddenContentBlocks",
|
"name": "hiddenContentBlocks",
|
||||||
"description": null,
|
"description": null,
|
||||||
|
|
@ -9385,6 +9410,33 @@
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": null
|
"deprecationReason": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "joinClass",
|
||||||
|
"description": null,
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "input",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "INPUT_OBJECT",
|
||||||
|
"name": "JoinClassInput",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "JoinClassPayload",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "addProject",
|
"name": "addProject",
|
||||||
"description": null,
|
"description": null,
|
||||||
|
|
@ -10534,6 +10586,16 @@
|
||||||
},
|
},
|
||||||
"defaultValue": null
|
"defaultValue": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "userIdInput",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Int",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "clientMutationId",
|
"name": "clientMutationId",
|
||||||
"description": null,
|
"description": null,
|
||||||
|
|
@ -11609,6 +11671,88 @@
|
||||||
"enumValues": null,
|
"enumValues": null,
|
||||||
"possibleTypes": null
|
"possibleTypes": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "JoinClassPayload",
|
||||||
|
"description": null,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "success",
|
||||||
|
"description": null,
|
||||||
|
"args": [],
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Boolean",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "schoolClass",
|
||||||
|
"description": null,
|
||||||
|
"args": [],
|
||||||
|
"type": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "SchoolClassNode",
|
||||||
|
"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": "JoinClassInput",
|
||||||
|
"description": null,
|
||||||
|
"fields": null,
|
||||||
|
"inputFields": [
|
||||||
|
{
|
||||||
|
"name": "code",
|
||||||
|
"description": null,
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"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",
|
||||||
|
|
@ -15826,5 +15970,5 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
describe('Survey', () => {
|
describe('Bookmarks', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// todo: mock all the graphql queries and mutations
|
// todo: mock all the graphql queries and mutations
|
||||||
cy.exec("python ../server/manage.py prepare_bookmarks_for_cypress");
|
cy.exec("python ../server/manage.py prepare_bookmarks_for_cypress");
|
||||||
|
|
@ -27,7 +27,6 @@ describe('Survey', () => {
|
||||||
|
|
||||||
cy.get('@interviewContent').within(() => {
|
cy.get('@interviewContent').within(() => {
|
||||||
cy.get('.bookmark-actions__edit-note').click();
|
cy.get('.bookmark-actions__edit-note').click();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.get('[data-cy=bookmark-note]').within(() => {
|
cy.get('[data-cy=bookmark-note]').within(() => {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
const schema = require('../fixtures/schema.json');
|
||||||
|
const me = require('../fixtures/me.join-class.json');
|
||||||
|
|
||||||
|
describe('Join Class', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.server();
|
||||||
|
|
||||||
|
cy.mockGraphql({
|
||||||
|
schema: schema,
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.viewport('macbook-15');
|
||||||
|
cy.apolloLogin('rahel.cueni', 'test');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should join class', () => {
|
||||||
|
cy.mockGraphqlOps({
|
||||||
|
operations: {
|
||||||
|
MeQuery: me,
|
||||||
|
JoinClass: {
|
||||||
|
joinClass: {
|
||||||
|
success: true,
|
||||||
|
schoolClass: {
|
||||||
|
id: "U2Nob29sQ2xhc3NOb2RlOjI=",
|
||||||
|
name: "KF1A",
|
||||||
|
__typename: "SchoolClassNode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
cy.visit('/me/profile');
|
||||||
|
|
||||||
|
cy.get('[data-cy=user-widget-avatar]').click();
|
||||||
|
cy.get('[data-cy=class-selection]').click();
|
||||||
|
cy.get('[data-cy=class-selection-entry]').should('have.length', 1);
|
||||||
|
|
||||||
|
cy.get('[data-cy=join-class-link]').click();
|
||||||
|
|
||||||
|
cy.get('[data-cy=input-class-code]').type('XXXX');
|
||||||
|
cy.get('[data-cy=join-class]').click();
|
||||||
|
|
||||||
|
cy.get('[data-cy=class-selection]').click();
|
||||||
|
cy.get('[data-cy=class-selection-entry]').should('have.length', 2);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
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', () => {
|
||||||
|
|
||||||
|
cy.server();
|
||||||
|
|
||||||
|
cy.mockGraphql({
|
||||||
|
schema: schema,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
cy.apolloLogin('hansli', 'test');
|
||||||
|
|
||||||
|
cy.mockGraphqlOps({
|
||||||
|
operations: {
|
||||||
|
MeQuery: me,
|
||||||
|
JoinClass: {
|
||||||
|
joinClass: {
|
||||||
|
success: true,
|
||||||
|
schoolClass: {
|
||||||
|
id: "U2Nob29sQ2xhc3NOb2RlOjI=",
|
||||||
|
name: "KF1A",
|
||||||
|
__typename: "SchoolClassNode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.visit('/');
|
||||||
|
cy.get('[data-cy=join-class-title]').should('contain', 'Zugangscode');
|
||||||
|
cy.get('[data-cy=input-class-code]').type('XXXX');
|
||||||
|
cy.get('[data-cy=join-class]').click();
|
||||||
|
cy.get('[data-cy=class-list-title]').should('contain', 'Klassenliste');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -10,7 +10,6 @@ describe('Survey', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.viewport('macbook-15');
|
cy.viewport('macbook-15');
|
||||||
|
|
||||||
cy.apolloLogin('rahel.cueni', 'test');
|
cy.apolloLogin('rahel.cueni', 'test');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,15 +59,8 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
&__link {
|
&__link {
|
||||||
font-size: 1.0625rem;
|
|
||||||
padding: 0 24px;
|
padding: 0 24px;
|
||||||
font-family: $sans-serif-font-family;
|
@include default-link;
|
||||||
font-weight: $font-weight-regular;
|
|
||||||
color: $color-silver-dark;
|
|
||||||
|
|
||||||
&--active {
|
|
||||||
color: $color-brand;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$parent: &;
|
$parent: &;
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<header class="header-bar">
|
<header class="header-bar">
|
||||||
<top-navigation></top-navigation>
|
<content-navigation></content-navigation>
|
||||||
<router-link to="/" class="header-bar__logo" data-cy="home-link">
|
<router-link to="/" class="header-bar__logo" data-cy="home-link">
|
||||||
<logo></logo>
|
<logo></logo>
|
||||||
</router-link>
|
</router-link>
|
||||||
<div class="user-header">
|
<div class="user-header">
|
||||||
<class-selection-widget />
|
<a class="user-header__sidebar-link" @click="openSidebar()"><current-class class="user-header__current-class"/></a>
|
||||||
|
|
||||||
<user-widget v-bind="me"></user-widget>
|
<user-widget v-bind="me"></user-widget>
|
||||||
</div>
|
</div>
|
||||||
<book-navigation v-if="showSubnavigation">
|
<book-navigation v-if="showSubnavigation">
|
||||||
|
|
@ -14,23 +15,26 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import TopNavigation from '@/components/TopNavigation.vue';
|
import ContentNavigation from '@/components/ContentNavigation.vue';
|
||||||
import BookNavigation from '@/components/book-navigation/BookNavigation';
|
import BookNavigation from '@/components/book-navigation/BookNavigation';
|
||||||
import UserWidget from '@/components/UserWidget.vue';
|
import UserWidget from '@/components/UserWidget.vue';
|
||||||
import LogoutWidget from '@/components/LogoutWidget.vue';
|
import LogoutWidget from '@/components/LogoutWidget.vue';
|
||||||
import Logo from '@/components/icons/Logo';
|
import Logo from '@/components/icons/Logo';
|
||||||
import ClassSelectionWidget from '@/components/ClassSelectionWidget';
|
import CurrentClass from '@/components/school-class/CurrentClass';
|
||||||
|
|
||||||
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
import openSidebar from '@/mixins/open-sidebar';
|
||||||
|
import me from '@/mixins/me';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [openSidebar, me],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
TopNavigation,
|
ContentNavigation,
|
||||||
UserWidget,
|
UserWidget,
|
||||||
LogoutWidget,
|
LogoutWidget,
|
||||||
BookNavigation,
|
BookNavigation,
|
||||||
Logo,
|
Logo,
|
||||||
ClassSelectionWidget
|
CurrentClass
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -38,18 +42,6 @@
|
||||||
return this.$route.meta.subnavigation;
|
return this.$route.meta.subnavigation;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
me: {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
apollo: {
|
|
||||||
me: {
|
|
||||||
query: ME_QUERY,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -127,5 +119,13 @@
|
||||||
|
|
||||||
.user-header {
|
.user-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
&__current-class {
|
||||||
|
margin-right: $large-spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__sidebar-link {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="user-widget" :class="{'user-widget--is-profile': isProfile}">
|
<div class="user-widget" :class="{'user-widget--is-profile': isProfile}">
|
||||||
<div class="user-widget__avatar" @click="toggleShowPopover()">
|
<div class="user-widget__avatar" data-cy="user-widget-avatar" @click="openSidebar()">
|
||||||
<avatar :avatar-url="avatarUrl" :icon-highlighted="isProfile"/>
|
<avatar :avatar-url="avatarUrl" :icon-highlighted="isProfile"/>
|
||||||
</div>
|
</div>
|
||||||
<widget-popover v-if="showPopover && showMenu"
|
|
||||||
@hide-me="showPopover = false"
|
|
||||||
:mobile="mobile"
|
|
||||||
class="user-widget__popover ">
|
|
||||||
<li class="popover-links__link popover-links__link--large popover-links__link--emph">{{firstName}} {{lastName}}
|
|
||||||
</li>
|
|
||||||
<li class="popover-links__link popover-links__link--large">
|
|
||||||
<router-link to="/me/activity" @click="toggleShowPopover()">Aktivität</router-link>
|
|
||||||
</li>
|
|
||||||
<li class="popover-links__link popover-links__link--large" @click="toggleShowPopover()">
|
|
||||||
<router-link to="/me/profile">Profil</router-link>
|
|
||||||
</li>
|
|
||||||
<li class="popover-links__link popover-links__link--large" @click="toggleShowPopover()">
|
|
||||||
<router-link to="/me/myclasses">Klassenliste</router-link>
|
|
||||||
</li>
|
|
||||||
<li class="popover-links__link popover-links__link--large" data-cy="logout" @click="logout()">
|
|
||||||
<a>Logout</a>
|
|
||||||
</li>
|
|
||||||
</widget-popover>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import LOGOUT_MUTATION from '@/graphql/gql/mutations/logoutUser.gql';
|
|
||||||
import Avatar from '@/components/profile/Avatar';
|
import Avatar from '@/components/profile/Avatar';
|
||||||
import WidgetPopover from '@/components/WidgetPopover';
|
import openSidebar from '@/mixins/open-sidebar';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
// todo: clean up unneeded props
|
||||||
props: {
|
props: {
|
||||||
firstName: {
|
firstName: {
|
||||||
type: String
|
type: String
|
||||||
|
|
@ -51,31 +32,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
mixins: [openSidebar],
|
||||||
return {
|
|
||||||
showPopover: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
toggleShowPopover() {
|
|
||||||
if (this.showMenu) {
|
|
||||||
this.showPopover = !this.showPopover;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
logout() {
|
|
||||||
this.$apollo.mutate({
|
|
||||||
mutation: LOGOUT_MUTATION,
|
|
||||||
}).then(({data}) => {
|
|
||||||
if (data.logout.success) {
|
|
||||||
location.replace('/logout')
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
Avatar, WidgetPopover
|
Avatar
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isProfile() {
|
isProfile() {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mobile-navigation">
|
<div class="mobile-navigation">
|
||||||
<top-navigation class="mobile-navigation__main" :mobile="true"></top-navigation>
|
<content-navigation class="mobile-navigation__main" :mobile="true"></content-navigation>
|
||||||
<div class="mobile-navigation__close-button" @click="hideMobileNavigation">
|
<div class="mobile-navigation__close-button" @click="hideMobileNavigation">
|
||||||
<cross class="mobile-navigation__close-icon"></cross>
|
<cross class="mobile-navigation__close-icon"></cross>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -16,14 +16,14 @@
|
||||||
import Cross from '@/components/icons/Cross';
|
import Cross from '@/components/icons/Cross';
|
||||||
import UserWidget from '@/components/UserWidget';
|
import UserWidget from '@/components/UserWidget';
|
||||||
import LogoutWidget from '@/components/LogoutWidget';
|
import LogoutWidget from '@/components/LogoutWidget';
|
||||||
import TopNavigation from '@/components/TopNavigation';
|
import ContentNavigation from '@/components/ContentNavigation';
|
||||||
import ClassSelectionWidget from '@/components/ClassSelectionWidget';
|
import ClassSelectionWidget from '@/components/school-class/ClassSelectionWidget';
|
||||||
|
|
||||||
import {meQuery} from '@/graphql/queries';
|
import {meQuery} from '@/graphql/queries';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
TopNavigation,
|
ContentNavigation,
|
||||||
Cross,
|
Cross,
|
||||||
UserWidget,
|
UserWidget,
|
||||||
LogoutWidget,
|
LogoutWidget,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 400 400">
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip-path">
|
||||||
|
<circle class="cls-1" cx="200" cy="200" r="197"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<g id="bg">
|
||||||
|
<circle class="cls-2" cx="200" cy="200" r="197"/>
|
||||||
|
</g>
|
||||||
|
<g id="objects">
|
||||||
|
<g class="cls-3">
|
||||||
|
<path class="cls-4"
|
||||||
|
d="M380.03229,346.82485l-99.89952-29.68554-75.91247-7.58045h-2.40707l-78.31955,7.58045-103.526,30.44984s75.62737,95.55272,183.04906,95.55272S380.03229,346.82485,380.03229,346.82485Z"/>
|
||||||
|
<path class="cls-5"
|
||||||
|
d="M272.15765,65.53589a86.01591,86.01591,0,0,0-20.524-14.33295,103.61294,103.61294,0,0,0-49.8204-11.92284,103.61271,103.61271,0,0,0-49.8198,11.92255A86.01706,86.01706,0,0,0,131.4688,65.53589c-47.89186,45.957-59.684,136.053-53.17653,195.51559,22.30221,28.20465,70.43168,35.98046,103.97675,29.59277-1.699-28.98109-18.28465-55.36789-18.09824-84.40807,16.82179-9.95623,9.42329-59.11938,10.702-81.59916l26.94039-6.97556,26.9404,6.97556c1.27875,22.47978-6.11974,71.64293,10.70211,81.59916.18642,29.04018-16.39928,55.427-18.09824,84.40807,33.545,6.38769,81.67447-1.38812,103.97668-29.59277C331.84167,201.58886,320.04951,111.49293,272.15765,65.53589Z"/>
|
||||||
|
<path class="cls-6"
|
||||||
|
d="M237.057,266.23162H166.56944c0,34.9025-27.34,45.73431-43.07576,50.90769,15.73577,67.03868,142.10688,67.03868,156.63909,0C260.78642,311.96593,241.52987,305.94826,237.057,266.23162Z"/>
|
||||||
|
<ellipse class="cls-7" cx="202.35227" cy="179.47219" rx="79.23803" ry="97.02616"/>
|
||||||
|
<path class="cls-8"
|
||||||
|
d="M117.61519,154.166c10.8764-3.25836,22.30778,5.29762,25.51206,19.09451,3.20424,13.7965-3.02464,27.64251-13.90108,30.90087-10.87644,3.25678-22.30919-5.29877-25.51339-19.09389-3.20425-13.79708,3.026-27.64273,13.90241-30.90149Z"/>
|
||||||
|
<path class="cls-5"
|
||||||
|
d="M158.77973,88.35379c34.35007,53.44875,83.71361,66.59188,126.44529,82.83956C322.20435,145.19484,244.7734,11.96688,158.77973,88.35379Z"/>
|
||||||
|
<path class="cls-5"
|
||||||
|
d="M181.50651,94.88021c-10.83147,33.057-30.31262,48.8307-55.51284,58.59347C109.4863,115.38906,134.25941,72.625,181.50651,94.88021Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.cls-1 {
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-2 {
|
||||||
|
fill: #d1eee7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-3 {
|
||||||
|
clip-path: url(#clip-path);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-4 {
|
||||||
|
fill: #17a887;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-4, .cls-5, .cls-6, .cls-8 {
|
||||||
|
fill-rule: evenodd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-5 {
|
||||||
|
fill: #102d24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-6 {
|
||||||
|
fill: #e5f5f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-7, .cls-8 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,28 +1,49 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="avatar">
|
<div class="avatar">
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<user-icon v-show="!isAvatarLoaded" class="avatar__placeholder" :class="{'avatar__placeholder--highlighted': iconHighlighted}"></user-icon>
|
<default-avatar v-show="!isAvatarLoaded" class="avatar__placeholder"
|
||||||
|
:class="{'avatar__placeholder--highlighted': iconHighlighted}"></default-avatar>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="show">
|
<transition name="show">
|
||||||
<div v-show="isAvatarLoaded" class="avatar__image" ref="avatarImage" :style="{'background-image': `url(${this.avatarUrl})`}"></div>
|
<div v-show="isAvatarLoaded" class="avatar__image" ref="avatarImage"
|
||||||
|
:style="{'background-image': `url(${this.avatarUrl})`}"></div>
|
||||||
</transition>
|
</transition>
|
||||||
<img class="avatar__fake-image" :src="avatarUrl" ref="fakeImage"/>
|
<img class="avatar__fake-image" :src="avatarUrl" ref="fakeImage"/>
|
||||||
|
|
||||||
|
<div class="avatar__edit" v-if="editable" @click="closeSidebar">
|
||||||
|
<router-link :to="{name: 'profile'}">
|
||||||
|
<pen-icon></pen-icon>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import DefaultAvatar from '@/components/icons/DefaultAvatar';
|
||||||
|
import PenIcon from '@/components/icons/PenIcon';
|
||||||
|
|
||||||
import UserIcon from '@/components/icons/UserIcon';
|
import TOGGLE_SIDEBAR from '@/graphql/gql/local/mutations/toggleSidebar.gql';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['avatarUrl', 'iconHighlighted'],
|
props: {
|
||||||
components: {UserIcon},
|
avatarUrl: {
|
||||||
data () {
|
type: String
|
||||||
|
},
|
||||||
|
iconHighlighted: {},
|
||||||
|
editable: {
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
DefaultAvatar,
|
||||||
|
PenIcon
|
||||||
|
},
|
||||||
|
data() {
|
||||||
return {
|
return {
|
||||||
isAvatarLoaded: false
|
isAvatarLoaded: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted() {
|
||||||
if (this.avatarUrl !== '') {
|
if (this.avatarUrl !== '') {
|
||||||
this.$refs.fakeImage.addEventListener('load', () => {
|
this.$refs.fakeImage.addEventListener('load', () => {
|
||||||
if (this.$refs.fakeImage) {
|
if (this.$refs.fakeImage) {
|
||||||
|
|
@ -30,7 +51,17 @@
|
||||||
this.isAvatarLoaded = true;
|
this.isAvatarLoaded = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
closeSidebar() {
|
||||||
|
this.$apollo.mutate({
|
||||||
|
mutation: TOGGLE_SIDEBAR,
|
||||||
|
variables: {
|
||||||
|
open: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -45,11 +76,11 @@
|
||||||
width: $max-width;
|
width: $max-width;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-radius: 50%;
|
|
||||||
|
|
||||||
&__placeholder {
|
&__placeholder {
|
||||||
height: $max-width;
|
height: $max-width;
|
||||||
fill: $color-silver-dark;
|
fill: $color-silver-dark;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
&--highlighted {
|
&--highlighted {
|
||||||
fill: $color-brand;
|
fill: $color-brand;
|
||||||
|
|
@ -57,14 +88,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&__image {
|
&__image {
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center center;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
background-size: cover;
|
&--landscape {
|
||||||
background-position: center center;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
border: 0;
|
|
||||||
|
|
||||||
&--landscape {
|
|
||||||
width: auto;
|
width: auto;
|
||||||
height: $max-width;
|
height: $max-width;
|
||||||
}
|
}
|
||||||
|
|
@ -75,10 +106,25 @@
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-leave-active, .show-enter-active {
|
&__edit {
|
||||||
|
position: absolute;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
display: block;
|
||||||
|
left: 50%;
|
||||||
|
bottom: -7px;
|
||||||
|
transform: translateX(50%);
|
||||||
|
background-color: $color-white;
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: 6px;
|
||||||
|
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-leave-active, .show-enter-active {
|
||||||
transition: opacity .5s;
|
transition: opacity .5s;
|
||||||
}
|
}
|
||||||
.fade-leave-to, .show-enter {
|
.fade-leave-to, .show-enter {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
|
<!-- Not currently in use, but keeping the file in case it's needed again -->
|
||||||
<div class="password-reset">
|
<div class="password-reset">
|
||||||
<h2 class="password-reset__header">Passwort ändern</h2>
|
<h2 class="password-reset__header">Passwort ändern</h2>
|
||||||
<div v-if="showSuccess" class="success-message">
|
<div v-if="showSuccess" class="success-message">
|
||||||
|
|
@ -7,7 +8,7 @@
|
||||||
<password-change-form
|
<password-change-form
|
||||||
@passwordSubmited="resetPassword"
|
@passwordSubmited="resetPassword"
|
||||||
:oldPasswordErrors="oldPasswordErrors"
|
:oldPasswordErrors="oldPasswordErrors"
|
||||||
:newPasswordErrors="newPasswordErrors" />
|
:newPasswordErrors="newPasswordErrors"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -29,7 +30,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
resetPassword (passwords) {
|
resetPassword(passwords) {
|
||||||
this.$apollo.mutate({
|
this.$apollo.mutate({
|
||||||
mutation: UPDATE_PASSWORD_MUTATION,
|
mutation: UPDATE_PASSWORD_MUTATION,
|
||||||
variables: {
|
variables: {
|
||||||
|
|
@ -37,7 +38,7 @@
|
||||||
passwordInput: passwords
|
passwordInput: passwords
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).then(({ data }) => {
|
}).then(({data}) => {
|
||||||
if (data.updatePassword.success) {
|
if (data.updatePassword.success) {
|
||||||
this.oldPasswordErrors = [];
|
this.oldPasswordErrors = [];
|
||||||
this.newPasswordErrors = [];
|
this.newPasswordErrors = [];
|
||||||
|
|
@ -56,14 +57,14 @@
|
||||||
console.log('fail', error)
|
console.log('fail', error)
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleOldPasswordError (error) {
|
handleOldPasswordError(error) {
|
||||||
this.oldPasswordErrors = error.errors.map((fieldError) => {
|
this.oldPasswordErrors = error.errors.map((fieldError) => {
|
||||||
if (fieldError.code === 'invalid') {
|
if (fieldError.code === 'invalid') {
|
||||||
return 'Die Eingabe ist falsch'
|
return 'Die Eingabe ist falsch'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleNewPasswordError (error) {
|
handleNewPasswordError(error) {
|
||||||
this.newPasswordErrors = error.errors.map((fieldError) => {
|
this.newPasswordErrors = error.errors.map((fieldError) => {
|
||||||
if (fieldError.code === 'invalid') {
|
if (fieldError.code === 'invalid') {
|
||||||
return 'Das Passwort muss Grossbuchstaben, Zahlen und Sonderzeichen beinhalten'
|
return 'Das Passwort muss Grossbuchstaben, Zahlen und Sonderzeichen beinhalten'
|
||||||
|
|
|
||||||
|
|
@ -11,22 +11,18 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<avatar-upload-form v-else @avatarUpdate="updateAvatar"/>
|
<avatar-upload-form v-else @avatarUpdate="updateAvatar"/>
|
||||||
<password-change />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import UPDATE_AVATAR_QUERY from '@/graphql/gql/mutations/updateAvatarUrl.gql';
|
import UPDATE_AVATAR_QUERY from '@/graphql/gql/mutations/updateAvatarUrl.gql';
|
||||||
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
||||||
import PasswordChange from '@/components/profile/PasswordChange';
|
|
||||||
import AvatarUploadForm from '@/components/profile/AvatarUploadForm';
|
import AvatarUploadForm from '@/components/profile/AvatarUploadForm';
|
||||||
import Avatar from '@/components/profile/Avatar';
|
import Avatar from '@/components/profile/Avatar';
|
||||||
import TrashIcon from '@/components/icons/TrashIcon';
|
import TrashIcon from '@/components/icons/TrashIcon';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
PasswordChange,
|
|
||||||
AvatarUploadForm,
|
AvatarUploadForm,
|
||||||
Avatar,
|
Avatar,
|
||||||
TrashIcon
|
TrashIcon
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
<template>
|
||||||
|
<div class="profile-sidebar" v-if="sidebar.open">
|
||||||
|
<a class="profile-sidebar__close-link" @click="closeSidebar">
|
||||||
|
<cross class="profile-sidebar__close-icon"></cross>
|
||||||
|
</a>
|
||||||
|
<profile-widget class="profile-sidebar__item"></profile-widget>
|
||||||
|
<div class="profile-sidebar__item" @click="closeSidebar">
|
||||||
|
<router-link to="/me/activity" class="profile-sidebar__link">Meine Aktivitäten</router-link>
|
||||||
|
</div>
|
||||||
|
<div class="profile-sidebar__item">
|
||||||
|
<h3 class="profile-sidebar__subtitle">Klasse</h3>
|
||||||
|
<class-selection-widget></class-selection-widget>
|
||||||
|
<router-link :to="{name: 'my-classes'}" class="profile-sidebar__link">Klassenliste anzeigen</router-link>
|
||||||
|
</div>
|
||||||
|
<div class="profile-sidebar__item" @click="closeSidebar">
|
||||||
|
<router-link :to="{name:'join-class'}" class="profile-sidebar__link">Zugangscode eingeben</router-link>
|
||||||
|
</div>
|
||||||
|
<div class="profile-sidebar__item" @click="logout">
|
||||||
|
<a class="profile-sidebar__link">Logout</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ProfileWidget from '@/components/profile/ProfileWidget';
|
||||||
|
import Cross from '@/components/icons/Cross';
|
||||||
|
|
||||||
|
import SIDEBAR from '@/graphql/gql/local/sidebar.gql';
|
||||||
|
import TOGGLE_SIDEBAR from '@/graphql/gql/local/mutations/toggleSidebar.gql';
|
||||||
|
import LOGOUT_MUTATION from '@/graphql/gql/mutations/logoutUser.gql';
|
||||||
|
import ClassSelectionWidget from '@/components/school-class/ClassSelectionWidget';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
ClassSelectionWidget,
|
||||||
|
ProfileWidget,
|
||||||
|
Cross
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
closeSidebar() {
|
||||||
|
this.$apollo.mutate({
|
||||||
|
mutation: TOGGLE_SIDEBAR,
|
||||||
|
variables: {
|
||||||
|
open: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
logout() {
|
||||||
|
this.$apollo.mutate({
|
||||||
|
mutation: LOGOUT_MUTATION,
|
||||||
|
}).then(({data}) => {
|
||||||
|
if (data.logout.success) {
|
||||||
|
location.replace('/logout')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
apollo: {
|
||||||
|
sidebar: {
|
||||||
|
query: SIDEBAR
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data: () => ({
|
||||||
|
sidebar: {
|
||||||
|
open: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/styles/_variables.scss";
|
||||||
|
@import "@/styles/_mixins.scss";
|
||||||
|
|
||||||
|
.profile-sidebar {
|
||||||
|
padding: $large-spacing 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 333px;
|
||||||
|
background-color: $color-white;
|
||||||
|
z-index: 10;
|
||||||
|
box-shadow: 0 3px 9px 0 rgba(0, 0, 0, 0.12);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
border-bottom: 1px solid $color-silver-light;
|
||||||
|
padding: $large-spacing $medium-spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__subtitle {
|
||||||
|
@include small-text;
|
||||||
|
margin: 0;
|
||||||
|
margin-bottom: $small-spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__link {
|
||||||
|
@include default-link;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__close-link {
|
||||||
|
position: absolute;
|
||||||
|
right: $small-spacing;
|
||||||
|
top: $small-spacing;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__close-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
<template>
|
||||||
|
<div class="profile-widget">
|
||||||
|
<div class="profile-widget__avatar">
|
||||||
|
<avatar :avatar-url="me.avatarUrl" :editable="true"></avatar>
|
||||||
|
</div>
|
||||||
|
<h3 class="profile-widget__name">{{me.firstName}} {{me.lastName}}</h3>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Avatar from '@/components/profile/Avatar';
|
||||||
|
import {meQuery} from '@/graphql/queries';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Avatar
|
||||||
|
},
|
||||||
|
|
||||||
|
apollo: {
|
||||||
|
me: meQuery
|
||||||
|
},
|
||||||
|
|
||||||
|
data: () => ({
|
||||||
|
me: {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/styles/_variables.scss";
|
||||||
|
@import "@/styles/_mixins.scss";
|
||||||
|
|
||||||
|
.profile-widget {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
@include heading-3;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__avatar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: $medium-spacing;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,31 +1,37 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="class-selection" v-if="isTeacher">
|
<div class="class-selection" v-if="currentClassSelection">
|
||||||
<div class="class-selection__selected-class selected-class" @click="showPopover = !showPopover">
|
<div data-cy="class-selection" class="class-selection__selected-class selected-class"
|
||||||
<p class="selected-class__text">Klasse: {{currentClassSelection.name}}</p>
|
@click="showPopover = !showPopover">
|
||||||
|
<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"
|
||||||
:mobile="mobile"
|
:mobile="mobile"
|
||||||
class="class-selection__popover">
|
class="class-selection__popover">
|
||||||
<li class="popover-links__link popover-links__link--large" v-for="schoolClass in schoolClasses"
|
<li data-cy="class-selection-entry" class="popover-links__link popover-links__link--large"
|
||||||
|
v-for="schoolClass in me.schoolClasses"
|
||||||
:key="schoolClass.id"
|
:key="schoolClass.id"
|
||||||
:label="schoolClass.name"
|
:label="schoolClass.name"
|
||||||
:item="schoolClass"
|
:item="schoolClass"
|
||||||
@click="updateFilter(schoolClass)">
|
@click="updateFilter(schoolClass)">
|
||||||
{{schoolClass.name}}
|
{{schoolClass.name}}
|
||||||
</li>
|
</li>
|
||||||
</widget-popover>
|
</widget-popover>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import WidgetPopover from '@/components/WidgetPopover';
|
import WidgetPopover from '@/components/WidgetPopover';
|
||||||
|
import ChevronDown from '@/components/icons/ChevronDown';
|
||||||
|
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 UPDATE_USER_SETTING from '@/graphql/gql/mutations/updateUserSetting.gql';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
WidgetPopover
|
WidgetPopover,
|
||||||
|
ChevronDown,
|
||||||
|
CurrentClass
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -37,7 +43,13 @@
|
||||||
|
|
||||||
apollo: {
|
apollo: {
|
||||||
me: {
|
me: {
|
||||||
query: ME_QUERY
|
query: ME_QUERY,
|
||||||
|
manual: true,
|
||||||
|
result({data, loading, networkStatus}) {
|
||||||
|
if (!loading) {
|
||||||
|
this.me = this.$getRidOfEdges(data).me;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -47,7 +59,8 @@
|
||||||
selectedClass: {
|
selectedClass: {
|
||||||
id: ''
|
id: ''
|
||||||
},
|
},
|
||||||
permissions: []
|
permissions: [],
|
||||||
|
schoolClasses: []
|
||||||
},
|
},
|
||||||
showPopover: false
|
showPopover: false
|
||||||
}
|
}
|
||||||
|
|
@ -64,7 +77,7 @@
|
||||||
},
|
},
|
||||||
update(store, data) {
|
update(store, data) {
|
||||||
let meData = store.readQuery({query: ME_QUERY});
|
let meData = store.readQuery({query: ME_QUERY});
|
||||||
meData.me.selectedClass = selectedClass
|
meData.me.selectedClass = selectedClass;
|
||||||
store.writeQuery({query: ME_QUERY, data: meData});
|
store.writeQuery({query: ME_QUERY, data: meData});
|
||||||
}
|
}
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
|
|
@ -76,16 +89,10 @@
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
currentClassSelection() {
|
currentClassSelection() {
|
||||||
let currentClass = this.schoolClasses.find(schoolClass => {
|
let currentClass = this.me.schoolClasses.find(schoolClass => {
|
||||||
return schoolClass.id === this.me.selectedClass.id
|
return schoolClass.id === this.me.selectedClass.id
|
||||||
})
|
});
|
||||||
return currentClass || this.schoolClasses[0];
|
return currentClass || this.me.schoolClasses[0];
|
||||||
},
|
|
||||||
schoolClasses() {
|
|
||||||
return this.$getRidOfEdges(this.me.schoolClasses);
|
|
||||||
},
|
|
||||||
isTeacher() {
|
|
||||||
return this.me.permissions.includes('users.can_manage_school_class_content');
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -97,25 +104,42 @@
|
||||||
@import "@/styles/_mixins.scss";
|
@import "@/styles/_mixins.scss";
|
||||||
|
|
||||||
.class-selection {
|
.class-selection {
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-right: $large-spacing;
|
margin-bottom: $medium-spacing;
|
||||||
|
border: 1px solid $color-silver;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: $small-spacing $medium-spacing;
|
||||||
|
/*justify-self: space-between;*/
|
||||||
|
|
||||||
&__popover {
|
&__popover {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
top: 40px;
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
transform: translateY($small-spacing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected-class {
|
.selected-class {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
&__text {
|
&__text {
|
||||||
line-height: $large-spacing;
|
line-height: $large-spacing;
|
||||||
@include regular-text;
|
@include regular-text;
|
||||||
color: $color-silver-dark;
|
color: $color-silver-dark;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__dropdown-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
fill: $color-brand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.popover-links__link {
|
.popover-links__link {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<template>
|
||||||
|
<span class="current-class">Klasse: {{currentClassName}}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import me from '@/mixins/me';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [me],
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
currentClassName() {
|
||||||
|
let currentClass = this.me.schoolClasses.find(schoolClass => {
|
||||||
|
return schoolClass.id === this.me.selectedClass.id
|
||||||
|
});
|
||||||
|
return currentClass ? currentClass.name : this.me.schoolClasses.length ? this.me.schoolClasses[0].name : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/styles/_variables.scss";
|
||||||
|
@import "@/styles/_mixins.scss";
|
||||||
|
|
||||||
|
.current-class {
|
||||||
|
line-height: $large-spacing;
|
||||||
|
@include regular-text;
|
||||||
|
color: $color-silver-dark;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -111,6 +111,10 @@ export default function (uri, networkErrorCallback) {
|
||||||
helloEmail: {
|
helloEmail: {
|
||||||
__typename: 'HelloEmail',
|
__typename: 'HelloEmail',
|
||||||
email: ''
|
email: ''
|
||||||
|
},
|
||||||
|
sidebar: {
|
||||||
|
__typename: 'Sidebar',
|
||||||
|
open: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
mutation($open: Boolean!) {
|
||||||
|
toggleSidebar(open: $open) @client
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
query Sidebar {
|
||||||
|
sidebar @client {
|
||||||
|
open
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
mutation JoinClass($input: JoinClassInput!) {
|
||||||
|
joinClass(input: $input) {
|
||||||
|
success
|
||||||
|
schoolClass {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import SCROLL_POSITION from '@/graphql/gql/local/scrollPosition.gql';
|
import SCROLL_POSITION from '@/graphql/gql/local/scrollPosition.gql';
|
||||||
import HELLO_EMAIL from '@/graphql/gql/local/helloEmail.gql';
|
import HELLO_EMAIL from '@/graphql/gql/local/helloEmail.gql';
|
||||||
|
import SIDEBAR from '@/graphql/gql/local/sidebar.gql';
|
||||||
|
|
||||||
export const resolvers = {
|
export const resolvers = {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
|
|
@ -15,5 +16,11 @@ export const resolvers = {
|
||||||
cache.writeQuery({query: HELLO_EMAIL, data});
|
cache.writeQuery({query: HELLO_EMAIL, data});
|
||||||
return data.helloEmail;
|
return data.helloEmail;
|
||||||
},
|
},
|
||||||
|
toggleSidebar: (_, {open}, {cache}) => {
|
||||||
|
const data = cache.readQuery({query: SIDEBAR});
|
||||||
|
data.sidebar.open = open;
|
||||||
|
cache.writeQuery({query: SIDEBAR, data});
|
||||||
|
return data.sidebar;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,11 @@ export const typeDefs = gql`
|
||||||
email: String!
|
email: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type Sidebar {
|
||||||
|
open: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
scrollTo(scrollTo: String!): ScrollPosition
|
scrollTo(scrollTo: String!): ScrollPosition
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="blank-layout">
|
<div class="blank-layout">
|
||||||
|
<profile-sidebar></profile-sidebar>
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -14,9 +15,9 @@
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import ProfileSidebar from '@/components/profile/ProfileSidebar';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {},
|
components: {ProfileSidebar},
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="container skillbox" :class="specialContainerClass">
|
<div class="container skillbox" :class="specialContainerClass">
|
||||||
|
<profile-sidebar></profile-sidebar>
|
||||||
<header-bar class="header skillbox__header">
|
<header-bar class="header skillbox__header">
|
||||||
</header-bar>
|
</header-bar>
|
||||||
|
|
||||||
|
|
@ -13,11 +14,13 @@
|
||||||
<script>
|
<script>
|
||||||
import HeaderBar from '@/components/HeaderBar';
|
import HeaderBar from '@/components/HeaderBar';
|
||||||
import MobileHeader from '@/components/MobileHeader';
|
import MobileHeader from '@/components/MobileHeader';
|
||||||
|
import ProfileSidebar from '@/components/profile/ProfileSidebar';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
HeaderBar,
|
HeaderBar,
|
||||||
MobileHeader
|
MobileHeader,
|
||||||
|
ProfileSidebar
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
|
|
||||||
|
|
@ -170,10 +170,9 @@ router.beforeEach(async (to, from, next) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle users students without class
|
if ((to.name !== 'join-class' && to.name !== 'licenseActivation') && loginRequired(to) && await redirectStudentsWithoutClass()) {
|
||||||
if ((to.name !== 'noClass' && to.name !== 'licenseActivation') && loginRequired(to) && await redirectStudentsWithoutClass()) {
|
next({name: 'join-class'})
|
||||||
next({name: 'noClass'})
|
return
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
next();
|
next();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
me: {
|
||||||
|
selectedClass: {
|
||||||
|
id: ''
|
||||||
|
},
|
||||||
|
permissions: [],
|
||||||
|
schoolClasses: []
|
||||||
|
},
|
||||||
|
showPopover: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
apollo: {
|
||||||
|
me: {
|
||||||
|
query: ME_QUERY,
|
||||||
|
manual: true,
|
||||||
|
result({data, loading, networkStatus}) {
|
||||||
|
if (!loading) {
|
||||||
|
this.me = this.$getRidOfEdges(data).me;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import TOGGLE_SIDEBAR from '@/graphql/gql/local/mutations/toggleSidebar.gql';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
openSidebar() {
|
||||||
|
this.$apollo.mutate({
|
||||||
|
mutation: TOGGLE_SIDEBAR,
|
||||||
|
variables: {
|
||||||
|
open: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1 data-cy="join-class-title">Zugangscode eingeben</h1>
|
||||||
|
<div>
|
||||||
|
<div class="skillboxform-input">
|
||||||
|
<label for="join-code" class="skillboxform-input__label">Zugangscode</label>
|
||||||
|
<input
|
||||||
|
id="join-code"
|
||||||
|
class="skillbox-input skillboxform-input__input"
|
||||||
|
:class="{'skillboxform-input__input--error': error}"
|
||||||
|
data-cy="input-class-code"
|
||||||
|
:value="code"
|
||||||
|
@input="updateCode">
|
||||||
|
<small
|
||||||
|
v-if="error"
|
||||||
|
class="skillboxform-input__error"
|
||||||
|
data-cy="email-local-errors"
|
||||||
|
>{{ error }}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a class="button button--primary button--big" data-cy="join-class" @click="joinClass(code)">Klasse beitreten</a>
|
||||||
|
<a class="button button--big" data-cy="join-class-cancel" @click="cancel">Abbrechen</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import JOIN_CLASS_MUTATION from '@/graphql/gql/mutations/joinClass.gql';
|
||||||
|
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: () => ({
|
||||||
|
code: '',
|
||||||
|
error: ''
|
||||||
|
}),
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
updateCode(event) {
|
||||||
|
this.code = event.target.value;
|
||||||
|
this.error = '';
|
||||||
|
},
|
||||||
|
joinClass(code) {
|
||||||
|
this.$apollo.mutate({
|
||||||
|
mutation: JOIN_CLASS_MUTATION,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
code
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update(store, {data: {joinClass: {schoolClass}}}) {
|
||||||
|
if (schoolClass) {
|
||||||
|
const data = store.readQuery({query: ME_QUERY});
|
||||||
|
if (data) {
|
||||||
|
data.me.schoolClasses.edges.push({
|
||||||
|
node: schoolClass,
|
||||||
|
__typename: 'SchoolClassNode'
|
||||||
|
});
|
||||||
|
store.writeQuery({query: ME_QUERY, data});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.$router.push({name: 'my-classes'});
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
console.debug(e);
|
||||||
|
if (e.message.indexOf('[CAJ]') > -1) {
|
||||||
|
this.error = 'Sie sind dieser Klasse bereits beigetreten.';
|
||||||
|
} else {
|
||||||
|
this.error = 'Dieser Zugangscode ist nicht gültig.';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
this.$router.go(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
<script>
|
<script>
|
||||||
import MODULE_ROOM_ENTRIES_QUERY from '@/graphql/gql/moduleRoomEntryQuery.gql';
|
import MODULE_ROOM_ENTRIES_QUERY from '@/graphql/gql/moduleRoomEntryQuery.gql';
|
||||||
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
||||||
import roomMixin from '@/components/mixins/room'
|
import roomMixin from '@/mixins/room'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['slug'],
|
props: ['slug'],
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="myclasses">
|
<div class="myclasses">
|
||||||
<h1 class="myclasses__header">Klassenliste</h1>
|
<h1 class="myclasses__header" data-cy="class-list-title">Klassenliste</h1>
|
||||||
<classlist v-for="schoolClass in schoolClasses" v-bind="schoolClass" :key="schoolClass.name" class="myclasses__class"></classlist>
|
<classlist v-for="schoolClass in schoolClasses" v-bind="schoolClass" :key="schoolClass.name" class="myclasses__class"></classlist>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,16 @@
|
||||||
<div class="profile">
|
<div class="profile">
|
||||||
<nav class="top-navigation profile-submenu profile__submenu">
|
<nav class="top-navigation profile-submenu profile__submenu">
|
||||||
<router-link to="/me/activity" active-class="top-navigation__link--active"
|
<router-link to="/me/activity" active-class="top-navigation__link--active"
|
||||||
class="top-navigation__link profile-submenu__item submenu-item">Aktivität
|
class="top-navigation__link profile-submenu__item submenu-item">Aktivität
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link to="/me/myclasses" active-class="top-navigation__link--active"
|
<router-link to="/me/myclasses" active-class="top-navigation__link--active"
|
||||||
class="top-navigation__link profile-submenu__item submenu-item">Klassenliste
|
class="top-navigation__link profile-submenu__item submenu-item">Klassenliste
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link to="/me/profile" active-class="top-navigation__link--active"
|
<router-link to="/me/profile" active-class="top-navigation__link--active"
|
||||||
class="top-navigation__link profile-submenu__item submenu-item">Profil
|
class="top-navigation__link profile-submenu__item submenu-item">Profil
|
||||||
|
</router-link>
|
||||||
|
<router-link :to="{name:'join-class'}" active-class="top-navigation__link--active" data-cy="join-class-link"
|
||||||
|
class="top-navigation__link profile-submenu__item submenu-item">Klasse beitreten
|
||||||
</router-link>
|
</router-link>
|
||||||
</nav>
|
</nav>
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ROOM_ENTRIES_QUERY from '@/graphql/gql/roomEntriesQuery.gql';
|
import ROOM_ENTRIES_QUERY from '@/graphql/gql/roomEntriesQuery.gql';
|
||||||
import roomMixin from '@/components/mixins/room'
|
import roomMixin from '@/mixins/room'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['slug'],
|
props: ['slug'],
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,9 @@
|
||||||
<div class="start-page__news news">
|
<div class="start-page__news news">
|
||||||
<h2 class="news__title">News</h2>
|
<h2 class="news__title">News</h2>
|
||||||
<news-teaser date="20. Dezember 2019" title="Blockchain"
|
<news-teaser date="20. Dezember 2019" title="Blockchain"
|
||||||
url="http://abunews-blockchain.webflow.io/"></news-teaser>
|
url="https://myskillbox-abu-news.webflow.io/blockchain"></news-teaser>
|
||||||
<news-teaser date="9. September 2019" title="Klima – was sonst?"
|
<news-teaser date="9. September 2019" title="Klima – was sonst?"
|
||||||
url="https://abunews-1f178193a10edaabf-4caff67b27d10.webflow.io/"></news-teaser>
|
url="https://myskillbox-abu-news.webflow.io/klima"></news-teaser>
|
||||||
<!--<news-teaser date="31. Oktober 2018" title="Sommerzeit - Festivalzeit"-->
|
<!--<news-teaser date="31. Oktober 2018" title="Sommerzeit - Festivalzeit"-->
|
||||||
<!--url="https://abunews.webflow.io/"></news-teaser>-->
|
<!--url="https://abunews.webflow.io/"></news-teaser>-->
|
||||||
<div class="news__more">Mehr...</div>
|
<div class="news__more">Mehr...</div>
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import checkEmail from '@/pages/check-email'
|
||||||
import emailVerification from '@/pages/email-verification'
|
import emailVerification from '@/pages/email-verification'
|
||||||
import licenseActivation from '@/pages/license-activation'
|
import licenseActivation from '@/pages/license-activation'
|
||||||
import forgotPassword from '@/pages/forgot-password'
|
import forgotPassword from '@/pages/forgot-password'
|
||||||
|
import joinClass from '@/pages/joinClass'
|
||||||
|
|
||||||
import store from '@/store/index';
|
import store from '@/store/index';
|
||||||
|
|
||||||
|
|
@ -136,6 +137,7 @@ const routes = [
|
||||||
{path: '', name: 'profile-activity', component: activity, meta: {isProfile: true}},
|
{path: '', name: 'profile-activity', component: activity, meta: {isProfile: true}},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{path: 'join-class', name: 'join-class', component: joinClass, meta: {layout: 'simple'}},
|
||||||
{
|
{
|
||||||
path: '/survey/:id',
|
path: '/survey/:id',
|
||||||
component: surveyPage,
|
component: surveyPage,
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,17 @@
|
||||||
font-size: toRem(18px);
|
font-size: toRem(18px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin default-link {
|
||||||
|
font-size: toRem(18px);
|
||||||
|
font-family: $sans-serif-font-family;
|
||||||
|
font-weight: $font-weight-regular;
|
||||||
|
color: $color-silver-dark;
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
color: $color-brand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@mixin page-form-input-heading {
|
@mixin page-form-input-heading {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: $medium-spacing;
|
margin-bottom: $medium-spacing;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import graphene
|
import graphene
|
||||||
|
from django.db.models import Q
|
||||||
from graphene import relay
|
from graphene import relay
|
||||||
from graphene_django import DjangoObjectType
|
from graphene_django import DjangoObjectType
|
||||||
from graphene_django.filter import DjangoFilterConnectionField
|
from graphene_django.filter import DjangoFilterConnectionField
|
||||||
|
|
@ -85,24 +86,20 @@ class ChapterNode(DjangoObjectType):
|
||||||
def resolve_content_blocks(self, info, **kwargs):
|
def resolve_content_blocks(self, info, **kwargs):
|
||||||
user = info.context.user
|
user = info.context.user
|
||||||
school_classes = user.school_classes.values_list('pk')
|
school_classes = user.school_classes.values_list('pk')
|
||||||
by_parent = ContentBlock.get_by_parent(self).prefetch_related(
|
|
||||||
'visible_for').prefetch_related(
|
by_parent = ContentBlock.get_by_parent(self) \
|
||||||
'hidden_for')
|
.prefetch_related('visible_for') \
|
||||||
|
.prefetch_related('hidden_for')
|
||||||
|
|
||||||
|
# don't filter the hidden blocks on the server any more, we do this on the client now, as they are not secret
|
||||||
|
default_blocks = Q(user_created=False)
|
||||||
|
owned_by_user = Q(user_created=True, owner=user)
|
||||||
|
teacher_created_and_visible = Q(Q(user_created=True) & Q(visible_for__in=school_classes))
|
||||||
|
|
||||||
if user.has_perm('users.can_manage_school_class_content'): # teacher
|
if user.has_perm('users.can_manage_school_class_content'): # teacher
|
||||||
publisher_content_blocks = by_parent.filter(user_created=False)
|
return by_parent.filter(default_blocks | owned_by_user)
|
||||||
user_created_content_blocks = by_parent.filter(user_created=True, owner=user)
|
|
||||||
else: # student
|
else: # student
|
||||||
publisher_content_blocks = by_parent.filter(user_created=False).exclude(
|
return by_parent.filter(default_blocks | teacher_created_and_visible)
|
||||||
hidden_for__in=school_classes)
|
|
||||||
|
|
||||||
self_created_content_blocks = by_parent.filter(user_created=True, owner=user)
|
|
||||||
|
|
||||||
user_created_content_blocks = by_parent.filter(user_created=True,
|
|
||||||
visible_for__in=school_classes).union(
|
|
||||||
self_created_content_blocks)
|
|
||||||
|
|
||||||
return publisher_content_blocks.union(user_created_content_blocks)
|
|
||||||
|
|
||||||
def resolve_bookmark(self, info, **kwags):
|
def resolve_bookmark(self, info, **kwags):
|
||||||
return ChapterBookmark.objects.filter(
|
return ChapterBookmark.objects.filter(
|
||||||
|
|
@ -234,24 +231,6 @@ class BookNode(DjangoObjectType):
|
||||||
return Topic.get_by_parent(self)
|
return Topic.get_by_parent(self)
|
||||||
|
|
||||||
|
|
||||||
# todo: do we need this?
|
|
||||||
# class FilteredChapterNode(DjangoObjectType):
|
|
||||||
# content_blocks = DjangoFilterConnectionField(ContentBlockNode)
|
|
||||||
#
|
|
||||||
# class Meta:
|
|
||||||
# model = Chapter
|
|
||||||
# only_fields = [
|
|
||||||
# 'slug', 'title', 'description',
|
|
||||||
# ]
|
|
||||||
# filter_fields = [
|
|
||||||
# 'slug', 'title',
|
|
||||||
# ]
|
|
||||||
# interfaces = (relay.Node,)
|
|
||||||
#
|
|
||||||
# def resolve_content_blocks(self, *args, **kwargs):
|
|
||||||
# return ContentBlock.get_by_parent(self)
|
|
||||||
|
|
||||||
|
|
||||||
class BookQuery(object):
|
class BookQuery(object):
|
||||||
book = relay.Node.Field(BookNode)
|
book = relay.Node.Field(BookNode)
|
||||||
topic = graphene.Field(TopicNode, slug=graphene.String())
|
topic = graphene.Field(TopicNode, slug=graphene.String())
|
||||||
|
|
@ -287,7 +266,6 @@ class BookQuery(object):
|
||||||
elif slug is not None:
|
elif slug is not None:
|
||||||
module = Module.objects.get(slug=slug)
|
module = Module.objects.get(slug=slug)
|
||||||
|
|
||||||
|
|
||||||
return module
|
return module
|
||||||
|
|
||||||
def resolve_topic(self, info, **kwargs):
|
def resolve_topic(self, info, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ user_data = [
|
||||||
{
|
{
|
||||||
'teacher': ('Nico', 'Zickgraf',),
|
'teacher': ('Nico', 'Zickgraf',),
|
||||||
'class': 'FLID2018a',
|
'class': 'FLID2018a',
|
||||||
|
'code': 'XXXX',
|
||||||
'students': [
|
'students': [
|
||||||
('Robin', 'Abbühl'),
|
('Robin', 'Abbühl'),
|
||||||
('Zeynep', 'Catal'),
|
('Zeynep', 'Catal'),
|
||||||
|
|
@ -15,12 +16,13 @@ user_data = [
|
||||||
('Kelly', 'To'),
|
('Kelly', 'To'),
|
||||||
('Deborah', 'Waldmeier'),
|
('Deborah', 'Waldmeier'),
|
||||||
('Rahel', 'Weiss'),
|
('Rahel', 'Weiss'),
|
||||||
('Nora', 'Zimmermann'),
|
('Cindy', 'Zimmermann'),
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'teacher': ('Michael', 'Gurtner',),
|
'teacher': ('Michael', 'Gurtner',),
|
||||||
'class': 'KF1A',
|
'class': 'KF1A',
|
||||||
|
'code': 'YYYY',
|
||||||
'students': [
|
'students': [
|
||||||
('Lisa', 'Arn'),
|
('Lisa', 'Arn'),
|
||||||
('Machado', 'Fernandes'),
|
('Machado', 'Fernandes'),
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ from wagtail.core.models import Page
|
||||||
from books.factories import BookFactory, TopicFactory, ModuleFactory, ChapterFactory, ContentBlockFactory
|
from books.factories import BookFactory, TopicFactory, ModuleFactory, ChapterFactory, ContentBlockFactory
|
||||||
from core.factories import UserFactory
|
from core.factories import UserFactory
|
||||||
from objectives.factories import ObjectiveGroupFactory, ObjectiveFactory
|
from objectives.factories import ObjectiveGroupFactory, ObjectiveFactory
|
||||||
from users.services import create_users
|
from users.models import Role
|
||||||
|
from users.services import create_users, create_student
|
||||||
|
|
||||||
from .data.module_data import data
|
from .data.module_data import data
|
||||||
from .data.user_data import user_data
|
from .data.user_data import user_data
|
||||||
|
|
@ -57,6 +58,13 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
create_users(user_data)
|
create_users(user_data)
|
||||||
|
|
||||||
|
# create student without class
|
||||||
|
create_student(
|
||||||
|
username='hansli',
|
||||||
|
first_name='Hansli',
|
||||||
|
last_name='Alleini'
|
||||||
|
)
|
||||||
|
|
||||||
for book_idx, book_data in enumerate(data):
|
for book_idx, book_data in enumerate(data):
|
||||||
book = BookFactory.create(parent=site.root_page, **self.filter_data(book_data, 'topics'))
|
book = BookFactory.create(parent=site.root_page, **self.filter_data(book_data, 'topics'))
|
||||||
|
|
||||||
|
|
@ -102,8 +110,6 @@ class Command(BaseCommand):
|
||||||
# ContentBlockFactory.create(parent=chapter, **self.filter_data(content_block_data, 'contents'))
|
# ContentBlockFactory.create(parent=chapter, **self.filter_data(content_block_data, 'contents'))
|
||||||
ContentBlockFactory.create(parent=chapter, module=module, **content_block_data)
|
ContentBlockFactory.create(parent=chapter, module=module, **content_block_data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# now create all and rooms
|
# now create all and rooms
|
||||||
management.call_command('dummy_rooms', verbosity=0)
|
management.call_command('dummy_rooms', verbosity=0)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ SIGNING_SECRET = os.environ.get('SIGNING_SECRET')
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = bool_value(os.environ.get('DEBUG', ''))
|
DEBUG = bool_value(os.environ.get('DEBUG', ''))
|
||||||
TEST = 'test' in sys.argv
|
TEST = 'test' in sys.argv
|
||||||
|
ENABLE_SILKY = bool_value(os.environ.get('ENABLE_SILKY', ''))
|
||||||
|
|
||||||
ALLOWED_HOSTS = ['*']
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
|
|
@ -107,6 +108,11 @@ if DEBUG:
|
||||||
)
|
)
|
||||||
CORS_ALLOW_CREDENTIALS = True
|
CORS_ALLOW_CREDENTIALS = True
|
||||||
|
|
||||||
|
# enable silk for performance measuring
|
||||||
|
if ENABLE_SILKY:
|
||||||
|
INSTALLED_APPS += ['silk']
|
||||||
|
MIDDLEWARE += ['silk.middleware.SilkyMiddleware', ]
|
||||||
|
|
||||||
MIDDLEWARE += [
|
MIDDLEWARE += [
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
{% load i18n %}{% autoescape off %}
|
{% load i18n %}
|
||||||
|
{% load core_tags %}
|
||||||
|
{% autoescape off %}
|
||||||
|
|
||||||
{% blocktrans %}Du erhältst dieses E-Mail, weil dein Passwort auf {{ site_name }} zurückgesetzt wurde.{% endblocktrans %}
|
{% blocktrans %}Du erhältst dieses E-Mail, weil dein Passwort auf {{ site_name }} zurückgesetzt wurde.{% endblocktrans %}
|
||||||
|
|
||||||
{% trans "Bitte öffnen Sie folgende Seite, um Ihr neues Passwort einzugeben:" %}
|
{% trans "Bitte öffnen Sie folgende Seite, um Ihr neues Passwort einzugeben:" %}
|
||||||
{% block reset_link %}
|
{% block reset_link %}
|
||||||
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
|
<a href="{% reset_link 'password_reset_confirm' protocol domain token uid %}">{% reset_link 'password_reset_confirm' protocol domain token uid %}</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% trans "Ihr Benutzername lautet:" %} {{ user.get_username }}
|
{% trans "Ihr Benutzername lautet:" %} {{ user.get_username }}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
{% load i18n %}{% autoescape off %}
|
{% load i18n %}
|
||||||
|
{% load core_tags %}
|
||||||
|
{% autoescape off %}
|
||||||
|
|
||||||
{% blocktrans %}Sie erhalten diese E-Mail, um Ihr Passwort auf mySkillbox initial zu setzen.{% endblocktrans %}
|
{% blocktrans %}Sie erhalten diese E-Mail, um Ihr Passwort auf mySkillbox initial zu setzen.{% endblocktrans %}
|
||||||
|
|
||||||
{% trans "Bitte öffnen Sie folgende Seite, um Ihr neues Passwort einzugeben:" %}
|
{% trans "Bitte öffnen Sie folgende Seite, um Ihr neues Passwort einzugeben:" %}
|
||||||
{% block reset_link %}
|
{% block reset_link %}
|
||||||
{{ protocol }}://{{ domain }}{% url 'set_password_confirm' uidb64=uid token=token %}
|
<a href="{% reset_link 'set_password_confirm' protocol domain token uid %}">{% reset_link 'set_password_confirm' protocol domain token uid %}</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% trans "Ihr Benutzername lautet:" %} {{ user.get_username }}
|
{% trans "Ihr Benutzername lautet:" %} {{ user.get_username }}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
{% load i18n %}{% autoescape off %}
|
{% load i18n %}
|
||||||
|
{% load core_tags %}
|
||||||
|
{% autoescape off %}
|
||||||
|
|
||||||
{% blocktrans %}Sie erhalten diese E-Mail, um Ihr Passwort auf mySkillbox initial zu setzen.{% endblocktrans %}
|
{% blocktrans %}Sie erhalten diese E-Mail, um Ihr Passwort auf mySkillbox initial zu setzen.{% endblocktrans %}
|
||||||
|
|
||||||
{% trans "Bitte öffnen Sie folgende Seite, um Ihr neues Passwort einzugeben:" %}
|
{% trans "Bitte öffnen Sie folgende Seite, um Ihr neues Passwort einzugeben:" %}
|
||||||
{% block reset_link %}
|
{% block reset_link %}
|
||||||
{{ protocol }}://{{ domain }}{% url 'set_password_confirm' uidb64=uid token=token %}
|
<a href="{% reset_link 'set_password_confirm' protocol domain token uid %}">{% reset_link 'set_password_confirm' protocol domain token uid %}</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% trans "Ihr Benutzername lautet:" %} {{ user.get_username }}
|
{% trans "Ihr Benutzername lautet:" %} {{ user.get_username }}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
|
from django.urls import reverse
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
@ -18,4 +19,9 @@ def json_dumps(obj):
|
||||||
|
|
||||||
@register.filter(name='class_name')
|
@register.filter(name='class_name')
|
||||||
def class_name(obj):
|
def class_name(obj):
|
||||||
return str(obj.__class__.__name__)
|
return str(obj.__class__.__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def reset_link(name, protocol, domain, token, uid):
|
||||||
|
return '{}://{}{}'.format(protocol, domain, reverse(name, kwargs={'uidb64': uid, 'token': token}))
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,9 @@ if settings.DEBUG and not settings.USE_AWS:
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
|
|
||||||
|
if settings.ENABLE_SILKY:
|
||||||
|
urlpatterns += [url(r'^silk/', include('silk.urls', namespace='silk'))]
|
||||||
|
|
||||||
# actually we use the cms in headless mode but need the url pattern to get the wagtail_serve function
|
# actually we use the cms in headless mode but need the url pattern to get the wagtail_serve function
|
||||||
urlpatterns += [url(r'pages/', include(wagtail_urls)), ]
|
urlpatterns += [url(r'pages/', include(wagtail_urls)), ]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import graphene
|
import graphene
|
||||||
|
from django.db.models import Q
|
||||||
from graphene import relay
|
from graphene import relay
|
||||||
from graphene_django import DjangoObjectType
|
from graphene_django import DjangoObjectType
|
||||||
from graphene_django.filter import DjangoFilterConnectionField
|
from graphene_django.filter import DjangoFilterConnectionField
|
||||||
|
|
@ -29,17 +30,14 @@ class ObjectiveGroupNode(DjangoObjectType):
|
||||||
user = info.context.user
|
user = info.context.user
|
||||||
school_classes = user.school_classes.values_list('pk')
|
school_classes = user.school_classes.values_list('pk')
|
||||||
|
|
||||||
|
objectives_from_publisher = Q(owner=None)
|
||||||
|
objectives_from_user = Q(owner=user)
|
||||||
|
objectives_from_teacher = Q(owner__isnull=False, visible_for__in=school_classes)
|
||||||
|
|
||||||
if user.has_perm('users.can_manage_school_class_content'): # teacher
|
if user.has_perm('users.can_manage_school_class_content'): # teacher
|
||||||
publisher_objectives = self.objectives.filter(owner=None)
|
return self.objectives.filter(objectives_from_publisher | objectives_from_user)
|
||||||
user_created_objectives = self.objectives.filter(owner=user)
|
|
||||||
else: # student
|
else: # student
|
||||||
publisher_objectives = self.objectives.filter(owner=None).exclude(
|
return self.objectives.filter(objectives_from_publisher | objectives_from_teacher)
|
||||||
hidden_for__in=school_classes)
|
|
||||||
|
|
||||||
user_created_objectives = self.objectives.filter(owner__isnull=False,
|
|
||||||
visible_for__in=school_classes)
|
|
||||||
|
|
||||||
return publisher_objectives.union(user_created_objectives)
|
|
||||||
|
|
||||||
|
|
||||||
class ObjectiveNode(DjangoObjectType):
|
class ObjectiveNode(DjangoObjectType):
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ class RoleInline(admin.TabularInline):
|
||||||
|
|
||||||
@admin.register(SchoolClass)
|
@admin.register(SchoolClass)
|
||||||
class SchoolClassAdmin(admin.ModelAdmin):
|
class SchoolClassAdmin(admin.ModelAdmin):
|
||||||
list_display = ('id', 'name')
|
list_display = ('name', 'code', 'is_deleted')
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Role)
|
@admin.register(Role)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.1.15 on 2020-02-11 10:14
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0009_auto_20191009_0905'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='schoolclass',
|
||||||
|
name='code',
|
||||||
|
field=models.CharField(blank=True, default=None, max_length=10, null=True, unique=True, verbose_name='Code zum Beitreten'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -95,9 +95,14 @@ 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)
|
||||||
|
code = models.CharField('Code zum Beitreten', blank=True, null=True, max_length=10, unique=True, default=None)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Schulklasse'
|
||||||
|
verbose_name_plural = 'Schulklassen'
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return 'SchoolClass {}-{}'.format(self.id, self.name)
|
return '{}'.format(self.name)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def generate_default_group_name(cls):
|
def generate_default_group_name(cls):
|
||||||
|
|
@ -130,6 +135,11 @@ class SchoolClass(models.Model):
|
||||||
def get_teacher(self):
|
def get_teacher(self):
|
||||||
return self.users.filter(user_roles__role__key='teacher').first()
|
return self.users.filter(user_roles__role__key='teacher').first()
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.code == '': # '' can't be unique, so we null it
|
||||||
|
self.code = None
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Role(models.Model):
|
class Role(models.Model):
|
||||||
key = models.CharField(_('Key'), max_length=100, blank=False, null=False, unique=True)
|
key = models.CharField(_('Key'), max_length=100, blank=False, null=False, unique=True)
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,20 @@
|
||||||
import graphene
|
import graphene
|
||||||
from django.contrib.auth import update_session_auth_hash
|
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 graphene import relay
|
from graphene import relay
|
||||||
|
|
||||||
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
|
||||||
|
from users.schema import SchoolClassNode
|
||||||
from users.serializers import PasswordSerialzer, AvatarUrlSerializer
|
from users.serializers import PasswordSerialzer, AvatarUrlSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class CodeNotFoundException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class FieldError(graphene.ObjectType):
|
class FieldError(graphene.ObjectType):
|
||||||
code = graphene.String()
|
code = graphene.String()
|
||||||
|
|
||||||
|
|
@ -102,8 +108,33 @@ class UpdateSetting(relay.ClientIDMutation):
|
||||||
errors = graphene.List(UpdateError)
|
errors = graphene.List(UpdateError)
|
||||||
|
|
||||||
|
|
||||||
|
class JoinClass(relay.ClientIDMutation):
|
||||||
|
class Input:
|
||||||
|
code = graphene.String(required=True)
|
||||||
|
|
||||||
|
success = graphene.Boolean()
|
||||||
|
school_class = graphene.Field(SchoolClassNode)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||||
|
user = info.context.user
|
||||||
|
code = kwargs.get('code')
|
||||||
|
try:
|
||||||
|
school_class = SchoolClass.objects.get(Q(code__iexact=code))
|
||||||
|
|
||||||
|
|
||||||
|
if user not in list(school_class.users.all()):
|
||||||
|
school_class.users.add(user)
|
||||||
|
else:
|
||||||
|
raise CodeNotFoundException('[CAJ] Schüler ist bereits in Klasse') # CAJ = Class Already Joined
|
||||||
|
|
||||||
|
return cls(success=True, school_class=school_class)
|
||||||
|
except SchoolClass.DoesNotExist:
|
||||||
|
raise CodeNotFoundException('[CNV] Code ist nicht gültig') # CAV = Code Not Valid
|
||||||
|
|
||||||
|
|
||||||
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()
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,23 @@ from users.factories import SchoolClassFactory
|
||||||
from users.models import Role, UserRole, DEFAULT_SCHOOL_ID
|
from users.models import Role, UserRole, DEFAULT_SCHOOL_ID
|
||||||
|
|
||||||
|
|
||||||
|
def create_student(**kwargs):
|
||||||
|
try:
|
||||||
|
student_role = Role.objects.get_default_student_role()
|
||||||
|
except Role.DoesNotExist:
|
||||||
|
Role.objects.create_default_roles()
|
||||||
|
student_role = Role.objects.get_default_student_role()
|
||||||
|
user = UserFactory(
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
UserRole.objects.create(user=user, role=student_role)
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
def create_users(data=None):
|
def create_users(data=None):
|
||||||
Role.objects.create_default_roles()
|
Role.objects.create_default_roles()
|
||||||
|
|
||||||
teacher_role = Role.objects.get_default_teacher_role()
|
teacher_role = Role.objects.get_default_teacher_role()
|
||||||
student_role = Role.objects.get_default_student_role()
|
|
||||||
|
|
||||||
if data is None:
|
if data is None:
|
||||||
teacher = UserFactory(username='teacher')
|
teacher = UserFactory(username='teacher')
|
||||||
|
|
@ -15,18 +27,17 @@ def create_users(data=None):
|
||||||
|
|
||||||
students = []
|
students = []
|
||||||
for i in range(1, 7):
|
for i in range(1, 7):
|
||||||
student = UserFactory(username='student{}'.format(i))
|
student = create_student(username='student{}'.format(i))
|
||||||
UserRole.objects.create(user=student, role=student_role)
|
|
||||||
students.append(student)
|
students.append(student)
|
||||||
|
|
||||||
SchoolClassFactory(
|
SchoolClassFactory(
|
||||||
users=[teacher] + students,
|
users=[teacher] + students,
|
||||||
name='skillbox'
|
name='skillbox',
|
||||||
)
|
)
|
||||||
teacher2 = UserFactory(username='teacher2')
|
teacher2 = UserFactory(username='teacher2')
|
||||||
UserRole.objects.create(user=teacher2, role=teacher_role)
|
UserRole.objects.create(user=teacher2, role=teacher_role)
|
||||||
student_second_class = UserFactory(username='student_second_class')
|
|
||||||
UserRole.objects.create(user=student_second_class, role=student_role)
|
student_second_class = create_student(username='student_second_class')
|
||||||
SchoolClassFactory(
|
SchoolClassFactory(
|
||||||
users=[teacher2, student_second_class],
|
users=[teacher2, student_second_class],
|
||||||
name='second_class'
|
name='second_class'
|
||||||
|
|
@ -45,16 +56,16 @@ def create_users(data=None):
|
||||||
students = []
|
students = []
|
||||||
|
|
||||||
for first, last in school_class.get('students'):
|
for first, last in school_class.get('students'):
|
||||||
student = UserFactory(
|
student = create_student(
|
||||||
username='{}.{}'.format(first, last).lower(),
|
username='{}.{}'.format(first, last).lower(),
|
||||||
first_name=first,
|
first_name=first,
|
||||||
last_name=last,
|
last_name=last,
|
||||||
email='{}.{}@skillbox.example'.format(first, last).lower()
|
email='{}.{}@skillbox.example'.format(first, last).lower()
|
||||||
)
|
)
|
||||||
UserRole.objects.create(user=student, role=student_role)
|
|
||||||
students.append(student)
|
students.append(student)
|
||||||
|
|
||||||
SchoolClassFactory(
|
SchoolClassFactory(
|
||||||
users=students + [teacher],
|
users=students + [teacher],
|
||||||
name=school_class.get('class'),
|
name=school_class.get('class'),
|
||||||
|
code=school_class.get('code', '')
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
from graphene import Context
|
||||||
|
from graphene.test import Client
|
||||||
|
|
||||||
|
from core.factories import UserFactory
|
||||||
|
from users.factories import SchoolClassFactory
|
||||||
|
from users.models import SchoolClass
|
||||||
|
from api.schema import schema
|
||||||
|
|
||||||
|
|
||||||
|
class JoinSchoolClassTest(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client(schema=schema)
|
||||||
|
self.user = UserFactory(username='ueli')
|
||||||
|
SchoolClassFactory(users=[self.user], name='Klasse 1A', code='XXXX')
|
||||||
|
SchoolClassFactory(name='Klasse 2B', code='YYYY')
|
||||||
|
self.mutation = """
|
||||||
|
mutation JoinClass($input: JoinClassInput!) {
|
||||||
|
joinClass(input: $input) {
|
||||||
|
success
|
||||||
|
schoolClass {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
self.variables = {
|
||||||
|
'input': {
|
||||||
|
'code': 'YYYY'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.context = Context(user=self.user)
|
||||||
|
|
||||||
|
def test_join_class(self):
|
||||||
|
self.assertEqual(self.user.school_classes.count(), 1)
|
||||||
|
|
||||||
|
executed = self.client.execute(self.mutation, variables=self.variables, context=self.context)
|
||||||
|
self.assertIsNone(executed.get('errors', None))
|
||||||
|
result = executed['data']['joinClass']
|
||||||
|
self.assertEqual(result['success'], True)
|
||||||
|
self.assertEqual(result['schoolClass']['name'], 'Klasse 2B')
|
||||||
|
self.assertEqual(self.user.school_classes.count(), 2)
|
||||||
|
|
||||||
|
def test_class_already_joined(self):
|
||||||
|
code = 'YYYY'
|
||||||
|
school_class = SchoolClass.objects.get(code=code)
|
||||||
|
school_class.users.add(self.user)
|
||||||
|
|
||||||
|
self.assertEqual(self.user.school_classes.count(), 2)
|
||||||
|
|
||||||
|
executed = self.client.execute(self.mutation, variables=self.variables, context=self.context)
|
||||||
|
self.assertIsNotNone(executed['errors'])
|
||||||
|
self.assertEqual(executed['errors'][0]['message'], '[CAJ] Schüler ist bereits in Klasse')
|
||||||
|
self.assertEqual(self.user.school_classes.count(), 2)
|
||||||
|
|
||||||
|
def test_code_not_valid(self):
|
||||||
|
code = '1234'
|
||||||
|
self.assertEqual(self.user.school_classes.count(), 1)
|
||||||
|
|
||||||
|
executed = self.client.execute(self.mutation, variables={
|
||||||
|
'input': {
|
||||||
|
'code': code
|
||||||
|
}
|
||||||
|
}, context=self.context)
|
||||||
|
self.assertIsNotNone(executed['errors'])
|
||||||
|
self.assertEqual(executed['errors'][0]['message'], '[CNV] Code ist nicht gültig')
|
||||||
|
self.assertEqual(self.user.school_classes.count(), 1)
|
||||||
Loading…
Reference in New Issue