Merged in feature/taskbase-spellcheck (pull request #45)

Feature/taskbase spellcheck

Approved-by: Christian Cueni <christian.cueni@iterativ.ch>
This commit is contained in:
Ramon Wenger 2020-02-06 14:16:40 +00:00 committed by Christian Cueni
commit 542c7c6d84
46 changed files with 16951 additions and 369 deletions

2
.gitignore vendored
View File

@ -29,7 +29,7 @@ node_modules
# Backend
staticfiles
schema.json
server/schema.json
# Wagtail assets
server/documents

View File

@ -13,8 +13,8 @@ coverage = "*"
[packages]
factory-boy = "==2.11.0"
wagtail_factories = {git = "https://github.com/mvantellingen/wagtail-factories.git",ref = "master"}
django = "==2.0.6"
wagtail_factories = "==2.0.0"
django = "<3"
whitenoise = "==4.0b4"
psycopg2 = "==2.7.3.2"
gunicorn = "==19.7.1"
@ -37,6 +37,5 @@ newrelic = "*"
sentry-sdk = "==0.7.2"
"django-sendgrid-v5" = "==0.8.0"
python-http-client = "==3.2.1"
coverage = "*"
graphql-relay = "*"
ipython = "*"
requests = "*"

405
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "725dd26226fe58559f67b30a09dec72086dc4681a69f2f2672f5bd87c9ac74b8"
"sha256": "e7807798e8b5c39d7c1bc57d61520f2c888da08d2b6061f07758e00b490fdbd6"
},
"pipfile-spec": 6,
"requires": {
@ -23,14 +23,6 @@
],
"version": "==7.0.0"
},
"appnope": {
"hashes": [
"sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0",
"sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"
],
"markers": "sys_platform == 'darwin'",
"version": "==0.1.0"
},
"backcall": {
"hashes": [
"sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4",
@ -56,25 +48,25 @@
},
"boto3": {
"hashes": [
"sha256:08d949fede71c14db8b9b638edaa13831d79daed84e2da27750629fd606bdb57",
"sha256:4d7c2cc266917cd0ff7e5e039158de80991e21696a2e8bf85201de2d06d7ceea"
"sha256:27e221d3868f35687807e5c920f7e8d4872f722f64196a7fd274a06ad65beec0",
"sha256:8ff4e3d9e5d6a26dd7494afc68dc96afe6b7bda88130cca84cd58702d888ed27"
],
"index": "pypi",
"version": "==1.10.9"
"version": "==1.11.10"
},
"botocore": {
"hashes": [
"sha256:61ad58e737e7a5d61308599606cd5a435cc3b97eb7fa693f99663c91bf1706d1",
"sha256:8cb038c110822681925a1f5d9005dc2bbc4259fff89d4abfaaf803a3489d0ee3"
"sha256:cf3144994191847e30ef76781af867009bdc233b3f1f4736615e5330687a891e",
"sha256:f11ff8616f46ca04697df031e622c9ed50931b9d649d4e719f961e9d80771e8d"
],
"version": "==1.13.9"
"version": "==1.14.10"
},
"certifi": {
"hashes": [
"sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50",
"sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef"
"sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3",
"sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"
],
"version": "==2019.9.11"
"version": "==2019.11.28"
},
"chardet": {
"hashes": [
@ -90,44 +82,6 @@
],
"version": "==7.0"
},
"coverage": {
"hashes": [
"sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6",
"sha256:0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650",
"sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5",
"sha256:19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d",
"sha256:23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351",
"sha256:245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755",
"sha256:331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef",
"sha256:386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca",
"sha256:3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca",
"sha256:60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9",
"sha256:63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc",
"sha256:6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5",
"sha256:6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f",
"sha256:7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe",
"sha256:826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888",
"sha256:93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5",
"sha256:9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce",
"sha256:af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5",
"sha256:bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e",
"sha256:bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e",
"sha256:c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9",
"sha256:dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437",
"sha256:df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1",
"sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c",
"sha256:e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24",
"sha256:e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47",
"sha256:eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2",
"sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28",
"sha256:ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c",
"sha256:efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7",
"sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0",
"sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"
],
"index": "pypi",
"version": "==4.5.4"
},
"decorator": {
"hashes": [
"sha256:54c38050039232e1db4ad7375cfce6748d7b41c29e95a081c8a6d2c30364a2ce",
@ -144,11 +98,11 @@
},
"django": {
"hashes": [
"sha256:3eb25c99df1523446ec2dc1b00e25eb2ecbdf42c9d8b0b8b32a204a8db9011f8",
"sha256:69ff89fa3c3a8337015478a1a0744f52a9fef5d12c1efa01a01f99bcce9bf10c"
"sha256:48522428f4a285cf265af969f4744c5ebb027c7f41958ba48b639ace2068ffe7",
"sha256:a794f7a2f4b7c928eecfbc4ebad03712ff27fb545abe269bf01aa8500781eb1c"
],
"index": "pypi",
"version": "==2.0.6"
"version": "==2.1.15"
},
"django-appconf": {
"hashes": [
@ -159,11 +113,11 @@
},
"django-compressor": {
"hashes": [
"sha256:47c86347f75c64954a06afbbfc820a750619e10c23a49272b865020a407b7edd",
"sha256:da9ee5ce4fc8b9211dcecd2229520514a4ba9ac3bcdc59b48092ec4d7f6b96b0"
"sha256:57ac0a696d061e5fc6fbc55381d2050f353b973fb97eee5593f39247bc0f30af",
"sha256:d2ed1c6137ddaac5536233ec0a819e14009553fee0a869bea65d03e5285ba74f"
],
"index": "pypi",
"version": "==2.3"
"version": "==2.4"
},
"django-cors-headers": {
"hashes": [
@ -191,17 +145,18 @@
},
"django-libsass": {
"hashes": [
"sha256:49db3334b87e1f7955c4f9fb9945bc296f8bfd27a14d6d89706e4b0e5dc5de1c"
"sha256:38fab4ce1245542f3afd7248dc48f8a0b261f5f6c61e7cc43969a9c9079b5ffd",
"sha256:3e74fd8e75ac0e6ebc0443efc3e530167981bf279fecc2294094c820ae266fbb"
],
"index": "pypi",
"version": "==0.7"
"version": "==0.8"
},
"django-modelcluster": {
"hashes": [
"sha256:59672386191d38c69d6f994c3f9aaab3ff93910ec71e78a0bb6aa9abd4d1b90a",
"sha256:eea507f2dd8bd283600c1cfa4de6776501a28f199089bb00613c1621505e80cc"
"sha256:09483ff1ede3cd87b56b0e6f732d33334c843adc6506dfed26c02998222751fe",
"sha256:de1b5cd348fd4929491ef2a9ba29d9b5a3fccd3bf6a15218fa1aa5be49d06070"
],
"version": "==4.4"
"version": "==4.4.1"
},
"django-sendgrid-v5": {
"hashes": [
@ -213,11 +168,11 @@
},
"django-storages": {
"hashes": [
"sha256:87287b7ad2e789cd603373439994e1ac6f94d9dc2e5f8173d2a87aa3ed458bd9",
"sha256:f3b3def96493d3ccde37b864cea376472baf6e8a596504b209278801c510b807"
"sha256:3103991c2ee8cef8a2ff096709973ffe7106183d211a79f22cf855f33533d924",
"sha256:a59e9923cbce7068792f75344ed7727021ee4ac20f227cf17297d0d03d141e91"
],
"index": "pypi",
"version": "==1.7.2"
"version": "==1.9.1"
},
"django-taggit": {
"hashes": [
@ -228,9 +183,9 @@
},
"django-treebeard": {
"hashes": [
"sha256:c21db06a8d4943bf2a28d9d7a119058698fb76116df2679ecbf15a46a501de42"
"sha256:83aebc34a9f06de7daaec330d858d1c47887e81be3da77e3541fe7368196dd8a"
],
"version": "==4.3"
"version": "==4.3.1"
},
"djangorestframework": {
"hashes": [
@ -264,10 +219,10 @@
},
"faker": {
"hashes": [
"sha256:5902379d8df308a204fc11c4f621590ee83975805a6c7b2228203b9defa45250",
"sha256:5e8c755c619f332d5ec28b7586389665f136bcf528e165eb925e87c06a63eda7"
"sha256:047d4d1791bfb3756264da670d99df13d799bb36e7d88774b1585a82d05dbaec",
"sha256:1b1a58961683b30c574520d0c739c4443e0ef6a185c04382e8cc888273dbebed"
],
"version": "==2.0.3"
"version": "==4.0.0"
},
"future": {
"hashes": [
@ -292,19 +247,17 @@
},
"graphql-core": {
"hashes": [
"sha256:1488f2a5c2272dc9ba66e3042a6d1c30cea0db4c80bd1e911c6791ad6187d91b",
"sha256:da64c472d720da4537a2e8de8ba859210b62841bd47a9be65ca35177f62fe0e4"
"sha256:74a8f509ae8c4a58271f5d6b46d5c75b4aed116821ab62dea252d8041bfe057a",
"sha256:c06e59153246dd48ddbf86ca94a8f045c1b71daf8154b4635e1a0e2e11d9b60b"
],
"version": "==2.2.1"
"version": "==2.3.1"
},
"graphql-relay": {
"hashes": [
"sha256:0e94201af4089e1f81f07d7bd8f84799768e39d70fa1ea16d1df505b46cc6335",
"sha256:75aa0758971e252964cb94068a4decd472d2a8295229f02189e3cbca1f10dbb5",
"sha256:7fa74661246e826ef939ee92e768f698df167a7617361ab399901eaebf80dce6"
"sha256:870b6b5304123a38a0b215a79eace021acce5a466bf40cd39fa18cb8528afabb",
"sha256:ac514cb86db9a43014d7e73511d521137ac12cf0101b2eaa5f0a3da2e10d913d"
],
"index": "pypi",
"version": "==2.0.0"
"version": "==2.0.1"
},
"gunicorn": {
"hashes": [
@ -330,11 +283,11 @@
},
"ipython": {
"hashes": [
"sha256:dfd303b270b7b5232b3d08bd30ec6fd685d8a58cabd54055e3d69d8f029f7280",
"sha256:ed7ebe1cba899c1c3ccad6f7f1c2d2369464cc77dba8eebc65e2043e19cda995"
"sha256:d9459e7237e2e5858738ff9c3e26504b79899b58a6d49e574d352493d80684c6",
"sha256:f6689108b1734501d3b59c84427259fd5ac5141afe2e846cfa8598eb811886c9"
],
"index": "pypi",
"version": "==7.9.0"
"version": "==7.12.0"
},
"ipython-genutils": {
"hashes": [
@ -345,10 +298,10 @@
},
"jedi": {
"hashes": [
"sha256:786b6c3d80e2f06fd77162a07fed81b8baa22dde5d62896a790a331d6ac21a27",
"sha256:ba859c74fa3c966a22f2aeebe1b74ee27e2a462f56d3f5f7ca4a59af61bfe42e"
"sha256:b4f4052551025c6b0b0b193b29a6ff7bdb74c52450631206c262aef9f7159ad2",
"sha256:d5c871cb9360b414f981e7072c52c33258d598305280fef91c6cae34739d65d5"
],
"version": "==0.15.1"
"version": "==0.16.0"
},
"jmespath": {
"hashes": [
@ -380,25 +333,25 @@
},
"newrelic": {
"hashes": [
"sha256:da9adab674d9fe7aa8fbabb6691b33fb7be94a32b6a80548ec7018be9df8ef65"
"sha256:8283dd54299b3fd2818a262f38f9193a2ee52b2a02fecca1a1d04764be533c92"
],
"index": "pypi",
"version": "==5.2.1.129"
"version": "==5.6.0.135"
},
"parso": {
"hashes": [
"sha256:63854233e1fadb5da97f2744b6b24346d2750b85965e7e399bec1620232797dc",
"sha256:666b0ee4a7a1220f65d367617f2cd3ffddff3e205f3f16a0284df30e774c2a9c"
"sha256:56b2105a80e9c4df49de85e125feb6be69f49920e121406f15e7acde6c9dfc57",
"sha256:951af01f61e6dccd04159042a0706a31ad437864ec6e25d0d7a96a9fbb9b0095"
],
"version": "==0.5.1"
"version": "==0.6.1"
},
"pexpect": {
"hashes": [
"sha256:2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1",
"sha256:9e2c1fd0e6ee3a49b28f95d4b33bc389c89b20af6a1255906e90ff1262ce62eb"
"sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937",
"sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"
],
"markers": "sys_platform != 'win32'",
"version": "==4.7.0"
"version": "==4.8.0"
},
"pickleshare": {
"hashes": [
@ -440,18 +393,16 @@
},
"promise": {
"hashes": [
"sha256:2ebbfc10b7abf6354403ed785fe4f04b9dfd421eb1a474ac8d187022228332af",
"sha256:348f5f6c3edd4fd47c9cd65aed03ac1b31136d375aa63871a57d3e444c85655c"
"sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0"
],
"version": "==2.2.1"
"version": "==2.3"
},
"prompt-toolkit": {
"hashes": [
"sha256:46642344ce457641f28fc9d1c9ca939b63dadf8df128b86f1b9860e59c73a5e4",
"sha256:e7f8af9e3d70f514373bf41aa51bc33af12a6db3f71461ea47fea985defb2c31",
"sha256:f15af68f66e664eaa559d4ac8a928111eebd5feda0c11738b5998045224829db"
"sha256:a402e9bf468b63314e37460b68ba68243d55b2f8c4d0192f85a019af3945050e",
"sha256:c93e53af97f630f12f5f62a3274e79527936ed466f038953dfa379d4941f651a"
],
"version": "==2.0.10"
"version": "==3.0.3"
},
"psycopg2": {
"hashes": [
@ -499,18 +450,17 @@
},
"pygments": {
"hashes": [
"sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127",
"sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"
"sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b",
"sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"
],
"version": "==2.4.2"
"version": "==2.5.2"
},
"python-dateutil": {
"hashes": [
"sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb",
"sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
],
"markers": "python_version >= '2.7'",
"version": "==2.8.0"
"version": "==2.8.1"
},
"python-dotenv": {
"hashes": [
@ -555,6 +505,7 @@
"sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
"sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
],
"index": "pypi",
"version": "==2.22.0"
},
"rjsmin": {
@ -584,18 +535,17 @@
},
"s3transfer": {
"hashes": [
"sha256:6efc926738a3cd576c2a79725fed9afde92378aa5c6a957e3af010cb019fac9d",
"sha256:b780f2411b824cb541dbcd2c713d0cb61c7d1bcadae204cdddda2b35cef493ba"
"sha256:2525bae2a530195576da53671bae8ca8c55ee8e33bc2225a65e804476611ea5a",
"sha256:4924e10451cc37901945806423d16c2c2040a6530645a614ed87e995ccec764c"
],
"version": "==0.2.1"
"version": "==0.3.2"
},
"sendgrid": {
"hashes": [
"sha256:a9999878aad90e32d7b62464454adc70bcef40085c729355ea58717bb0ea0dbd",
"sha256:cb0b21a83a54bc99d9befda1ea7b4f15fe8db362a152458e58abc5ce23d6d828",
"sha256:f04fee009c750b47ab984f3c4a735facacc7fba902052d597f7e60b601e56bcc"
"sha256:12ea8203c48c47b03dbe33e219408ca32c9259526d05b783418482d40f17943c",
"sha256:5ad98598c13b8cf5545d20f6fb6ecf967a08b48e3e175affabd82cfc71522d01"
],
"version": "==6.1.0"
"version": "==6.1.1"
},
"sentry-sdk": {
"hashes": [
@ -614,10 +564,10 @@
},
"six": {
"hashes": [
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
],
"version": "==1.12.0"
"version": "==1.14.0"
},
"text-unidecode": {
"hashes": [
@ -650,11 +600,10 @@
},
"urllib3": {
"hashes": [
"sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398",
"sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86"
"sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
"sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
],
"markers": "python_version >= '3.4'",
"version": "==1.25.6"
"version": "==1.25.8"
},
"wagtail": {
"hashes": [
@ -665,15 +614,19 @@
"version": "==2.4"
},
"wagtail-factories": {
"git": "https://github.com/mvantellingen/wagtail-factories.git",
"ref": "819666151f8c83317bdd0019074173ba2ade85eb"
"hashes": [
"sha256:2198e791854ef3883812f532a4d1b364aae1c68abc58a48b8ecec8c61e4a151f",
"sha256:416196b88dcdb2d2787926692d7eef7fa0664556900eb6166537026b085d9fbf"
],
"index": "pypi",
"version": "==2.0.0"
},
"wcwidth": {
"hashes": [
"sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e",
"sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"
"sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603",
"sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8"
],
"version": "==0.1.7"
"version": "==0.1.8"
},
"webencodings": {
"hashes": [
@ -699,21 +652,13 @@
}
},
"develop": {
"appnope": {
"hashes": [
"sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0",
"sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"
],
"markers": "sys_platform == 'darwin'",
"version": "==0.1.0"
},
"awscli": {
"hashes": [
"sha256:4a75cb44dc3dab14bc9bdb0d2731c37a5026bf0afa4acb620fec72c74e62915a",
"sha256:90b0b3e91a900e4569bc47f29769522337c46ff50e35f9e4a41830fdb425f000"
"sha256:4c49f085fb827ca1aeba5e6e5e39f6005110a0059b5c772aeb1d51c4f33c4028",
"sha256:9459ac705c2a5d8724057492800c52084df714b624853eb3331087ecf8726a22"
],
"index": "pypi",
"version": "==1.16.273"
"version": "==1.17.9"
},
"backcall": {
"hashes": [
@ -724,56 +669,54 @@
},
"botocore": {
"hashes": [
"sha256:61ad58e737e7a5d61308599606cd5a435cc3b97eb7fa693f99663c91bf1706d1",
"sha256:8cb038c110822681925a1f5d9005dc2bbc4259fff89d4abfaaf803a3489d0ee3"
"sha256:cf3144994191847e30ef76781af867009bdc233b3f1f4736615e5330687a891e",
"sha256:f11ff8616f46ca04697df031e622c9ed50931b9d649d4e719f961e9d80771e8d"
],
"version": "==1.13.9"
"version": "==1.14.10"
},
"colorama": {
"hashes": [
"sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d",
"sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"
],
"markers": "python_version != '2.6' and python_version != '3.3'",
"version": "==0.4.1"
},
"coverage": {
"hashes": [
"sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6",
"sha256:0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650",
"sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5",
"sha256:19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d",
"sha256:23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351",
"sha256:245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755",
"sha256:331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef",
"sha256:386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca",
"sha256:3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca",
"sha256:60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9",
"sha256:63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc",
"sha256:6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5",
"sha256:6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f",
"sha256:7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe",
"sha256:826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888",
"sha256:93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5",
"sha256:9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce",
"sha256:af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5",
"sha256:bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e",
"sha256:bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e",
"sha256:c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9",
"sha256:dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437",
"sha256:df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1",
"sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c",
"sha256:e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24",
"sha256:e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47",
"sha256:eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2",
"sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28",
"sha256:ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c",
"sha256:efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7",
"sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0",
"sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"
"sha256:15cf13a6896048d6d947bf7d222f36e4809ab926894beb748fc9caa14605d9c3",
"sha256:1daa3eceed220f9fdb80d5ff950dd95112cd27f70d004c7918ca6dfc6c47054c",
"sha256:1e44a022500d944d42f94df76727ba3fc0a5c0b672c358b61067abb88caee7a0",
"sha256:25dbf1110d70bab68a74b4b9d74f30e99b177cde3388e07cc7272f2168bd1477",
"sha256:3230d1003eec018ad4a472d254991e34241e0bbd513e97a29727c7c2f637bd2a",
"sha256:3dbb72eaeea5763676a1a1efd9b427a048c97c39ed92e13336e726117d0b72bf",
"sha256:5012d3b8d5a500834783689a5d2292fe06ec75dc86ee1ccdad04b6f5bf231691",
"sha256:51bc7710b13a2ae0c726f69756cf7ffd4362f4ac36546e243136187cfcc8aa73",
"sha256:527b4f316e6bf7755082a783726da20671a0cc388b786a64417780b90565b987",
"sha256:722e4557c8039aad9592c6a4213db75da08c2cd9945320220634f637251c3894",
"sha256:76e2057e8ffba5472fd28a3a010431fd9e928885ff480cb278877c6e9943cc2e",
"sha256:77afca04240c40450c331fa796b3eab6f1e15c5ecf8bf2b8bee9706cd5452fef",
"sha256:7afad9835e7a651d3551eab18cbc0fdb888f0a6136169fbef0662d9cdc9987cf",
"sha256:9bea19ac2f08672636350f203db89382121c9c2ade85d945953ef3c8cf9d2a68",
"sha256:a8b8ac7876bc3598e43e2603f772d2353d9931709345ad6c1149009fd1bc81b8",
"sha256:b0840b45187699affd4c6588286d429cd79a99d509fe3de0f209594669bb0954",
"sha256:b26aaf69713e5674efbde4d728fb7124e429c9466aeaf5f4a7e9e699b12c9fe2",
"sha256:b63dd43f455ba878e5e9f80ba4f748c0a2156dde6e0e6e690310e24d6e8caf40",
"sha256:be18f4ae5a9e46edae3f329de2191747966a34a3d93046dbdf897319923923bc",
"sha256:c312e57847db2526bc92b9bfa78266bfbaabac3fdcd751df4d062cd4c23e46dc",
"sha256:c60097190fe9dc2b329a0eb03393e2e0829156a589bd732e70794c0dd804258e",
"sha256:c62a2143e1313944bf4a5ab34fd3b4be15367a02e9478b0ce800cb510e3bbb9d",
"sha256:cc1109f54a14d940b8512ee9f1c3975c181bbb200306c6d8b87d93376538782f",
"sha256:cd60f507c125ac0ad83f05803063bed27e50fa903b9c2cfee3f8a6867ca600fc",
"sha256:d513cc3db248e566e07a0da99c230aca3556d9b09ed02f420664e2da97eac301",
"sha256:d649dc0bcace6fcdb446ae02b98798a856593b19b637c1b9af8edadf2b150bea",
"sha256:d7008a6796095a79544f4da1ee49418901961c97ca9e9d44904205ff7d6aa8cb",
"sha256:da93027835164b8223e8e5af2cf902a4c80ed93cb0909417234f4a9df3bcd9af",
"sha256:e69215621707119c6baf99bda014a45b999d37602cb7043d943c76a59b05bf52",
"sha256:ea9525e0fef2de9208250d6c5aeeee0138921057cd67fcef90fbed49c4d62d37",
"sha256:fca1669d464f0c9831fd10be2eef6b86f5ebd76c724d1e0706ebdff86bb4adf0"
],
"index": "pypi",
"version": "==4.5.4"
"version": "==5.0.3"
},
"decorator": {
"hashes": [
@ -792,18 +735,18 @@
},
"ipdb": {
"hashes": [
"sha256:473fdd798a099765f093231a8b1fabfa95b0b682fce12de0c74b61a4b4d8ee57"
"sha256:5d9a4a0e3b7027a158fc6f2929934341045b9c3b0b86ed5d7e84e409653f72fd"
],
"index": "pypi",
"version": "==0.12.2"
"version": "==0.12.3"
},
"ipython": {
"hashes": [
"sha256:dfd303b270b7b5232b3d08bd30ec6fd685d8a58cabd54055e3d69d8f029f7280",
"sha256:ed7ebe1cba899c1c3ccad6f7f1c2d2369464cc77dba8eebc65e2043e19cda995"
"sha256:d9459e7237e2e5858738ff9c3e26504b79899b58a6d49e574d352493d80684c6",
"sha256:f6689108b1734501d3b59c84427259fd5ac5141afe2e846cfa8598eb811886c9"
],
"index": "pypi",
"version": "==7.9.0"
"version": "==7.12.0"
},
"ipython-genutils": {
"hashes": [
@ -814,10 +757,10 @@
},
"jedi": {
"hashes": [
"sha256:786b6c3d80e2f06fd77162a07fed81b8baa22dde5d62896a790a331d6ac21a27",
"sha256:ba859c74fa3c966a22f2aeebe1b74ee27e2a462f56d3f5f7ca4a59af61bfe42e"
"sha256:b4f4052551025c6b0b0b193b29a6ff7bdb74c52450631206c262aef9f7159ad2",
"sha256:d5c871cb9360b414f981e7072c52c33258d598305280fef91c6cae34739d65d5"
],
"version": "==0.15.1"
"version": "==0.16.0"
},
"jmespath": {
"hashes": [
@ -828,18 +771,18 @@
},
"parso": {
"hashes": [
"sha256:63854233e1fadb5da97f2744b6b24346d2750b85965e7e399bec1620232797dc",
"sha256:666b0ee4a7a1220f65d367617f2cd3ffddff3e205f3f16a0284df30e774c2a9c"
"sha256:56b2105a80e9c4df49de85e125feb6be69f49920e121406f15e7acde6c9dfc57",
"sha256:951af01f61e6dccd04159042a0706a31ad437864ec6e25d0d7a96a9fbb9b0095"
],
"version": "==0.5.1"
"version": "==0.6.1"
},
"pexpect": {
"hashes": [
"sha256:2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1",
"sha256:9e2c1fd0e6ee3a49b28f95d4b33bc389c89b20af6a1255906e90ff1262ce62eb"
"sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937",
"sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"
],
"markers": "sys_platform != 'win32'",
"version": "==4.7.0"
"version": "==4.8.0"
},
"pickleshare": {
"hashes": [
@ -850,11 +793,10 @@
},
"prompt-toolkit": {
"hashes": [
"sha256:46642344ce457641f28fc9d1c9ca939b63dadf8df128b86f1b9860e59c73a5e4",
"sha256:e7f8af9e3d70f514373bf41aa51bc33af12a6db3f71461ea47fea985defb2c31",
"sha256:f15af68f66e664eaa559d4ac8a928111eebd5feda0c11738b5998045224829db"
"sha256:a402e9bf468b63314e37460b68ba68243d55b2f8c4d0192f85a019af3945050e",
"sha256:c93e53af97f630f12f5f62a3274e79527936ed466f038953dfa379d4941f651a"
],
"version": "==2.0.10"
"version": "==3.0.3"
},
"ptyprocess": {
"hashes": [
@ -865,44 +807,40 @@
},
"pyasn1": {
"hashes": [
"sha256:62cdade8b5530f0b185e09855dd422bc05c0bbff6b72ff61381c09dac7befd8c",
"sha256:a9495356ca1d66ed197a0f72b41eb1823cf7ea8b5bd07191673e8147aecf8604"
"sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"
],
"version": "==0.4.7"
"version": "==0.4.8"
},
"pygments": {
"hashes": [
"sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127",
"sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"
"sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b",
"sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"
],
"version": "==2.4.2"
"version": "==2.5.2"
},
"python-dateutil": {
"hashes": [
"sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb",
"sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
],
"markers": "python_version >= '2.7'",
"version": "==2.8.0"
"version": "==2.8.1"
},
"pyyaml": {
"hashes": [
"sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9",
"sha256:01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4",
"sha256:5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8",
"sha256:5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696",
"sha256:7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34",
"sha256:7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9",
"sha256:87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73",
"sha256:9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299",
"sha256:a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b",
"sha256:b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae",
"sha256:b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681",
"sha256:bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41",
"sha256:f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"
"sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc",
"sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803",
"sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc",
"sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15",
"sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075",
"sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd",
"sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31",
"sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f",
"sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c",
"sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04",
"sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4"
],
"markers": "python_version != '2.6' and python_version != '3.3'",
"version": "==5.1.2"
"version": "==5.2"
},
"rsa": {
"hashes": [
@ -913,17 +851,17 @@
},
"s3transfer": {
"hashes": [
"sha256:6efc926738a3cd576c2a79725fed9afde92378aa5c6a957e3af010cb019fac9d",
"sha256:b780f2411b824cb541dbcd2c713d0cb61c7d1bcadae204cdddda2b35cef493ba"
"sha256:2525bae2a530195576da53671bae8ca8c55ee8e33bc2225a65e804476611ea5a",
"sha256:4924e10451cc37901945806423d16c2c2040a6530645a614ed87e995ccec764c"
],
"version": "==0.2.1"
"version": "==0.3.2"
},
"six": {
"hashes": [
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
],
"version": "==1.12.0"
"version": "==1.14.0"
},
"traitlets": {
"hashes": [
@ -934,18 +872,17 @@
},
"urllib3": {
"hashes": [
"sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398",
"sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86"
"sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
"sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
],
"markers": "python_version >= '3.4'",
"version": "==1.25.6"
"version": "==1.25.8"
},
"wcwidth": {
"hashes": [
"sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e",
"sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"
"sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603",
"sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8"
],
"version": "==0.1.7"
"version": "==0.1.8"
}
}
}

View File

@ -175,3 +175,23 @@ You can and should then push the tag to the repo
You can later see the metadata of the tag with
git show release/v2019-09-10
## Testing
## Mocking GraphQL calls
We use [cypress-graphql-mock](https://github.com/tgriesser/cypress-graphql-mock) for mocking GraphQL calls in Cypress tests.
For an example, please see `spellcheck.spec.js`.
There is a schema.json in the fixtures folder. For now it has been generated once, and if there is a significant update to the schema on the server, it has to be regenerated.
To generate a new schema, use the management command
```
python manage.py graphql_schema --schema api.schema.schema --out schema.json --indent 4
```
Then, remove the `data` property from the generated `schema.json`, so the `__schema` property is on the top level.
Also remove the two objects with `"name": "__debug"` from the JSON file.

View File

@ -0,0 +1,34 @@
{
"assignments": {
"edges": [
{
"node": {
"id": "QXNzaWdubWVudE5vZGU6MQ==",
"title": "Ein Auftragstitel",
"assignment": "Ein Auftrag",
"solution": null,
"submission": {
"id": "U3R1ZGVudFN1Ym1pc3Npb25Ob2RlOjE=",
"text": "Hir ist ein Feler gewesen",
"final": false,
"document": "",
"submissionFeedback": {
"id": "U3VibWlzc2lvbkZlZWRiYWNrTm9kZTox",
"text": "\ud83d\ude42\ud83d\ude10\ud83e\udd2c\ud83d\udc4d\ud83e\udd22\ud83e\udd22\ud83e\udd22\ud83e\udd22\ud83d\ude2e\ud83e\udd17",
"teacher": {
"firstName": "Nico",
"lastName": "Zickgraf",
"__typename": "UserNode"
},
"__typename": "SubmissionFeedbackNode"
},
"__typename": "StudentSubmissionNode"
},
"__typename": "AssignmentNode"
},
"__typename": "AssignmentNodeEdge"
}
],
"__typename": "AssignmentNodeConnection"
}
}

View File

@ -0,0 +1,82 @@
{
"id": "TW9kdWxlTm9kZTozMQ==",
"title": "Geld",
"metaTitle": "Modul 2",
"teaser": " Geld braucht jeder von uns im t\u00e4glichen Leben.",
"intro": "\n <p>Jeder B\u00fcrger nutzt es. Nahezu jeden Tag. Kaum ein Tag vergeht, an dem wir nicht mit M\u00fcnzen oder Geldscheinen bezahlen, bargeldlose \u00dcberweisungen t\u00e4tigen oder andere Zahlungsmethoden verwenden. Doch was genau befindet sich da eigentlich in unserem Geldbeutel? Was ist das, was auf unseren Konten liegt und die Bezeichnung Geld tr\u00e4gt?</p>\n ",
"slug": "geld",
"heroImage": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==",
"solutionsEnabled": false,
"bookmark": null,
"__typename": "ModuleNode",
"assignments": {
"edges": [],
"__typename": "AssignmentNodeConnection"
},
"objectiveGroups": {
"edges": [],
"__typename": "ObjectiveGroupNodeConnection"
},
"chapters": {
"edges": [
{
"node": {
"id": "Q2hhcHRlck5vZGU6MzI=",
"title": "2.1 Eine Welt ohne Geld?",
"description": "",
"bookmark": null,
"contentBlocks": {
"edges": [
{
"node": {
"id": "Q29udGVudEJsb2NrTm9kZToxOQ==",
"slug": "assignment",
"title": "Assignment",
"type": "NORMAL",
"contents": [
{
"type": "assignment",
"value": {
"title": "Ein Auftragstitel",
"assignment": "Ein Auftrag",
"id": "QXNzaWdubWVudE5vZGU6MQ=="
},
"id": "df8212ee-3e82-49fa-977e-c4b60789163e"
}
],
"userCreated": false,
"mine": false,
"bookmarks": [
{
"uuid": "df8212ee-3e82-49fa-977e-c4b60789163e",
"note": {
"id": "Tm90ZU5vZGU6Mw==",
"text": "Noch eine Notiz",
"__typename": "NoteNode"
},
"__typename": "ContentBlockBookmarkNode"
}
],
"hiddenFor": {
"edges": [],
"__typename": "SchoolClassNodeConnection"
},
"visibleFor": {
"edges": [],
"__typename": "SchoolClassNodeConnection"
},
"__typename": "ContentBlockNode"
},
"__typename": "ContentBlockNodeEdge"
}
],
"__typename": "ContentBlockNodeConnection"
},
"__typename": "ChapterNode"
},
"__typename": "ChapterNodeEdge"
}
],
"__typename": "ChapterNodeConnection"
}
}

View File

@ -0,0 +1,120 @@
{
"id": "TW9kdWxlTm9kZToxNw==",
"title": "Lohn und Budget",
"metaTitle": "Modul 1",
"teaser": "Die Berufsbildung ist ein neuer Lebensabschnit",
"intro": "\n <p>Sie stehen am Anfang eines neuen Lebensabschnitts. In Ihrer Rolle als Berufslernende oder Berufslernender haben Sie Verantwortung übernommen.</p>\n <p>Wie erging es Ihnen am ersten Arbeits- und Schultag?</p>\n ",
"slug": "lohn-und-budget",
"heroImage": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==",
"solutionsEnabled": false,
"bookmark": {
"note": null,
"__typename": "ModuleBookmarkNode"
},
"__typename": "ModuleNode",
"assignments": {
"edges": [
{
"node": {
"id": "QXNzaWdubWVudE5vZGU6MQ==",
"title": "Ein Auftragstitel",
"assignment": "Ein Auftrag",
"solution": null,
"submission": {
"id": "U3R1ZGVudFN1Ym1pc3Npb25Ob2RlOjE=",
"text": "Hir ist ein Feler gewesen",
"final": false,
"document": "",
"submissionFeedback": {
"id": "U3VibWlzc2lvbkZlZWRiYWNrTm9kZTox",
"text": "🙂😐🤬👍🤢🤢🤢🤢😮🤗",
"teacher": {
"firstName": "Nico",
"lastName": "Zickgraf",
"__typename": "UserNode"
},
"__typename": "SubmissionFeedbackNode"
},
"__typename": "StudentSubmissionNode"
},
"__typename": "AssignmentNode"
},
"__typename": "AssignmentNodeEdge"
}
],
"__typename": "AssignmentNodeConnection"
},
"objectiveGroups": {
"edges": [],
"__typename": "ObjectiveGroupNodeConnection"
},
"chapters": {
"edges": [
{
"node": {
"id": "Q2hhcHRlck5vZGU6MTg=",
"title": "1.1 Lehrbeginn",
"description": "Wie sieht Ihr Konsumverhalten aus?",
"bookmark": {
"note": {
"id": "Tm90ZU5vZGU6Mg==",
"text": "Chapter Chapter",
"__typename": "NoteNode"
},
"__typename": "ChapterBookmarkNode"
},
"contentBlocks": {
"edges": [
{
"node": {
"id": "Q29udGVudEJsb2NrTm9kZToxOQ==",
"slug": "assignment",
"title": "Assignment",
"type": "NORMAL",
"contents": [
{
"type": "assignment",
"value": {
"title": "Ein Auftragstitel",
"assignment": "Ein Auftrag",
"id": "QXNzaWdubWVudE5vZGU6MQ=="
},
"id": "df8212ee-3e82-49fa-977e-c4b60789163e"
}
],
"userCreated": false,
"mine": false,
"bookmarks": [
{
"uuid": "df8212ee-3e82-49fa-977e-c4b60789163e",
"note": {
"id": "Tm90ZU5vZGU6Mw==",
"text": "Noch eine Notiz",
"__typename": "NoteNode"
},
"__typename": "ContentBlockBookmarkNode"
}
],
"hiddenFor": {
"edges": [],
"__typename": "SchoolClassNodeConnection"
},
"visibleFor": {
"edges": [],
"__typename": "SchoolClassNodeConnection"
},
"__typename": "ContentBlockNode"
},
"__typename": "ContentBlockNodeEdge"
}
],
"__typename": "ContentBlockNodeConnection"
},
"__typename": "ChapterNode"
},
"__typename": "ChapterNodeEdge"
}
],
"__typename": "ChapterNodeConnection"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
{
"correct": false,
"results": [
{
"sentence": "Hir ist ein Feler gewesen",
"offset": 0,
"length": 3,
"sentenceOffset": 0,
"affected": "Hir",
"corrected": "Dir",
"__typename": "SpellCheckStepNode"
},
{
"sentence": "Hir ist ein Feler gewesen",
"offset": 12,
"length": 5,
"sentenceOffset": 0,
"affected": "Feler",
"corrected": "Fehler",
"__typename": "SpellCheckStepNode"
},
{
"sentence": "Hir ist ein Feler gewesen",
"offset": 18,
"length": 7,
"sentenceOffset": 0,
"affected": "gewesen",
"corrected": "gewesen.",
"__typename": "SpellCheckStepNode"
}
],
"__typename": "SpellCheckPayload"
}

View File

@ -1,5 +1,6 @@
describe('Survey', () => {
beforeEach(() => {
// todo: mock all the graphql queries and mutations
cy.exec("python ../server/manage.py prepare_bookmarks_for_cypress");
cy.viewport('macbook-15');

View File

@ -7,6 +7,7 @@ describe('Change Password Page', () => {
const validationOldWrongMsg = 'Die Eingabe ist falsch';
beforeEach(function () {
// todo: mock all the graphql queries and mutations
cy.clearCookies();
cy.visit('/me/profile');
cy.login('rahel.cueni', 'test');

View File

@ -1,24 +1,82 @@
const schema = require('../fixtures/schema.json');
const assignments = require('../fixtures/assignments.json');
const lohnModule = require('../fixtures/module.json');
const geldModule = require('../fixtures/module-geld.json');
describe('Current Module', () => {
before(() => {
cy.server();
cy.mockGraphql({
schema: schema,
// endpoint: '/api/graphql'
operations: {
MeQuery: variables => {
return {
me: {
'lastModule': {
// 'id': 'TW9kdWxlTm9kZToxNw==',
'slug': 'lohn-und-budget',
'__typename': 'ModuleNode'
},
'__typename': 'UserNode',
'permissions': []
}
}
},
AssignmentsQuery: {
assignments
},
ModulesQuery: variables => {
let module;
if (variables.slug === 'lohn-und-budget') {
module = lohnModule;
} else {
module = geldModule
}
return {
module
}
},
UpdateLastModule: variables => {
let module;
if (variables.input.id === 'TW9kdWxlTm9kZToxNw==') {
module = lohnModule
} else {
module = geldModule
}
return {
updateLastModule: {
module,
errors: null,
__typename: 'UpdateLastModulePayload'
}
}
}
}
});
});
it('is set correctly', () => {
// cy.route('POST', '/api/graphql/').as('graphQL');
cy.startGraphQLCapture();
cy.viewport('macbook-15');
cy.visit('/module/lohn-und-budget');
cy.login('nico.zickgraf', 'test');
cy.apolloLogin('nico.zickgraf', 'test');
cy.visit('/book/topic/geld-und-kauf');
cy.contains('Modul 1').click();
cy.get('[data-cy=module-title]').should('contain', 'Lohn und Budget')
cy.get('[data-cy=module-title]').should('contain', 'Lohn und Budget');
cy.visit('/');
cy.waitFor('MeQuery');
cy.get('[data-cy="home-link"]').click();
cy.get('[data-cy="current-module-link"]').click();
cy.get('[data-cy=module-title]').should('contain', 'Lohn und Budget')
cy.get('[data-cy=module-title]').should('contain', 'Lohn und Budget');
cy.visit('/module/geld');
cy.get('[data-cy=module-title]').should('contain', 'Geld')
cy.visit('/book/topic/geld-und-kauf');
cy.contains('Modul 2').click();
cy.get('[data-cy=module-title]').should('contain', 'Geld');
cy.get('[data-cy="home-link"]').click();
cy.get('[data-cy="current-module-link"]').click();
cy.get('[data-cy=module-title]').should('contain', 'Geld')
})
})
});

View File

@ -1,5 +1,6 @@
describe('The Logged In Home Page', () => {
it('successfully loads', () => {
// todo: use graphql login
cy.visit('/');
cy.login('test', 'test');

View File

@ -1,8 +1,53 @@
const schema = require('../fixtures/schema.json');
describe('New project', () => {
it('creates a new project and displays it', () => {
cy.server();
cy.mockGraphql({
schema: schema,
operations: {
ProjectsQuery: {
projects: {
edges: [
{
node: {
id: 'UHJvamVjdE5vZGU6NjY=',
title: 'Some random title',
appearance: 'blue',
description: 'This description rocks',
slug: 'some-random-title',
objectives: 'Git gud',
final: false,
student: {
firstName: 'Rahel',
lastName: 'Cueni',
id: 'VXNlck5vZGU6NQ==',
avatarUrl: '',
__typename: 'UserNode'
},
entriesCount: 0,
__typename: 'ProjectNode',
},
__typename: 'ProjectNodeEdge'
}
]
}
},
AddProject: variables => ({
addProject: {
project: Object.assign({}, variables.input.project),
errors: null,
__typename: 'AddProjectPayload'
}
})
}
})
;
cy.viewport('macbook-15');
cy.apolloLogin('rahel.cueni', 'test');
cy.visit('/portfolio');
cy.login('rahel.cueni', 'test');
cy.get('[data-cy=add-project-button]').click();
cy.get('[data-cy=page-form-input-titel]').type('Some random title');

View File

@ -1,11 +1,102 @@
const schema = require('../fixtures/schema.json');
describe('Project Entry', () => {
beforeEach(() => {
cy.exec("python ../server/manage.py prepare_projects_for_cypress");
cy.viewport('macbook-15');
cy.startGraphQLCapture();
cy.login('rahel.cueni', 'test', true);
cy.get('body').contains('Neues Wissen erwerben');
cy.apolloLogin('rahel.cueni', 'test');
cy.mockGraphql({
schema: schema,
operations: {
MeQuery: {
me: {
id: 'VXNlck5vZGU6NQ==',
permissions: []
}
},
ProjectsQuery: {
projects: {
edges: [{
node: {
id: 'UHJvamVjdE5vZGU6MzM=',
title: 'Groot',
appearance: 'red',
'description': 'I am Groot',
'slug': 'groot',
'objectives': 'Be Groot\nBe awesome',
'final': false,
'student': {
'firstName': 'Rahel',
'lastName': 'Cueni',
'id': 'VXNlck5vZGU6NQ==',
'avatarUrl': '',
'__typename': 'UserNode'
},
'entriesCount': 2,
'__typename': 'ProjectNode'
},
'__typename': 'ProjectNodeEdge'
}],
'__typename': 'ProjectNodeConnection'
}
},
ProjectQuery: {
"project": {
"id": "UHJvamVjdE5vZGU6MzY=",
"title": "Groot",
"appearance": "yellow",
"description": "I am Groot",
"slug": "groot",
"objectives": "Be Groot\nBe awesome",
"final": false,
"student": {
"firstName": "Rahel",
"lastName": "Cueni",
"id": "VXNlck5vZGU6NQ==",
"avatarUrl": "",
"__typename": "UserNode"
},
"entriesCount": 1,
"__typename": "ProjectNode",
"entries": {
"edges": [{
"node": {
"id": "UHJvamVjdEVudHJ5Tm9kZTo2NQ==",
"activity": "Kill Thanos",
"reflection": "He sucks",
"nextSteps": "Go for the head",
"documentUrl": "",
"__typename": "ProjectEntryNode",
"created": "2020-01-20T15:20:31.262510+00:00"
}, "__typename": "ProjectEntryNodeEdge"
}], "__typename": "ProjectEntryNodeConnection"
}
}
},
AddProjectEntry: variables => ({
addProjectEntry: {
projectEntry: Object.assign({}, variables.input.projectEntry, {
created: '2020-01-20T15:26:58.722773+00:00'
}),
errors: null,
__typename: 'AddProjectEntryPayload'
}
}),
UpdateProjectEntry: variables => ({
updateProjectEntry: {
projectEntry: variables.input.projectEntry,
errors: null,
__typename: 'UpdateProjectEntryPayload'
}
}),
DeleteProjectEntry: {
deleteProjectEntry: {
success: true,
__typename: 'DeleteProjectEntryPayload'
}
}
}
});
});
it('should create a new project entry', () => {
@ -22,7 +113,7 @@ describe('Project Entry', () => {
cy.get('[data-cy=text-form-input]').type('Stay with Rocket\nMeet Quill');
});
cy.get('[data-cy=modal-save-button]').click();
cy.waitFor('AddProjectEntryMutation');
cy.get('.project-entry:last-of-type').within(() => {
cy.get('.project-entry__paragraph:first-of-type').contains('Join the Guardians')
});
@ -39,20 +130,19 @@ describe('Project Entry', () => {
cy.get('[data-cy=text-form-input]').clear().type('Defeat Thanos');
});
cy.get('[data-cy=modal-save-button]').click();
cy.waitFor('UpdateProjectEntry');
cy.get('.project-entry__paragraph:first-of-type').contains('Defeat Thanos');
});
it('should delete the last entry', () => {
cy.visit('/portfolio/groot');
cy.get('.project-entry').should('have.length', 2);
cy.get('.project-entry').should('have.length', 1);
cy.get('.project-entry:last-of-type').within(() => {
cy.get('[data-cy=project-entry-more]').click();
cy.get('[data-cy=delete-project-entry]').click();
});
cy.get('.project-entry').should('have.length', 1);
cy.get('.project-entry').should('have.length', 0);
});
});

View File

@ -1,5 +1,7 @@
describe('The Room Page', () => {
it('displays new room entry with author name', () => {
// todo: mock all the graphql queries and mutations
cy.viewport('macbook-15');
cy.visit('/room/ein-historisches-festival');
cy.login('rahel.cueni', 'test');

View File

@ -1,4 +1,5 @@
describe('The Rooms Page', () => {
// todo: mock all the graphql queries and mutations
it('goes to the rooms page', () => {
cy.visit('/rooms');
cy.login('nico.zickgraf', 'test');

View File

@ -1,5 +1,8 @@
// todo: reenable with a script that does the mutations, or with a workaround for fetch request checking in cypress
describe('Solutions', () => {
// todo: mock all the graphql queries and mutations
// todo: enable again
// // it('does not display the solution at first, then displays them after clicking', () => {
// // cy.viewport('macbook-15');
// // cy.login('nico.zickgraf', 'test');

View File

@ -0,0 +1,41 @@
const schema = require('../fixtures/schema.json');
const assignments = require('../fixtures/assignments.json');
const module = require('../fixtures/module.json');
const spellCheck = require('../fixtures/spell-check.json');
describe('Spellcheck', () => {
before(() => {
cy.server();
cy.mockGraphql({
schema: schema,
// endpoint: '/api/graphql'
operations: {
MeQuery: {
me: {
permissions: []
}
},
AssignmentsQuery: {
assignments
},
ModulesQuery: {
module
},
SpellCheck: {
spellCheck
}
}
});
});
it('should highlight three errors', () => {
cy.apolloLogin('rahel.cueni', 'test');
cy.visit('/module/lohn-und-budget/');
cy.get('.spellcheck__correction').should('have.length', 0);
cy.get('.submission-form-container__spellcheck').click();
cy.get('.spellcheck__correction').should('have.length', 3);
});
});

View File

@ -1,14 +1,57 @@
const schema = require('../fixtures/schema.json');
const module = require('../fixtures/module.json');
describe('Survey', () => {
beforeEach(() => {
cy.exec("python ../server/manage.py prepare_surveys_for_cypress");
cy.server();
cy.mockGraphql({
schema: schema,
});
cy.viewport('macbook-15');
cy.startGraphQLCapture();
cy.login('rahel.cueni', 'test', true);
cy.get('body').contains('Neues Wissen erwerben');
cy.apolloLogin('rahel.cueni', 'test');
});
it('should display and fill out the survey', () => {
let answer = null;
cy.mockGraphqlOps({
operations: {
MeQuery: {
me: {
permissions: []
}
},
ModuleQuery: variables => ({module}),
SurveyQuery: () => ({
survey: {
id: 'U3VydmV5Tm9kZTox',
title: 'Test',
data: '{"pages": [{"name": "Seite 1", "elements": [{"name": "Fall 1", "type": "panel", "title": "Fall 1", "elements": [{"name": "A: Max gibt ihr das Geld und muss das Billardspiel absagen.", "type": "text", "placeHolder": "Passende Tugenden erfassen...", "correctAnswer": "Triss"}, {"name": "question2", "type": "text", "title": "B: Max gibt ihr das Geld nicht und geht Billard spielen.", "placeHolder": "Passende Tugenden erfassen...", "correctAnswer": "Jaskier"}], "description": "Max hat Ende Monat noch Fr. 20.\\u2013 \\u00fcbrig, die er gespart hat, um mit seinem besten Kumpel, der ein halbes Jahr im Ausland verweilte, Billard spielen zu gehen. Doch dann bittet ihn seine j\\u00fcngere Schwester um Geld. Sie hat ein unverhofftes Date mit einem jungen Mann, in den sie sich bereits vor Monaten unsterblich verliebt hat. Leider ist ihr Kontostand aber bereits auf Null."}]}, {"name": "Seite 2", "elements": [{"name": "panel1", "type": "panel", "title": "Fall 2", "elements": [{"name": "question1", "type": "text", "title": "A: Silvio bringt seinen Mitfahrer in Sicherheit.", "placeHolder": "Passende Tugenden erfassen...", "correctAnswer": "Yennefer", "useDisplayValuesInTitle": false}, {"name": "question3", "type": "text", "title": "B: Silvio sperrt die Strasse ab.", "placeHolder": "Passende Tugenden erfassen...", "correctAnswer": "Geralt"}], "description": "Auf der Autobahn brennt ein Lastwagen, der jederzeit explodieren kann. Silvio, dem Fahrer, bleiben nur noch wenige Minuten: Entweder bringt er seinen ohnm\\u00e4chtig gewordenen Mitfahrer in Sicherheit oder er sperrt die Strasse ab, die nach wie vor dicht befahren wird."}]}], "completeText": "Abschliessen", "showQuestionNumbers": "off"}',
module: {
id: 'TW9kdWxlTm9kZToxNw==',
__typename: 'ModuleNode'
},
answer,
__typename: 'SurveyNode'
},
}),
UpdateAnswer: variables => {
answer = variables.input.answer;
return {
updateAnswer: {
answer,
__typename: 'UpdateAnswerPayload'
}
}
}
}
});
cy.visit('/survey/U3VydmV5Tm9kZTox');
cy.get('.survey__panel-title').should('contain', 'Fall 1')
@ -17,15 +60,12 @@ describe('Survey', () => {
cy.get('#sq_101i').type('Demut');
cy.get('[value="Speichern & Weiter"]').click();
//cy.get('.button--primary').click()
cy.get('#sq_102i').type('Keuschheit');
cy.get('#sq_103i').type('Geduld');
cy.get('[value=Abschliessen]').click();
cy.waitFor('UpdateAnswer');
cy.visit('/survey/U3VydmV5Tm9kZTox');
cy.get('#sq_100i').should('have.value', 'Wohlwollen')

View File

@ -23,9 +23,35 @@
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
// installed a fork of the original package, because of this issue:
// https://github.com/tgriesser/cypress-graphql-mock/issues/23
// todo: once above issue is fixed, go back to the original repo -> npm install cypress-graphql-mock
// import 'cypress-graphql-mock';
import '@iam4x/cypress-graphql-mock';
Cypress.Commands.add('apolloLogin', (username, password) => {
const payload = {
'operationName': 'Login',
'variables': {
'input': {
'usernameInput': username,
'passwordInput': password
}
},
'query': 'mutation Login($input: LoginInput!) {\n login(input: $input) {\n success\n errors {\n field\n __typename\n }\n __typename\n }\n}\n'
};
cy.request({
method: 'POST',
url: '/api/graphql-public/',
body: payload
});
});
// todo: replace with apollo call
Cypress.Commands.add("login", (username, password, visitLogin=false) => {
Cypress.Commands.add("login", (username, password, visitLogin = false) => {
if (visitLogin) {
cy.visit('/login');
}
@ -67,7 +93,7 @@ Cypress.Commands.add('startGraphQLCapture', () => {
// from https://stackoverflow.com/questions/53814647/how-can-i-alias-specific-graphql-requests-in-cypress
Cypress.Commands.add('waitFor', operationName => {
cy.wait('@graphQL').then(({request}) => {
if(request.body.operationName !== operationName) {
if (request.body.operationName !== operationName) {
return cy.waitFor(operationName);
}
});

View File

@ -20,7 +20,7 @@ import './commands'
// require('./commands')
// from https://stackoverflow.com/questions/49079005/how-to-stub-a-call-to-graphql-using-cypress#49088084
Cypress.on('window:before:load', win => {
win.fetch = null;
win.Blob = null;
});
// Cypress.on('window:before:load', win => {
// win.fetch = null;
// win.Blob = null;
// });

View File

@ -2524,6 +2524,15 @@
"lodash.once": "^4.1.1"
}
},
"@iam4x/cypress-graphql-mock": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/@iam4x/cypress-graphql-mock/-/cypress-graphql-mock-0.0.1.tgz",
"integrity": "sha512-pi2nNDwX9o+63qbwK4ogUAk5oX2HunclFcQ1YCsTx4DBoAdudfiKUegyqFXrii+wtyDaXUa88UrkrudMMcnCbA==",
"requires": {
"graphql-tools": "^4.0.3",
"tslib": "^1.9.3"
}
},
"@jest/console": {
"version": "24.7.1",
"resolved": "https://registry.npmjs.org/@jest/console/-/console-24.7.1.tgz",
@ -7463,6 +7472,11 @@
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
},
"deprecated-decorator": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz",
"integrity": "sha1-AJZjF7ehL+kvPMgx91g68ym4bDc="
},
"des.js": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz",
@ -9742,6 +9756,60 @@
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.1.tgz",
"integrity": "sha512-jApXqWBzNXQ8jYa/HLkZJaVw9jgwNqZkywa2zfFn16Iv1Zb7ELNHkJaXHR7Quvd5SIGsy6Ny7SUKATgnu05uEg=="
},
"graphql-tools": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/graphql-tools/-/graphql-tools-4.0.6.tgz",
"integrity": "sha512-jHLQw8x3xmSNRBCsaZqelXXsFfUSUSktSCUP8KYHiX1Z9qEuwcMpAf+FkdBzk8aTAFqOlPdNZ3OI4DKKqGKUqg==",
"requires": {
"apollo-link": "^1.2.3",
"apollo-utilities": "^1.0.1",
"deprecated-decorator": "^0.1.6",
"iterall": "^1.1.3",
"uuid": "^3.1.0"
},
"dependencies": {
"apollo-link": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.13.tgz",
"integrity": "sha512-+iBMcYeevMm1JpYgwDEIDt/y0BB7VWyvlm/7x+TIPNLHCTCMgcEgDuW5kH86iQZWo0I7mNwQiTOz+/3ShPFmBw==",
"requires": {
"apollo-utilities": "^1.3.0",
"ts-invariant": "^0.4.0",
"tslib": "^1.9.3",
"zen-observable-ts": "^0.8.20"
},
"dependencies": {
"apollo-utilities": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.3.3.tgz",
"integrity": "sha512-F14aX2R/fKNYMvhuP2t9GD9fggID7zp5I96MF5QeKYWDWTrkRdHRp4+SVfXUVN+cXOaB/IebfvRtzPf25CM0zw==",
"requires": {
"@wry/equality": "^0.1.2",
"fast-json-stable-stringify": "^2.0.0",
"ts-invariant": "^0.4.0",
"tslib": "^1.10.0"
},
"dependencies": {
"tslib": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
}
}
}
}
},
"zen-observable-ts": {
"version": "0.8.20",
"resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.20.tgz",
"integrity": "sha512-2rkjiPALhOtRaDX6pWyNqK1fnP5KkJJybYebopNSn6wDG1lxBoFs2+nwwXKoA6glHIrtwrfBBy6da0stkKtTAA==",
"requires": {
"tslib": "^1.9.3",
"zen-observable": "^0.8.0"
}
}
}
},
"growl": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz",
@ -11500,7 +11568,6 @@
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"

View File

@ -21,6 +21,7 @@
"@babel/preset-env": "^7.5.4",
"@babel/preset-stage-2": "^7.0.0",
"@babel/runtime": "^7.5.4",
"@iam4x/cypress-graphql-mock": "0.0.1",
"apollo-cache-inmemory": "^1.6.5",
"apollo-client": "^2.6.8",
"apollo-link": "^1.2.13",

View File

@ -13,20 +13,24 @@
@saveInput="saveInput"
@reopen="reopen"
@changeDocumentUrl="changeDocumentUrl"
@spellcheck="spellcheck"
:user-input="submission"
placeholder="Ergebnis erfassen"
action="Ergebnis mit Lehrperson teilen"
shared-msg="Das Ergebnis wurde mit der Lehrperson geteilt."
:saved="!unsaved"
>
:spellcheck="true"
>
</submission-form>
<div v-if="this.assignment.submission.submissionFeedback" class="assignment__feedback">
<p>{{feedbackText}}</p>
</div>
<spell-check :corrections="corrections" :text="submission.text"></spell-check>
<p v-if="this.assignment.submission.submissionFeedback" class="assignment__feedback" v-html="feedbackText">
</p>
</template>
<template v-if="!isStudent">
<router-link class="button button--primary" :to="{name: 'submissions', params: { id: assignment.id }}">Zu den Ergebnissen
<router-link class="button button--primary" :to="{name: 'submissions', params: { id: assignment.id }}">Zu den
Ergebnissen
</router-link>
</template>
</div>
@ -38,6 +42,7 @@
import ME_QUERY from '@/graphql/gql/meQuery.gql';
import UPDATE_ASSIGNMENT_MUTATION from '@/graphql/gql/mutations/updateAssignmentMutation.gql';
import UPDATE_ASSIGNMENT_MUTATION_WITH_SUCCESS from '@/graphql/gql/mutations/updateAssignmentMutationWithSuccess.gql';
import SPELL_CHECK_MUTATION from '@/graphql/gql/mutations/spellCheck.gql';
import debounce from 'lodash/debounce';
import cloneDeep from 'lodash/cloneDeep'
@ -48,6 +53,7 @@
import DocumentBlock from '@/components/content-blocks/DocumentBlock';
import Solution from '@/components/content-blocks/Solution';
import SimpleFileUpload from '@/components/SimpleFileUpload';
import SpellCheck from '@/components/content-blocks/assignment/SpellCheck';
export default {
props: ['value'],
@ -59,7 +65,8 @@
FinalSubmission,
Solution,
SimpleFileUpload,
SubmissionForm
SubmissionForm,
SpellCheck
},
computed: {
@ -83,7 +90,7 @@
},
feedbackText() {
let feedback = this.assignment.submission.submissionFeedback;
return `Feedback von ${feedback.teacher.firstName} ${feedback.teacher.lastName}: ${feedback.text}`;
return `<span class="inline-title">Feedback von ${feedback.teacher.firstName} ${feedback.teacher.lastName}:</span> ${feedback.text}`;
}
},
@ -129,6 +136,8 @@
});
}, 500),
saveInput: function (answer) {
// reset corrections on input
this.corrections = '';
this.unsaved = true;
/*
We update the assignment on this component, so the changes are reflected on it. The server does not return
@ -142,6 +151,8 @@
this._save(this.assignment.submission);
},
turnIn() {
// reset corrections on turn in
this.corrections = '';
this.$apollo.mutate({
mutation: UPDATE_ASSIGNMENT_MUTATION,
variables: {
@ -178,6 +189,21 @@
final: false,
}
},
spellcheck() {
let self = this;
this.$apollo.mutate({
mutation: SPELL_CHECK_MUTATION,
variables: {
input: {
assignment: this.assignment.id,
text: this.assignment.submission.text
}
},
update(store, {data: {spellCheck: {results}}}) {
self.corrections = results;
}
});
}
},
apollo: {
@ -212,7 +238,8 @@
},
inputType: 'text',
unsaved: false,
saving: 0
saving: 0,
corrections: ''
}
}
}
@ -256,8 +283,9 @@
}
&__feedback {
margin-top: $medium-spacing;
@include regular-text;
}
}
</style>

View File

@ -0,0 +1,59 @@
<template>
<p class="spellcheck" v-if="corrections">
<span class="inline-title">Rechtschreibung:</span> <span v-html="highlightedText"></span>
</p>
</template>
<script>
export default {
props: ['corrections', 'text'],
computed: {
highlightedText() {
if (!this.corrections) {
return ''
}
let parts = [];
let index = 0;
[...this.corrections] // no side effects, as sort changes the source array
.sort((e1, e2) => (e1.offset + e1.sentenceOffset) - (e2.offset + e2.sentenceOffset))
.forEach(current => {
let realOffset = current.offset + current.sentenceOffset;
parts.push({
correct: true,
text: this.text.substring(index, realOffset)
}, {
correct: false,
text: this.text.substring(realOffset, realOffset + current.length)
});
index = realOffset + current.length
});
parts.push({
correct: true,
text: this.text.substring(index, this.text.length + 1)
});
return parts
.filter(part => part.text.length)
.reduce((previous, part) => {
if (part.correct) {
return `${previous}${part.text}`;
} else {
return `${previous}<span class="spellcheck__correction">${part.text}</span>`;
}
}, '');
}
}
}
</script>
<style lang="scss">
@import "@/styles/_mixins.scss";
.spellcheck {
@include regular-text;
&__correction {
background: yellow;
}
}
</style>

View File

@ -1,44 +1,50 @@
<template>
<div class="feedback__submission submission-form-container">
<div class="submission-form-container__inputs">
<submission-input
@input="saveInput"
:input-text="userInput.text"
:saved="saved"
:final="final"
:placeholder="placeholder"
:reopen="reopenSubmission"
></submission-input>
</div>
<div class="submission-form-container__actions" v-if="!final">
<button class="submission-form-container__submit button button--primary button--white-bg"
@click="$emit('turnIn')"
>{{action}}
</button>
<div v-if="userInput.document">
<document-block
:value="{url: userInput.document}"
show-trash-icon
v-on:trash="changeDocumentUrl('')"
></document-block>
</div>
<simple-file-upload
v-if="allowsDocuments"
v-on:link-change-url="changeDocumentUrl"
:value="userInput.document"
class="submission-form-container__document"
></simple-file-upload>
<slot></slot>
</div>
<final-submission
v-if="final"
:user-input="userInput"
:shared-msg="sharedMsg"
@reopen="$emit('reopen')"></final-submission>
<div class="feedback__submission submission-form-container">
<div class="submission-form-container__inputs">
<submission-input
@input="saveInput"
:input-text="userInput.text"
:saved="saved"
:final="final"
:placeholder="placeholder"
:reopen="reopenSubmission"
></submission-input>
</div>
<div class="submission-form-container__actions" v-if="!final">
<button class="submission-form-container__submit button button--primary button--white-bg"
@click="$emit('turnIn')"
>{{action}}
</button>
<button
class="submission-form-container__submit submission-form-container__spellcheck button button--primary button--white-bg"
v-if="spellcheck"
@click="$emit('spellcheck')"
>Rechtschreibung prüfen
</button>
<div v-if="userInput.document">
<document-block
:value="{url: userInput.document}"
show-trash-icon
v-on:trash="changeDocumentUrl('')"
></document-block>
</div>
<simple-file-upload
v-if="allowsDocuments"
v-on:link-change-url="changeDocumentUrl"
:value="userInput.document"
class="submission-form-container__document"
></simple-file-upload>
<slot></slot>
</div>
<final-submission
v-if="final"
:user-input="userInput"
:shared-msg="sharedMsg"
@reopen="$emit('reopen')"></final-submission>
</div>
</template>
<script>
@ -48,13 +54,6 @@
import DocumentBlock from '@/components/content-blocks/DocumentBlock';
export default {
components: {
SubmissionInput,
FinalSubmission,
SimpleFileUpload,
DocumentBlock
},
props: {
userInput: Object,
saved: Boolean,
@ -62,9 +61,20 @@
action: String,
reopen: Function,
document: String,
spellcheck: {
type: Boolean,
default: false
},
sharedMsg: String
},
components: {
SubmissionInput,
FinalSubmission,
SimpleFileUpload,
DocumentBlock
},
computed: {
final() {
return !!this.userInput && this.userInput.final
@ -90,34 +100,35 @@
</script>
<style scoped lang="scss">
@import '@/styles/_mixins.scss';
@import '@/styles/_mixins.scss';
.submission-form-container {
.submission-form-container {
@include input-box-shadow;
background-color: $color-white;
border-radius: $input-border-radius;
border: 1px solid $color-silver;
padding: $medium-spacing;
@include input-box-shadow;
background-color: $color-white;
border-radius: $input-border-radius;
border: 1px solid $color-silver;
padding: $medium-spacing;
margin-bottom: $medium-spacing;
&__inputs {
margin-bottom: 12px;
}
&__inputs {
margin-bottom: 12px;
}
&__submit {
margin-right: $medium-spacing;
}
&__submit {
margin-right: $medium-spacing;
}
&__actions {
display: flex;
align-items: center;
}
&__actions {
display: flex;
align-items: center;
}
&__document {
&:hover {
cursor: pointer;
&__document {
&:hover {
cursor: pointer;
}
}
}
}
</style>

View File

@ -78,8 +78,6 @@ export default function (uri) {
assignment: (_, args, {getCacheKey}) => getCacheKey({__typename: 'AssignmentNode', id: args.id}),
objective: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ObjectiveNode', id: args.id}),
objectiveGroup: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ObjectiveGroupNode', id: args.id}),
// todo: remove, the new client seems to cache this correctly by itself
// module: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ModuleNode', id: args.slug}),
projectEntry: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ProjectEntryNode', id: args.id}),
}
}
@ -93,6 +91,7 @@ export default function (uri) {
try {
return cache.originalReadQuery(...args);
} catch (err) {
console.error(err);
return undefined;
}
};

View File

@ -1,5 +1,5 @@
#import "../fragments/projectParts.gql"
mutation AddProjectMutation($input: AddProjectInput!){
mutation AddProject($input: AddProjectInput!){
addProject(input: $input){
project {
...ProjectParts

View File

@ -1,5 +1,5 @@
#import "../fragments/projectEntryParts.gql"
mutation AddProjectEntryMutation($input: AddProjectEntryInput!) {
mutation AddProjectEntry($input: AddProjectEntryInput!) {
addProjectEntry(input: $input) {
projectEntry {
...ProjectEntryParts

View File

@ -0,0 +1,14 @@
mutation SpellCheck($input: SpellCheckInput!) {
spellCheck(input: $input) {
correct
results {
sentence
offset
sentenceOffset
length
affected
corrected
}
}
}

View File

@ -125,6 +125,11 @@
line-height: $default-heading-line-height;
}
@mixin inline-title {
@include regular-text;
color: $color-silver-dark;
}
@mixin lead-paragraph {
font-family: $serif-font-family;
line-height: 1.4;

View File

@ -71,3 +71,7 @@ input, textarea, select, button {
margin-bottom: 7.5px;
font-weight: 600;
}
.inline-title {
@include inline-title;
}

10
package-lock.json generated
View File

@ -9,9 +9,9 @@
"resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz",
"integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=",
"requires": {
"babel-runtime": "6.26.0",
"core-js": "2.5.7",
"regenerator-runtime": "0.10.5"
"babel-runtime": "^6.26.0",
"core-js": "^2.5.0",
"regenerator-runtime": "^0.10.5"
}
},
"babel-runtime": {
@ -19,8 +19,8 @@
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
"integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
"requires": {
"core-js": "2.5.7",
"regenerator-runtime": "0.11.1"
"core-js": "^2.4.0",
"regenerator-runtime": "^0.11.0"
},
"dependencies": {
"regenerator-runtime": {

View File

@ -16,6 +16,7 @@ from objectives.mutations import ObjectiveMutations
from objectives.schema import ObjectivesQuery
from portfolio.mutations import PortfolioMutations
from portfolio.schema import PortfolioQuery
from spellcheck.mutations import SpellCheckMutations
from surveys.schema import SurveysQuery
from surveys.mutations import SurveyMutations
from rooms.mutations import RoomMutations
@ -26,19 +27,18 @@ from registration.mutations_public import RegistrationMutations
class Query(UsersQuery, AllUsersQuery, ModuleRoomsQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery,
StudentSubmissionQuery, BasicKnowledgeQuery, PortfolioQuery, SurveysQuery,
graphene.ObjectType):
StudentSubmissionQuery, BasicKnowledgeQuery, PortfolioQuery, SurveysQuery, graphene.ObjectType):
node = relay.Node.Field()
if settings.DEBUG:
debug = graphene.Field(DjangoDebug, name='__debug')
debug = graphene.Field(DjangoDebug, name='_debug')
class Mutation(BookMutations, RoomMutations, AssignmentMutations, ObjectiveMutations, CoreMutations, PortfolioMutations,
ProfileMutations, SurveyMutations, NoteMutations, RegistrationMutations, graphene.ObjectType):
ProfileMutations, SurveyMutations, NoteMutations, RegistrationMutations, SpellCheckMutations,
graphene.ObjectType):
if settings.DEBUG:
debug = graphene.Field(DjangoDebug, name='__debug')
debug = graphene.Field(DjangoDebug, name='_debug')
schema = graphene.Schema(query=Query, mutation=Mutation)

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.6 on 2019-12-11 10:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assignments', '0010_auto_20191210_1427'),
]
operations = [
migrations.AddField(
model_name='assignment',
name='taskbase_id',
field=models.CharField(blank=True, max_length=255, null=True),
),
]

View File

@ -15,6 +15,7 @@ class Assignment(TimeStampedModel):
on_delete=models.PROTECT) # probably don't want to delete all assignments if a user gets deleted
module = models.ForeignKey('books.Module', related_name='assignments', on_delete=models.CASCADE)
user_created = models.BooleanField(default=False)
taskbase_id = models.CharField(max_length=255, null=True, blank=True)
panels = [
FieldPanel('title'),

View File

@ -4,6 +4,7 @@ import factory
import wagtail_factories
from django.contrib.auth import get_user_model
from factory import CREATE_STRATEGY
from wagtail.core import blocks
from wagtail.core.models import Page
from wagtail.core.rich_text import RichText
@ -96,6 +97,17 @@ class AssignmentBlockFactory(wagtail_factories.StructBlockFactory):
class Meta:
model = AssignmentBlock
@classmethod
def _build(cls, model_class, *args, **kwargs):
block = model_class()
return blocks.StructValue(
block,
# todo: build in a more generic fashion
[
(name, kwargs['assignment']) for name, child_block in block.child_blocks.items()
],
)
class VideoBlockFactory(wagtail_factories.StructBlockFactory):
url = factory.LazyAttribute(lambda x: 'https://www.youtube.com/watch?v=lO9d-AJai8Q')
@ -139,7 +151,7 @@ class ContentBlockFactory(BasePageFactory):
owner=user,
module=module
)
kwargs['{}__{}__{}__{}'.format(stream_field_name, idx, block_type, 'assignment_id')] = assignment.id
kwargs['{}__{}__{}__{}'.format(stream_field_name, idx, block_type, 'assignment')] = assignment
else:

View File

@ -43,6 +43,7 @@ class UpdateSolutionVisibility(relay.ClientIDMutation):
class UpdateLastModule(relay.ClientIDMutation):
class Input:
# todo: use slug here too
id = graphene.ID()
module = graphene.Field(ModuleNode)

View File

@ -49,6 +49,19 @@ module_1_chapter_1 = {
'title': '1.1 Lehrbeginn',
'description': 'Wie sieht Ihr Konsumverhalten aus?',
'content_blocks': [
{
'type': 'normal',
'title': 'Assignment',
'contents': [
{
'type': 'assignment',
'value': {
'assignment': 'Ein Auftrag',
'title': 'Ein Auftragstitel'
}
},
]
},
{
'type': 'task',
'title': 'Auftrag 1',

View File

@ -252,7 +252,8 @@ AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME')
AWS_S3_FILE_OVERWRITE = False
# use with cloudfront
AWS_S3_CUSTOM_DOMAIN = '{}.s3-{}.amazonaws.com'.format(AWS_STORAGE_BUCKET_NAME, os.environ.get('AWS_REGION', 'eu-west-1'))
AWS_S3_CUSTOM_DOMAIN = '{}.s3-{}.amazonaws.com'.format(AWS_STORAGE_BUCKET_NAME,
os.environ.get('AWS_REGION', 'eu-west-1'))
if USE_AWS:
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
# use with cloudfront
@ -316,7 +317,6 @@ if not DEBUG and os.environ.get('SENTRY_DSN'):
integrations=[DjangoIntegration()]
)
# LOGGING['handlers'] = {
# 'sentry': {
# 'level': 'ERROR', # ERROR, WARNING, INFO
@ -360,3 +360,9 @@ EMAIL_BACKEND = 'sendgrid_backend.SendgridBackend'
SENDGRID_API_KEY = os.environ.get("SENDGRID_API_KEY")
SENDGRID_SANDBOX_MODE_IN_DEBUG = False
DEFAULT_FROM_EMAIL = 'myskillbox <noreply@myskillbox.ch>'
TASKBASE_USER = os.environ.get("TASKBASE_USER")
TASKBASE_PASSWORD = os.environ.get("TASKBASE_PASSWORD")
TASKBASE_SUPERUSER = os.environ.get("TASKBASE_SUPERUSER")
TASKBASE_SUPERPASSWORD = os.environ.get("TASKBASE_SUPERPASSWORD")
TASKBASE_BASEURL = os.environ.get("TASKBASE_BASEURL")

View File

@ -86,7 +86,6 @@ class RoomsQuery(object):
class ModuleRoomsQuery(object):
module_room = graphene.Field(RoomNode, slug=graphene.String(), class_id=graphene.ID())
def resolve_module_room(self, info, **kwargs):

View File

View File

@ -0,0 +1,87 @@
import json
import re
import requests
# from spellcheck.client import TaskbaseClient
# client = TaskbaseClient('info@iterativ.ch', 'myverysafepassword1234', 'https://dev-iterativ.taskbase.com')
# client.spellcheck('aOciP9H7tNu7pLsR4ohllk', 'Dies ist ein Sats mit filen Felern')
class TaskbaseException(Exception):
pass
class TaskbaseClient:
# def __init__(self, resource_url):
# # resource url should be in the form https://username:password@baseurl
# pattern = re.compile(r'(\w+)://(\w+):(\w+)@([a-zA-Z0-9.]+)')
# scheme, username, password, url = pattern.match(resource_url).groups()
# self.username = username
# self.password = password
# self.token = None
# self.base_url = '{}://{}'.format(scheme, url)
def __init__(self, username, password, base_url):
self.username = username
self.password = password
self.base_url = base_url
self.token = None
def login(self):
payload = {
'username': self.username,
'password': self.password
}
response = requests.post('{}/api/login'.format(self.base_url), json=payload)
data = response.json()
self.token = data['accessToken']
def register_assignment(self, assignment):
if self.token is None:
self.login()
headers = {'Authorization': 'Bearer {}'.format(self.token), 'Content-Type': 'application/json'}
payload = {'type': 'SPELL_CHECK'}
# we first need to register the assignment via API, so we get an ID
response = requests.post('{}/api/task'.format(self.base_url), json=payload, headers=headers)
if response.status_code == 200:
data = response.json()
assignment.taskbase_id = data['id']
assignment.save()
payload = {
"id": assignment.taskbase_id,
"type": "SPELL_CHECK",
"documentType": "SPELL_CHECK",
"title": assignment.title,
"description": assignment.assignment,
"solutionSteps": [assignment.solution] if assignment.solution is not None else []
}
# we can then update the task via API with the description, title and sample solution
response = requests.post('{}/api/task/{}'.format(self.base_url, assignment.taskbase_id), json=payload,
headers=headers)
if response.status_code != 200:
raise TaskbaseException('Something went wrong')
else: # todo: define what to do here
raise TaskbaseException('Something went wrong')
def spellcheck(self, task, text):
if self.token is None:
self.login()
payload = {
"taskId": task,
"input": {
"text": text,
"type": "SPELL_CHECK"
}
}
headers = {'Authorization': 'Bearer {}'.format(self.token), 'Content-Type': 'application/json'}
response = requests.post('{}/api/grade'.format(self.base_url), json=payload, headers=headers)
if response.status_code == 200:
return response.content
else: # todo: define what to do here
raise Exception('Something went wrong')

View File

@ -0,0 +1,69 @@
import json
import graphene
from django.conf import settings
from graphene import relay
# class SpellCheckPartNode(graphene.ObjectType):
# sentence = graphene.String()
# offset = graphene.Int()
# length = graphene.Int()
# affected = graphene.String()
# corrected = graphene.String()
from api.utils import get_object
from assignments.models import Assignment
from spellcheck.client import TaskbaseClient
def sentence_offset_to_snake_case(result):
result['sentence_offset'] = result['sentenceOffset']
return result
class SpellCheckStepNode(graphene.ObjectType):
# id = graphene.String()
# part = graphene.Field(SpellCheckPartNode)
sentence = graphene.String()
offset = graphene.Int()
sentence_offset = graphene.Int()
length = graphene.Int()
affected = graphene.String()
corrected = graphene.String()
class SpellCheck(relay.ClientIDMutation):
class Input:
text = graphene.String(required=True)
assignment = graphene.ID(required=True)
results = graphene.List(SpellCheckStepNode)
correct = graphene.Boolean()
@classmethod
def mutate_and_get_payload(cls, root, info, **kwargs):
user = info.context.user
text = kwargs.get('text')
assignment_id = kwargs.get('assignment')
assignment = get_object(Assignment, assignment_id)
client = TaskbaseClient(settings.TASKBASE_USER, settings.TASKBASE_PASSWORD, settings.TASKBASE_BASEURL)
if assignment.taskbase_id is None or assignment.taskbase_id == '':
# we need to use another user (with more privileges) here, because why not
superclient = TaskbaseClient(settings.TASKBASE_SUPERUSER, settings.TASKBASE_SUPERPASSWORD,
settings.TASKBASE_BASEURL)
superclient.register_assignment(assignment)
data = json.loads(client.spellcheck(assignment.taskbase_id, text))
# the property is called sentenceOffset, but graphene expects the property to be called sentence_offset.
# we convert it manually here. fixme: is there a better way to declare this in the SpellCheckStepNode?
transformed_results = list(map(lambda x: x['part'], data['steps']))
return cls(correct=data['correct'] == 'CORRECT', results=[
sentence_offset_to_snake_case(result) for result in transformed_results
])
class SpellCheckMutations:
spell_check = SpellCheck.Field()

View File