From 2d13735e57aa4a20eaa36011db24e443a460d1a0 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Wed, 29 Jul 2020 19:04:09 -0700 Subject: lock loginpass version to prevent conflicting authlib version May be possible to upgrade both of these libraries together, but that isn't the purpose of current development. --- python/Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/Pipfile b/python/Pipfile index 1c7ebf3a..d8822f7e 100644 --- a/python/Pipfile +++ b/python/Pipfile @@ -30,7 +30,7 @@ flask-wtf = "*" Flask-Misaka = "*" flask-mwoauth = "*" WTForms = "*" -loginpass = ">=0.4" +loginpass = "==0.4" # loginpass 0.4 is not actually compatible with newer authlib authlib = "<0.13" requests = ">=2" -- cgit v1.2.3 From e4047b7156c1d6d4d926870efac0f1b3f5de1751 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Wed, 29 Jul 2020 19:04:57 -0700 Subject: pipenv: add toml library (and update lock) --- python/Pipfile | 1 + python/Pipfile.lock | 602 ++++++++++++++++++++++++++++------------------------ 2 files changed, 327 insertions(+), 276 deletions(-) diff --git a/python/Pipfile b/python/Pipfile index d8822f7e..b8569589 100644 --- a/python/Pipfile +++ b/python/Pipfile @@ -55,6 +55,7 @@ langdetect = "*" pathlib2 = "*" pycountry = "*" tldextract = "*" +toml = ">=0.10" [requires] # We install Python 3.7 using a PPA (deadsnakes) on Internet Archive cluster diff --git a/python/Pipfile.lock b/python/Pipfile.lock index 2f428b1c..af6478a5 100644 --- a/python/Pipfile.lock +++ b/python/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "94b31d68f1fdf85834841d90948ba599ba1a69723b0dd899f8ba9e709bc5307c" + "sha256": "e7d45cf28f10e1b2b01bd25ca2f660011c6ed9195974431b42c9e2d4d409bdff" }, "pipfile-spec": 6, "requires": { @@ -47,43 +47,43 @@ }, "certifi": { "hashes": [ - "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304", - "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519" + "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", + "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" ], - "version": "==2020.4.5.1" + "version": "==2020.6.20" }, "cffi": { "hashes": [ - "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff", - "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b", - "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac", - "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0", - "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384", - "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26", - "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6", - "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b", - "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e", - "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd", - "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2", - "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66", - "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc", - "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8", - "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55", - "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4", - "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5", - "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d", - "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78", - "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa", - "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793", - "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f", - "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a", - "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f", - "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30", - "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f", - "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3", - "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c" - ], - "version": "==1.14.0" + "sha256:267adcf6e68d77ba154334a3e4fc921b8e63cbb38ca00d33d40655d4228502bc", + "sha256:26f33e8f6a70c255767e3c3f957ccafc7f1f706b966e110b855bfe944511f1f9", + "sha256:3cd2c044517f38d1b577f05927fb9729d3396f1d44d0c659a445599e79519792", + "sha256:4a03416915b82b81af5502459a8a9dd62a3c299b295dcdf470877cb948d655f2", + "sha256:4ce1e995aeecf7cc32380bc11598bfdfa017d592259d5da00fc7ded11e61d022", + "sha256:4f53e4128c81ca3212ff4cf097c797ab44646a40b42ec02a891155cd7a2ba4d8", + "sha256:4fa72a52a906425416f41738728268072d5acfd48cbe7796af07a923236bcf96", + "sha256:66dd45eb9530e3dde8f7c009f84568bc7cac489b93d04ac86e3111fb46e470c2", + "sha256:6923d077d9ae9e8bacbdb1c07ae78405a9306c8fd1af13bfa06ca891095eb995", + "sha256:833401b15de1bb92791d7b6fb353d4af60dc688eaa521bd97203dcd2d124a7c1", + "sha256:8416ed88ddc057bab0526d4e4e9f3660f614ac2394b5e019a628cdfff3733849", + "sha256:892daa86384994fdf4856cb43c93f40cbe80f7f95bb5da94971b39c7f54b3a9c", + "sha256:98be759efdb5e5fa161e46d404f4e0ce388e72fbf7d9baf010aff16689e22abe", + "sha256:a6d28e7f14ecf3b2ad67c4f106841218c8ab12a0683b1528534a6c87d2307af3", + "sha256:b1d6ebc891607e71fd9da71688fcf332a6630b7f5b7f5549e6e631821c0e5d90", + "sha256:b2a2b0d276a136146e012154baefaea2758ef1f56ae9f4e01c612b0831e0bd2f", + "sha256:b87dfa9f10a470eee7f24234a37d1d5f51e5f5fa9eeffda7c282e2b8f5162eb1", + "sha256:bac0d6f7728a9cc3c1e06d4fcbac12aaa70e9379b3025b27ec1226f0e2d404cf", + "sha256:c991112622baee0ae4d55c008380c32ecfd0ad417bcd0417ba432e6ba7328caa", + "sha256:cda422d54ee7905bfc53ee6915ab68fe7b230cacf581110df4272ee10462aadc", + "sha256:d3148b6ba3923c5850ea197a91a42683f946dba7e8eb82dfa211ab7e708de939", + "sha256:d6033b4ffa34ef70f0b8086fd4c3df4bf801fee485a8a7d4519399818351aa8e", + "sha256:ddff0b2bd7edcc8c82d1adde6dbbf5e60d57ce985402541cd2985c27f7bec2a0", + "sha256:e23cb7f1d8e0f93addf0cae3c5b6f00324cccb4a7949ee558d7b6ca973ab8ae9", + "sha256:effd2ba52cee4ceff1a77f20d2a9f9bf8d50353c854a282b8760ac15b9833168", + "sha256:f90c2267101010de42f7273c94a1f026e56cbc043f9330acd8a80e64300aba33", + "sha256:f960375e9823ae6a07072ff7f8a85954e5a6434f97869f50d0e41649a1c8144f", + "sha256:fcf32bf76dc25e30ed793145a57426064520890d7c02866eb93d3e4abe516948" + ], + "version": "==1.14.1" }, "chardet": { "hashes": [ @@ -113,71 +113,72 @@ "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==7.1.2" }, "confluent-kafka": { "hashes": [ - "sha256:1b10a9e4ede8c7ee382c16075b55275963d3fe9b8eec3fc511d0868847cc6eed", - "sha256:1c46cbc2eb0876f0cdbd33ed7ea684ed1b009a25b65cf87736d3506d2f4ae57e", - "sha256:2500a78334d642e49b98710722e548c0e3d5dc4c6eae63f02d66448678ed2922", - "sha256:2515771b18d190df2182881abcf02fe8fde0aab567402ff36295b35cd495de65", - "sha256:3150c8875511e2cea4086206f3c10448f744c9c35f9033fd0874c8c55f7b87e2", - "sha256:4b0a3c47f9183570e9ee77ae8c36080fbc1996045251e25772944e4dadf1db21", - "sha256:4f875798bbc766767b9c6ed95b084fde851e0bf074527ab0daffa87f4e750635", - "sha256:515049659b045b99e0464d5ff5def4785478490563bc5ac1341a4f29dc335e82", - "sha256:52088adf1abdf3a384a54ec7a3bfaa0b61e5da8cc03a2e26a8351bbbf49f72a9", - "sha256:5342d3ff348b8082eaa4c63f4c82a72f3bf0ef8efa12a8580c890fa6e160f761", - "sha256:55734905c5a8642e596cf1e60ec4d86f05d31a185cbc71d1c73430bb0c08db19", - "sha256:624349587e97135996383c58edd8d53b38c57d653e6536c1f816049fc75faea3", - "sha256:804a7d71b3cb61444930af67986064c9555b8c33f05a27003ea314d6c847e522", - "sha256:931231853cec933addfafa27772177dcfab899d82e2e39fe7485c0602088daf7", - "sha256:a4f5edc1d7958bbf5f12ba83c1f83e22a66daa9c4318c7f28c5bb1db9289fe09", - "sha256:a591936a90095144451f041315239b2c823b7a15fa820cf45e45c422591345d6", - "sha256:a6eb8f3f553e98a6ef0d00f9cf8e4e8dde73c914a43a00fecef97330de80bcea", - "sha256:aa48215edcf16071d44ba29951c82c5f541d5ec915590aff0b4240e8e13f3ba3", - "sha256:bfacb9fa0e3a5e31a5ac9a5da15de656e95e7153e022ec5620095b76a6098ec0", - "sha256:bfbcbe7068690369ac2de3fe953854de34ad5e901157e96bcb990ca8b86d1d93", - "sha256:c2660807e5c1ecd723e280f76918794c3fd84595000c1e8de1f254f5d89a785c", - "sha256:c42ff838ee5e248f95f65b5adca4e2fdd4a2817fa26cede36d83a426e0f1370c", - "sha256:c5b741764d8ea2b8334fdaf4b56297c5bab780142f1c0cad0bd642cac30cb89e", - "sha256:dac33a04f73093de275953867a05de244560aa9842def6316cbb52bc0f02eff3", - "sha256:f1695a00789795f9f798588bb62688b563baf471a76ca20fa01c957844938d7d", - "sha256:f25836e03559a381ba74b9a6940b716e61ba8ae2db2d5d3a40accbc60617e1af" + "sha256:00acc73f7d49961bf427f5e4fd6c0a220a6bfa5ccc91e0ad1f9ffa1751a169b0", + "sha256:0a59afbb90bdd22b9acdd3bb134f5ee1dff3cc5df55eaf52bf97b2f8d0d00de3", + "sha256:13b0e2011560f461ff39daf38089dd7f91404b3e66dba0456ccce0700f93c4f2", + "sha256:175c7064c8f19975616974558c45f42c147a202d4b1c0b0a83afefb920367696", + "sha256:22d7201d1aa89f1c5546749e781492925ed3eb0d7bd8f781fc57294cd45ddde3", + "sha256:3034cacc3b0d03eb3ce39cc5a64c1070d223870246f5d90c9113996be9db7df8", + "sha256:3e2d4f55ca952aeada3831d6615dc13a8a42c8e97175855ca08bbc6e6091b080", + "sha256:5a1c47320d6afc5b2599f8f8e143aed6845a2d903facde984606e02f10f11221", + "sha256:7b03bd9cc7b5e4df0a27eed359762c61a35313d4981ef1d9b418069eee454e66", + "sha256:85ff4823770ce2efaabb46d88e5ae26a840e0051fd481abaa805f21a5a84d003", + "sha256:9534cd2c0313df75b70eb4cf729382998970d97bbdda5cf3aef7081b855ccebe", + "sha256:99b13d0957a5967c85aee6138ef5f9acec90294267a549c5683744f20cf5d7b4", + "sha256:9a1c77291c1ac4b991aa0358f2f44636686eb8f52fb628502d30c312160a14e9", + "sha256:9c47b8aacfe347bffd86bf75b98626718912b63df87f256dff1abc06a0355410", + "sha256:a116382ae67e0d6a54684bab4ee9b1be54e789d031a6e5e74c3edc657c79d23c", + "sha256:b1c89f3653385acc5da71570e03281f35ac6960367f2b2a426ae431deb1a1a35", + "sha256:bb77276d569f511abe4a5b32a53f8a30285bc7be68219e5711a44720bf356ac2", + "sha256:bbd9633552840ab9367fb762ea21272759db8caec2c34ff16ee28be177644cdf", + "sha256:bfdfa81e4e72d2c24e408a5e199aae0a477499ae40647dfa6906d002d9b07f38", + "sha256:c7461d6db081c23a6d38ceba348e7c178d7e974cf22c45ba8a4918ecb8855a44", + "sha256:d6a5d4c72360a75e875e88f7cce42b66a786d037ca2002303ab1c580d49caf53", + "sha256:dabed41cc60d1fc6d3cb44a90fe02e5192c9bf0f73c7b35761981e62ecabc592", + "sha256:dd544847c713eeeb525031348ff6ffea4ecdd11c13590893e599a9d4676a9bd4", + "sha256:eba169a9de8c978c9f33c763857c5279eceac46a4fd55a381c2528b9d4b3359e", + "sha256:f2d1ee0bfdf618017bbfaa42406546155c1a86263e4f286295318578c723803b" ], "index": "pypi", - "version": "==1.4.2" + "version": "==1.5.0" }, "cryptography": { "hashes": [ - "sha256:091d31c42f444c6f519485ed528d8b451d1a0c7bf30e8ca583a0cac44b8a0df6", - "sha256:18452582a3c85b96014b45686af264563e3e5d99d226589f057ace56196ec78b", - "sha256:1dfa985f62b137909496e7fc182dac687206d8d089dd03eaeb28ae16eec8e7d5", - "sha256:1e4014639d3d73fbc5ceff206049c5a9a849cefd106a49fa7aaaa25cc0ce35cf", - "sha256:22e91636a51170df0ae4dcbd250d318fd28c9f491c4e50b625a49964b24fe46e", - "sha256:3b3eba865ea2754738616f87292b7f29448aec342a7c720956f8083d252bf28b", - "sha256:651448cd2e3a6bc2bb76c3663785133c40d5e1a8c1a9c5429e4354201c6024ae", - "sha256:726086c17f94747cedbee6efa77e99ae170caebeb1116353c6cf0ab67ea6829b", - "sha256:844a76bc04472e5135b909da6aed84360f522ff5dfa47f93e3dd2a0b84a89fa0", - "sha256:88c881dd5a147e08d1bdcf2315c04972381d026cdb803325c03fe2b4a8ed858b", - "sha256:96c080ae7118c10fcbe6229ab43eb8b090fccd31a09ef55f83f690d1ef619a1d", - "sha256:a0c30272fb4ddda5f5ffc1089d7405b7a71b0b0f51993cb4e5dbb4590b2fc229", - "sha256:bb1f0281887d89617b4c68e8db9a2c42b9efebf2702a3c5bf70599421a8623e3", - "sha256:c447cf087cf2dbddc1add6987bbe2f767ed5317adb2d08af940db517dd704365", - "sha256:c4fd17d92e9d55b84707f4fd09992081ba872d1a0c610c109c18e062e06a2e55", - "sha256:d0d5aeaedd29be304848f1c5059074a740fa9f6f26b84c5b63e8b29e73dfc270", - "sha256:daf54a4b07d67ad437ff239c8a4080cfd1cc7213df57d33c97de7b4738048d5e", - "sha256:e993468c859d084d5579e2ebee101de8f5a27ce8e2159959b6673b418fd8c785", - "sha256:f118a95c7480f5be0df8afeb9a11bd199aa20afab7a96bcf20409b411a3a85f0" - ], - "version": "==2.9.2" + "sha256:0c608ff4d4adad9e39b5057de43657515c7da1ccb1807c3a27d4cf31fc923b4b", + "sha256:0cbfed8ea74631fe4de00630f4bb592dad564d57f73150d6f6796a24e76c76cd", + "sha256:124af7255ffc8e964d9ff26971b3a6153e1a8a220b9a685dc407976ecb27a06a", + "sha256:384d7c681b1ab904fff3400a6909261cae1d0939cc483a68bdedab282fb89a07", + "sha256:45741f5499150593178fc98d2c1a9c6722df88b99c821ad6ae298eff0ba1ae71", + "sha256:4b9303507254ccb1181d1803a2080a798910ba89b1a3c9f53639885c90f7a756", + "sha256:4d355f2aee4a29063c10164b032d9fa8a82e2c30768737a2fd56d256146ad559", + "sha256:51e40123083d2f946794f9fe4adeeee2922b581fa3602128ce85ff813d85b81f", + "sha256:8713ddb888119b0d2a1462357d5946b8911be01ddbf31451e1d07eaa5077a261", + "sha256:8e924dbc025206e97756e8903039662aa58aa9ba357d8e1d8fc29e3092322053", + "sha256:8ecef21ac982aa78309bb6f092d1677812927e8b5ef204a10c326fc29f1367e2", + "sha256:8ecf9400d0893836ff41b6f977a33972145a855b6efeb605b49ee273c5e6469f", + "sha256:9367d00e14dee8d02134c6c9524bb4bd39d4c162456343d07191e2a0b5ec8b3b", + "sha256:a09fd9c1cca9a46b6ad4bea0a1f86ab1de3c0c932364dbcf9a6c2a5eeb44fa77", + "sha256:ab49edd5bea8d8b39a44b3db618e4783ef84c19c8b47286bf05dfdb3efb01c83", + "sha256:bea0b0468f89cdea625bb3f692cd7a4222d80a6bdafd6fb923963f2b9da0e15f", + "sha256:bec7568c6970b865f2bcebbe84d547c52bb2abadf74cefce396ba07571109c67", + "sha256:ce82cc06588e5cbc2a7df3c8a9c778f2cb722f56835a23a68b5a7264726bb00c", + "sha256:dea0ba7fe6f9461d244679efa968d215ea1f989b9c1957d7f10c21e5c7c09ad6" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==3.0" }, "dateparser": { "hashes": [ - "sha256:1b1f0e3034f82d1f92b45fa445826da6a36d67af8a1169e04869685594276011", - "sha256:fb5bfde4795fa4b179fe05c2c25b3981f785de26bec37e247dee1079c63d5689" + "sha256:7552c994f893b5cb8fcf103b4cd2ff7f57aab9bfd2619fdf0cf571c0740fd90b", + "sha256:e875efd8c57c85c2d02b238239878db59ff1971f5a823457fcc69e493bf6ebfa" ], "index": "pypi", - "version": "==0.7.4" + "version": "==0.7.6" }, "elasticsearch": { "hashes": [ @@ -196,7 +197,8 @@ "version": "==6.4.0" }, "fatcat-openapi-client": { - "path": "./../python_openapi_client" + "path": "./../python_openapi_client", + "version": "==0.3.2" }, "flask": { "hashes": [ @@ -255,29 +257,32 @@ }, "ftfy": { "hashes": [ - "sha256:67f9c8b33a4b742376a3eda11b0e3bd5c0cbe719d95ea0bfd3736a7bdd1c24c8" + "sha256:51c7767f8c4b47d291fcef30b9625fb5341c06a31e6a3b627039c706c42f3720" ], "index": "pypi", - "version": "==5.7" + "version": "==5.8" }, "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" }, "idna": { "hashes": [ - "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", - "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" + "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", + "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" ], - "version": "==2.9" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.10" }, "itsdangerous": { "hashes": [ "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.1.0" }, "jinja2": { @@ -285,6 +290,7 @@ "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==2.11.2" }, "kazoo": { @@ -312,35 +318,40 @@ }, "lxml": { "hashes": [ - "sha256:06748c7192eab0f48e3d35a7adae609a329c6257495d5e53878003660dc0fec6", - "sha256:0790ddca3f825dd914978c94c2545dbea5f56f008b050e835403714babe62a5f", - "sha256:1aa7a6197c1cdd65d974f3e4953764eee3d9c7b67e3966616b41fab7f8f516b7", - "sha256:22c6d34fdb0e65d5f782a4d1a1edb52e0a8365858dafb1c08cb1d16546cf0786", - "sha256:2754d4406438c83144f9ffd3628bbe2dcc6d62b20dbc5c1ec4bc4385e5d44b42", - "sha256:27ee0faf8077c7c1a589573b1450743011117f1aa1a91d5ae776bbc5ca6070f2", - "sha256:2b02c106709466a93ed424454ce4c970791c486d5fcdf52b0d822a7e29789626", - "sha256:2d1ddce96cf15f1254a68dba6935e6e0f1fe39247de631c115e84dd404a6f031", - "sha256:4f282737d187ae723b2633856085c31ae5d4d432968b7f3f478a48a54835f5c4", - "sha256:51bb4edeb36d24ec97eb3e6a6007be128b720114f9a875d6b370317d62ac80b9", - "sha256:7eee37c1b9815e6505847aa5e68f192e8a1b730c5c7ead39ff317fde9ce29448", - "sha256:7fd88cb91a470b383aafad554c3fe1ccf6dfb2456ff0e84b95335d582a799804", - "sha256:9144ce36ca0824b29ebc2e02ca186e54040ebb224292072250467190fb613b96", - "sha256:925baf6ff1ef2c45169f548cc85204433e061360bfa7d01e1be7ae38bef73194", - "sha256:a636346c6c0e1092ffc202d97ec1843a75937d8c98aaf6771348ad6422e44bb0", - "sha256:a87dbee7ad9dce3aaefada2081843caf08a44a8f52e03e0a4cc5819f8398f2f4", - "sha256:a9e3b8011388e7e373565daa5e92f6c9cb844790dc18e43073212bb3e76f7007", - "sha256:afb53edf1046599991fb4a7d03e601ab5f5422a5435c47ee6ba91ec3b61416a6", - "sha256:b26719890c79a1dae7d53acac5f089d66fd8cc68a81f4e4bd355e45470dc25e1", - "sha256:b7462cdab6fffcda853338e1741ce99706cdf880d921b5a769202ea7b94e8528", - "sha256:b77975465234ff49fdad871c08aa747aae06f5e5be62866595057c43f8d2f62c", - "sha256:c47a8a5d00060122ca5908909478abce7bbf62d812e3fc35c6c802df8fb01fe7", - "sha256:c79e5debbe092e3c93ca4aee44c9a7631bdd407b2871cb541b979fd350bbbc29", - "sha256:d8d40e0121ca1606aa9e78c28a3a7d88a05c06b3ca61630242cded87d8ce55fa", - "sha256:ee2be8b8f72a2772e72ab926a3bccebf47bb727bda41ae070dc91d1fb759b726", - "sha256:f95d28193c3863132b1f55c1056036bf580b5a488d908f7d22a04ace8935a3a9", - "sha256:fadd2a63a2bfd7fb604508e553d1cf68eca250b2fbdbd81213b5f6f2fbf23529" - ], - "version": "==4.5.1" + "sha256:05a444b207901a68a6526948c7cc8f9fe6d6f24c70781488e32fd74ff5996e3f", + "sha256:08fc93257dcfe9542c0a6883a25ba4971d78297f63d7a5a26ffa34861ca78730", + "sha256:107781b213cf7201ec3806555657ccda67b1fccc4261fb889ef7fc56976db81f", + "sha256:121b665b04083a1e85ff1f5243d4a93aa1aaba281bc12ea334d5a187278ceaf1", + "sha256:1fa21263c3aba2b76fd7c45713d4428dbcc7644d73dcf0650e9d344e433741b3", + "sha256:2b30aa2bcff8e958cd85d907d5109820b01ac511eae5b460803430a7404e34d7", + "sha256:4b4a111bcf4b9c948e020fd207f915c24a6de3f1adc7682a2d92660eb4e84f1a", + "sha256:5591c4164755778e29e69b86e425880f852464a21c7bb53c7ea453bbe2633bbe", + "sha256:59daa84aef650b11bccd18f99f64bfe44b9f14a08a28259959d33676554065a1", + "sha256:5a9c8d11aa2c8f8b6043d845927a51eb9102eb558e3f936df494e96393f5fd3e", + "sha256:5dd20538a60c4cc9a077d3b715bb42307239fcd25ef1ca7286775f95e9e9a46d", + "sha256:74f48ec98430e06c1fa8949b49ebdd8d27ceb9df8d3d1c92e1fdc2773f003f20", + "sha256:786aad2aa20de3dbff21aab86b2fb6a7be68064cbbc0219bde414d3a30aa47ae", + "sha256:7ad7906e098ccd30d8f7068030a0b16668ab8aa5cda6fcd5146d8d20cbaa71b5", + "sha256:80a38b188d20c0524fe8959c8ce770a8fdf0e617c6912d23fc97c68301bb9aba", + "sha256:8f0ec6b9b3832e0bd1d57af41f9238ea7709bbd7271f639024f2fc9d3bb01293", + "sha256:92282c83547a9add85ad658143c76a64a8d339028926d7dc1998ca029c88ea6a", + "sha256:94150231f1e90c9595ccc80d7d2006c61f90a5995db82bccbca7944fd457f0f6", + "sha256:9dc9006dcc47e00a8a6a029eb035c8f696ad38e40a27d073a003d7d1443f5d88", + "sha256:a76979f728dd845655026ab991df25d26379a1a8fc1e9e68e25c7eda43004bed", + "sha256:aa8eba3db3d8761db161003e2d0586608092e217151d7458206e243be5a43843", + "sha256:bea760a63ce9bba566c23f726d72b3c0250e2fa2569909e2d83cda1534c79443", + "sha256:c3f511a3c58676147c277eff0224c061dd5a6a8e1373572ac817ac6324f1b1e0", + "sha256:c9d317efde4bafbc1561509bfa8a23c5cab66c44d49ab5b63ff690f5159b2304", + "sha256:cc411ad324a4486b142c41d9b2b6a722c534096963688d879ea6fa8a35028258", + "sha256:cdc13a1682b2a6241080745b1953719e7fe0850b40a5c71ca574f090a1391df6", + "sha256:cfd7c5dd3c35c19cec59c63df9571c67c6d6e5c92e0fe63517920e97f61106d1", + "sha256:e1cacf4796b20865789083252186ce9dc6cc59eca0c2e79cca332bdff24ac481", + "sha256:e70d4e467e243455492f5de463b72151cc400710ac03a0678206a5f27e79ddef", + "sha256:ecc930ae559ea8a43377e8b60ca6f8d61ac532fc57efb915d899de4a67928efd", + "sha256:f161af26f596131b63b236372e4ce40f3167c1b5b5d459b29d2514bd8c9dc9ee" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==4.5.2" }, "markupsafe": { "hashes": [ @@ -378,6 +389,7 @@ "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.1.1" }, "misaka": { @@ -399,6 +411,7 @@ "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889", "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==3.1.0" }, "pathlib2": { @@ -411,16 +424,17 @@ }, "pycountry": { "hashes": [ - "sha256:3c57aa40adcf293d59bebaffbe60d8c39976fba78d846a018dc0c2ec9c6cb3cb" + "sha256:81084a53d3454344c0292deebc20fcd0a1488c136d4900312cbd465cf552cb42" ], "index": "pypi", - "version": "==19.8.18" + "version": "==20.7.3" }, "pycparser": { "hashes": [ "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.20" }, "pygal": { @@ -448,10 +462,10 @@ }, "pylatexenc": { "hashes": [ - "sha256:bf15f2de2159501c9aa753929b5e0bec9aede4bfecb3b355f0bba8868eba0d1a" + "sha256:cd51da5ed908251c600b3f27fc1a8690727ba1e5f0b65f613aea1aca91c2530b" ], "index": "pypi", - "version": "==2.4" + "version": "==2.6" }, "pymacaroons": { "hashes": [ @@ -463,29 +477,25 @@ }, "pynacl": { "hashes": [ - "sha256:05c26f93964373fc0abe332676cb6735f0ecad27711035b9472751faa8521255", - "sha256:0c6100edd16fefd1557da078c7a31e7b7d7a52ce39fdca2bec29d4f7b6e7600c", - "sha256:0d0a8171a68edf51add1e73d2159c4bc19fc0718e79dec51166e940856c2f28e", - "sha256:1c780712b206317a746ace34c209b8c29dbfd841dfbc02aa27f2084dd3db77ae", - "sha256:2424c8b9f41aa65bbdbd7a64e73a7450ebb4aa9ddedc6a081e7afcc4c97f7621", - "sha256:2d23c04e8d709444220557ae48ed01f3f1086439f12dbf11976e849a4926db56", - "sha256:30f36a9c70450c7878053fa1344aca0145fd47d845270b43a7ee9192a051bf39", - "sha256:37aa336a317209f1bb099ad177fef0da45be36a2aa664507c5d72015f956c310", - "sha256:4943decfc5b905748f0756fdd99d4f9498d7064815c4cf3643820c9028b711d1", - "sha256:53126cd91356342dcae7e209f840212a58dcf1177ad52c1d938d428eebc9fee5", - "sha256:57ef38a65056e7800859e5ba9e6091053cd06e1038983016effaffe0efcd594a", - "sha256:5bd61e9b44c543016ce1f6aef48606280e45f892a928ca7068fba30021e9b786", - "sha256:6482d3017a0c0327a49dddc8bd1074cc730d45db2ccb09c3bac1f8f32d1eb61b", - "sha256:7d3ce02c0784b7cbcc771a2da6ea51f87e8716004512493a2b69016326301c3b", - "sha256:a14e499c0f5955dcc3991f785f3f8e2130ed504fa3a7f44009ff458ad6bdd17f", - "sha256:a39f54ccbcd2757d1d63b0ec00a00980c0b382c62865b61a505163943624ab20", - "sha256:aabb0c5232910a20eec8563503c153a8e78bbf5459490c49ab31f6adf3f3a415", - "sha256:bd4ecb473a96ad0f90c20acba4f0bf0df91a4e03a1f4dd6a4bdc9ca75aa3a715", - "sha256:bf459128feb543cfca16a95f8da31e2e65e4c5257d2f3dfa8c0c1031139c9c92", - "sha256:e2da3c13307eac601f3de04887624939aca8ee3c9488a0bb0eca4fb9401fc6b1", - "sha256:f67814c38162f4deb31f68d590771a29d5ae3b1bd64b75cf232308e5c74777e0" - ], - "version": "==1.3.0" + "sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4", + "sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4", + "sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574", + "sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d", + "sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25", + "sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f", + "sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505", + "sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122", + "sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7", + "sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420", + "sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f", + "sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96", + "sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6", + "sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514", + "sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff", + "sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.4.0" }, "python-dateutil": { "hashes": [ @@ -497,11 +507,11 @@ }, "python-dotenv": { "hashes": [ - "sha256:25c0ff1a3e12f4bde8d592cc254ab075cfe734fc5dd989036716fd17ee7e5ec7", - "sha256:3b9909bc96b0edc6b01586e1eed05e71174ef4e04c71da5786370cebea53ad74" + "sha256:8c10c99a1b25d9a68058a1ad6f90381a62ba68230ca93966882a4dbc3bc9c33d", + "sha256:c10863aee750ad720f4f43436565e4c1698798d763b63234fb5021b6c616e423" ], "index": "pypi", - "version": "==0.13.0" + "version": "==0.14.0" }, "python-magic": { "hashes": [ @@ -527,6 +537,9 @@ "version": "==2020.1" }, "raven": { + "extras": [ + "flask" + ], "hashes": [ "sha256:3fa6de6efa2493a7c827472e984ce9b020797d0da16f1db67197bcc23c8fae54", "sha256:44a13f87670836e153951af9a3c80405d36b43097db869a36e92809673692ce4" @@ -536,37 +549,37 @@ }, "regex": { "hashes": [ - "sha256:1386e75c9d1574f6aa2e4eb5355374c8e55f9aac97e224a8a5a6abded0f9c927", - "sha256:27ff7325b297fb6e5ebb70d10437592433601c423f5acf86e5bc1ee2919b9561", - "sha256:329ba35d711e3428db6b45a53b1b13a0a8ba07cbbcf10bbed291a7da45f106c3", - "sha256:3a9394197664e35566242686d84dfd264c07b20f93514e2e09d3c2b3ffdf78fe", - "sha256:51f17abbe973c7673a61863516bdc9c0ef467407a940f39501e786a07406699c", - "sha256:579ea215c81d18da550b62ff97ee187b99f1b135fd894a13451e00986a080cad", - "sha256:70c14743320a68c5dac7fc5a0f685be63bc2024b062fe2aaccc4acc3d01b14a1", - "sha256:7e61be8a2900897803c293247ef87366d5df86bf701083b6c43119c7c6c99108", - "sha256:8044d1c085d49673aadb3d7dc20ef5cb5b030c7a4fa253a593dda2eab3059929", - "sha256:89d76ce33d3266173f5be80bd4efcbd5196cafc34100fdab814f9b228dee0fa4", - "sha256:99568f00f7bf820c620f01721485cad230f3fb28f57d8fbf4a7967ec2e446994", - "sha256:a7c37f048ec3920783abab99f8f4036561a174f1314302ccfa4e9ad31cb00eb4", - "sha256:c2062c7d470751b648f1cacc3f54460aebfc261285f14bc6da49c6943bd48bdd", - "sha256:c9bce6e006fbe771a02bda468ec40ffccbf954803b470a0345ad39c603402577", - "sha256:ce367d21f33e23a84fb83a641b3834dd7dd8e9318ad8ff677fbfae5915a239f7", - "sha256:ce450ffbfec93821ab1fea94779a8440e10cf63819be6e176eb1973a6017aff5", - "sha256:ce5cc53aa9fbbf6712e92c7cf268274eaff30f6bd12a0754e8133d85a8fb0f5f", - "sha256:d466967ac8e45244b9dfe302bbe5e3337f8dc4dec8d7d10f5e950d83b140d33a", - "sha256:d881c2e657c51d89f02ae4c21d9adbef76b8325fe4d5cf0e9ad62f850f3a98fd", - "sha256:e565569fc28e3ba3e475ec344d87ed3cd8ba2d575335359749298a0899fe122e", - "sha256:ea55b80eb0d1c3f1d8d784264a6764f931e172480a2f1868f2536444c5f01e01" - ], - "version": "==2020.5.14" + "sha256:0dc64ee3f33cd7899f79a8d788abfbec168410be356ed9bd30bbd3f0a23a7204", + "sha256:1269fef3167bb52631ad4fa7dd27bf635d5a0790b8e6222065d42e91bede4162", + "sha256:14a53646369157baa0499513f96091eb70382eb50b2c82393d17d7ec81b7b85f", + "sha256:3a3af27a8d23143c49a3420efe5b3f8cf1a48c6fc8bc6856b03f638abc1833bb", + "sha256:46bac5ca10fb748d6c55843a931855e2727a7a22584f302dd9bb1506e69f83f6", + "sha256:4c037fd14c5f4e308b8370b447b469ca10e69427966527edcab07f52d88388f7", + "sha256:51178c738d559a2d1071ce0b0f56e57eb315bcf8f7d4cf127674b533e3101f88", + "sha256:5ea81ea3dbd6767873c611687141ec7b06ed8bab43f68fad5b7be184a920dc99", + "sha256:6961548bba529cac7c07af2fd4d527c5b91bb8fe18995fed6044ac22b3d14644", + "sha256:75aaa27aa521a182824d89e5ab0a1d16ca207318a6b65042b046053cfc8ed07a", + "sha256:7a2dd66d2d4df34fa82c9dc85657c5e019b87932019947faece7983f2089a840", + "sha256:8a51f2c6d1f884e98846a0a9021ff6861bdb98457879f412fdc2b42d14494067", + "sha256:9c568495e35599625f7b999774e29e8d6b01a6fb684d77dee1f56d41b11b40cd", + "sha256:9eddaafb3c48e0900690c1727fba226c4804b8e6127ea409689c3bb492d06de4", + "sha256:bbb332d45b32df41200380fff14712cb6093b61bd142272a10b16778c418e98e", + "sha256:bc3d98f621898b4a9bc7fecc00513eec8f40b5b83913d74ccb445f037d58cd89", + "sha256:c11d6033115dc4887c456565303f540c44197f4fc1a2bfb192224a301534888e", + "sha256:c50a724d136ec10d920661f1442e4a8b010a4fe5aebd65e0c2241ea41dbe93dc", + "sha256:d0a5095d52b90ff38592bbdc2644f17c6d495762edf47d876049cfd2968fbccf", + "sha256:d6cff2276e502b86a25fd10c2a96973fdb45c7a977dca2138d661417f3728341", + "sha256:e46d13f38cfcbb79bfdb2964b0fe12561fe633caf964a77a5f8d4e45fe5d2ef7" + ], + "version": "==2020.7.14" }, "requests": { "hashes": [ - "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", - "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" + "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b", + "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898" ], "index": "pypi", - "version": "==2.23.0" + "version": "==2.24.0" }, "requests-file": { "hashes": [ @@ -596,6 +609,7 @@ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.15.0" }, "soupsieve": { @@ -603,6 +617,7 @@ "sha256:1634eea42ab371d3d346309b93df7870a88610f0725d47528be902a0d95ecc55", "sha256:a59dc181727e95d25f781f0eb4fd1825ff45590ec8ff49eadfd7f1a537cc0232" ], + "markers": "python_version >= '3.5'", "version": "==2.0.1" }, "tabulate": { @@ -620,6 +635,14 @@ "index": "pypi", "version": "==2.2.2" }, + "toml": { + "hashes": [ + "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", + "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" + ], + "index": "pypi", + "version": "==0.10.1" + }, "tzlocal": { "hashes": [ "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44", @@ -629,62 +652,66 @@ }, "urllib3": { "hashes": [ - "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", - "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115" + "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a", + "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461" ], - "version": "==1.25.9" + "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.25.10" }, "wcwidth": { "hashes": [ - "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1", - "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1" + "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784", + "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83" ], - "version": "==0.1.9" + "version": "==0.2.5" }, "werkzeug": { "hashes": [ "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==1.0.1" }, "wtforms": { "hashes": [ - "sha256:6ff8635f4caeed9f38641d48cfe019d0d3896f41910ab04494143fc027866e1b", - "sha256:861a13b3ae521d6700dac3b2771970bd354a63ba7043ecc3a82b5288596a1972" + "sha256:43f19879b2a9b8dfd81d2e4e427ce44d3e5c09dbe08f2af8f4be9586b7dfc33d", + "sha256:715ebd303f47384bf6468fd9dfff52c6acc400e71204df8acfa6ef7bf40e1c27" ], "index": "pypi", - "version": "==2.3.1" + "version": "==2.3.2" } }, "develop": { "astroid": { "hashes": [ - "sha256:4c17cea3e592c21b6e222f673868961bad77e1f985cb1694ed077475a89229c1", - "sha256:d8506842a3faf734b81599c8b98dcc423de863adcc1999248480b18bd31a0f38" + "sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703", + "sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386" ], - "version": "==2.4.1" + "markers": "python_version >= '3.5'", + "version": "==2.4.2" }, "attrs": { "hashes": [ "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==19.3.0" }, "backcall": { "hashes": [ - "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4", - "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2" + "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e", + "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255" ], - "version": "==0.1.0" + "version": "==0.2.0" }, "certifi": { "hashes": [ - "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304", - "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519" + "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", + "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" ], - "version": "==2020.4.5.1" + "version": "==2020.6.20" }, "chardet": { "hashes": [ @@ -695,39 +722,43 @@ }, "coverage": { "hashes": [ - "sha256:00f1d23f4336efc3b311ed0d807feb45098fc86dee1ca13b3d6768cdab187c8a", - "sha256:01333e1bd22c59713ba8a79f088b3955946e293114479bbfc2e37d522be03355", - "sha256:0cb4be7e784dcdc050fc58ef05b71aa8e89b7e6636b99967fadbdba694cf2b65", - "sha256:0e61d9803d5851849c24f78227939c701ced6704f337cad0a91e0972c51c1ee7", - "sha256:1601e480b9b99697a570cea7ef749e88123c04b92d84cedaa01e117436b4a0a9", - "sha256:2742c7515b9eb368718cd091bad1a1b44135cc72468c731302b3d641895b83d1", - "sha256:2d27a3f742c98e5c6b461ee6ef7287400a1956c11421eb574d843d9ec1f772f0", - "sha256:402e1744733df483b93abbf209283898e9f0d67470707e3c7516d84f48524f55", - "sha256:5c542d1e62eece33c306d66fe0a5c4f7f7b3c08fecc46ead86d7916684b36d6c", - "sha256:5f2294dbf7875b991c381e3d5af2bcc3494d836affa52b809c91697449d0eda6", - "sha256:6402bd2fdedabbdb63a316308142597534ea8e1895f4e7d8bf7476c5e8751fef", - "sha256:66460ab1599d3cf894bb6baee8c684788819b71a5dc1e8fa2ecc152e5d752019", - "sha256:782caea581a6e9ff75eccda79287daefd1d2631cc09d642b6ee2d6da21fc0a4e", - "sha256:79a3cfd6346ce6c13145731d39db47b7a7b859c0272f02cdb89a3bdcbae233a0", - "sha256:7a5bdad4edec57b5fb8dae7d3ee58622d626fd3a0be0dfceda162a7035885ecf", - "sha256:8fa0cbc7ecad630e5b0f4f35b0f6ad419246b02bc750de7ac66db92667996d24", - "sha256:a027ef0492ede1e03a8054e3c37b8def89a1e3c471482e9f046906ba4f2aafd2", - "sha256:a3f3654d5734a3ece152636aad89f58afc9213c6520062db3978239db122f03c", - "sha256:a82b92b04a23d3c8a581fc049228bafde988abacba397d57ce95fe95e0338ab4", - "sha256:acf3763ed01af8410fc36afea23707d4ea58ba7e86a8ee915dfb9ceff9ef69d0", - "sha256:adeb4c5b608574a3d647011af36f7586811a2c1197c861aedb548dd2453b41cd", - "sha256:b83835506dfc185a319031cf853fa4bb1b3974b1f913f5bb1a0f3d98bdcded04", - "sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e", - "sha256:bf9cb9a9fd8891e7efd2d44deb24b86d647394b9705b744ff6f8261e6f29a730", - "sha256:c317eaf5ff46a34305b202e73404f55f7389ef834b8dbf4da09b9b9b37f76dd2", - "sha256:dbe8c6ae7534b5b024296464f387d57c13caa942f6d8e6e0346f27e509f0f768", - "sha256:de807ae933cfb7f0c7d9d981a053772452217df2bf38e7e6267c9cbf9545a796", - "sha256:dead2ddede4c7ba6cb3a721870f5141c97dc7d85a079edb4bd8d88c3ad5b20c7", - "sha256:dec5202bfe6f672d4511086e125db035a52b00f1648d6407cc8e526912c0353a", - "sha256:e1ea316102ea1e1770724db01998d1603ed921c54a86a2efcb03428d5417e489", - "sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052" - ], - "version": "==5.1" + "sha256:098a703d913be6fbd146a8c50cc76513d726b022d170e5e98dc56d958fd592fb", + "sha256:16042dc7f8e632e0dcd5206a5095ebd18cb1d005f4c89694f7f8aafd96dd43a3", + "sha256:1adb6be0dcef0cf9434619d3b892772fdb48e793300f9d762e480e043bd8e716", + "sha256:27ca5a2bc04d68f0776f2cdcb8bbd508bbe430a7bf9c02315cd05fb1d86d0034", + "sha256:28f42dc5172ebdc32622a2c3f7ead1b836cdbf253569ae5673f499e35db0bac3", + "sha256:2fcc8b58953d74d199a1a4d633df8146f0ac36c4e720b4a1997e9b6327af43a8", + "sha256:304fbe451698373dc6653772c72c5d5e883a4aadaf20343592a7abb2e643dae0", + "sha256:30bc103587e0d3df9e52cd9da1dd915265a22fad0b72afe54daf840c984b564f", + "sha256:40f70f81be4d34f8d491e55936904db5c527b0711b2a46513641a5729783c2e4", + "sha256:4186fc95c9febeab5681bc3248553d5ec8c2999b8424d4fc3a39c9cba5796962", + "sha256:46794c815e56f1431c66d81943fa90721bb858375fb36e5903697d5eef88627d", + "sha256:4869ab1c1ed33953bb2433ce7b894a28d724b7aa76c19b11e2878034a4e4680b", + "sha256:4f6428b55d2916a69f8d6453e48a505c07b2245653b0aa9f0dee38785939f5e4", + "sha256:52f185ffd3291196dc1aae506b42e178a592b0b60a8610b108e6ad892cfc1bb3", + "sha256:538f2fd5eb64366f37c97fdb3077d665fa946d2b6d95447622292f38407f9258", + "sha256:64c4f340338c68c463f1b56e3f2f0423f7b17ba6c3febae80b81f0e093077f59", + "sha256:675192fca634f0df69af3493a48224f211f8db4e84452b08d5fcebb9167adb01", + "sha256:700997b77cfab016533b3e7dbc03b71d33ee4df1d79f2463a318ca0263fc29dd", + "sha256:8505e614c983834239f865da2dd336dcf9d72776b951d5dfa5ac36b987726e1b", + "sha256:962c44070c281d86398aeb8f64e1bf37816a4dfc6f4c0f114756b14fc575621d", + "sha256:9e536783a5acee79a9b308be97d3952b662748c4037b6a24cbb339dc7ed8eb89", + "sha256:9ea749fd447ce7fb1ac71f7616371f04054d969d412d37611716721931e36efd", + "sha256:a34cb28e0747ea15e82d13e14de606747e9e484fb28d63c999483f5d5188e89b", + "sha256:a3ee9c793ffefe2944d3a2bd928a0e436cd0ac2d9e3723152d6fd5398838ce7d", + "sha256:aab75d99f3f2874733946a7648ce87a50019eb90baef931698f96b76b6769a46", + "sha256:b1ed2bdb27b4c9fc87058a1cb751c4df8752002143ed393899edb82b131e0546", + "sha256:b360d8fd88d2bad01cb953d81fd2edd4be539df7bfec41e8753fe9f4456a5082", + "sha256:b8f58c7db64d8f27078cbf2a4391af6aa4e4767cc08b37555c4ae064b8558d9b", + "sha256:c1bbb628ed5192124889b51204de27c575b3ffc05a5a91307e7640eff1d48da4", + "sha256:c2ff24df02a125b7b346c4c9078c8936da06964cc2d276292c357d64378158f8", + "sha256:c890728a93fffd0407d7d37c1e6083ff3f9f211c83b4316fae3778417eab9811", + "sha256:c96472b8ca5dc135fb0aa62f79b033f02aa434fb03a8b190600a5ae4102df1fd", + "sha256:ce7866f29d3025b5b34c2e944e66ebef0d92e4a4f2463f7266daa03a1332a651", + "sha256:e26c993bd4b220429d4ec8c1468eca445a4064a61c74ca08da7429af9bc53bb0" + ], + "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": "==5.2.1" }, "decorator": { "hashes": [ @@ -738,34 +769,35 @@ }, "flake8": { "hashes": [ - "sha256:c69ac1668e434d37a2d2880b3ca9aafd54b3a10a3ac1ab101d22f29e29cf8634", - "sha256:ccaa799ef9893cebe69fdfefed76865aeaefbb94cb8545617b2298786a4de9a5" + "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c", + "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208" ], "index": "pypi", - "version": "==3.8.2" + "version": "==3.8.3" }, "idna": { "hashes": [ - "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", - "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" + "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", + "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" ], - "version": "==2.9" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.10" }, "importlib-metadata": { "hashes": [ - "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f", - "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e" + "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83", + "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070" ], "markers": "python_version < '3.8'", - "version": "==1.6.0" + "version": "==1.7.0" }, "ipython": { "hashes": [ - "sha256:5b241b84bbf0eb085d43ae9d46adf38a13b45929ca7774a740990c2c242534bb", - "sha256:f0126781d0f959da852fb3089e170ed807388e986a8dd4e6ac44855845b0fb1c" + "sha256:2dbcc8c27ca7d3cfe4fcdff7f45b27f9a8d3edfa70ff8024a71c7a8eb5f09d64", + "sha256:9f4fcb31d3b2c533333893b9172264e4821c1ac91839500f31bd43f2c59b3ccf" ], "index": "pypi", - "version": "==7.14.0" + "version": "==7.16.1" }, "ipython-genutils": { "hashes": [ @@ -779,14 +811,16 @@ "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==4.3.21" }, "jedi": { "hashes": [ - "sha256:cd60c93b71944d628ccac47df9a60fec53150de53d42dc10a7fc4b5ba6aae798", - "sha256:df40c97641cb943661d2db4c33c2e1ff75d491189423249e989bcea4464f3030" + "sha256:86ed7d9b750603e4ba582ea8edc678657fb4007894a12bcf6f4bb97892f31d20", + "sha256:98cc583fa0f2f8304968199b01b6b4b94f469a1f4a74c1560506ca2a211378b5" ], - "version": "==0.17.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==0.17.2" }, "lazy-object-proxy": { "hashes": [ @@ -812,6 +846,7 @@ "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.4.3" }, "mccabe": { @@ -823,24 +858,27 @@ }, "more-itertools": { "hashes": [ - "sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be", - "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982" + "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5", + "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2" ], - "version": "==8.3.0" + "markers": "python_version >= '3.5'", + "version": "==8.4.0" }, "packaging": { "hashes": [ "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==20.4" }, "parso": { "hashes": [ - "sha256:158c140fc04112dc45bca311633ae5033c2c2a7b732fa33d0955bad8152a8dd0", - "sha256:908e9fae2144a076d72ae4e25539143d40b8e3eafbaeae03c1bfe226f4cdf12c" + "sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea", + "sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9" ], - "version": "==0.7.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.7.1" }, "pexpect": { "hashes": [ @@ -869,6 +907,7 @@ "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.13.1" }, "prompt-toolkit": { @@ -876,6 +915,7 @@ "sha256:563d1a4140b63ff9dd587bda9557cffb2fe73650205ab6f4383092fb882e7dc8", "sha256:df7e9e63aea609b1da3a65641ceaf5bc7d05e0a04de5bd45d05dbeffbabf9e04" ], + "markers": "python_full_version >= '3.6.1'", "version": "==3.0.5" }, "psycopg2": { @@ -894,6 +934,7 @@ "sha256:d3b29d717d39d3580efd760a9a46a7418408acebbb784717c90d708c9ed5f055", "sha256:f7d46240f7a1ae1dd95aab38bd74f7428d46531f69219954266d669da60c0818" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.8.5" }, "ptyprocess": { @@ -905,16 +946,18 @@ }, "py": { "hashes": [ - "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa", - "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0" + "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2", + "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342" ], - "version": "==1.8.1" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.9.0" }, "pycodestyle": { "hashes": [ "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.6.0" }, "pyflakes": { @@ -922,6 +965,7 @@ "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.2.0" }, "pygments": { @@ -929,46 +973,48 @@ "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44", "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324" ], + "markers": "python_version >= '3.5'", "version": "==2.6.1" }, "pylint": { "hashes": [ - "sha256:b95e31850f3af163c2283ed40432f053acbc8fc6eba6a069cb518d9dbf71848c", - "sha256:dd506acce0427e9e08fb87274bcaa953d38b50a58207170dbf5b36cf3e16957b" + "sha256:7dd78437f2d8d019717dbf287772d0b2dbdfd13fc016aa7faa08d67bccc46adc", + "sha256:d0ece7d223fe422088b0e8f13fa0a1e8eb745ebffcb8ed53d3e95394b6101a1c" ], "index": "pypi", - "version": "==2.5.2" + "version": "==2.5.3" }, "pyparsing": { "hashes": [ "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" }, "pytest": { "hashes": [ - "sha256:95c710d0a72d91c13fae35dce195633c929c3792f54125919847fdcdf7caa0d3", - "sha256:eb2b5e935f6a019317e455b6da83dd8650ac9ffd2ee73a7b657a30873d67a698" + "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1", + "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8" ], "index": "pypi", - "version": "==5.4.2" + "version": "==5.4.3" }, "pytest-cov": { "hashes": [ - "sha256:b6a814b8ed6247bd81ff47f038511b57fe1ce7f4cc25b9106f1a4b106f1d9322", - "sha256:c87dfd8465d865655a8213859f1b4749b43448b5fae465cb981e16d52a811424" + "sha256:1a629dc9f48e53512fcbfda6b07de490c374b0c83c55ff7a1720b3fccff0ac87", + "sha256:6e6d18092dce6fad667cd7020deed816f858ad3b49d5b5e2b1cc1c97a4dba65c" ], "index": "pypi", - "version": "==2.9.0" + "version": "==2.10.0" }, "pytest-mock": { "hashes": [ - "sha256:997729451dfc36b851a9accf675488c7020beccda15e11c75632ee3d1b1ccd71", - "sha256:ce610831cedeff5331f4e2fc453a5dd65384303f680ab34bee2c6533855b431c" + "sha256:5564c7cd2569b603f8451ec77928083054d8896046830ca763ed68f4112d17c7", + "sha256:7122d55505d5ed5a6f3df940ad174b3f606ecae5e9bc379569cdcbd4cd9d2b83" ], "index": "pypi", - "version": "==3.1.0" + "version": "==3.2.0" }, "pytest-pylint": { "hashes": [ @@ -987,25 +1033,26 @@ }, "requests": { "hashes": [ - "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", - "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" + "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b", + "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898" ], "index": "pypi", - "version": "==2.23.0" + "version": "==2.24.0" }, "responses": { "hashes": [ - "sha256:1a78bc010b20a5022a2c0cb76b8ee6dc1e34d887972615ebd725ab9a166a4960", - "sha256:3d596d0be06151330cb230a2d630717ab20f7a81f205019481e206eb5db79915" + "sha256:7bb697a5fedeb41d81e8b87f152d453d5cab42dcd1691b6a7d6097e94d33f373", + "sha256:af94d28cdfb48ded0ad82a5216616631543650f440334a693479b8991a6594a2" ], "index": "pypi", - "version": "==0.10.14" + "version": "==0.10.15" }, "six": { "hashes": [ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.15.0" }, "toml": { @@ -1013,6 +1060,7 @@ "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" ], + "index": "pypi", "version": "==0.10.1" }, "traitlets": { @@ -1046,22 +1094,23 @@ "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" ], - "markers": "implementation_name == 'cpython' and python_version < '3.8'", + "markers": "python_version < '3.8' and implementation_name == 'cpython'", "version": "==1.4.1" }, "urllib3": { "hashes": [ - "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", - "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115" + "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a", + "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461" ], - "version": "==1.25.9" + "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.25.10" }, "wcwidth": { "hashes": [ - "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1", - "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1" + "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784", + "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83" ], - "version": "==0.1.9" + "version": "==0.2.5" }, "wrapt": { "hashes": [ @@ -1074,6 +1123,7 @@ "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" ], + "markers": "python_version >= '3.6'", "version": "==3.1.0" } } -- cgit v1.2.3 From 79ebac108ece33e77b53cfd18a370dd728baa702 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Thu, 30 Jul 2020 23:44:57 -0700 Subject: pipenv: lock pycountry to 19.10 version datacite importer had errors otherwise --- python/Pipfile | 2 +- python/Pipfile.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/python/Pipfile b/python/Pipfile index b8569589..4c0977a1 100644 --- a/python/Pipfile +++ b/python/Pipfile @@ -53,7 +53,7 @@ elasticsearch = ">=6.0.0,<7.0.0" dateparser = ">=0.7" langdetect = "*" pathlib2 = "*" -pycountry = "*" +pycountry = "==19.8.18" tldextract = "*" toml = ">=0.10" diff --git a/python/Pipfile.lock b/python/Pipfile.lock index af6478a5..9b1a5d15 100644 --- a/python/Pipfile.lock +++ b/python/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e7d45cf28f10e1b2b01bd25ca2f660011c6ed9195974431b42c9e2d4d409bdff" + "sha256": "b2e4ed259ddec6c4016e6857ec856146c0fb279b3d6a91d138c1611e9a591c3a" }, "pipfile-spec": 6, "requires": { @@ -424,10 +424,10 @@ }, "pycountry": { "hashes": [ - "sha256:81084a53d3454344c0292deebc20fcd0a1488c136d4900312cbd465cf552cb42" + "sha256:3c57aa40adcf293d59bebaffbe60d8c39976fba78d846a018dc0c2ec9c6cb3cb" ], "index": "pypi", - "version": "==20.7.3" + "version": "==19.8.18" }, "pycparser": { "hashes": [ @@ -675,11 +675,11 @@ }, "wtforms": { "hashes": [ - "sha256:43f19879b2a9b8dfd81d2e4e427ce44d3e5c09dbe08f2af8f4be9586b7dfc33d", - "sha256:715ebd303f47384bf6468fd9dfff52c6acc400e71204df8acfa6ef7bf40e1c27" + "sha256:7b504fc724d0d1d4d5d5c114e778ec88c37ea53144683e084215eed5155ada4c", + "sha256:81195de0ac94fbc8368abbaf9197b88c4f3ffd6c2719b5bf5fc9da744f3d829c" ], "index": "pypi", - "version": "==2.3.2" + "version": "==2.3.3" } }, "develop": { -- cgit v1.2.3 From 69e281deaf601b39e8ef51d603e3e5e16dc71777 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Wed, 29 Jul 2020 19:27:35 -0700 Subject: basic toml transform helper --- python/fatcat_tools/transforms/__init__.py | 2 +- python/fatcat_tools/transforms/entities.py | 22 +++++++++++++++++++--- python/tests/transform_toml.py | 22 ++++++++++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 python/tests/transform_toml.py diff --git a/python/fatcat_tools/transforms/__init__.py b/python/fatcat_tools/transforms/__init__.py index 3f4700ff..7cc8f699 100644 --- a/python/fatcat_tools/transforms/__init__.py +++ b/python/fatcat_tools/transforms/__init__.py @@ -1,5 +1,5 @@ -from .entities import entity_to_dict, entity_from_json, entity_from_dict +from .entities import entity_to_dict, entity_from_json, entity_from_dict, entity_from_toml, entity_to_toml from .elasticsearch import release_to_elasticsearch, container_to_elasticsearch, changelog_to_elasticsearch, file_to_elasticsearch from .csl import release_to_csl, citeproc_csl from .ingest import release_ingest_request diff --git a/python/fatcat_tools/transforms/entities.py b/python/fatcat_tools/transforms/entities.py index 53455e85..3892d54a 100644 --- a/python/fatcat_tools/transforms/entities.py +++ b/python/fatcat_tools/transforms/entities.py @@ -1,9 +1,10 @@ import json +import toml import collections from fatcat_openapi_client import ApiClient -def entity_to_dict(entity, api_client=None): +def entity_to_dict(entity, api_client=None) -> dict: """ Hack to take advantage of the code-generated serialization code. @@ -17,7 +18,7 @@ def entity_to_dict(entity, api_client=None): api_client = ApiClient() return api_client.sanitize_for_serialization(entity) -def entity_from_json(json_str, entity_type, api_client=None): +def entity_from_json(json_str: str, entity_type, api_client=None): """ Hack to take advantage of the code-generated deserialization code @@ -29,6 +30,21 @@ def entity_from_json(json_str, entity_type, api_client=None): thing.data = json_str return api_client.deserialize(thing, entity_type) -def entity_from_dict(obj, entity_type, api_client=None): +def entity_from_dict(obj: dict, entity_type, api_client=None): json_str = json.dumps(obj) return entity_from_json(json_str, entity_type, api_client=api_client) + +def entity_to_toml(entity, api_client=None, pop_fields=None) -> str: + """ + pop_fields parameter can be used to strip out some fields from the resulting + TOML. Eg, for fields which should not be edited, like the ident. + """ + obj = entity_to_dict(entity, api_client=api_client) + pop_fields = pop_fields or [] + for k in pop_fields: + obj.pop(k, None) + return toml.dumps(obj) + +def entity_from_toml(toml_str: str, entity_type, api_client=None): + obj = toml.loads(toml_str) + return entity_from_dict(obj, entity_type, api_client=api_client) diff --git a/python/tests/transform_toml.py b/python/tests/transform_toml.py new file mode 100644 index 00000000..d12ba027 --- /dev/null +++ b/python/tests/transform_toml.py @@ -0,0 +1,22 @@ + +import json + +from fatcat_tools import * +from fatcat_openapi_client import * +from import_crossref import crossref_importer +from fixtures import * + + +def test_basic_toml(crossref_importer): + with open('tests/files/crossref-works.single.json', 'r') as f: + # not a single line + raw = json.loads(f.read()) + r = crossref_importer.parse_record(raw) + r.state = 'active' + toml_str = entity_to_toml(r) + r2 = entity_from_toml(toml_str, ReleaseEntity) + assert r == r2 + + toml_str = entity_to_toml(r, pop_fields=['ident', 'revision', 'blah', 'extra']) + r3 = entity_from_toml(toml_str, ReleaseEntity) + assert r != r3 -- cgit v1.2.3 From 81a3d98f8bf647ded6923eb77d3df791888e0a2a Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Thu, 30 Jul 2020 17:26:49 -0700 Subject: generic helpers for TOML editing routes --- python/fatcat_web/editing_routes.py | 183 ++++++++++++++++++++++++++++++++++-- python/fatcat_web/forms.py | 28 +++++- 2 files changed, 201 insertions(+), 10 deletions(-) diff --git a/python/fatcat_web/editing_routes.py b/python/fatcat_web/editing_routes.py index ffce35f3..9598a754 100644 --- a/python/fatcat_web/editing_routes.py +++ b/python/fatcat_web/editing_routes.py @@ -2,7 +2,7 @@ from flask import render_template, abort, redirect, session, flash from flask_login import login_required -from fatcat_openapi_client import Editgroup +from fatcat_openapi_client import * from fatcat_openapi_client.rest import ApiException from fatcat_tools.transforms import * from fatcat_web import app, api, auth_api @@ -13,6 +13,82 @@ from fatcat_web.entity_helpers import * ### Helper Methods ########################################################## +def generic_entity_create_from_toml(user_api, entity_type: str, editgroup_id: str, toml_str: str) -> EntityEdit: + if entity_type == 'container': + entity = entity_from_toml(toml_str, ContainerEntity) + edit = user_api.create_container(editgroup_id, entity) + elif entity_type == 'creator': + entity = entity_from_toml(toml_str, CreatorEntity) + edit = user_api.create_creator(editgroup_id, entity) + elif entity_type == 'file': + entity = entity_from_toml(toml_str, FileEntity) + edit = user_api.create_file(editgroup_id, entity) + elif entity_type == 'fileset': + entity = entity_from_toml(toml_str, FilesetEntity) + edit = user_api.create_fileset(editgroup_id, entity) + elif entity_type == 'webcapture': + entity = entity_from_toml(toml_str, WebcaptureEntity) + edit = user_api.create_webcapture(editgroup_id, entity) + elif entity_type == 'release': + entity = entity_from_toml(toml_str, ReleaseEntity) + edit = user_api.create_release(editgroup_id, entity) + elif entity_type == 'work': + entity = entity_from_toml(toml_str, WorkEntity) + edit = user_api.create_work(editgroup_id, entity) + else: + raise NotImplementedError + return edit + +def generic_entity_delete_edit(user_api, entity_type: str, editgroup_id: str, edit_id: str) -> None: + try: + if entity_type == 'container': + user_api.delete_container_edit(editgroup_id, edit_id) + elif entity_type == 'creator': + user_api.delete_creator_edit(editgroup_id, edit_id) + elif entity_type == 'file': + user_api.delete_file_edit(editgroup_id, edit_id) + elif entity_type == 'fileset': + user_api.delete_fileset_edit(editgroup_id, edit_id) + elif entity_type == 'webcapture': + user_api.delete_webcapture_edit(editgroup_id, edit_id) + elif entity_type == 'release': + user_api.delete_release_edit(editgroup_id, edit_id) + elif entity_type == 'work': + user_api.delete_work_edit(editgroup_id, edit_id) + else: + raise NotImplementedError + except ApiException as ae: + if ae.status == 404: + pass + else: + raise ae + +def generic_entity_update_from_toml(user_api, entity_type: str, editgroup_id: str, existing_ident, toml_str: str) -> EntityEdit: + if entity_type == 'container': + entity = entity_from_toml(toml_str, ContainerEntity) + edit = user_api.update_container(editgroup_id, existing_ident, entity) + elif entity_type == 'creator': + entity = entity_from_toml(toml_str, CreatorEntity) + edit = user_api.update_creator(editgroup_id, existing_ident, entity) + elif entity_type == 'file': + entity = entity_from_toml(toml_str, FileEntity) + edit = user_api.update_file(editgroup_id, existing_ident, entity) + elif entity_type == 'fileset': + entity = entity_from_toml(toml_str, FilesetEntity) + edit = user_api.update_fileset(editgroup_id, existing_ident, entity) + elif entity_type == 'webcapture': + entity = entity_from_toml(toml_str, WebcaptureEntity) + edit = user_api.update_webcapture(editgroup_id, existing_ident, entity) + elif entity_type == 'release': + entity = entity_from_toml(toml_str, ReleaseEntity) + edit = user_api.update_release(editgroup_id, existing_ident, entity) + elif entity_type == 'work': + entity = entity_from_toml(toml_str, WorkEntity) + edit = user_api.update_work(editgroup_id, existing_ident, entity) + else: + raise NotImplementedError + return edit + def form_editgroup_get_or_create(api, edit_form): """ This function expects a submitted, validated edit form @@ -138,14 +214,7 @@ def generic_entity_edit(editgroup_id, entity_type, existing_ident, edit_template # a "update pointer" edit existing.revision = None try: - if entity_type == 'container': - user_api.delete_container_edit(editgroup.editgroup_id, existing_edit.edit_id) - elif entity_type == 'file': - user_api.delete_file_edit(editgroup.editgroup_id, existing_edit.edit_id) - elif entity_type == 'release': - user_api.delete_release_edit(editgroup.editgroup_id, existing_edit.edit_id) - else: - raise NotImplementedError + generic_entity_delete_edit(user_api, entity_type, editgroup.editgroup_id, existing_edit.edit_id) except ApiException as ae: if ae.status == 404: pass @@ -194,6 +263,102 @@ def generic_entity_edit(editgroup_id, entity_type, existing_ident, edit_template existing_ident=existing_ident, editgroup=editgroup, potential_editgroups=potential_editgroups), status +def generic_entity_toml_edit(editgroup_id, entity_type, existing_ident, edit_template): + """ + Similar to generic_entity_edit(), but for TOML editing mode. + + Handles both creation and update/edit paths. + """ + + # fetch editgroup (if set) or 404 + editgroup = None + if editgroup_id: + try: + editgroup = api.get_editgroup(editgroup_id) + except ApiException as ae: + raise ae + + # check that editgroup is edit-able + if editgroup.changelog_index != None: + flash("Editgroup already merged") + abort(400) + + # fetch entity (if set) or 404 + existing = None + existing_edit = None + if editgroup and existing_ident: + existing, existing_edit = generic_get_editgroup_entity(editgroup, entity_type, existing_ident) + elif existing_ident: + existing = generic_get_entity(entity_type, existing_ident) + + # parse form (if submitted) + status = 200 + form = EntityTomlForm() + + if form.is_submitted(): + if form.validate_on_submit(): + # API on behalf of user + user_api = auth_api(session['api_token']) + if not editgroup: + editgroup = form_editgroup_get_or_create(user_api, form) + + if editgroup: + + if not existing_ident: # it's a create + try: + edit = generic_entity_create_from_toml(user_api, entity_type, editgroup.editgroup_id, form.toml.data) + except ValueError as ve: + form.toml.errors = [ve] + status = 400 + except ApiException as ae: + app.log.warning(ae) + raise ae + if status == 200: + return redirect('/editgroup/{}/{}/{}'.format(editgroup.editgroup_id, entity_type, edit.ident)) + else: # it's an update + # TODO: some danger of wiping database state here is + # "updated edit" causes, eg, a 4xx error. Better to allow + # this in the API itself. For now, form validation *should* + # catch most errors, and if not editor can hit back and try + # again. This means, need to allow failure of deletion. + if existing_edit: + # need to clear revision on object or this becomes just + # a "update pointer" edit + existing.revision = None + generic_entity_delete_edit(user_api, entity_type, editgroup.editgroup_id, existing_edit.edit_id) + try: + edit = generic_entity_update_from_toml(user_api, entity_type, editgroup.editgroup_id, existing.ident, form.toml.data) + except ValueError as ve: + form.toml.errors = [ve] + status = 400 + except ApiException as ae: + app.log.warning(ae) + raise ae + if status == 200: + return redirect('/editgroup/{}/{}/{}'.format(editgroup.editgroup_id, entity_type, edit.ident)) + else: + status = 400 + elif form.errors: + status = 400 + app.log.info("form errors (did not validate): {}".format(form.errors)) + + else: # form is not submitted + if existing: + form = EntityTomlForm.from_entity(existing) + + editor_editgroups = api.get_editor_editgroups(session['editor']['editor_id'], limit=20) + potential_editgroups = [e for e in editor_editgroups if e.changelog_index == None and e.submitted == None] + + if not form.is_submitted(): + # default to most recent not submitted, fallback to "create new" + form.editgroup_id.data = "" + if potential_editgroups: + form.editgroup_id.data = potential_editgroups[0].editgroup_id + + return render_template(edit_template, form=form, entity_type=entity_type, + existing_ident=existing_ident, editgroup=editgroup, + potential_editgroups=potential_editgroups), status + def generic_edit_delete(editgroup_id, entity_type, edit_id): # fetch editgroup (if set) or 404 editgroup = None diff --git a/python/fatcat_web/forms.py b/python/fatcat_web/forms.py index 15585bf6..b06788b8 100644 --- a/python/fatcat_web/forms.py +++ b/python/fatcat_web/forms.py @@ -4,10 +4,12 @@ Note: in thoery could use, eg, https://github.com/christabor/swagger_wtforms, but can't find one that is actually maintained. """ +import toml from flask_wtf import FlaskForm from wtforms import SelectField, DateField, StringField, IntegerField, \ - HiddenField, FormField, FieldList, validators + HiddenField, FormField, FieldList, validators, ValidationError, TextAreaField +from fatcat_tools import entity_to_toml from fatcat_openapi_client import ContainerEntity, FileEntity, \ ReleaseEntity, ReleaseContrib, FileUrl, ReleaseExtIds @@ -413,3 +415,27 @@ class SavePaperNowForm(FlaskForm): ingest_request['link_source'] = 'arxiv' ingest_request['link_source_id'] = release.ext_ids.arxiv return ingest_request + +def valid_toml(form, field): + try: + toml.loads(field.data) + except toml.TomlDecodeError as tpe: + raise ValidationError(tpe) + +class EntityTomlForm(EntityEditForm): + + toml = TextAreaField( + "TOML", + [validators.DataRequired(), + valid_toml, + ], + ) + + @staticmethod + def from_entity(entity): + """ + Initializes form with TOML version of existing entity + """ + etf = EntityTomlForm() + etf.toml.data = entity_to_toml(entity) + return etf -- cgit v1.2.3 From 18933a623450d42e5834cf31cee1f4cfa7f5bb7a Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Thu, 30 Jul 2020 17:27:11 -0700 Subject: editing: more 'raise' status instead of 'abort()' --- python/fatcat_web/editing_routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/fatcat_web/editing_routes.py b/python/fatcat_web/editing_routes.py index 9598a754..f3c6fdd0 100644 --- a/python/fatcat_web/editing_routes.py +++ b/python/fatcat_web/editing_routes.py @@ -151,7 +151,7 @@ def generic_entity_edit(editgroup_id, entity_type, existing_ident, edit_template try: editgroup = api.get_editgroup(editgroup_id) except ApiException as ae: - abort(ae.status) + raise ae # check that editgroup is edit-able if editgroup.changelog_index != None: -- cgit v1.2.3 From 90b06f6f6db1a40946ba280f7324b15fec2f667e Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Thu, 30 Jul 2020 17:28:11 -0700 Subject: generic HTML views for TOML editing --- python/fatcat_web/templates/base.html | 4 +++ python/fatcat_web/templates/edit_macros.html | 17 ++++++++++ .../fatcat_web/templates/entity_create_toml.html | 20 +++++++++++ python/fatcat_web/templates/entity_edit_toml.html | 39 ++++++++++++++++++++++ 4 files changed, 80 insertions(+) create mode 100644 python/fatcat_web/templates/entity_create_toml.html create mode 100644 python/fatcat_web/templates/entity_edit_toml.html diff --git a/python/fatcat_web/templates/base.html b/python/fatcat_web/templates/base.html index 18c66077..2c18ec44 100644 --- a/python/fatcat_web/templates/base.html +++ b/python/fatcat_web/templates/base.html @@ -25,6 +25,10 @@ @media only screen and (max-width: 479px) { .mobile-hide{ display: none !important; } } + + .field textarea#toml { + font-family: monospace; + } {% block extra_head %}{% endblock %} diff --git a/python/fatcat_web/templates/edit_macros.html b/python/fatcat_web/templates/edit_macros.html index a207e51e..60c17aa9 100644 --- a/python/fatcat_web/templates/edit_macros.html +++ b/python/fatcat_web/templates/edit_macros.html @@ -17,6 +17,23 @@ {%- endmacro %} +{% macro form_toml_field(field, div_classes="") -%} +

Raw Metadata (TOML)

+
+

TOML is a markup language, similar to + YAML or Wikitext. The schema here is the same as the Fatcat API JSON schema, but + with a syntax that is easier to read and edit by hand. All nested metadata + fields are available here; refer to the fatcat guide for metadata + documentation and style guide, or TOML documentation for syntax notes (eg, + what those double brackets mean, and how to represent lists of authors or + references). +
+
+ {{ field(rows=24) }} + {{ form_field_errors(field) }} +

+{%- endmacro %} + {% macro form_field_inline(field, div_classes="") -%}
diff --git a/python/fatcat_web/templates/entity_create_toml.html b/python/fatcat_web/templates/entity_create_toml.html new file mode 100644 index 00000000..ec5bc4a2 --- /dev/null +++ b/python/fatcat_web/templates/entity_create_toml.html @@ -0,0 +1,20 @@ +{% extends "entity_edit_toml.html" %} + +{% block edit_form_prefix %} +
+

Create New {{ entity_type }} Entity (TOML mode)

+ +
+{% endblock %} + +{% block edit_form_suffix %} +

+ +

+ New {{ entity_type }} entity will be part of the current editgroup, which + needs to be submited and approved before the entity will formally be included + in the catalog. +

+
+{% endblock %} + diff --git a/python/fatcat_web/templates/entity_edit_toml.html b/python/fatcat_web/templates/entity_edit_toml.html new file mode 100644 index 00000000..4b6e7b6d --- /dev/null +++ b/python/fatcat_web/templates/entity_edit_toml.html @@ -0,0 +1,39 @@ +{% import "edit_macros.html" as edit_macros %} +{% extends "base.html" %} + +{% block body %} +{% block edit_form_prefix %} +
+

Edit Entity (TOML mode)

+ +
+{% endblock %} + +

See the catalog + style guide for schema notes, and the editing + tutorial if this is your first time making an edit. + + {{ form.hidden_tag() }} + +

Editgroup Metadata

+ {{ edit_macros.editgroup_dropdown(form, editgroup, potential_editgroups) }} + + {{ edit_macros.form_toml_field(form.toml, "required") }} + +

Submit

+ {{ edit_macros.form_field_basic(form.edit_description) }} + This description will be attached to the individual edit, not to the + editgroup as a whole. + +{% block edit_form_suffix %} +

+ +

+ Edit will be part of the current editgroup, which needs to be submited and + approved before the change is included in the catalog. +

+
+{% endblock %} +{% endblock %} + -- cgit v1.2.3 From b14040ecb359d1575280b24eaab9bd0e4964e3f7 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Thu, 30 Jul 2020 17:31:17 -0700 Subject: wire up new TOML views --- python/fatcat_web/editing_routes.py | 193 +++++++++++++++++----- python/fatcat_web/templates/container_create.html | 4 +- python/fatcat_web/templates/container_edit.html | 5 + python/fatcat_web/templates/edit_macros.html | 2 +- python/fatcat_web/templates/editgroup_view.html | 2 +- python/fatcat_web/templates/entity_edit.html | 8 - python/fatcat_web/templates/entity_edit_toml.html | 12 ++ python/fatcat_web/templates/file_create.html | 4 +- python/fatcat_web/templates/file_edit.html | 5 + python/fatcat_web/templates/home.html | 12 +- python/fatcat_web/templates/release_create.html | 4 +- python/fatcat_web/templates/release_edit.html | 6 + python/tests/web_editing.py | 66 ++++++-- python/tests/web_entity_views.py | 16 +- 14 files changed, 256 insertions(+), 83 deletions(-) delete mode 100644 python/fatcat_web/templates/entity_edit.html diff --git a/python/fatcat_web/editing_routes.py b/python/fatcat_web/editing_routes.py index f3c6fdd0..e84b14f7 100644 --- a/python/fatcat_web/editing_routes.py +++ b/python/fatcat_web/editing_routes.py @@ -366,27 +366,18 @@ def generic_edit_delete(editgroup_id, entity_type, edit_id): try: editgroup = api.get_editgroup(editgroup_id) except ApiException as ae: - raise ae + abort(ae.status) # check that editgroup is edit-able if editgroup.changelog_index != None: - abort(400, "Editgroup already merged") + flash("Editgroup already merged") + abort(400) # API on behalf of user user_api = auth_api(session['api_token']) # do the deletion - try: - if entity_type == 'container': - user_api.delete_container_edit(editgroup.editgroup_id, edit_id) - elif entity_type == 'file': - user_api.delete_file_edit(editgroup.editgroup_id, edit_id) - elif entity_type == 'release': - user_api.delete_release_edit(editgroup.editgroup_id, edit_id) - else: - raise NotImplementedError - except ApiException as ae: - raise ae + generic_entity_delete_edit(user_api, entity_type, editgroup.editgroup_id, edit_id) return redirect("/editgroup/{}".format(editgroup_id)) @@ -452,69 +443,187 @@ def release_editgroup_edit(editgroup_id, ident): def release_edit_delete(editgroup_id, edit_id): return generic_edit_delete(editgroup_id, 'release', edit_id) +@app.route('/editgroup//creator/edit//delete', methods=['POST']) +def creator_edit_delete(editgroup_id, edit_id): + return generic_edit_delete(editgroup_id, 'creator', edit_id) + +@app.route('/editgroup//fileset/edit//delete', methods=['POST']) +def fileset_edit_delete(editgroup_id, edit_id): + return generic_edit_delete(editgroup_id, 'fileset', edit_id) -### Not-Implemented Views ################################################### +@app.route('/editgroup//webcapture/edit//delete', methods=['POST']) +def webcapture_edit_delete(editgroup_id, edit_id): + return generic_edit_delete(editgroup_id, 'webcapture', edit_id) + +@app.route('/editgroup//work/edit//delete', methods=['POST']) +def work_edit_delete(editgroup_id, edit_id): + return generic_edit_delete(editgroup_id, 'work', edit_id) + +### TOML Views ############################################################## + +@app.route('/container/create/toml', methods=['GET', 'POST']) +@login_required +def container_create_toml_view(): + return generic_entity_toml_edit(None, 'container', None, 'entity_create_toml.html') + +@app.route('/container//edit/toml', methods=['GET', 'POST']) +@login_required +def container_edit_toml(ident): + return generic_entity_toml_edit(None, 'container', ident, 'entity_edit_toml.html') + +@app.route('/editgroup//container//edit/toml', methods=['GET', 'POST']) +@login_required +def container_editgroup_edit_toml(editgroup_id, ident): + return generic_entity_toml_edit(editgroup_id, 'container', ident, 'entity_edit_toml.html') + +@app.route('/creator/create/toml', methods=['GET', 'POST']) +@login_required +def creator_create_toml_view(): + return generic_entity_toml_edit(None, 'creator', None, 'entity_create_toml.html') + +@app.route('/creator//edit/toml', methods=['GET', 'POST']) +@login_required +def creator_edit_toml(ident): + return generic_entity_toml_edit(None, 'creator', ident, 'entity_edit_toml.html') + +@app.route('/editgroup//creator//edit/toml', methods=['GET', 'POST']) +@login_required +def creator_editgroup_edit_toml(editgroup_id, ident): + return generic_entity_toml_edit(editgroup_id, 'creator', ident, 'entity_edit_toml.html') + +@app.route('/file/create/toml', methods=['GET', 'POST']) +@login_required +def file_create_toml_view(): + return generic_entity_toml_edit(None, 'file', None, 'entity_create_toml.html') + +@app.route('/file//edit/toml', methods=['GET', 'POST']) +@login_required +def file_edit_toml(ident): + return generic_entity_toml_edit(None, 'file', ident, 'entity_edit_toml.html') + +@app.route('/editgroup//file//edit/toml', methods=['GET', 'POST']) +@login_required +def file_editgroup_edit_toml(editgroup_id, ident): + return generic_entity_toml_edit(editgroup_id, 'file', ident, 'entity_edit_toml.html') + +@app.route('/fileset/create/toml', methods=['GET', 'POST']) +@login_required +def fileset_create_toml_view(): + return generic_entity_toml_edit(None, 'fileset', None, 'entity_create_toml.html') + +@app.route('/fileset//edit/toml', methods=['GET', 'POST']) +@login_required +def fileset_edit_toml(ident): + return generic_entity_toml_edit(None, 'fileset', ident, 'entity_edit_toml.html') + +@app.route('/editgroup//fileset//edit/toml', methods=['GET', 'POST']) +@login_required +def fileset_editgroup_edit_toml(editgroup_id, ident): + return generic_entity_toml_edit(editgroup_id, 'fileset', ident, 'entity_edit_toml.html') + +@app.route('/webcapture/create/toml', methods=['GET', 'POST']) +@login_required +def webcapture_create_toml_view(): + return generic_entity_toml_edit(None, 'webcapture', None, 'entity_create_toml.html') + +@app.route('/webcapture//edit/toml', methods=['GET', 'POST']) +@login_required +def webcapture_edit_toml(ident): + return generic_entity_toml_edit(None, 'webcapture', ident, 'entity_edit_toml.html') + +@app.route('/editgroup//webcapture//edit/toml', methods=['GET', 'POST']) +@login_required +def webcapture_editgroup_edit_toml(editgroup_id, ident): + return generic_entity_toml_edit(editgroup_id, 'webcapture', ident, 'entity_edit_toml.html') + +@app.route('/release/create/toml', methods=['GET', 'POST']) +@login_required +def release_create_toml_view(): + return generic_entity_toml_edit(None, 'release', None, 'entity_create_toml.html') + +@app.route('/release//edit/toml', methods=['GET', 'POST']) +@login_required +def release_edit_toml(ident): + return generic_entity_toml_edit(None, 'release', ident, 'entity_edit_toml.html') + +@app.route('/editgroup//release//edit/toml', methods=['GET', 'POST']) +@login_required +def release_editgroup_edit_toml(editgroup_id, ident): + return generic_entity_toml_edit(editgroup_id, 'release', ident, 'entity_edit_toml.html') + +@app.route('/work/create/toml', methods=['GET', 'POST']) +@login_required +def work_create_toml_view(): + return generic_entity_toml_edit(None, 'work', None, 'entity_create_toml.html') + +@app.route('/work//edit/toml', methods=['GET', 'POST']) +@login_required +def work_edit_toml(ident): + return generic_entity_toml_edit(None, 'work', ident, 'entity_edit_toml.html') + +@app.route('/editgroup//work//edit/toml', methods=['GET', 'POST']) +@login_required +def work_editgroup_edit_toml(editgroup_id, ident): + return generic_entity_toml_edit(editgroup_id, 'work', ident, 'entity_edit_toml.html') + +### TOML-Only Editing Redirects ################################################ @app.route('/creator/create', methods=['GET']) +@login_required def creator_create_view(): - return abort(404) + return redirect('/creator/create/toml') @app.route('/creator//edit', methods=['GET']) +@login_required def creator_edit(ident): - return render_template('entity_edit.html'), 404 + return redirect(f'/creator/{ident}/edit/toml') @app.route('/editgroup//creator//edit', methods=['GET', 'POST']) +@login_required def creator_editgroup_edit(editgroup_id, ident): - return abort(404) - -@app.route('/editgroup//creator/edit//delete', methods=['POST']) -def creator_edit_delete(editgroup_id, edit_id): - return abort(404) + return redirect(f'/editgroup/{editgroup_id}/creator/{ident}/edit/toml') @app.route('/fileset/create', methods=['GET']) +@login_required def fileset_create_view(): - return abort(404) + return redirect('/fileset/create/toml') @app.route('/fileset//edit', methods=['GET']) +@login_required def fileset_edit(ident): - return render_template('entity_edit.html'), 404 + return redirect(f'/fileset/{ident}/edit/toml') @app.route('/editgroup//fileset//edit', methods=['GET', 'POST']) +@login_required def fileset_editgroup_edit(editgroup_id, ident): - return abort(404) - -@app.route('/editgroup//fileset/edit//delete', methods=['POST']) -def fileset_edit_delete(editgroup_id, edit_id): - return abort(404) + return redirect(f'/editgroup/{editgroup_id}/fileset/{ident}/edit/toml') @app.route('/webcapture/create', methods=['GET']) +@login_required def webcapture_create_view(): - return abort(404) + return redirect('/webcapture/create/toml') @app.route('/webcapture//edit', methods=['GET']) +@login_required def webcapture_edit(ident): - return render_template('entity_edit.html'), 404 + return redirect(f'/webcapture/{ident}/edit/toml') @app.route('/editgroup//webcapture//edit', methods=['GET', 'POST']) +@login_required def webcapture_editgroup_edit(editgroup_id, ident): - return abort(404) - -@app.route('/editgroup//webcapture/edit//delete', methods=['POST']) -def webcapture_edit_delete(editgroup_id, edit_id): - return abort(404) + return redirect(f'/editgroup/{editgroup_id}/webcapture/{ident}/edit/toml') @app.route('/work/create', methods=['GET']) +@login_required def work_create_view(): - return abort(404) + return redirect('/work/create/toml') @app.route('/work//edit', methods=['GET']) +@login_required def work_edit(ident): - return render_template('entity_edit.html'), 404 + return redirect(f'/work/{ident}/edit/toml') @app.route('/editgroup//work//edit', methods=['GET', 'POST']) +@login_required def work_editgroup_edit(editgroup_id, ident): - return abort(404) - -@app.route('/editgroup//work/edit//delete', methods=['POST']) -def work_edit_delete(editgroup_id, edit_id): - return abort(404) + return redirect(f'/editgroup/{editgroup_id}/work/{ident}/edit/toml') diff --git a/python/fatcat_web/templates/container_create.html b/python/fatcat_web/templates/container_create.html index 5786d05d..be8c5671 100644 --- a/python/fatcat_web/templates/container_create.html +++ b/python/fatcat_web/templates/container_create.html @@ -9,13 +9,15 @@ a journal (eg, "New England Journal of Medicine"), conference proceedings, a book series, or a blog. Not all publications are in a container.
+

Experienced users can also use the TOML + creation form to access all metadata fields in a raw format. {% endblock %} {% block edit_form_suffix %}

- New entity will be part of the current editgroup, which needs to be + New container entity will be part of the current editgroup, which needs to be submited and approved before the entity will formally be included in the catalog. diff --git a/python/fatcat_web/templates/container_edit.html b/python/fatcat_web/templates/container_edit.html index 5188ce0d..fd07b3da 100644 --- a/python/fatcat_web/templates/container_edit.html +++ b/python/fatcat_web/templates/container_edit.html @@ -7,6 +7,11 @@

Edit Container Entity

+ +

Experienced users can also use the TOML editing form to access all metadata + fields in a raw format. {% endblock %}

See the catalog diff --git a/python/fatcat_web/templates/edit_macros.html b/python/fatcat_web/templates/edit_macros.html index 60c17aa9..d4839373 100644 --- a/python/fatcat_web/templates/edit_macros.html +++ b/python/fatcat_web/templates/edit_macros.html @@ -55,7 +55,7 @@ {% macro editgroup_dropdown(form, editgroup=None, potential_editgroups=None) -%} {% if editgroup %}

You are updating an existing un-merged editgroup: {{ editgroup.editgroup_id }}. -

Description: {{ editgroup.description }} +

Description: {{ editgroup.description or "" }} {% else %} {% if not potential_editgroups %}

You have no un-submitted editgroups in progress; a new one will be diff --git a/python/fatcat_web/templates/editgroup_view.html b/python/fatcat_web/templates/editgroup_view.html index e8146d19..a36dc3e5 100644 --- a/python/fatcat_web/templates/editgroup_view.html +++ b/python/fatcat_web/templates/editgroup_view.html @@ -25,7 +25,7 @@ updated {% endif %} [view edit] - {% if auth_to.edit and not editgroup.changelog_index and not editgroup.submitted and entity_type in ('release', 'file', 'container') %} + {% if auth_to.edit and not editgroup.changelog_index and not editgroup.submitted %} [re-edit] diff --git a/python/fatcat_web/templates/entity_edit.html b/python/fatcat_web/templates/entity_edit.html deleted file mode 100644 index 97f7bf46..00000000 --- a/python/fatcat_web/templates/entity_edit.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "base.html" %} -{% block body %} - -

Not Implemented

- -

Entity editing via the web interface isn't implemented yet. Sorry! - -{% endblock %} diff --git a/python/fatcat_web/templates/entity_edit_toml.html b/python/fatcat_web/templates/entity_edit_toml.html index 4b6e7b6d..807e4d2b 100644 --- a/python/fatcat_web/templates/entity_edit_toml.html +++ b/python/fatcat_web/templates/entity_edit_toml.html @@ -37,3 +37,15 @@ {% endblock %} {% endblock %} +{% block postscript %} + + +{% endblock %} diff --git a/python/fatcat_web/templates/file_create.html b/python/fatcat_web/templates/file_create.html index a7c99b96..affcfb6e 100644 --- a/python/fatcat_web/templates/file_create.html +++ b/python/fatcat_web/templates/file_create.html @@ -5,13 +5,15 @@

Create New File Entity

+

Experienced users can also use the TOML + creation form to access all metadata fields in a raw format. {% endblock %} {% block edit_form_suffix %}

- New entity will be part of the current editgroup, which needs to be + New file entity will be part of the current editgroup, which needs to be submited and approved before the entity will formally be included in the catalog. diff --git a/python/fatcat_web/templates/file_edit.html b/python/fatcat_web/templates/file_edit.html index e8a421b3..b7876fc5 100644 --- a/python/fatcat_web/templates/file_edit.html +++ b/python/fatcat_web/templates/file_edit.html @@ -7,6 +7,11 @@

Edit File Entity

+ +

Experienced users can also use the TOML editing form to access all metadata + fields in a raw format. {% endblock %}

See the catalog diff --git a/python/fatcat_web/templates/home.html b/python/fatcat_web/templates/home.html index 4557e212..de32d6a4 100644 --- a/python/fatcat_web/templates/home.html +++ b/python/fatcat_web/templates/home.html @@ -171,11 +171,10 @@ Creator
authors, editors, translators +
Create {% if config.FATCAT_DOMAIN == 'fatcat.wiki' %} - Author {% else %} - Author (prod)
Dummy
Realistic @@ -206,11 +205,10 @@

File Set
datasets, suplementary materials + Create {% if config.FATCAT_DOMAIN == 'fatcat.wiki' %} - Dataset {% else %} - Dataset (prod)
Dummy
Realistic @@ -218,12 +216,11 @@ Web Capture
HTML and interactive articles, blog posts + Create {% if config.FATCAT_DOMAIN == 'fatcat.wiki' %} - D-Lib
Blog Post {% else %} - D-Lib (prod)
Dummy
Realistic @@ -231,11 +228,10 @@ Work
for grouping Releases + Create {% if config.FATCAT_DOMAIN == 'fatcat.wiki' %} - Paper {% else %} - Paper (prod)
Dummy
Realistic diff --git a/python/fatcat_web/templates/release_create.html b/python/fatcat_web/templates/release_create.html index 5ec2efe5..4f5dabd7 100644 --- a/python/fatcat_web/templates/release_create.html +++ b/python/fatcat_web/templates/release_create.html @@ -5,13 +5,15 @@

Create New Release Entity

+

Experienced users can also use the TOML + creation form to access all metadata fields in a raw format. {% endblock %} {% block edit_form_suffix %}

- New entity will be part of the current editgroup, which needs to be + New release entity will be part of the current editgroup, which needs to be submited and approved before the entity will formally be included in the catalog. diff --git a/python/fatcat_web/templates/release_edit.html b/python/fatcat_web/templates/release_edit.html index a4a7e56f..21c8cf68 100644 --- a/python/fatcat_web/templates/release_edit.html +++ b/python/fatcat_web/templates/release_edit.html @@ -7,6 +7,11 @@

Edit Release Entity

+ +

Experienced users can also use the TOML editing form to access all metadata + fields in a raw format. {% endblock %}

See the catalog @@ -14,6 +19,7 @@ href="https://guide.fatcat.wiki/editing_quickstart.html">the editing tutorial if this is your first time making an edit. + {{ form.hidden_tag() }}

Editgroup Metadata

diff --git a/python/tests/web_editing.py b/python/tests/web_editing.py index 17f4f5ae..ea244388 100644 --- a/python/tests/web_editing.py +++ b/python/tests/web_editing.py @@ -2,7 +2,7 @@ from fixtures import * -def test_web_release_create_merge(app_admin, api): +def test_web_release_create_accept(app_admin, api): eg = quick_eg(api) @@ -129,18 +129,60 @@ def test_web_file_create(app_admin, api): follow_redirects=True) assert rv.status_code == 200 +DUMMY_DEMO_ENTITIES = { + 'container': 'aaaaaaaaaaaaaeiraaaaaaaaam', + 'creator': 'aaaaaaaaaaaaaircaaaaaaaaaq', + 'file': 'aaaaaaaaaaaaamztaaaaaaaaam', + 'fileset': 'aaaaaaaaaaaaaztgaaaaaaaaai', + 'webcapture': 'aaaaaaaaaaaaa53xaaaaaaaaai', + 'release': 'aaaaaaaaaaaaarceaaaaaaaaai', + 'work': 'aaaaaaaaaaaaavkvaaaaaaaaai', +} def test_web_edit_get(app_admin): # these are all existing entities - rv = app_admin.get('/release/aaaaaaaaaaaaarceaaaaaaaaai/edit') - assert rv.status_code == 200 - assert b'A bigger example' in rv.data - - rv = app_admin.get('/file/aaaaaaaaaaaaamztaaaaaaaaam/edit') - assert rv.status_code == 200 - assert b'ffc1005680cb620eec4c913437dfabbf311b535cfe16cbaeb2faec1f92afc362' in rv.data - - rv = app_admin.get('/container/aaaaaaaaaaaaaeiraaaaaaaaam/edit') - assert rv.status_code == 200 - assert b'1549-1277' in rv.data + for entity_type in ['release', 'file', 'container']: + rv = app_admin.get(f'/{entity_type}/{DUMMY_DEMO_ENTITIES[entity_type]}/edit') + assert rv.status_code == 200 + if entity_type == 'release': + assert b'A bigger example' in rv.data + elif entity_type == 'file': + assert b'ffc1005680cb620eec4c913437dfabbf311b535cfe16cbaeb2faec1f92afc362' in rv.data + elif entity_type == 'container': + assert b'1549-1277' in rv.data + + rv = app_admin.get(f'/{entity_type}/{DUMMY_DEMO_ENTITIES[entity_type]}/edit/toml') + assert rv.status_code == 200 + if entity_type == 'release': + assert b'A bigger example' in rv.data + elif entity_type == 'file': + assert b'ffc1005680cb620eec4c913437dfabbf311b535cfe16cbaeb2faec1f92afc362' in rv.data + elif entity_type == 'container': + assert b'1549-1277' in rv.data + + # TOML-only endpoints + for entity_type in ['creator', 'fileset', 'webcapture', 'work']: + rv = app_admin.get(f'/{entity_type}/{DUMMY_DEMO_ENTITIES[entity_type]}/edit') + assert rv.status_code == 302 + + rv = app_admin.get(f'/{entity_type}/{DUMMY_DEMO_ENTITIES[entity_type]}/edit/toml') + assert rv.status_code == 200 + + +def test_web_create_get(app_admin): + + for entity_type in ['release', 'file', 'container']: + rv = app_admin.get(f'/{entity_type}/create') + assert rv.status_code == 200 + + rv = app_admin.get(f'/{entity_type}/create/toml') + assert rv.status_code == 200 + + # these are TOML only + for entity_type in ['creator', 'fileset', 'webcapture', 'work']: + rv = app_admin.get(f'/{entity_type}/create') + assert rv.status_code == 302 + + rv = app_admin.get(f'/{entity_type}/create/toml') + assert rv.status_code == 200 diff --git a/python/tests/web_entity_views.py b/python/tests/web_entity_views.py index b01bd815..7b973ef2 100644 --- a/python/tests/web_entity_views.py +++ b/python/tests/web_entity_views.py @@ -210,9 +210,9 @@ def test_web_creator(app): rv = app.get('/creator/aaaaaaaaaaaaaircaaaaaaaaai') assert rv.status_code == 200 rv = app.get('/creator/aaaaaaaaaaaaaircaaaaaaaaai/edit') - assert rv.status_code == 404 + assert rv.status_code == 302 rv = app.get('/creator/create') - assert rv.status_code == 404 + assert rv.status_code == 302 def test_web_file(app): @@ -266,9 +266,9 @@ def test_web_fileset(app): rv = app.get('/fileset/aaaaaaaaaaaaaztgaaaaaaaaai') assert rv.status_code == 200 rv = app.get('/fileset/aaaaaaaaaaaaaztgaaaaaaaaai/edit') - assert rv.status_code == 404 + assert rv.status_code == 302 rv = app.get('/fileset/create') - assert rv.status_code == 404 + assert rv.status_code == 302 def test_web_webcatpure(app): @@ -277,9 +277,9 @@ def test_web_webcatpure(app): rv = app.get('/webcapture/aaaaaaaaaaaaa53xaaaaaaaaai') assert rv.status_code == 200 rv = app.get('/webcapture/aaaaaaaaaaaaa53xaaaaaaaaai/edit') - assert rv.status_code == 404 + assert rv.status_code == 302 rv = app.get('/webcapture/create') - assert rv.status_code == 404 + assert rv.status_code == 302 def test_web_release(app): @@ -376,6 +376,6 @@ def test_web_work(app): rv = app.get('/work/aaaaaaaaaaaaavkvaaaaaaaaai') assert rv.status_code == 200 rv = app.get('/work/aaaaaaaaaaaaavkvaaaaaaaaai/edit') - assert rv.status_code == 404 + assert rv.status_code == 302 rv = app.get('/work/create') - assert rv.status_code == 404 + assert rv.status_code == 302 -- cgit v1.2.3 From 6c2bc56b29d42737a62513520aec5dd148ccdcc9 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Thu, 30 Jul 2020 19:18:17 -0700 Subject: fix search redirect codes in new tests --- python/tests/web_search.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/tests/web_search.py b/python/tests/web_search.py index e34cd065..a7bf7ec7 100644 --- a/python/tests/web_search.py +++ b/python/tests/web_search.py @@ -10,22 +10,22 @@ from fixtures import * def test_generic_search(app): rv = app.get('/search?q=blood') - assert rv.status_code == 301 + assert rv.status_code == 302 assert "/release/search" in rv.location # file sha1sum rv = app.get('/search?q=0262d5351e8e7a0af27af8ceaf7b4e581da085f2') - assert rv.status_code == 301 + assert rv.status_code == 302 assert "/file/lookup" in rv.location # PMCID rv = app.get('/search?q=PMC12345') - assert rv.status_code == 301 + assert rv.status_code == 302 assert "/release/lookup" in rv.location # ISSN rv = app.get('/search?q=1234-5678') - assert rv.status_code == 301 + assert rv.status_code == 302 assert "/container/lookup" in rv.location def test_release_search(app, mocker): -- cgit v1.2.3 From a928882cb14eca8c50919f1af7489831b8341569 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Thu, 30 Jul 2020 19:36:01 -0700 Subject: remove some meta-fields from TOML form (all entities) --- python/fatcat_web/forms.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/python/fatcat_web/forms.py b/python/fatcat_web/forms.py index b06788b8..13776d10 100644 --- a/python/fatcat_web/forms.py +++ b/python/fatcat_web/forms.py @@ -437,5 +437,9 @@ class EntityTomlForm(EntityEditForm): Initializes form with TOML version of existing entity """ etf = EntityTomlForm() - etf.toml.data = entity_to_toml(entity) + if entity.state == 'active': + pop_fields = ['ident', 'state', 'revision', 'redirect'] + else: + pop_fields = ['ident', 'state'] + etf.toml.data = entity_to_toml(entity, pop_fields=pop_fields) return etf -- cgit v1.2.3 From bedf147fecf7134fd22ea0ca7c94c5b7d4554416 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Thu, 30 Jul 2020 20:30:57 -0700 Subject: TOML editing proposal --- proposals/20200729_toml_editing.md | 43 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 proposals/20200729_toml_editing.md diff --git a/proposals/20200729_toml_editing.md b/proposals/20200729_toml_editing.md new file mode 100644 index 00000000..bdb8c12f --- /dev/null +++ b/proposals/20200729_toml_editing.md @@ -0,0 +1,43 @@ + +status: implemented + +TOML Editing of Entity Metadata +=============================== + +Goal is to enable full-power editing through the web interface, of the raw +entity schema and "extra" metadata, for all entity types. + +A side-effect of this should be enabling redirect editing between entities, as +well as "undeleting" or other state transitions (other than deletion). + +Plan: + +- find and add a toml transform library to pipenv deps. preferably with good + human-readable parsing errors +- TOML/JSON transform helpers, with tests +- implement generic TOML entity editing view (HTML) +- implement generic TOML entity editing endpoints + +Some metadata fields are removed before displaying TOML to edit. For example, +the ident, revision, and redirect fields for 'active' entities. It should still +be possible to do redirects by entering only the redirect field in the TOML +form. + +## UI Integration + +For existing edit forms, add a link to the "advanced" editing option. + +For endpoints without a form-based option (yet), do an HTTP redirect to the +TOML editing option. + +## New Webface Views + +`//create/toml` + GET: display template to be filled in + POST: form submit for creation +`///edit/toml` + GET: transform entity for editing + POST: form submit for edit +`/editgroup////edit/toml` + GET: transform entity for editing + POST: form submit for edit -- cgit v1.2.3 From 29593e492b632c8884c31993230d258d646e3d8c Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Thu, 30 Jul 2020 20:31:56 -0700 Subject: routes: handle case of viewing deleted entity in editgroup context Eg, consider deleting an entity. When viewing the editgroup, want to be able to click the deleted entity and see the "deleted entity" page instead of a generic 404. --- python/fatcat_web/entity_helpers.py | 27 ++++++++++++++++++++++++- python/fatcat_web/routes.py | 2 +- python/fatcat_web/templates/deleted_entity.html | 2 +- python/fatcat_web/templates/entity_macros.html | 12 ++++++----- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/python/fatcat_web/entity_helpers.py b/python/fatcat_web/entity_helpers.py index c9a57290..7156b9be 100644 --- a/python/fatcat_web/entity_helpers.py +++ b/python/fatcat_web/entity_helpers.py @@ -1,5 +1,6 @@ from flask import abort +from fatcat_openapi_client import * from fatcat_openapi_client.rest import ApiException, ApiValueError from fatcat_tools.transforms import * from fatcat_web import api @@ -148,6 +149,26 @@ def generic_get_entity_revision(entity_type, revision_id): except ApiValueError: abort(400) +def generic_deleted_entity(entity_type, ident): + if entity_type == 'container': + entity = ContainerEntity() + elif entity_type == 'creator': + entity = CreatorEntity() + elif entity_type == 'file': + entity = FileEntity() + elif entity_type == 'fileset': + entity = FilesetEntity() + elif entity_type == 'webcapture': + entity = WebcaptureEntity() + elif entity_type == 'release': + entity = ReleaseEntity() + elif entity_type == 'work': + entity = WorkEntity() + else: + raise NotImplementedError + entity.ident = ident + return entity + def generic_get_editgroup_entity(editgroup, entity_type, ident): if entity_type == 'container': edits = editgroup.edits.containers @@ -166,14 +187,18 @@ def generic_get_editgroup_entity(editgroup, entity_type, ident): else: raise NotImplementedError revision_id = None + edit = None for e in edits: if e.ident == ident: revision_id = e.revision edit = e break - if not revision_id: + if not edit: # couldn't find relevant edit in this editgroup abort(404) + if not revision_id: + # deletion, presumably + return generic_deleted_entity(entity_type, ident), edit try: entity = generic_get_entity_revision(entity_type, revision_id) diff --git a/python/fatcat_web/routes.py b/python/fatcat_web/routes.py index 74805c69..da2bb6cf 100644 --- a/python/fatcat_web/routes.py +++ b/python/fatcat_web/routes.py @@ -227,7 +227,7 @@ def generic_editgroup_entity_view(editgroup_id, entity_type, ident, view_templat entity, edit = generic_get_editgroup_entity(editgroup, entity_type, ident) - if entity.state == "deleted": + if entity.revision is None or entity.state == "deleted": return render_template('deleted_entity.html', entity=entity, entity_type=entity_type, editgroup=editgroup) diff --git a/python/fatcat_web/templates/deleted_entity.html b/python/fatcat_web/templates/deleted_entity.html index eefc87cf..4c6b14b6 100644 --- a/python/fatcat_web/templates/deleted_entity.html +++ b/python/fatcat_web/templates/deleted_entity.html @@ -30,7 +30,7 @@ Entity Type: {{ entity_type }}
-{{ entity_macros.fatcat_bits(entity, entity_type, "") }} +{{ entity_macros.fatcat_bits(entity, entity_type, "", editgroup=editgroup) }}
diff --git a/python/fatcat_web/templates/entity_macros.html b/python/fatcat_web/templates/entity_macros.html index 718c071c..0ce646bf 100644 --- a/python/fatcat_web/templates/entity_macros.html +++ b/python/fatcat_web/templates/entity_macros.html @@ -33,7 +33,7 @@ {% if entity.state %} State is "{{ entity.state }}". {% endif %} - {% if entity.state != "deleted" %} + {% if entity.revision %} Revision:
{{ entity.revision }} {% endif %} @@ -43,11 +43,13 @@ {%- else -%} https://api.{{ config.FATCAT_DOMAIN }} {%- endif -%} - /v0/{{ entity_type }} - {%- if entity.ident -%} - /{{ entity.ident }} + /v0 + {%- if editgroup and entity.ident -%} + /editgroup/{{ editgroup.editgroup_id }}{# /{{ entity_type }}/{{ entity.ident }} #} + {%- elif entity.ident -%} + /{{ entity_type }}/{{ entity.ident }} {%- elif entity.revision -%} - /rev/{{ entity.revision }} + /{{ entity_type }}/rev/{{ entity.revision }} {% endif %} {% if expand %}?expand={{ expand}}{% endif %}"> As JSON object via API -- cgit v1.2.3 From b7aed96075187af72b510c55e762d1e13c0a5306 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Thu, 30 Jul 2020 20:35:32 -0700 Subject: implement webface entity deletion --- python/fatcat_web/editing_routes.py | 229 ++++++++++++++++++++++--- python/fatcat_web/templates/entity_delete.html | 49 ++++++ python/tests/web_editing.py | 57 ++++++ 3 files changed, 308 insertions(+), 27 deletions(-) create mode 100644 python/fatcat_web/templates/entity_delete.html diff --git a/python/fatcat_web/editing_routes.py b/python/fatcat_web/editing_routes.py index e84b14f7..8e3b03b0 100644 --- a/python/fatcat_web/editing_routes.py +++ b/python/fatcat_web/editing_routes.py @@ -1,4 +1,6 @@ +from typing import Optional + from flask import render_template, abort, redirect, session, flash from flask_login import login_required @@ -63,6 +65,28 @@ def generic_entity_delete_edit(user_api, entity_type: str, editgroup_id: str, ed else: raise ae +def generic_entity_delete_entity(user_api, entity_type: str, editgroup_id: str, entity_ident: str) -> None: + try: + if entity_type == 'container': + edit = user_api.delete_container(editgroup_id, entity_ident) + elif entity_type == 'creator': + edit = user_api.delete_creator(editgroup_id, entity_ident) + elif entity_type == 'file': + edit = user_api.delete_file(editgroup_id, entity_ident) + elif entity_type == 'fileset': + edit = user_api.delete_fileset(editgroup_id, entity_ident) + elif entity_type == 'webcapture': + edit = user_api.delete_webcapture(editgroup_id, entity_ident) + elif entity_type == 'release': + edit = user_api.delete_release(editgroup_id, entity_ident) + elif entity_type == 'work': + edit = user_api.delete_work(editgroup_id, entity_ident) + else: + raise NotImplementedError + except ApiException as ae: + raise ae + return edit + def generic_entity_update_from_toml(user_api, entity_type: str, editgroup_id: str, existing_ident, toml_str: str) -> EntityEdit: if entity_type == 'container': entity = entity_from_toml(toml_str, ContainerEntity) @@ -359,6 +383,87 @@ def generic_entity_toml_edit(editgroup_id, entity_type, existing_ident, edit_tem existing_ident=existing_ident, editgroup=editgroup, potential_editgroups=potential_editgroups), status +def generic_entity_delete(editgroup_id: Optional[str], entity_type: str, existing_ident: str): + """ + Similar to generic_entity_edit(), but for deleting entities. This is a bit + simpler! + + Handles both creation and update/edit paths. + """ + + # fetch editgroup (if set) or 404 + editgroup = None + if editgroup_id: + try: + editgroup = api.get_editgroup(editgroup_id) + except ApiException as ae: + raise ae + + # check that editgroup is edit-able + if editgroup.changelog_index != None: + flash("Editgroup already merged") + abort(400) + + # fetch entity (if set) or 404 + existing = None + existing_edit = None + if editgroup and existing_ident: + existing, existing_edit = generic_get_editgroup_entity(editgroup, entity_type, existing_ident) + elif existing_ident: + existing = generic_get_entity(entity_type, existing_ident) + + # parse form (if submitted) + status = 200 + form = EntityEditForm() + + if form.is_submitted(): + if form.validate_on_submit(): + # API on behalf of user + user_api = auth_api(session['api_token']) + if not editgroup: + editgroup = form_editgroup_get_or_create(user_api, form) + + if editgroup: + # TODO: some danger of wiping database state here is + # "updated edit" causes, eg, a 4xx error. Better to allow + # this in the API itself. For now, form validation *should* + # catch most errors, and if not editor can hit back and try + # again. This means, need to allow failure of deletion. + if existing_edit: + # need to clear revision on object or this becomes just + # a "update pointer" edit + existing.revision = None + generic_entity_delete_edit(user_api, entity_type, editgroup.editgroup_id, existing_edit.edit_id) + try: + edit = generic_entity_delete_entity(user_api, entity_type, editgroup.editgroup_id, existing.ident) + except ApiException as ae: + app.log.warning(ae) + raise ae + if status == 200: + return redirect('/editgroup/{}/{}/{}'.format(editgroup.editgroup_id, entity_type, edit.ident)) + else: + status = 400 + elif form.errors: + status = 400 + app.log.info("form errors (did not validate): {}".format(form.errors)) + + else: # form is not submitted + if existing: + form = EntityTomlForm.from_entity(existing) + + editor_editgroups = api.get_editor_editgroups(session['editor']['editor_id'], limit=20) + potential_editgroups = [e for e in editor_editgroups if e.changelog_index == None and e.submitted == None] + + if not form.is_submitted(): + # default to most recent not submitted, fallback to "create new" + form.editgroup_id.data = "" + if potential_editgroups: + form.editgroup_id.data = potential_editgroups[0].editgroup_id + + return render_template("entity_delete.html", form=form, entity_type=entity_type, + existing_ident=existing_ident, editgroup=editgroup, + potential_editgroups=potential_editgroups), status + def generic_edit_delete(editgroup_id, entity_type, edit_id): # fetch editgroup (if set) or 404 editgroup = None @@ -390,19 +495,43 @@ def container_create_view(): @app.route('/container//edit', methods=['GET', 'POST']) @login_required -def container_edit(ident): +def container_edit_view(ident): return generic_entity_edit(None, 'container', ident, 'container_edit.html') +@app.route('/container//delete', methods=['GET', 'POST']) +@login_required +def container_delete_view(ident): + return generic_entity_delete(None, 'container', ident) + @app.route('/editgroup//container//edit', methods=['GET', 'POST']) @login_required -def container_editgroup_edit(editgroup_id, ident): +def container_editgroup_edit_view(editgroup_id, ident): return generic_entity_edit(editgroup_id, 'container', ident, 'container_edit.html') +@app.route('/editgroup//container//delete', methods=['GET', 'POST']) +@login_required +def container_editgroup_delete_view(editgroup_id, ident): + return generic_entity_delete(editgroup_id, 'container', ident) + @app.route('/editgroup//container/edit//delete', methods=['POST']) @login_required def container_edit_delete(editgroup_id, edit_id): return generic_edit_delete(editgroup_id, 'container', edit_id) +@app.route('/creator//delete', methods=['GET', 'POST']) +@login_required +def creator_delete_view(ident): + return generic_entity_delete(None, 'creator', ident) + +@app.route('/editgroup//creator/edit//delete', methods=['POST']) +def creator_edit_delete(editgroup_id, edit_id): + return generic_edit_delete(editgroup_id, 'creator', edit_id) + +@app.route('/editgroup//creator//delete', methods=['GET', 'POST']) +@login_required +def creator_editgroup_delete(editgroup_id, ident): + return generic_entity_delete(editgroup_id, 'creator', ident) + @app.route('/file/create', methods=['GET', 'POST']) @login_required def file_create_view(): @@ -410,19 +539,57 @@ def file_create_view(): @app.route('/file//edit', methods=['GET', 'POST']) @login_required -def file_edit(ident): +def file_edit_view(ident): return generic_entity_edit(None, 'file', ident, 'file_edit.html') +@app.route('/file//delete', methods=['GET', 'POST']) +@login_required +def file_delete_view(ident): + return generic_entity_delete(None, 'file', ident) + @app.route('/editgroup//file//edit', methods=['GET', 'POST']) @login_required -def file_editgroup_edit(editgroup_id, ident): +def file_editgroup_edit_view(editgroup_id, ident): return generic_entity_edit(editgroup_id, 'file', ident, 'file_edit.html') +@app.route('/editgroup//file//delete', methods=['GET', 'POST']) +@login_required +def file_editgroup_delete_view(editgroup_id, ident): + return generic_entity_delete(editgroup_id, 'file', ident) + @app.route('/editgroup//file/edit//delete', methods=['POST']) @login_required def file_edit_delete(editgroup_id, edit_id): return generic_edit_delete(editgroup_id, 'file', edit_id) +@app.route('/fileset//delete', methods=['GET', 'POST']) +@login_required +def fileset_delete_view(ident): + return generic_entity_delete(None, 'fileset', ident) + +@app.route('/editgroup//fileset/edit//delete', methods=['POST']) +def fileset_edit_delete(editgroup_id, edit_id): + return generic_edit_delete(editgroup_id, 'fileset', edit_id) + +@app.route('/editgroup//fileset//delete', methods=['GET', 'POST']) +@login_required +def fileset_editgroup_delete(editgroup_id, ident): + return generic_entity_delete(editgroup_id, 'fileset', ident) + +@app.route('/webcapture//delete', methods=['GET', 'POST']) +@login_required +def webcapture_delete_view(ident): + return generic_entity_delete(None, 'webcapture', ident) + +@app.route('/editgroup//webcapture/edit//delete', methods=['POST']) +def webcapture_edit_delete(editgroup_id, edit_id): + return generic_edit_delete(editgroup_id, 'webcapture', edit_id) + +@app.route('/editgroup//webcapture//delete', methods=['GET', 'POST']) +@login_required +def webcapture_editgroup_delete(editgroup_id, ident): + return generic_entity_delete(editgroup_id, 'webcapture', ident) + @app.route('/release/create', methods=['GET', 'POST']) @login_required def release_create_view(): @@ -430,35 +597,43 @@ def release_create_view(): @app.route('/release//edit', methods=['GET', 'POST']) @login_required -def release_edit(ident): +def release_edit_view(ident): return generic_entity_edit(None, 'release', ident, 'release_edit.html') +@app.route('/release//delete', methods=['GET', 'POST']) +@login_required +def release_delete_view(ident): + return generic_entity_delete(None, 'release', ident) + @app.route('/editgroup//release//edit', methods=['GET', 'POST']) @login_required def release_editgroup_edit(editgroup_id, ident): return generic_entity_edit(editgroup_id, 'release', ident, 'release_edit.html') +@app.route('/editgroup//release//delete', methods=['GET', 'POST']) +@login_required +def release_editgroup_delete(editgroup_id, ident): + return generic_entity_delete(editgroup_id, 'release', ident) + @app.route('/editgroup//release/edit//delete', methods=['POST']) @login_required def release_edit_delete(editgroup_id, edit_id): return generic_edit_delete(editgroup_id, 'release', edit_id) -@app.route('/editgroup//creator/edit//delete', methods=['POST']) -def creator_edit_delete(editgroup_id, edit_id): - return generic_edit_delete(editgroup_id, 'creator', edit_id) - -@app.route('/editgroup//fileset/edit//delete', methods=['POST']) -def fileset_edit_delete(editgroup_id, edit_id): - return generic_edit_delete(editgroup_id, 'fileset', edit_id) - -@app.route('/editgroup//webcapture/edit//delete', methods=['POST']) -def webcapture_edit_delete(editgroup_id, edit_id): - return generic_edit_delete(editgroup_id, 'webcapture', edit_id) +@app.route('/work//delete', methods=['GET', 'POST']) +@login_required +def work_delete_view(ident): + return generic_entity_delete(None, 'work', ident) @app.route('/editgroup//work/edit//delete', methods=['POST']) def work_edit_delete(editgroup_id, edit_id): return generic_edit_delete(editgroup_id, 'work', edit_id) +@app.route('/editgroup//work//delete', methods=['GET', 'POST']) +@login_required +def work_editgroup_delete(editgroup_id, ident): + return generic_entity_delete(editgroup_id, 'work', ident) + ### TOML Views ############################################################## @app.route('/container/create/toml', methods=['GET', 'POST']) @@ -468,7 +643,7 @@ def container_create_toml_view(): @app.route('/container//edit/toml', methods=['GET', 'POST']) @login_required -def container_edit_toml(ident): +def container_edit_toml_view(ident): return generic_entity_toml_edit(None, 'container', ident, 'entity_edit_toml.html') @app.route('/editgroup//container//edit/toml', methods=['GET', 'POST']) @@ -483,7 +658,7 @@ def creator_create_toml_view(): @app.route('/creator//edit/toml', methods=['GET', 'POST']) @login_required -def creator_edit_toml(ident): +def creator_edit_toml_view(ident): return generic_entity_toml_edit(None, 'creator', ident, 'entity_edit_toml.html') @app.route('/editgroup//creator//edit/toml', methods=['GET', 'POST']) @@ -498,7 +673,7 @@ def file_create_toml_view(): @app.route('/file//edit/toml', methods=['GET', 'POST']) @login_required -def file_edit_toml(ident): +def file_edit_toml_view(ident): return generic_entity_toml_edit(None, 'file', ident, 'entity_edit_toml.html') @app.route('/editgroup//file//edit/toml', methods=['GET', 'POST']) @@ -513,7 +688,7 @@ def fileset_create_toml_view(): @app.route('/fileset//edit/toml', methods=['GET', 'POST']) @login_required -def fileset_edit_toml(ident): +def fileset_edit_toml_view(ident): return generic_entity_toml_edit(None, 'fileset', ident, 'entity_edit_toml.html') @app.route('/editgroup//fileset//edit/toml', methods=['GET', 'POST']) @@ -528,7 +703,7 @@ def webcapture_create_toml_view(): @app.route('/webcapture//edit/toml', methods=['GET', 'POST']) @login_required -def webcapture_edit_toml(ident): +def webcapture_edit_toml_view(ident): return generic_entity_toml_edit(None, 'webcapture', ident, 'entity_edit_toml.html') @app.route('/editgroup//webcapture//edit/toml', methods=['GET', 'POST']) @@ -543,7 +718,7 @@ def release_create_toml_view(): @app.route('/release//edit/toml', methods=['GET', 'POST']) @login_required -def release_edit_toml(ident): +def release_edit_toml_view(ident): return generic_entity_toml_edit(None, 'release', ident, 'entity_edit_toml.html') @app.route('/editgroup//release//edit/toml', methods=['GET', 'POST']) @@ -558,7 +733,7 @@ def work_create_toml_view(): @app.route('/work//edit/toml', methods=['GET', 'POST']) @login_required -def work_edit_toml(ident): +def work_edit_toml_view(ident): return generic_entity_toml_edit(None, 'work', ident, 'entity_edit_toml.html') @app.route('/editgroup//work//edit/toml', methods=['GET', 'POST']) @@ -575,7 +750,7 @@ def creator_create_view(): @app.route('/creator//edit', methods=['GET']) @login_required -def creator_edit(ident): +def creator_edit_view(ident): return redirect(f'/creator/{ident}/edit/toml') @app.route('/editgroup//creator//edit', methods=['GET', 'POST']) @@ -590,7 +765,7 @@ def fileset_create_view(): @app.route('/fileset//edit', methods=['GET']) @login_required -def fileset_edit(ident): +def fileset_edit_view(ident): return redirect(f'/fileset/{ident}/edit/toml') @app.route('/editgroup//fileset//edit', methods=['GET', 'POST']) @@ -605,7 +780,7 @@ def webcapture_create_view(): @app.route('/webcapture//edit', methods=['GET']) @login_required -def webcapture_edit(ident): +def webcapture_edit_view(ident): return redirect(f'/webcapture/{ident}/edit/toml') @app.route('/editgroup//webcapture//edit', methods=['GET', 'POST']) @@ -620,7 +795,7 @@ def work_create_view(): @app.route('/work//edit', methods=['GET']) @login_required -def work_edit(ident): +def work_edit_view(ident): return redirect(f'/work/{ident}/edit/toml') @app.route('/editgroup//work//edit', methods=['GET', 'POST']) diff --git a/python/fatcat_web/templates/entity_delete.html b/python/fatcat_web/templates/entity_delete.html new file mode 100644 index 00000000..b2e13af4 --- /dev/null +++ b/python/fatcat_web/templates/entity_delete.html @@ -0,0 +1,49 @@ +{% import "edit_macros.html" as edit_macros %} +{% extends "base.html" %} + +{% block body %} +{% block edit_form_prefix %} +
+

Delete Entity

+ + +{% endblock %} + +

See the catalog + style guide for schema notes, and the editing + tutorial if this is your first time making an edit. + + {{ form.hidden_tag() }} + +

Editgroup Metadata

+ {{ edit_macros.editgroup_dropdown(form, editgroup, potential_editgroups) }} + +

Submit

+ {{ edit_macros.form_field_basic(form.edit_description) }} + This description will be attached to the individual edit, not to the + editgroup as a whole. + +{% block edit_form_suffix %} +

+ +

+ Deletion will be part of the current editgroup, which needs to be submited and + approved before the change is included in the catalog. + +

+{% endblock %} +{% endblock %} + +{% block postscript %} + + +{% endblock %} diff --git a/python/tests/web_editing.py b/python/tests/web_editing.py index ea244388..8386badb 100644 --- a/python/tests/web_editing.py +++ b/python/tests/web_editing.py @@ -129,6 +129,57 @@ def test_web_file_create(app_admin, api): follow_redirects=True) assert rv.status_code == 200 +def test_web_file_toml_create(app_admin, api): + + eg = quick_eg(api) + + # bogus/bad submit + rv = app_admin.post('/file/create/toml', + data={ + 'editgroup_id': eg.editgroup_id, + }, + follow_redirects=True) + assert rv.status_code == 400 + + # ok/valid submit + rv = app_admin.post('/file/create/toml', + data={ + 'editgroup_id': eg.editgroup_id, + 'toml': """ +size = 12345 +sha1 = "45be56a396c4d03faaa41e055170c23534dec736" + """, + }, + follow_redirects=True) + assert rv.status_code == 200 + + # upper-case SHA-1 + rv = app_admin.post('/file/create/toml', + data={ + 'editgroup_id': eg.editgroup_id, + 'toml': """ +size = 12345 +sha1 = "45BE56A396C4D03FAAA41E055170C23534DEC736" + """, + }, + follow_redirects=True) + assert rv.status_code == 400 + +def test_web_file_delete(app_admin, api): + + eg = quick_eg(api) + + rv = app_admin.get('/file/aaaaaaaaaaaaamztaaaaaaaaam/delete') + assert rv.status_code == 200 + + rv = app_admin.post('/file/aaaaaaaaaaaaamztaaaaaaaaam/delete', + data={ + 'editgroup_id': eg.editgroup_id, + }, + follow_redirects=True) + assert rv.status_code == 200 + # NOTE: did not *accept* the deletion edit + DUMMY_DEMO_ENTITIES = { 'container': 'aaaaaaaaaaaaaeiraaaaaaaaam', 'creator': 'aaaaaaaaaaaaaircaaaaaaaaaq', @@ -186,3 +237,9 @@ def test_web_create_get(app_admin): rv = app_admin.get(f'/{entity_type}/create/toml') assert rv.status_code == 200 + +def test_web_edit_delete(app_admin): + + for entity_type in DUMMY_DEMO_ENTITIES.keys(): + rv = app_admin.get(f'/{entity_type}/{DUMMY_DEMO_ENTITIES[entity_type]}/delete') + assert rv.status_code == 200 -- cgit v1.2.3 From 701062f94d2cebede755d4f2c43e516b698341b1 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Fri, 31 Jul 2020 00:52:21 -0700 Subject: fix typo bug resulting in lost/bad ext_id web edits --- python/fatcat_web/forms.py | 4 ++-- python/tests/web_editing.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/python/fatcat_web/forms.py b/python/fatcat_web/forms.py index 13776d10..1bd932bb 100644 --- a/python/fatcat_web/forms.py +++ b/python/fatcat_web/forms.py @@ -152,11 +152,11 @@ class ReleaseEntityForm(EntityEditForm): a = None setattr(re, simple_attr, a) for extid_attr in RELEASE_EXTID_ATTRS: - a = getattr(self, simple_attr).data + a = getattr(self, extid_attr).data # special case blank strings if a == '': a = None - setattr(re.ext_ids, simple_attr, a) + setattr(re.ext_ids, extid_attr, a) # bunch of complexity here to preserve old contrib metadata (eg, # affiliation and extra) not included in current forms # TODO: this may be broken; either way needs tests diff --git a/python/tests/web_editing.py b/python/tests/web_editing.py index 8386badb..fb8b3f93 100644 --- a/python/tests/web_editing.py +++ b/python/tests/web_editing.py @@ -24,6 +24,18 @@ def test_web_release_create_accept(app_admin, api): #assert b'badmojo' in rv.data assert b'Not a valid choice' in rv.data + # bad wikidata QID + rv = app_admin.post('/release/create', + data={ + 'editgroup_id': eg.editgroup_id, + 'release_type': 'article-journal', + 'release_stage': 'published', + 'title': 'something bogus', + 'wikidata_qid': '884', + }, + follow_redirects=True) + assert rv.status_code == 400 + # ok/valid submit rv = app_admin.post('/release/create', data={ @@ -31,9 +43,11 @@ def test_web_release_create_accept(app_admin, api): 'release_type': 'article-journal', 'release_stage': 'published', 'title': 'something bogus', + 'doi': '10.1234/999999', }, follow_redirects=True) assert rv.status_code == 200 + assert b'10.1234/999999' in rv.data rv = app_admin.get('/editgroup/{}'.format(eg.editgroup_id)) assert rv.status_code == 200 -- cgit v1.2.3 From 1d916d1cc43dae81b60801d128f2290f88bd5a07 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Fri, 31 Jul 2020 00:53:41 -0700 Subject: release form validators and tweak labels --- python/fatcat_web/forms.py | 45 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/python/fatcat_web/forms.py b/python/fatcat_web/forms.py index 1bd932bb..2efc84a3 100644 --- a/python/fatcat_web/forms.py +++ b/python/fatcat_web/forms.py @@ -64,11 +64,37 @@ class ReleaseContribForm(FlaskForm): default='author') RELEASE_SIMPLE_ATTRS = ['title', 'original_title', 'work_id', 'container_id', - 'release_type', 'release_stage', 'release_date', 'volume', 'issue', - 'pages', 'publisher', 'language', 'license_slug'] + 'release_type', 'release_stage', 'withdrawn_status', 'release_date', + 'release_year', 'volume', 'issue', 'pages', 'publisher', 'language', + 'license_slug'] RELEASE_EXTID_ATTRS = ['doi', 'wikidata_qid', 'isbn13', 'pmid', 'pmcid'] +def valid_date(form, field): + try: + datetime.date.fromisoformat(field.data) + except ValueError as ve: + raise ValidationError( + f"Date must be valid ISO format (like '2017-04-20'): {field.data}") + +def valid_year(form, field): + try: + year = int(field.data) + except ValueError as ve: + raise ValidationError( + f"Date must be valid ISO format (like '2017-04-20'): {field.data}") + if year > datetime.date.today().year + 5: + raise ValidationError( + f"Year is too far in the future: {year}") + if year < 10: + raise ValidationError( + f"Year is too far in the past: {year}") + +def valid_2char_ascii(form, field): + if len(field.data) != 2 or len(field.data.encode('utf-8')) != 2 or not field.data.isalpha() or field.data != field.data.lower(): + raise ValidationError( + f"Must be 2-character ISO format, lower case: {field.data}") + class ReleaseEntityForm(EntityEditForm): """ TODO: @@ -77,7 +103,7 @@ class ReleaseEntityForm(EntityEditForm): """ title = StringField('Title', [validators.DataRequired()]) - original_title = StringField('Original Title') + original_title = StringField('Title in Original Language (if different)') work_id = StringField('Work FCID', [validators.Optional(True), validators.Length(min=26, max=26)]) @@ -90,8 +116,9 @@ class ReleaseEntityForm(EntityEditForm): default='') release_stage = SelectField(choices=release_stage_options) release_date = DateField('Release Date', - [validators.Optional(True)]) - #release_year + [validators.Optional(True), valid_date]) + release_year = DateField('Release Year', + [validators.Optional(True), valid_year]) doi = StringField('DOI', [validators.Regexp(r'^10\..*\/.*', message="DOI must be valid"), validators.Optional(True)]) @@ -106,7 +133,8 @@ class ReleaseEntityForm(EntityEditForm): issue = StringField('Issue') pages = StringField('Pages') publisher = StringField('Publisher (optional)') - language = StringField('Language (code)') + language = StringField('Language (code)', + [validators.Optional(True), valid_2char_ascii]) license_slug = StringField('License (slug)') contribs = FieldList(FormField(ReleaseContribForm)) #refs @@ -206,8 +234,9 @@ class ContainerEntityForm(EntityEditForm): issnl = StringField("ISSN-L (linking)") issne = StringField("ISSN (electronic)") issnp = StringField("ISSN (print)") - original_name = StringField("Original Name (native language)") - country = StringField("Country of Publication (ISO code)") + original_name = StringField("Name in Original Language (if different)") + country = StringField("Country of Publication (ISO code)", + [validators.Optional(True), valid_2char_ascii]) wikidata_qid = StringField('Wikidata QID') urls = FieldList( StringField("Container URLs", -- cgit v1.2.3 From 86e712c9cc1ffc2047df4e311de845462c223586 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Fri, 31 Jul 2020 12:12:38 -0700 Subject: editing: withdrawn_status, release_year --- python/fatcat_web/forms.py | 42 +++++++++++++++------------ python/fatcat_web/templates/release_edit.html | 26 +++++++++++++---- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/python/fatcat_web/forms.py b/python/fatcat_web/forms.py index 2efc84a3..ff885fcb 100644 --- a/python/fatcat_web/forms.py +++ b/python/fatcat_web/forms.py @@ -4,6 +4,8 @@ Note: in thoery could use, eg, https://github.com/christabor/swagger_wtforms, but can't find one that is actually maintained. """ +import datetime + import toml from flask_wtf import FlaskForm from wtforms import SelectField, DateField, StringField, IntegerField, \ @@ -31,6 +33,16 @@ release_stage_options = [ ('published', 'Published'), ('updated', 'Updated'), ] +withdrawn_status_options = [ + ('', 'Not Withdrawn (blank)'), + ('retracted', 'Retracted'), + ('withdrawn', 'Withdrawn'), + ('concern', 'Concern Noted'), + ('spam', 'Spam'), + ('legal', 'Legal Taketown'), + ('safety', 'Public Safety'), + ('national-security', 'National Security'), +] role_type_options = [ ('author', 'Author'), ('editor', 'Editor'), @@ -70,25 +82,13 @@ RELEASE_SIMPLE_ATTRS = ['title', 'original_title', 'work_id', 'container_id', RELEASE_EXTID_ATTRS = ['doi', 'wikidata_qid', 'isbn13', 'pmid', 'pmcid'] -def valid_date(form, field): - try: - datetime.date.fromisoformat(field.data) - except ValueError as ve: - raise ValidationError( - f"Date must be valid ISO format (like '2017-04-20'): {field.data}") - def valid_year(form, field): - try: - year = int(field.data) - except ValueError as ve: - raise ValidationError( - f"Date must be valid ISO format (like '2017-04-20'): {field.data}") - if year > datetime.date.today().year + 5: + if field.data > datetime.date.today().year + 5: raise ValidationError( - f"Year is too far in the future: {year}") - if year < 10: + f"Year is too far in the future: {field.data}") + if field.data < 10: raise ValidationError( - f"Year is too far in the past: {year}") + f"Year is too far in the past: {field.data}") def valid_2char_ascii(form, field): if len(field.data) != 2 or len(field.data.encode('utf-8')) != 2 or not field.data.isalpha() or field.data != field.data.lower(): @@ -115,9 +115,13 @@ class ReleaseEntityForm(EntityEditForm): choices=release_type_options, default='') release_stage = SelectField(choices=release_stage_options) + withdrawn_status = SelectField("Withdrawn Status", + [validators.Optional(True)], + choices=withdrawn_status_options, + default='') release_date = DateField('Release Date', - [validators.Optional(True), valid_date]) - release_year = DateField('Release Year', + [validators.Optional(True)]) + release_year = IntegerField('Release Year', [validators.Optional(True), valid_year]) doi = StringField('DOI', [validators.Regexp(r'^10\..*\/.*', message="DOI must be valid"), @@ -185,6 +189,8 @@ class ReleaseEntityForm(EntityEditForm): if a == '': a = None setattr(re.ext_ids, extid_attr, a) + if self.release_date.data: + re.release_year = self.release_date.data.year # bunch of complexity here to preserve old contrib metadata (eg, # affiliation and extra) not included in current forms # TODO: this may be broken; either way needs tests diff --git a/python/fatcat_web/templates/release_edit.html b/python/fatcat_web/templates/release_edit.html index 21c8cf68..50f73b8c 100644 --- a/python/fatcat_web/templates/release_edit.html +++ b/python/fatcat_web/templates/release_edit.html @@ -27,21 +27,22 @@

The Basics

+ + {{ edit_macros.form_field_inline(form.release_type, "required") }} + {{ edit_macros.form_field_inline(form.title, "required") }} + {{ edit_macros.form_field_inline(form.original_title) }} +
- {{ edit_macros.form_field_basic(form.release_type, "required") }} - {{ edit_macros.form_field_basic(form.release_stage) }} + {{ edit_macros.form_field_basic(form.release_year) }} + {{ edit_macros.form_field_basic(form.release_date) }}
- {{ edit_macros.form_field_inline(form.title, "required") }} - {{ edit_macros.form_field_inline(form.original_title) }} - {{ edit_macros.form_field_inline(form.work_id) }} - {{ edit_macros.form_field_inline(form.release_date) }}
@@ -53,6 +54,19 @@
+
+
+
+
+ {{ edit_macros.form_field_basic(form.release_stage) }} + {{ edit_macros.form_field_basic(form.withdrawn_status) }} +
+
+
+
+ + {{ edit_macros.form_field_inline(form.work_id) }} +

Contributors

-- cgit v1.2.3 From 81c031ec822aece655104cf545a556ce72d7cd73 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Fri, 31 Jul 2020 12:19:18 -0700 Subject: update top-level README (checklist done) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c06fe6e5..dcad54c3 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ schema, lives under `./python_openapi_client/`. - [x] Authorization (aka, roles) - Web Interface - [x] Migrate Python codebase - - [ ] Creation and editing of all entities + - [x] Creation and editing of all entities - Other - [x] Elasticsearch schema - [x] Basic logging -- cgit v1.2.3 From 31f59b4b0ba7ff95b685c8826a7d019fb142f65c Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Fri, 31 Jul 2020 12:53:56 -0700 Subject: web: add links to deletion pages from edit pages --- python/fatcat_web/templates/container_edit.html | 3 +++ python/fatcat_web/templates/entity_edit_toml.html | 4 ++++ python/fatcat_web/templates/file_edit.html | 3 +++ python/fatcat_web/templates/release_edit.html | 3 +++ 4 files changed, 13 insertions(+) diff --git a/python/fatcat_web/templates/container_edit.html b/python/fatcat_web/templates/container_edit.html index fd07b3da..99f77d53 100644 --- a/python/fatcat_web/templates/container_edit.html +++ b/python/fatcat_web/templates/container_edit.html @@ -12,6 +12,9 @@ %}/editgroup/{{ editgroup.editgroup_id }}{% endif %}/container/{{ existing_ident }}/edit/toml">TOML editing form to access all metadata fields in a raw format. + {% if not editgroup %} + You can also delete this entity. + {% endif %} {% endblock %}

See the catalog diff --git a/python/fatcat_web/templates/entity_edit_toml.html b/python/fatcat_web/templates/entity_edit_toml.html index 807e4d2b..64768d6e 100644 --- a/python/fatcat_web/templates/entity_edit_toml.html +++ b/python/fatcat_web/templates/entity_edit_toml.html @@ -7,6 +7,10 @@

Edit Entity (TOML mode)

+ + {% if not editgroup %} +

You can also delete this entity. + {% endif %} {% endblock %}

See the catalog diff --git a/python/fatcat_web/templates/file_edit.html b/python/fatcat_web/templates/file_edit.html index b7876fc5..745b0c41 100644 --- a/python/fatcat_web/templates/file_edit.html +++ b/python/fatcat_web/templates/file_edit.html @@ -12,6 +12,9 @@ %}/editgroup/{{ editgroup.editgroup_id }}{% endif %}/file/{{ existing_ident }}/edit/toml">TOML editing form to access all metadata fields in a raw format. + {% if not editgroup %} + You can also delete this entity. + {% endif %} {% endblock %}

See the catalog diff --git a/python/fatcat_web/templates/release_edit.html b/python/fatcat_web/templates/release_edit.html index 50f73b8c..c26c9850 100644 --- a/python/fatcat_web/templates/release_edit.html +++ b/python/fatcat_web/templates/release_edit.html @@ -12,6 +12,9 @@ %}/editgroup/{{ editgroup.editgroup_id }}{% endif %}/release/{{ existing_ident }}/edit/toml">TOML editing form to access all metadata fields in a raw format. + {% if not editgroup %} + You can also delete this entity. + {% endif %} {% endblock %}

See the catalog -- cgit v1.2.3