From de26a9f8acfa88263e276580d13e4a9db59eff96 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Tue, 11 May 2021 23:54:08 +0200 Subject: [PATCH] Add objectives to snapshots --- Pipfile.lock | 83 ++++--------------- .../migrations/0029_auto_20210511_1301.py | 34 ++++++++ server/books/models/snapshot.py | 44 +++++++++- server/books/schema/mutations/snapshot.py | 9 +- server/books/schema/nodes/snapshot.py | 53 +++++++++++- server/books/tests/test_snapshots.py | 19 ++--- .../migrations/0015_auto_20210511_2150.py | 28 +++++++ server/objectives/models.py | 26 +++++- server/objectives/schema.py | 8 +- 9 files changed, 219 insertions(+), 85 deletions(-) create mode 100644 server/books/migrations/0029_auto_20210511_1301.py create mode 100644 server/objectives/migrations/0015_auto_20210511_2150.py diff --git a/Pipfile.lock b/Pipfile.lock index e7972165..dcac60a4 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -55,19 +55,18 @@ }, "boto3": { "hashes": [ - "sha256:56f1766f1271b6b4e979c7b56225377f8912050e5935adc5c1c9e3a0338b949e", - "sha256:c61c809d288e88b9a0d926f56f803d0128b498aa9b45a42a6e03cd9a83e5c124" + "sha256:2f0d76660d484ff4c8c2efe9171c1281b38681e6806f87cf100e822432eda11e", + "sha256:cbaa8df5faf81730f117bfa0e3fcda68ec3fa9449a05847aa6140a3f4c087765" ], "index": "pypi", - "version": "==1.17.68" + "version": "==1.17.69" }, "botocore": { "hashes": [ - "sha256:0f693f5ad6348ec1a62b3a66fee2840d3b722d66b44896022d644275ff8b143d", - "sha256:eb3544911cb0316a33b328a27d137130af278a9c0006be0c95e5e402b01d9865" + "sha256:7e94d3777763ece33d282b437e3b05b5567b9af816bd7819dbe4eb9bc6db6082", + "sha256:f755b19ddebda0f8ab7afc75ebcb5412dd802eca0a7e670f5fff8c5e58bc88b1" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.20.68" + "version": "==1.20.69" }, "certifi": { "hashes": [ @@ -81,7 +80,6 @@ "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==4.0.0" }, "decorator": { @@ -89,7 +87,6 @@ "sha256:6f201a6c4dac3d187352661f508b9364ec8091217442c9478f1f83c003a0f060", "sha256:945d84890bb20cc4a2f4a31fc4311c0c473af65ea318617f13a7257c9a58bc98" ], - "markers": "python_version >= '3.5'", "version": "==5.0.7" }, "dj-database-url": { @@ -189,7 +186,6 @@ "sha256:710b4d15ec1996550cc68a0abbc41903ca7d832540e52b1336e6858737e410d8", "sha256:bb8f27684814cd1414b2af75b857b5e26a40912631904038a7ecacd2bfafc3ac" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.24.0" }, "django-treebeard": { @@ -197,7 +193,6 @@ "sha256:7c2b1cdb1e9b46d595825186064a1228bc4d00dbbc186db5b0b9412357fba91c", "sha256:80150017725239702054e5fa64dc66e383dc13ac262c8d47ee5a82cb005969da" ], - "markers": "python_version >= '3.6'", "version": "==4.5.1" }, "djangorestframework": { @@ -228,14 +223,12 @@ "sha256:156854f36d4086bb21ff85a79b4d6a6403a240cd2c17a33a44b8ea4ff4e957c2", "sha256:a2ed065342e91a7672407325848cd5728d5e5eb4928d0a1c478fd4f0dd97d1f7" ], - "markers": "python_version >= '3.6'", "version": "==8.1.2" }, "future": { "hashes": [ "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.18.2" }, "gprof2dot": { @@ -286,7 +279,6 @@ "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==1.1" }, "idna": { @@ -294,7 +286,6 @@ "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.10" }, "ipython": { @@ -325,7 +316,6 @@ "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419", "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==2.11.3" }, "jmespath": { @@ -333,7 +323,6 @@ "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9", "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.10.0" }, "libsass": { @@ -409,7 +398,6 @@ "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be", "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.1.1" }, "matplotlib-inline": { @@ -417,7 +405,6 @@ "sha256:5cf1176f554abb4fa98cb362aa2b55c500147e4bdbb07e3fda359143e1da0811", "sha256:f41d5ff73c9f5385775d5c0bc13b424535c8402fe70ea8210f93e11f3683993e" ], - "markers": "python_version >= '3.5'", "version": "==0.1.2" }, "newrelic": { @@ -442,7 +429,6 @@ "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==20.9" }, "parso": { @@ -450,7 +436,6 @@ "sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea", "sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.7.1" }, "pexpect": { @@ -526,7 +511,6 @@ "sha256:bf00f22079f5fadc949f42ae8ff7f05702826a97059ffcc6281036ad40ac6f04", "sha256:e1b4f11b9336a28fa11810bc623c357420f69dfdb6d2dac41ca2c21a55c033bc" ], - "markers": "python_full_version >= '3.6.1'", "version": "==3.0.18" }, "psycopg2": { @@ -562,7 +546,6 @@ "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068", "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.7.0" }, "pygments": { @@ -570,7 +553,6 @@ "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f", "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e" ], - "markers": "python_version >= '3.5'", "version": "==2.9.0" }, "pyparsing": { @@ -578,7 +560,6 @@ "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.4.7" }, "python-dateutil": { @@ -586,7 +567,6 @@ "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.8.1" }, "python-dotenv": { @@ -672,7 +652,6 @@ "sha256:273bdc0abec649bf6319df7b6267980f79e53ab64e92906d65eea6d4330d00b4", "sha256:74b0dcf9a79188948f61f456bd1bf67ffa676a5d388aba1c76bff516566d7084" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==6.7.0" }, "sentry-sdk": { @@ -688,7 +667,6 @@ "sha256:58b46ce1cc4d43af0aac3ac9a047bdb0f44e05f0b2fa2eec755863331700c865", "sha256:85c97f94c8957fa4e6dab113156c182fb346d56d059af78aad710bced15f16fb" ], - "markers": "python_version >= '2.6'", "version": "==3.6.1" }, "six": { @@ -696,7 +674,6 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, "sqlparse": { @@ -704,7 +681,6 @@ "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0", "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8" ], - "markers": "python_version >= '3.5'", "version": "==0.4.1" }, "starkbank-ecdsa": { @@ -725,7 +701,6 @@ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.10.2" }, "traitlets": { @@ -733,16 +708,15 @@ "sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396", "sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426" ], - "markers": "python_version >= '3.7'", "version": "==5.0.5" }, "typing": { "hashes": [ - "sha256:1187fb9c82fd670d10aa07bbb6cfcfe4bdda42d6fab8d5134f04e8c4d0b71cc9", - "sha256:283d868f5071ab9ad873e5e52268d611e851c870a2ba354193026f2dfb29d8b5" + "sha256:12fbdfbe7d6cca1a42e485229afcb0b0c8259258cfb919b8a5e2a5c953742f89", + "sha256:13b4ad211f54ddbf93e5901a9967b1e07720c1d1b78d596ac6a439641aa1b130", + "sha256:c7219ef20c5fbf413b4567092adfc46fa6203cb8454eda33c3fc1afe1398a308" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==3.7.4.3" + "version": "==3.10.0.0" }, "unidecode": { "hashes": [ @@ -764,7 +738,6 @@ "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df", "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", "version": "==1.26.4" }, "wagtail": { @@ -826,7 +799,6 @@ "sha256:92906c611ce6c967347bbfea733f13d6313901d54dcca88195eaeb52b2a8e8ee", "sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78" ], - "markers": "python_version >= '3.6'", "version": "==3.3.4" }, "autopep8": { @@ -838,11 +810,11 @@ }, "awscli": { "hashes": [ - "sha256:57ae60a3f59cac265a9e5321c618b8768fdee89565089ada271e24489be5110d", - "sha256:a26b5e24f70cb2c542128ccc11e9d38e43cded687d60cd2ca18b3d28cd902509" + "sha256:1098eaf29a066965c0111469df450174f321698e425c0a112d315d091b5461f1", + "sha256:8103f89ba9ca5ccc3b45da97c5c0bd767fcbaff83d720386e57eaa379ff09458" ], "index": "pypi", - "version": "==1.19.68" + "version": "==1.19.69" }, "backcall": { "hashes": [ @@ -853,11 +825,10 @@ }, "botocore": { "hashes": [ - "sha256:0f693f5ad6348ec1a62b3a66fee2840d3b722d66b44896022d644275ff8b143d", - "sha256:eb3544911cb0316a33b328a27d137130af278a9c0006be0c95e5e402b01d9865" + "sha256:7e94d3777763ece33d282b437e3b05b5567b9af816bd7819dbe4eb9bc6db6082", + "sha256:f755b19ddebda0f8ab7afc75ebcb5412dd802eca0a7e670f5fff8c5e58bc88b1" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.20.68" + "version": "==1.20.69" }, "certifi": { "hashes": [ @@ -871,7 +842,6 @@ "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==4.0.0" }, "colorama": { @@ -879,7 +849,6 @@ "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==0.4.3" }, "coverage": { @@ -945,7 +914,6 @@ "sha256:6f201a6c4dac3d187352661f508b9364ec8091217442c9478f1f83c003a0f060", "sha256:945d84890bb20cc4a2f4a31fc4311c0c473af65ea318617f13a7257c9a58bc98" ], - "markers": "python_version >= '3.5'", "version": "==5.0.7" }, "django": { @@ -969,7 +937,6 @@ "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.15.2" }, "gprof2dot": { @@ -983,7 +950,6 @@ "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.10" }, "ipdb": { @@ -1021,7 +987,6 @@ "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419", "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==2.11.3" }, "jmespath": { @@ -1029,7 +994,6 @@ "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9", "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.10.0" }, "markupsafe": { @@ -1087,7 +1051,6 @@ "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be", "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.1.1" }, "matplotlib-inline": { @@ -1095,7 +1058,6 @@ "sha256:5cf1176f554abb4fa98cb362aa2b55c500147e4bdbb07e3fda359143e1da0811", "sha256:f41d5ff73c9f5385775d5c0bc13b424535c8402fe70ea8210f93e11f3683993e" ], - "markers": "python_version >= '3.5'", "version": "==0.1.2" }, "parso": { @@ -1103,7 +1065,6 @@ "sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea", "sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.7.1" }, "pexpect": { @@ -1126,7 +1087,6 @@ "sha256:bf00f22079f5fadc949f42ae8ff7f05702826a97059ffcc6281036ad40ac6f04", "sha256:e1b4f11b9336a28fa11810bc623c357420f69dfdb6d2dac41ca2c21a55c033bc" ], - "markers": "python_full_version >= '3.6.1'", "version": "==3.0.18" }, "ptyprocess": { @@ -1159,7 +1119,6 @@ "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068", "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.7.0" }, "pygments": { @@ -1167,7 +1126,6 @@ "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f", "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e" ], - "markers": "python_version >= '3.5'", "version": "==2.9.0" }, "python-dateutil": { @@ -1175,7 +1133,6 @@ "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.8.1" }, "pytz": { @@ -1217,7 +1174,6 @@ "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6", "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", "version": "==5.4.1" }, "requests": { @@ -1233,7 +1189,7 @@ "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2", "sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9" ], - "markers": "python_version >= '3.5' and python_version < '4'", + "markers": "python_version > '2.7'", "version": "==4.7.2" }, "s3transfer": { @@ -1248,7 +1204,6 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, "sqlparse": { @@ -1256,7 +1211,6 @@ "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0", "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8" ], - "markers": "python_version >= '3.5'", "version": "==0.4.1" }, "toml": { @@ -1264,7 +1218,6 @@ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.10.2" }, "traitlets": { @@ -1272,7 +1225,6 @@ "sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396", "sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426" ], - "markers": "python_version >= '3.7'", "version": "==5.0.5" }, "urllib3": { @@ -1280,7 +1232,6 @@ "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df", "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", "version": "==1.26.4" }, "wcwidth": { diff --git a/server/books/migrations/0029_auto_20210511_1301.py b/server/books/migrations/0029_auto_20210511_1301.py new file mode 100644 index 00000000..4173cb52 --- /dev/null +++ b/server/books/migrations/0029_auto_20210511_1301.py @@ -0,0 +1,34 @@ +# Generated by Django 2.2.22 on 2021-05-11 13:01 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('objectives', '0014_delete_objectiveprogressstatus'), + ('books', '0028_snapshot_shared'), + ] + + operations = [ + migrations.AddField( + model_name='snapshot', + name='hidden_objectives', + field=models.ManyToManyField(related_name='hidden_for_snapshots', to='objectives.Objective'), + ), + migrations.CreateModel( + name='ObjectiveGroupSnapshot', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('hidden', models.BooleanField(default=False)), + ('objective_group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='objectives.ObjectiveGroup')), + ('snapshot', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='objective_group_snapshots', to='books.Snapshot')), + ], + ), + migrations.AddField( + model_name='snapshot', + name='objective_groups', + field=models.ManyToManyField(through='books.ObjectiveGroupSnapshot', to='objectives.ObjectiveGroup'), + ), + ] diff --git a/server/books/models/snapshot.py b/server/books/models/snapshot.py index 8125dbd2..4a9859b8 100644 --- a/server/books/models/snapshot.py +++ b/server/books/models/snapshot.py @@ -3,6 +3,7 @@ from django.db import models from django.db.models import Q from books.models import Chapter, ContentBlock, ContentBlockSnapshot +from objectives.models import ObjectiveSnapshot class ChapterSnapshot(models.Model): @@ -24,6 +25,19 @@ class ChapterSnapshot(models.Model): description_hidden = models.BooleanField(default=False) +class ObjectiveGroupSnapshot(models.Model): + objective_group = models.ForeignKey( + 'objectives.ObjectiveGroup', + on_delete=models.CASCADE + ) + snapshot = models.ForeignKey( + 'books.Snapshot', + related_name='objective_group_snapshots', + on_delete=models.CASCADE + ) + hidden = models.BooleanField(default=False) + + class SnapshotManager(models.Manager): def create_snapshot(self, module, school_class, user, *args, **kwargs): snapshot = self.create(module=module, creator=user, *args, **kwargs) @@ -41,7 +55,7 @@ class SnapshotManager(models.Manager): snapshot.hidden_content_blocks.add(content_block) for content_block in base_qs.filter(Q(user_created=True) & Q(owner=user)): new_content_block = ContentBlockSnapshot( - hidden=False, + hidden=False, # todo snapshot=snapshot, contents=content_block.contents, type=content_block.type, @@ -52,6 +66,26 @@ class SnapshotManager(models.Manager): revision.publish() new_content_block.save() + for objective_group in module.objective_groups.all(): + ObjectiveGroupSnapshot.objects.create( + objective_group=objective_group, + snapshot=snapshot, + hidden=objective_group.hidden_for.filter(id=school_class.id).exists(), + ) + base_qs = objective_group.objectives.filter(objectivesnapshot__isnull=True) + for objective in base_qs.filter(owner__isnull=True): + if objective.hidden_for.filter(id=school_class.id).exists(): + snapshot.hidden_objectives.add(objective) + for objective in base_qs.filter(owner=user): + ObjectiveSnapshot.objects.create( + hidden=False, # todo + snapshot=snapshot, + text=objective.text, + group=objective_group, + order=objective.order + ) + + return snapshot @@ -72,6 +106,14 @@ class Snapshot(models.Model): created = models.DateTimeField(auto_now_add=True) creator = models.ForeignKey(get_user_model(), on_delete=models.SET_NULL, null=True) shared = models.BooleanField(default=False) + objective_groups = models.ManyToManyField( + 'objectives.ObjectiveGroup', + through=ObjectiveGroupSnapshot + ) + hidden_objectives = models.ManyToManyField( + 'objectives.Objective', + related_name='hidden_for_snapshots' + ) objects = SnapshotManager() diff --git a/server/books/schema/mutations/snapshot.py b/server/books/schema/mutations/snapshot.py index 5688855c..6d88c5b8 100644 --- a/server/books/schema/mutations/snapshot.py +++ b/server/books/schema/mutations/snapshot.py @@ -61,13 +61,20 @@ class ApplySnapshot(relay.ClientIDMutation): for content_block in snapshot.hidden_content_blocks.all(): content_block.hidden_for.add(selected_class) for custom_content_block in snapshot.custom_content_blocks.all(): - custom_content_block.to_regular_content_block(user, selected_class) + custom_content_block.to_regular_content_block(owner=user, school_class=selected_class) for chapter_snapshot in snapshot.chapters.through.objects.all(): chapter = chapter_snapshot.chapter if chapter_snapshot.title_hidden: chapter.title_hidden_for.add(selected_class) if chapter_snapshot.description_hidden: chapter.description_hidden_for.add(selected_class) + for objective_group_snapshot in snapshot.objective_groups.through.objects.all(): + if objective_group_snapshot.hidden: + objective_group_snapshot.objective_group.hidden_for.add(selected_class) + for objective in snapshot.hidden_objectives.all(): + objective.hidden_for.add(selected_class) + for custom_objective in snapshot.custom_objectives.all(): + custom_objective.to_regular_objective(owner=user, school_class=selected_class) return cls(success=True, module=snapshot.module) diff --git a/server/books/schema/nodes/snapshot.py b/server/books/schema/nodes/snapshot.py index 5df5041f..f2d64608 100644 --- a/server/books/schema/nodes/snapshot.py +++ b/server/books/schema/nodes/snapshot.py @@ -2,12 +2,11 @@ import graphene from django.db.models import Q from graphene import relay, ObjectType from graphene_django import DjangoObjectType -from graphene_django.filter import DjangoFilterConnectionField from books.models.snapshot import Snapshot -from ..interfaces import ModuleInterface, ChapterInterface +from ..interfaces import ChapterInterface from ..interfaces.contentblock import ContentBlockInterface -from ...models import Module, Chapter, ChapterSnapshot, ContentBlock +from ...models import ContentBlock class SnapshotContentBlock: @@ -43,6 +42,27 @@ class SnapshotChapter: # all with snapshotcontentblock with this snapshot +class SnapshotObjective: + def __init__(self, objective, snapshot): + self.text = objective.text + self.hidden = snapshot.hidden_objectives.filter(id=objective.id).exists() + + +class SnapshotObjectiveGroup: + def __init__(self, objective_group, snapshot): + self.title = objective_group.title + base_qs = objective_group.objectives + default = Q(owner__isnull=True) + this_snapshot = Q(objectivesnapshot__snapshot=snapshot) + self.objectives = [ + SnapshotObjective( + objective=objective, + snapshot=snapshot + ) + for objective in base_qs.filter(default | this_snapshot) + ] + + class SnapshotContentBlockNode(ObjectType): class Meta: interfaces = (relay.Node, ContentBlockInterface,) @@ -66,6 +86,22 @@ class SnapshotChangesNode(ObjectType): new_content_blocks = graphene.Int(required=True) +class SnapshotObjectiveNode(ObjectType): + class Meta: + interfaces = (relay.Node,) + + hidden = graphene.Boolean(required=True) + text = graphene.String(required=True) + + +class SnapshotObjectiveGroupNode(ObjectType): + class Meta: + interfaces = (relay.Node,) + + title = graphene.String(required=True) + objectives = graphene.List(SnapshotObjectiveNode) + + class SnapshotNode(DjangoObjectType): title = graphene.String() chapters = graphene.List(SnapshotChapterNode) @@ -75,6 +111,7 @@ class SnapshotNode(DjangoObjectType): mine = graphene.Boolean() shared = graphene.Boolean(required=True) creator = graphene.String(required=True) + objective_groups = graphene.List(SnapshotObjectiveGroupNode) class Meta: model = Snapshot @@ -122,3 +159,13 @@ class SnapshotNode(DjangoObjectType): @staticmethod def resolve_creator(parent, info, **kwargs): return f'{parent.creator.first_name} {parent.creator.last_name}' + + @staticmethod + def resolve_objective_groups(parent, info, **kwargs): + return [ + SnapshotObjectiveGroup( + objective_group=objective_group, + snapshot=parent + ) + for objective_group in parent.objective_groups.all() + ] diff --git a/server/books/tests/test_snapshots.py b/server/books/tests/test_snapshots.py index 641289d7..94f9941e 100644 --- a/server/books/tests/test_snapshots.py +++ b/server/books/tests/test_snapshots.py @@ -50,12 +50,11 @@ mutation CreateSnapshot($input: CreateSnapshotInput!) { snapshot { id created - creator { - username - } + creator objectiveGroups { objectives { text + hidden } } chapters { @@ -121,9 +120,7 @@ query SnapshotQuery($slug: String!) { id title created - creator { - username - } + creator } } } @@ -206,6 +203,8 @@ class CreateSnapshotTestCase(SkillboxTestCase): objectives = module['objectiveGroups'][0]['objectives'] + self.assertEqual(len(objectives), 3) + hidden_objective = [objective for objective in objectives if objective['text'] == self.hidden_objective.text][0] custom_objective = [objective for objective in objectives if @@ -236,7 +235,7 @@ class CreateSnapshotTestCase(SkillboxTestCase): chapter = snapshot.get('chapters')[0] self.assertIsNotNone(snapshot.get('created')) - self.assertEqual(snapshot.get('creator').get('username'), self.teacher.username) + self.assertEqual(snapshot.get('creator'), f'{self.teacher.first_name} {self.teacher.last_name}') self.assertTrue(chapter['titleHidden']) self.assertFalse(chapter['descriptionHidden']) @@ -267,6 +266,7 @@ class CreateSnapshotTestCase(SkillboxTestCase): self.snapshot = Snapshot.objects.create_snapshot(module=self.module, school_class=self.skillbox_class, user=self.teacher) self.assertEqual(Snapshot.objects.count(), 1) + self.assertEqual(self.snapshot.custom_objectives.count(), 1) school_class_name = 'second_class' second_class = SchoolClass.objects.get(name=school_class_name) request = RequestFactory().get('/') @@ -302,9 +302,6 @@ class CreateSnapshotTestCase(SkillboxTestCase): self.assertEqual(second['hidden'], True) self.assertEqual(third['title'], 'custom') - def test_not_too_much_user_creator_info(self): - self.assertTrue(False) - def test_apply_initial_snapshot(self): teacher2 = User.objects.get(username='teacher2') teacher2_client = self.get_client(user=teacher2) @@ -364,7 +361,7 @@ class SnapshotTestCase(SkillboxTestCase): self.assertIsNone(result.get('errors')) snapshots = result['data']['module']['snapshots'] self.assertEqual(len(snapshots), 1) - self.assertEqual(snapshots[0]['creator']['username'], 'teacher') + self.assertEqual(snapshots[0]['creator'], f'{self.teacher.first_name} {self.teacher.last_name}') def test_share_snapshot(self): self.assertFalse(self.snapshot.shared) diff --git a/server/objectives/migrations/0015_auto_20210511_2150.py b/server/objectives/migrations/0015_auto_20210511_2150.py new file mode 100644 index 00000000..0378a3b3 --- /dev/null +++ b/server/objectives/migrations/0015_auto_20210511_2150.py @@ -0,0 +1,28 @@ +# Generated by Django 2.2.22 on 2021-05-11 21:50 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('books', '0029_auto_20210511_1301'), + ('objectives', '0014_delete_objectiveprogressstatus'), + ] + + operations = [ + migrations.AlterModelOptions( + name='objective', + options={'verbose_name': 'Lernziel', 'verbose_name_plural': 'Lernziele'}, + ), + migrations.CreateModel( + name='ObjectiveSnapshot', + fields=[ + ('objective_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='objectives.Objective')), + ('hidden', models.BooleanField(default=False)), + ('snapshot', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='custom_objectives', to='books.Snapshot')), + ], + bases=('objectives.objective',), + ), + ] diff --git a/server/objectives/models.py b/server/objectives/models.py index 7b8166b8..f0eca18f 100644 --- a/server/objectives/models.py +++ b/server/objectives/models.py @@ -48,7 +48,8 @@ class Objective(models.Model): class Meta: verbose_name = 'Lernziel' verbose_name_plural = 'Lernziele' - ordering = [F('owner').asc(nulls_first=True), F('order').asc(nulls_last=True)] + # todo: reinstate ordering in resolver + # ordering = [F('owner').asc(nulls_first=True), F('order').asc(nulls_last=True)] text = models.CharField('text', blank=True, null=False, max_length=255) group = models.ForeignKey(ObjectiveGroup, blank=False, null=False, on_delete=models.CASCADE, @@ -64,3 +65,26 @@ class Objective(models.Model): def sync_visibility(self, school_class_template, school_class_to_sync): sync_hidden_for(self, school_class_template, school_class_to_sync) sync_visible_for(self, school_class_template, school_class_to_sync) + + +class ObjectiveSnapshot(Objective): + hidden = models.BooleanField(default=False) + snapshot = models.ForeignKey( + 'books.Snapshot', + on_delete=models.SET_NULL, + null=True, + related_name='custom_objectives' + ) + + def to_regular_objective(self, owner, school_class): + objective = Objective.objects.create( + owner=owner, + text=self.text, + group=self.group, + order=self.order + ) + + objective.visible_for.add(school_class) + objective.save() + + return objective diff --git a/server/objectives/schema.py b/server/objectives/schema.py index 84614661..e9e35046 100644 --- a/server/objectives/schema.py +++ b/server/objectives/schema.py @@ -47,15 +47,19 @@ class ObjectiveGroupNode(DjangoObjectType, HiddenForMixin): def resolve_objectives(self, info, **kwargs): user = info.context.user school_classes = user.school_classes.values_list('pk') + base_qs = self.objectives.filter(objectivesnapshot__isnull=True) objectives_from_publisher = Q(owner=None) objectives_from_user = Q(owner=user) objectives_from_teacher = Q(owner__isnull=False, visible_for__in=school_classes) + # todo + # raise NotImplemented('not in correct order') + if user.has_perm('users.can_manage_school_class_content'): # teacher - return self.objectives.filter(objectives_from_publisher | objectives_from_user) + return base_qs.filter(objectives_from_publisher | objectives_from_user) else: # student - return self.objectives.filter(objectives_from_publisher | objectives_from_teacher) + return base_qs.filter(objectives_from_publisher | objectives_from_teacher) class ObjectivesQuery(object):