diff --git a/.python-version b/.python-version index 78c9a28efce..2419ad5b0a3 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.9.12 +3.11.9 diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 5a812c75e8a..d81bb1045f8 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,7 +9,7 @@ version: 2 build: os: ubuntu-20.04 tools: - python: "3.9" + python: "3.12" # Build documentation in the docs/ directory with Sphinx sphinx: diff --git a/Dockerfile b/Dockerfile index 36972f882c3..aad7d5e7ac9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ # run the application specified. docker-compose does not use this. # Grab a python image -FROM python:3.9 +FROM python:3.11 SHELL ["/bin/bash", "--login", "-c"] # Just needed for all things python (note this is setting an env variable) diff --git a/Pipfile b/Pipfile index 2b411cce45c..bb442791d89 100644 --- a/Pipfile +++ b/Pipfile @@ -51,4 +51,4 @@ sphinx-rtd-theme = "*" sphinx-intl = "*" [requires] -python_version = "3.9" +python_version = "3.11" diff --git a/Pipfile.lock b/Pipfile.lock index 7de712ca7db..ac8af7ac321 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "ed77cfd488f49a5f8d7dc88cca9ba162c39a55425e0335847cf91ff7c43cb51a" + "sha256": "2c6c7279511a59524494d4ae59fb78ac13efe891c03b28c26ea8c01ce6975a4d" }, "pipfile-spec": 6, "requires": { - "python_version": "3.9" + "python_version": "3.11" }, "sources": [ { @@ -18,42 +18,35 @@ "default": { "appnope": { "hashes": [ - "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24", - "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e" + "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", + "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c" ], "markers": "sys_platform == 'darwin'", - "version": "==0.1.3" + "version": "==0.1.4" }, "asgiref": { "hashes": [ - "sha256:71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac", - "sha256:9567dfe7bd8d3c8c892227827c41cce860b368104c3431da67a0c5a65a949506" + "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", + "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590" ], "index": "pypi", - "version": "==3.6.0" - }, - "async-timeout": { - "hashes": [ - "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15", - "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c" - ], - "markers": "python_version >= '3.6'", - "version": "==4.0.2" + "markers": "python_version >= '3.8'", + "version": "==3.8.1" }, "attrs": { "hashes": [ - "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836", - "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99" + "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", + "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1" ], - "markers": "python_version >= '3.6'", - "version": "==22.2.0" + "markers": "python_version >= '3.7'", + "version": "==23.2.0" }, "autobahn": { "hashes": [ - "sha256:c5ef8ca7422015a1af774a883b8aef73d4954c9fcd182c9b5244e08e973f7c3a" + "sha256:ec9421c52a2103364d1ef0468036e6019ee84f71721e86b36fe19ad6966c1181" ], - "markers": "python_version >= '3.7'", - "version": "==23.1.2" + "markers": "python_version >= '3.9'", + "version": "==23.6.2" }, "automat": { "hashes": [ @@ -71,148 +64,150 @@ }, "bleach": { "hashes": [ - "sha256:1a1a85c1595e07d8db14c5f09f09e6433502c51c595970edc090551f0db99414", - "sha256:33c16e3353dbd13028ab4799a0f89a83f113405c766e9c122df8a06f5b85b3f4" + "sha256:0a31f1837963c41d46bbf1331b8778e1308ea0791db03cc4e7357b97cf42a8fe", + "sha256:3225f354cfc436b9789c66c4ee030194bee0568fbf9cbdad3bc8b5c26c5f12b6" ], - "markers": "python_version >= '3.7'", - "version": "==6.0.0" + "markers": "python_version >= '3.8'", + "version": "==6.1.0" }, "certifi": { "hashes": [ - "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", - "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" + "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f", + "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1" ], "markers": "python_version >= '3.6'", - "version": "==2022.12.7" + "version": "==2024.2.2" }, "cffi": { "hashes": [ - "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5", - "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef", - "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104", - "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426", - "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405", - "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375", - "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a", - "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e", - "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc", - "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf", - "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185", - "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497", - "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3", - "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35", - "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c", - "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83", - "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21", - "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca", - "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984", - "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac", - "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd", - "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee", - "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a", - "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2", - "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192", - "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7", - "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585", - "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f", - "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e", - "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27", - "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b", - "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e", - "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e", - "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d", - "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c", - "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415", - "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82", - "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02", - "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314", - "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325", - "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c", - "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3", - "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914", - "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045", - "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d", - "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9", - "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5", - "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2", - "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c", - "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3", - "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2", - "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8", - "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d", - "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d", - "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9", - "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162", - "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76", - "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4", - "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e", - "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9", - "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6", - "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b", - "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01", - "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0" - ], - "version": "==1.15.1" + "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc", + "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a", + "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417", + "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab", + "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520", + "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36", + "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743", + "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8", + "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed", + "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684", + "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56", + "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324", + "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d", + "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235", + "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e", + "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088", + "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000", + "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7", + "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e", + "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673", + "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c", + "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe", + "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2", + "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098", + "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8", + "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a", + "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0", + "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b", + "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896", + "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e", + "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9", + "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2", + "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b", + "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6", + "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404", + "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f", + "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0", + "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4", + "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc", + "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936", + "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba", + "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872", + "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb", + "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614", + "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1", + "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d", + "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969", + "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b", + "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4", + "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627", + "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956", + "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357" + ], + "markers": "platform_python_implementation != 'PyPy'", + "version": "==1.16.0" }, "channels": { "hashes": [ - "sha256:0ce53507a7da7b148eaa454526e0e05f7da5e5d1c23440e4886cf146981d8420", - "sha256:2253334ac76f67cba68c2072273f7e0e67dbdac77eeb7e318f511d2f9a53c5e4" + "sha256:a3c4419307f582c3f71d67bfb6eff748ae819c2f360b9b141694d84f242baa48", + "sha256:e0ed375719f5c1851861f05ed4ce78b0166f9245ca0ecd836cb77d4bb531489d" ], "index": "pypi", - "version": "==4.0.0" + "markers": "python_version >= '3.8'", + "version": "==4.1.0" }, "channels-redis": { "hashes": [ - "sha256:122414f29f525f7b9e0c9d59cdcfc4dc1b0eecba16fbb6a1c23f1d9b58f49dcb", - "sha256:81b59d68f53313e1aa891f23591841b684abb936b42e4d1a966d9e4dc63a95ec" + "sha256:01c26c4d5d3a203f104bba9e5585c0305a70df390d21792386586068162027fd", + "sha256:2c5b944a39bd984b72aa8005a3ae11637bf29b5092adeb91c9aad4ab819a8ac4" ], "index": "pypi", - "version": "==4.0.0" + "markers": "python_version >= '3.8'", + "version": "==4.2.0" }, "constantly": { "hashes": [ - "sha256:586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35", - "sha256:dd2fa9d6b1a51a83f0d7dd76293d734046aa176e384bf6e33b7e44880eb37c5d" + "sha256:3fd9b4d1c3dc1ec9757f3c52aef7e53ad9323dbe39f51dfd4c43853b68dfa3f9", + "sha256:aa92b70a33e2ac0bb33cd745eb61776594dc48764b06c35e0efd050b7f1c7cbd" ], - "version": "==15.1.0" + "markers": "python_version >= '3.8'", + "version": "==23.10.4" }, "cryptography": { "hashes": [ - "sha256:103e8f7155f3ce2ffa0049fe60169878d47a4364b277906386f8de21c9234aa1", - "sha256:23df8ca3f24699167daf3e23e51f7ba7334d504af63a94af468f468b975b7dd7", - "sha256:2725672bb53bb92dc7b4150d233cd4b8c59615cd8288d495eaa86db00d4e5c06", - "sha256:30b1d1bfd00f6fc80d11300a29f1d8ab2b8d9febb6ed4a38a76880ec564fae84", - "sha256:35d658536b0a4117c885728d1a7032bdc9a5974722ae298d6c533755a6ee3915", - "sha256:50cadb9b2f961757e712a9737ef33d89b8190c3ea34d0fb6675e00edbe35d074", - "sha256:5f8c682e736513db7d04349b4f6693690170f95aac449c56f97415c6980edef5", - "sha256:6236a9610c912b129610eb1a274bdc1350b5df834d124fa84729ebeaf7da42c3", - "sha256:788b3921d763ee35dfdb04248d0e3de11e3ca8eb22e2e48fef880c42e1f3c8f9", - "sha256:8bc0008ef798231fac03fe7d26e82d601d15bd16f3afaad1c6113771566570f3", - "sha256:8f35c17bd4faed2bc7797d2a66cbb4f986242ce2e30340ab832e5d99ae60e011", - "sha256:b49a88ff802e1993b7f749b1eeb31134f03c8d5c956e3c125c75558955cda536", - "sha256:bc0521cce2c1d541634b19f3ac661d7a64f9555135e9d8af3980965be717fd4a", - "sha256:bc5b871e977c8ee5a1bbc42fa8d19bcc08baf0c51cbf1586b0e87a2694dde42f", - "sha256:c43ac224aabcbf83a947eeb8b17eaf1547bce3767ee2d70093b461f31729a480", - "sha256:d15809e0dbdad486f4ad0979753518f47980020b7a34e9fc56e8be4f60702fac", - "sha256:d7d84a512a59f4412ca8549b01f94be4161c94efc598bf09d027d67826beddc0", - "sha256:e029b844c21116564b8b61216befabca4b500e6816fa9f0ba49527653cae2108", - "sha256:e8a0772016feeb106efd28d4a328e77dc2edae84dfbac06061319fdb669ff828", - "sha256:e944fe07b6f229f4c1a06a7ef906a19652bdd9fd54c761b0ff87e83ae7a30354", - "sha256:eb40fe69cfc6f5cdab9a5ebd022131ba21453cf7b8a7fd3631f45bbf52bed612", - "sha256:fa507318e427169ade4e9eccef39e9011cdc19534f55ca2f36ec3f388c1f70f3", - "sha256:ffd394c7896ed7821a6d13b24657c6a34b6e2650bd84ae063cf11ccffa4f1a97" + "sha256:00c0faa5b021457848d031ecff041262211cc1e2bce5f6e6e6c8108018f6b44a", + "sha256:073104df012fc815eed976cd7d0a386c8725d0d0947cf9c37f6c36a6c20feb1b", + "sha256:076c92b08dd1ab88108bc84545187e10d3693a9299c593f98c4ea195a0b0ead7", + "sha256:089aeb297ff89615934b22c7631448598495ffd775b7d540a55cfee35a677bf4", + "sha256:3b750279f3e7715df6f68050707a0cee7cbe81ba2eeb2f21d081bd205885ffed", + "sha256:43e521f21c2458038d72e8cdfd4d4d9f1d00906a7b6636c4272e35f650d1699b", + "sha256:4bdb39ecbf05626e4bfa1efd773bb10346af297af14fb3f4c7cb91a1d2f34a46", + "sha256:5967e3632f42b0c0f9dc2c9da88c79eabdda317860b246d1fbbde4a8bbbc3b44", + "sha256:65d529c31bd65d54ce6b926a01e1b66eacf770b7e87c0622516a840e400ec732", + "sha256:6981acac509cc9415344cb5bfea8130096ea6ebcc917e75503143a1e9e829160", + "sha256:81dbe47e28b703bc4711ac74a64ef8b758a0cf056ce81d08e39116ab4bc126fa", + "sha256:8b90c57b3cd6128e0863b894ce77bd36fcb5f430bf2377bc3678c2f56e232316", + "sha256:9184aff0856261ecb566a3eb26a05dfe13a292c85ce5c59b04e4aa09e5814187", + "sha256:945a43ebf036dd4b43ebfbbd6b0f2db29ad3d39df824fb77476ca5777a9dde33", + "sha256:97eeacae9aa526ddafe68b9202a535f581e21d78f16688a84c8dcc063618e121", + "sha256:9f1a3bc2747166b0643b00e0b56cd9b661afc9d5ff963acaac7a9c7b2b1ef638", + "sha256:9ff75b88a4d273c06d968ad535e6cb6a039dd32db54fe36f05ed62ac3ef64a44", + "sha256:aeb6f56b004e898df5530fa873e598ec78eb338ba35f6fa1449970800b1d97c2", + "sha256:b16b90605c62bcb3aa7755d62cf5e746828cfc3f965a65211849e00c46f8348d", + "sha256:b99831397fdc6e6e0aa088b060c278c6e635d25c0d4d14bdf045bf81792fda0a", + "sha256:bc954251edcd8a952eeaec8ae989fec7fe48109ab343138d537b7ea5bb41071a", + "sha256:c05230d8aaaa6b8ab3ab41394dc06eb3d916131df1c9dcb4c94e8f041f704b74", + "sha256:d16a310c770cc49908c500c2ceb011f2840674101a587d39fa3ea828915b7e83", + "sha256:d93080d2b01b292e7ee4d247bf93ed802b0100f5baa3fa5fd6d374716fa480d4", + "sha256:e1f5f15c5ddadf6ee4d1d624a2ae940f14bd74536230b0056ccb28bb6248e42a", + "sha256:e3442601d276bd9e961d618b799761b4e5d892f938e8a4fe1efbe2752be90455", + "sha256:e85f433230add2aa26b66d018e21134000067d210c9c68ef7544ba65fc52e3eb", + "sha256:eecca86813c6a923cabff284b82ff4d73d9e91241dc176250192c3a9b9902a54", + "sha256:f1e933b238978ccfa77b1fee0a297b3c04983f4cb84ae1c33b0ea4ae08266cc9", + "sha256:f4cece02478d73dacd52be57a521d168af64ae03d2a567c0c4eb6f189c3b9d79", + "sha256:f567a82b7c2b99257cca2a1c902c1b129787278ff67148f188784245c7ed5495", + "sha256:f987a244dfb0333fbd74a691c36000a2569eaf7c7cc2ac838f85f59f0588ddc9" ], - "markers": "python_version >= '3.6'", - "version": "==39.0.2" + "markers": "python_version >= '3.7'", + "version": "==42.0.6" }, "daphne": { "hashes": [ - "sha256:a288ece46012b6b719c37150be67c69ebfca0793a8521bf821533bad983179b2", - "sha256:cce9afc8f49a4f15d4270b8cfb0e0fe811b770a5cc795474e97e4da287497666" + "sha256:618d1322bb4d875342b99dd2a10da2d9aae7ee3645f765965fdc1e658ea5290a", + "sha256:fcbcace38eb86624ae247c7ffdc8ac12f155d7d19eafac4247381896d6f33761" ], "index": "pypi", - "version": "==4.0.0" + "markers": "python_version >= '3.8'", + "version": "==4.1.2" }, "decorator": { "hashes": [ @@ -228,6 +223,7 @@ "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61" ], "index": "pypi", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==0.7.1" }, "dj-cmd": { @@ -240,27 +236,29 @@ }, "dj-database-url": { "hashes": [ - "sha256:5c2993b91801c0f78a8b19e642b497b90831124cbade0c265900d4c1037b4730", - "sha256:b23b15046cb38180e0c95207bcc90fe5e9dbde8eef16065907dd85cf4ca7036c" + "sha256:04bc34b248d4c21aaa13e4ab419ae6575ef5f10f3df735ce7da97722caa356e0", + "sha256:f2042cefe1086e539c9da39fad5ad7f61173bf79665e69bf7e4de55fa88b135f" ], "index": "pypi", - "version": "==1.2.0" + "version": "==2.1.0" }, "django": { "hashes": [ - "sha256:44f714b81c5f190d9d2ddad01a532fe502fa01c4cb8faf1d081f4264ed15dcd8", - "sha256:f2f431e75adc40039ace496ad3b9f17227022e8b11566f4b363da44c7e44761e" + "sha256:4bd01a8c830bb77a8a3b0e7d8b25b887e536ad17a81ba2dce5476135c73312bd", + "sha256:916423499d75d62da7aa038d19aef23d23498d8df229775eb0a6309ee1013775" ], "index": "pypi", - "version": "==4.1.7" + "markers": "python_version >= '3.10'", + "version": "==5.0.4" }, "django-appconf": { "hashes": [ - "sha256:ae9f864ee1958c815a965ed63b3fba4874eec13de10236ba063a788f9a17389d", - "sha256:be3db0be6c81fa84742000b89a81c016d70ae66a7ccb620cdef592b1f1a6aaa4" + "sha256:c3ae442fba1ff7ec830412c5184b17169a7a1e71cf0864a4c3f93cf4c98a1993", + "sha256:cfe87ea827c4ee04b9a70fab90b86d704cb02f2981f89da8423cb0fabf88efbf" ], "index": "pypi", - "version": "==1.0.5" + "markers": "python_version >= '3.7'", + "version": "==1.0.6" }, "django-better-admin-arrayfield": { "hashes": [ @@ -272,34 +270,38 @@ }, "django-cors-headers": { "hashes": [ - "sha256:5fbd58a6fb4119d975754b2bc090f35ec160a8373f276612c675b00e8a138739", - "sha256:684180013cc7277bdd8702b80a3c5a4b3fcae4abb2bf134dceb9f5dfe300228e" + "sha256:0b1fd19297e37417fc9f835d39e45c8c642938ddba1acce0c1753d3edef04f36", + "sha256:0bf65ef45e606aff1994d35503e6b677c0b26cafff6506f8fd7187f3be840207" ], "index": "pypi", - "version": "==3.14.0" + "markers": "python_version >= '3.8'", + "version": "==4.3.1" }, "django-dynamic-preferences": { "hashes": [ - "sha256:c00abcb8d524067390a66518cfcd32683b87ad3cc620d5913649fc7707b80833" + "sha256:0d3d456626244d0bdaf312c81f2b3e14bd16134e8fcf53a33fd12e5d0bdd88dd", + "sha256:527b943d2b5a5c1ea2de8f941778a39efddf2b18ccf44e6aaab05290256ec82f" ], "index": "pypi", - "version": "==1.14.0" + "version": "==1.16.0" }, "django-extensions": { "hashes": [ - "sha256:2a4f4d757be2563cd1ff7cfdf2e57468f5f931cc88b23cf82ca75717aae504a4", - "sha256:421464be390289513f86cb5e18eb43e5dc1de8b4c27ba9faa3b91261b0d67e09" + "sha256:44d27919d04e23b3f40231c4ab7af4e61ce832ef46d610cc650d53e68328410a", + "sha256:9600b7562f79a92cbf1fde6403c04fee314608fefbb595502e34383ae8203401" ], "index": "pypi", - "version": "==3.2.1" + "markers": "python_version >= '3.6'", + "version": "==3.2.3" }, "django-formtools": { "hashes": [ - "sha256:deb932be55b1d9419e37dc4d65dfbfeb8d307b71c8c11fd52f159aba5fc0deed", - "sha256:f5f32f62ec8192cd1bc55bd929ca7dff5a5f2addf9027db95a5906ecfaa64836" + "sha256:47cb34552c6efca088863d693284d04fc36eaaf350eb21e1a1d935e0df523c93", + "sha256:bce9b64eda52cc1eef6961cc649cf75aacd1a707c2fff08d6c3efcbc8e7e761a" ], "index": "pypi", - "version": "==2.4" + "markers": "python_version >= '3.8'", + "version": "==2.5.1" }, "django-gfklookupwidget": { "hashes": [ @@ -310,18 +312,19 @@ }, "django-ipware": { "hashes": [ - "sha256:602a58325a4808bd19197fef2676a0b2da2df40d0ecf21be414b2ff48c72ad05", - "sha256:878dbb06a87e25550798e9ef3204ed70a200dd8b15e47dcef848cf08244f04c9" + "sha256:d9ec43d2bf7cdf216fed8d494a084deb5761a54860a53b2e74346a4f384cff47", + "sha256:db16bbee920f661ae7f678e4270460c85850f03c6761a4eaeb489bdc91f64709" ], "index": "pypi", - "version": "==4.0.2" + "markers": "python_version >= '3.8'", + "version": "==7.0.1" }, "django-jet-reboot": { "hashes": [ - "sha256:87263acbbbd56b5e92c98987d2e8132034d65261473cceb8cca57dafaab85b87" + "sha256:e1cc40606331539106cae58701bbaf76c0a4486bbe1aee6d85b2f20ce8ad3f71" ], "index": "pypi", - "version": "==1.3.3" + "version": "==1.3.7" }, "django-polymorphic": { "hashes": [ @@ -333,27 +336,29 @@ }, "django-redis": { "hashes": [ - "sha256:1d037dc02b11ad7aa11f655d26dac3fb1af32630f61ef4428860a2e29ff92026", - "sha256:8a99e5582c79f894168f5865c52bd921213253b7fd64d16733ae4591564465de" + "sha256:6a02abaa34b0fea8bf9b707d2c363ab6adc7409950b2db93602e6cb292818c42", + "sha256:ebc88df7da810732e2af9987f7f426c96204bf89319df4c6da6ca9a2942edd5b" ], "index": "pypi", - "version": "==5.2.0" + "markers": "python_version >= '3.6'", + "version": "==5.4.0" }, "django-split-settings": { "hashes": [ - "sha256:31415a618256b54c5cee8662cbaa72a890683b8b7465d64ba88fdd3affdd6c60", - "sha256:4b3be146776d49c61bd9dcf89fad40edb1544f13ab27a87a0b1aecf5a0d636f4" + "sha256:c1f57f6b54fc0d93082c12163e76fad082c214f5fa0d16d84a1226d2c9f14f26", + "sha256:c902ef60d5fe8190ff224284f68e3c9015b6f1aca9e9d6bd70bf86394ff32634" ], "index": "pypi", - "version": "==1.2.0" + "markers": "python_version >= '3.9' and python_version < '4.0'", + "version": "==1.3.1" }, "django-statici18n": { "hashes": [ - "sha256:00079579035d5b45320830191e2c047f8643b7906307eff9833f0fa95068a603", - "sha256:5f4bb3d58670def2df490babe338524927cfb2ebe2e5e20538b98d9424e83d0e" + "sha256:323ebecbfa39408ad242a5e782083bac73bf0e13d93c3e2b29fd32d3379eb3c8", + "sha256:80ac9f21cb80c1cc5a60b558a104b47acc0914fe2bae9fef74f5a0100c8f3d36" ], "index": "pypi", - "version": "==2.3.1" + "version": "==2.5.0" }, "django-summernote": { "hashes": [ @@ -364,11 +369,12 @@ }, "djangorestframework": { "hashes": [ - "sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8", - "sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08" + "sha256:3ccc0475bce968608cf30d07fb17d8e52d1d7fc8bfe779c905463200750cbca6", + "sha256:f88fad74183dfc7144b2756d0d2ac716ea5b4c7c9840995ac3bfd8ec034333c1" ], "index": "pypi", - "version": "==3.14.0" + "markers": "python_version >= '3.6'", + "version": "==3.15.1" }, "drf-link-header-pagination": { "hashes": [ @@ -376,23 +382,26 @@ "sha256:9890a871803395544c5a0ee0665fc2d2701fcdf151570ed15c8b8d873aefe27d" ], "index": "pypi", + "markers": "python_version >= '3.6'", "version": "==0.2.0" }, "drf-spectacular": { "hashes": [ - "sha256:789696f9845ef2397c52f66154aec6d96411baf6aa09a5d40c5f0b0e99f6b3d8", - "sha256:d58684e702f5ad436c5bd1735d46df0e123e64de883092d38f1debb9fa4a03c9" + "sha256:a199492f2163c4101055075ebdbb037d59c6e0030692fc83a1a8c0fc65929981", + "sha256:b1c04bf8b2fbbeaf6f59414b4ea448c8787aba4d32f76055c3b13335cf7ec37b" ], "index": "pypi", - "version": "==0.25.1" + "markers": "python_version >= '3.7'", + "version": "==0.27.2" }, "gunicorn": { "hashes": [ - "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", - "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8" + "sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9", + "sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63" ], "index": "pypi", - "version": "==20.1.0" + "markers": "python_version >= '3.7'", + "version": "==22.0.0" }, "honcho": { "hashes": [ @@ -404,11 +413,11 @@ }, "html2text": { "hashes": [ - "sha256:c7c629882da0cf377d66f073329ccf34a12ed2adf0169b9285ae4e63ef54c82b", - "sha256:e296318e16b059ddb97f7a8a1d6a5c1d7af4544049a01e261731d2d5cc277bbb" + "sha256:05f8e367d15aaabc96415376776cdd11afd5127a77fce6e36afc60c563ca2c32" ], "index": "pypi", - "version": "==2020.1.16" + "markers": "python_version >= '3.8'", + "version": "==2024.2.26" }, "hyperlink": { "hashes": [ @@ -419,10 +428,10 @@ }, "idna": { "hashes": [ - "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", - "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", + "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" ], - "version": "==3.4" + "version": "==3.7" }, "incremental": { "hashes": [ @@ -445,88 +454,102 @@ "sha256:c175d2440a1caff76116eb719d40538fbb316e214eda85c5515c303aacbfb23e" ], "index": "pypi", + "markers": "python_version >= '3.7'", "version": "==7.34.0" }, "jedi": { "hashes": [ - "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e", - "sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612" + "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd", + "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0" ], "markers": "python_version >= '3.6'", - "version": "==0.18.2" + "version": "==0.19.1" }, "jsonschema": { "hashes": [ - "sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d", - "sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6" + "sha256:5b22d434a45935119af990552c862e5d6d564e8f6601206b305a61fdf661a2b7", + "sha256:ff4cfd6b1367a40e7bc6411caec72effadd3db0bbe5017de188f2d6108335802" ], - "markers": "python_version >= '3.7'", - "version": "==4.17.3" + "markers": "python_version >= '3.8'", + "version": "==4.22.0" + }, + "jsonschema-specifications": { + "hashes": [ + "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc", + "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c" + ], + "markers": "python_version >= '3.8'", + "version": "==2023.12.1" }, "matplotlib-inline": { "hashes": [ - "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311", - "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304" + "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", + "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca" ], - "markers": "python_version >= '3.5'", - "version": "==0.1.6" + "markers": "python_version >= '3.8'", + "version": "==0.1.7" }, "msgpack": { "hashes": [ - "sha256:002b5c72b6cd9b4bafd790f364b8480e859b4712e91f43014fe01e4f957b8467", - "sha256:0a68d3ac0104e2d3510de90a1091720157c319ceeb90d74f7b5295a6bee51bae", - "sha256:0df96d6eaf45ceca04b3f3b4b111b86b33785683d682c655063ef8057d61fd92", - "sha256:0dfe3947db5fb9ce52aaea6ca28112a170db9eae75adf9339a1aec434dc954ef", - "sha256:0e3590f9fb9f7fbc36df366267870e77269c03172d086fa76bb4eba8b2b46624", - "sha256:11184bc7e56fd74c00ead4f9cc9a3091d62ecb96e97653add7a879a14b003227", - "sha256:112b0f93202d7c0fef0b7810d465fde23c746a2d482e1e2de2aafd2ce1492c88", - "sha256:1276e8f34e139aeff1c77a3cefb295598b504ac5314d32c8c3d54d24fadb94c9", - "sha256:1576bd97527a93c44fa856770197dec00d223b0b9f36ef03f65bac60197cedf8", - "sha256:1e91d641d2bfe91ba4c52039adc5bccf27c335356055825c7f88742c8bb900dd", - "sha256:26b8feaca40a90cbe031b03d82b2898bf560027160d3eae1423f4a67654ec5d6", - "sha256:2999623886c5c02deefe156e8f869c3b0aaeba14bfc50aa2486a0415178fce55", - "sha256:2a2df1b55a78eb5f5b7d2a4bb221cd8363913830145fad05374a80bf0877cb1e", - "sha256:2bb8cdf50dd623392fa75525cce44a65a12a00c98e1e37bf0fb08ddce2ff60d2", - "sha256:2cc5ca2712ac0003bcb625c96368fd08a0f86bbc1a5578802512d87bc592fe44", - "sha256:35bc0faa494b0f1d851fd29129b2575b2e26d41d177caacd4206d81502d4c6a6", - "sha256:3c11a48cf5e59026ad7cb0dc29e29a01b5a66a3e333dc11c04f7e991fc5510a9", - "sha256:449e57cc1ff18d3b444eb554e44613cffcccb32805d16726a5494038c3b93dab", - "sha256:462497af5fd4e0edbb1559c352ad84f6c577ffbbb708566a0abaaa84acd9f3ae", - "sha256:4733359808c56d5d7756628736061c432ded018e7a1dff2d35a02439043321aa", - "sha256:48f5d88c99f64c456413d74a975bd605a9b0526293218a3b77220a2c15458ba9", - "sha256:49565b0e3d7896d9ea71d9095df15b7f75a035c49be733051c34762ca95bbf7e", - "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250", - "sha256:4d5834a2a48965a349da1c5a79760d94a1a0172fbb5ab6b5b33cbf8447e109ce", - "sha256:4dea20515f660aa6b7e964433b1808d098dcfcabbebeaaad240d11f909298075", - "sha256:545e3cf0cf74f3e48b470f68ed19551ae6f9722814ea969305794645da091236", - "sha256:63e29d6e8c9ca22b21846234913c3466b7e4ee6e422f205a2988083de3b08cae", - "sha256:6916c78f33602ecf0509cc40379271ba0f9ab572b066bd4bdafd7434dee4bc6e", - "sha256:6a4192b1ab40f8dca3f2877b70e63799d95c62c068c84dc028b40a6cb03ccd0f", - "sha256:6c9566f2c39ccced0a38d37c26cc3570983b97833c365a6044edef3574a00c08", - "sha256:76ee788122de3a68a02ed6f3a16bbcd97bc7c2e39bd4d94be2f1821e7c4a64e6", - "sha256:7760f85956c415578c17edb39eed99f9181a48375b0d4a94076d84148cf67b2d", - "sha256:77ccd2af37f3db0ea59fb280fa2165bf1b096510ba9fe0cc2bf8fa92a22fdb43", - "sha256:81fc7ba725464651190b196f3cd848e8553d4d510114a954681fd0b9c479d7e1", - "sha256:85f279d88d8e833ec015650fd15ae5eddce0791e1e8a59165318f371158efec6", - "sha256:9667bdfdf523c40d2511f0e98a6c9d3603be6b371ae9a238b7ef2dc4e7a427b0", - "sha256:a75dfb03f8b06f4ab093dafe3ddcc2d633259e6c3f74bb1b01996f5d8aa5868c", - "sha256:ac5bd7901487c4a1dd51a8c58f2632b15d838d07ceedaa5e4c080f7190925bff", - "sha256:aca0f1644d6b5a73eb3e74d4d64d5d8c6c3d577e753a04c9e9c87d07692c58db", - "sha256:b17be2478b622939e39b816e0aa8242611cc8d3583d1cd8ec31b249f04623243", - "sha256:c1683841cd4fa45ac427c18854c3ec3cd9b681694caf5bff04edb9387602d661", - "sha256:c23080fdeec4716aede32b4e0ef7e213c7b1093eede9ee010949f2a418ced6ba", - "sha256:d5b5b962221fa2c5d3a7f8133f9abffc114fe218eb4365e40f17732ade576c8e", - "sha256:d603de2b8d2ea3f3bcb2efe286849aa7a81531abc52d8454da12f46235092bcb", - "sha256:e83f80a7fec1a62cf4e6c9a660e39c7f878f603737a0cdac8c13131d11d97f52", - "sha256:eb514ad14edf07a1dbe63761fd30f89ae79b42625731e1ccf5e1f1092950eaa6", - "sha256:eba96145051ccec0ec86611fe9cf693ce55f2a3ce89c06ed307de0e085730ec1", - "sha256:ed6f7b854a823ea44cf94919ba3f727e230da29feb4a99711433f25800cf747f", - "sha256:f0029245c51fd9473dc1aede1160b0a29f4a912e6b1dd353fa6d317085b219da", - "sha256:f5d869c18f030202eb412f08b28d2afeea553d6613aee89e200d7aca7ef01f5f", - "sha256:fb62ea4b62bfcb0b380d5680f9a4b3f9a2d166d9394e9bbd9666c0ee09a3645c", - "sha256:fcb8a47f43acc113e24e910399376f7277cf8508b27e5b88499f053de6b115a8" - ], - "version": "==1.0.4" + "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982", + "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3", + "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40", + "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee", + "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693", + "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950", + "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151", + "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24", + "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305", + "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b", + "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c", + "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659", + "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d", + "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18", + "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746", + "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868", + "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2", + "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba", + "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228", + "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2", + "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273", + "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c", + "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653", + "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a", + "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596", + "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd", + "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8", + "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa", + "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85", + "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc", + "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836", + "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3", + "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58", + "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128", + "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db", + "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f", + "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77", + "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad", + "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13", + "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8", + "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b", + "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a", + "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543", + "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b", + "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce", + "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d", + "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a", + "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c", + "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f", + "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e", + "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011", + "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04", + "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480", + "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a", + "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d", + "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d" + ], + "markers": "python_version >= '3.8'", + "version": "==1.0.8" }, "munkres": { "hashes": [ @@ -538,19 +561,28 @@ }, "networkx": { "hashes": [ - "sha256:58058d66b1818043527244fab9d41a51fcd7dcc271748015f3c181b8a90c8e2e", - "sha256:9a9992345353618ae98339c2b63d8201c381c2944f38a2ab49cb45a4c667e412" + "sha256:0c127d8b2f4865f59ae9cb8aafcd60b5c70f3241ebd66f7defad7c4ab90126c9", + "sha256:28575580c6ebdaf4505b22c6256a2b9de86b316dc63ba9e93abde3d78dfdbcf2" ], "index": "pypi", - "version": "==3.0" + "markers": "python_version >= '3.10'", + "version": "==3.3" + }, + "packaging": { + "hashes": [ + "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", + "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9" + ], + "markers": "python_version >= '3.7'", + "version": "==24.0" }, "parso": { "hashes": [ - "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0", - "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75" + "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", + "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d" ], "markers": "python_version >= '3.6'", - "version": "==0.8.3" + "version": "==0.8.4" }, "persisting-theory": { "hashes": [ @@ -561,11 +593,11 @@ }, "pexpect": { "hashes": [ - "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937", - "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c" + "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", + "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f" ], "markers": "sys_platform != 'win32'", - "version": "==4.8.0" + "version": "==4.9.0" }, "pickleshare": { "hashes": [ @@ -576,88 +608,90 @@ }, "prompt-toolkit": { "hashes": [ - "sha256:23ac5d50538a9a38c8bde05fecb47d0b403ecd0662857a86f886f798563d5b9b", - "sha256:45ea77a2f7c60418850331366c81cf6b5b9cf4c7fd34616f733c5427e6abbb1f" + "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d", + "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6" ], - "markers": "python_version >= '3.7'", - "version": "==3.0.38" + "markers": "python_full_version >= '3.7.0'", + "version": "==3.0.43" }, "psycopg2-binary": { "hashes": [ - "sha256:00475004e5ed3e3bf5e056d66e5dcdf41a0dc62efcd57997acd9135c40a08a50", - "sha256:01ad49d68dd8c5362e4bfb4158f2896dc6e0c02e87b8a3770fc003459f1a4425", - "sha256:024030b13bdcbd53d8a93891a2cf07719715724fc9fee40243f3bd78b4264b8f", - "sha256:02551647542f2bf89073d129c73c05a25c372fc0a49aa50e0de65c3c143d8bd0", - "sha256:043a9fd45a03858ff72364b4b75090679bd875ee44df9c0613dc862ca6b98460", - "sha256:05b3d479425e047c848b9782cd7aac9c6727ce23181eb9647baf64ffdfc3da41", - "sha256:0775d6252ccb22b15da3b5d7adbbf8cfe284916b14b6dc0ff503a23edb01ee85", - "sha256:1764546ffeaed4f9428707be61d68972eb5ede81239b46a45843e0071104d0dd", - "sha256:1e491e6489a6cb1d079df8eaa15957c277fdedb102b6a68cfbf40c4994412fd0", - "sha256:212757ffcecb3e1a5338d4e6761bf9c04f750e7d027117e74aa3cd8a75bb6fbd", - "sha256:215d6bf7e66732a514f47614f828d8c0aaac9a648c46a831955cb103473c7147", - "sha256:25382c7d174c679ce6927c16b6fbb68b10e56ee44b1acb40671e02d29f2fce7c", - "sha256:2abccab84d057723d2ca8f99ff7b619285d40da6814d50366f61f0fc385c3903", - "sha256:2d964eb24c8b021623df1c93c626671420c6efadbdb8655cb2bd5e0c6fa422ba", - "sha256:2ec46ed947801652c9643e0b1dc334cfb2781232e375ba97312c2fc256597632", - "sha256:2ef892cabdccefe577088a79580301f09f2a713eb239f4f9f62b2b29cafb0577", - "sha256:33e632d0885b95a8b97165899006c40e9ecdc634a529dca7b991eb7de4ece41c", - "sha256:3520d7af1ebc838cc6084a3281145d5cd5bdd43fdef139e6db5af01b92596cb7", - "sha256:3d790f84201c3698d1bfb404c917f36e40531577a6dda02e45ba29b64d539867", - "sha256:3fc33295cfccad697a97a76dec3f1e94ad848b7b163c3228c1636977966b51e2", - "sha256:422e3d43b47ac20141bc84b3d342eead8d8099a62881a501e97d15f6addabfe9", - "sha256:426c2ae999135d64e6a18849a7d1ad0e1bd007277e4a8f4752eaa40a96b550ff", - "sha256:46512486be6fbceef51d7660dec017394ba3e170299d1dc30928cbedebbf103a", - "sha256:46850a640df62ae940e34a163f72e26aca1f88e2da79148e1862faaac985c302", - "sha256:484405b883630f3e74ed32041a87456c5e0e63a8e3429aa93e8714c366d62bd1", - "sha256:4e7904d1920c0c89105c0517dc7e3f5c20fb4e56ba9cdef13048db76947f1d79", - "sha256:56b2957a145f816726b109ee3d4e6822c23f919a7d91af5a94593723ed667835", - "sha256:5c6527c8efa5226a9e787507652dd5ba97b62d29b53c371a85cd13f957fe4d42", - "sha256:5cbc554ba47ecca8cd3396ddaca85e1ecfe3e48dd57dc5e415e59551affe568e", - "sha256:5d28ecdf191db558d0c07d0f16524ee9d67896edf2b7990eea800abeb23ebd61", - "sha256:5fc447058d083b8c6ac076fc26b446d44f0145308465d745fba93a28c14c9e32", - "sha256:63e318dbe52709ed10d516a356f22a635e07a2e34c68145484ed96a19b0c4c68", - "sha256:68d81a2fe184030aa0c5c11e518292e15d342a667184d91e30644c9d533e53e1", - "sha256:6e63814ec71db9bdb42905c925639f319c80e7909fb76c3b84edc79dadef8d60", - "sha256:6f8a9bcab7b6db2e3dbf65b214dfc795b4c6b3bb3af922901b6a67f7cb47d5f8", - "sha256:70831e03bd53702c941da1a1ad36c17d825a24fbb26857b40913d58df82ec18b", - "sha256:74eddec4537ab1f701a1647214734bc52cee2794df748f6ae5908e00771f180a", - "sha256:7b3751857da3e224f5629400736a7b11e940b5da5f95fa631d86219a1beaafec", - "sha256:7cf1d44e710ca3a9ce952bda2855830fe9f9017ed6259e01fcd71ea6287565f5", - "sha256:7d07f552d1e412f4b4e64ce386d4c777a41da3b33f7098b6219012ba534fb2c2", - "sha256:7d88db096fa19d94f433420eaaf9f3c45382da2dd014b93e4bf3215639047c16", - "sha256:7ee3095d02d6f38bd7d9a5358fcc9ea78fcdb7176921528dd709cc63f40184f5", - "sha256:902844f9c4fb19b17dfa84d9e2ca053d4a4ba265723d62ea5c9c26b38e0aa1e6", - "sha256:937880290775033a743f4836aa253087b85e62784b63fd099ee725d567a48aa1", - "sha256:95076399ec3b27a8f7fa1cc9a83417b1c920d55cf7a97f718a94efbb96c7f503", - "sha256:9c38d3869238e9d3409239bc05bc27d6b7c99c2a460ea337d2814b35fb4fea1b", - "sha256:9e32cedc389bcb76d9f24ea8a012b3cb8385ee362ea437e1d012ffaed106c17d", - "sha256:9ffdc51001136b699f9563b1c74cc1f8c07f66ef7219beb6417a4c8aaa896c28", - "sha256:a0adef094c49f242122bb145c3c8af442070dc0e4312db17e49058c1702606d4", - "sha256:a36a0e791805aa136e9cbd0ffa040d09adec8610453ee8a753f23481a0057af5", - "sha256:a7e518a0911c50f60313cb9e74a169a65b5d293770db4770ebf004245f24b5c5", - "sha256:af0516e1711995cb08dc19bbd05bec7dbdebf4185f68870595156718d237df3e", - "sha256:b8104f709590fff72af801e916817560dbe1698028cd0afe5a52d75ceb1fce5f", - "sha256:b911dfb727e247340d36ae20c4b9259e4a64013ab9888ccb3cbba69b77fd9636", - "sha256:b9a794cef1d9c1772b94a72eec6da144c18e18041d294a9ab47669bc77a80c1d", - "sha256:b9c33d4aef08dfecbd1736ceab8b7b3c4358bf10a0121483e5cd60d3d308cc64", - "sha256:b9d38a4656e4e715d637abdf7296e98d6267df0cc0a8e9a016f8ba07e4aa3eeb", - "sha256:bcda1c84a1c533c528356da5490d464a139b6e84eb77cc0b432e38c5c6dd7882", - "sha256:bef7e3f9dc6f0c13afdd671008534be5744e0e682fb851584c8c3a025ec09720", - "sha256:c15ba5982c177bc4b23a7940c7e4394197e2d6a424a2d282e7c236b66da6d896", - "sha256:c5254cbd4f4855e11cebf678c1a848a3042d455a22a4ce61349c36aafd4c2267", - "sha256:c5682a45df7d9642eff590abc73157c887a68f016df0a8ad722dcc0f888f56d7", - "sha256:c5e65c6ac0ae4bf5bef1667029f81010b6017795dcb817ba5c7b8a8d61fab76f", - "sha256:d4c7b3a31502184e856df1f7bbb2c3735a05a8ce0ade34c5277e1577738a5c91", - "sha256:d892bfa1d023c3781a3cab8dd5af76b626c483484d782e8bd047c180db590e4c", - "sha256:dbc332beaf8492b5731229a881807cd7b91b50dbbbaf7fe2faf46942eda64a24", - "sha256:dc85b3777068ed30aff8242be2813038a929f2084f69e43ef869daddae50f6ee", - "sha256:e59137cdb970249ae60be2a49774c6dfb015bd0403f05af1fe61862e9626642d", - "sha256:e67b3c26e9b6d37b370c83aa790bbc121775c57bfb096c2e77eacca25fd0233b", - "sha256:e72c91bda9880f097c8aa3601a2c0de6c708763ba8128006151f496ca9065935", - "sha256:f95b8aca2703d6a30249f83f4fe6a9abf2e627aa892a5caaab2267d56be7ab69" + "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9", + "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77", + "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e", + "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84", + "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3", + "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2", + "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67", + "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876", + "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152", + "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f", + "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a", + "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6", + "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503", + "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f", + "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493", + "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996", + "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f", + "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e", + "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59", + "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94", + "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7", + "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682", + "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420", + "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae", + "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291", + "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe", + "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980", + "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93", + "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692", + "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119", + "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716", + "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472", + "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b", + "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2", + "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc", + "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c", + "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5", + "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab", + "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984", + "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9", + "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf", + "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0", + "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f", + "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212", + "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb", + "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be", + "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90", + "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041", + "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7", + "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860", + "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d", + "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245", + "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27", + "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417", + "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359", + "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202", + "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0", + "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7", + "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba", + "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1", + "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd", + "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07", + "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98", + "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55", + "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d", + "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972", + "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f", + "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e", + "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26", + "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957", + "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53", + "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52" ], "index": "pypi", - "version": "==2.9.5" + "markers": "python_version >= '3.7'", + "version": "==2.9.9" }, "ptyprocess": { "hashes": [ @@ -668,61 +702,42 @@ }, "pyasn1": { "hashes": [ - "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", - "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576", - "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf", - "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7", - "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", - "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00", - "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8", - "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86", - "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12", - "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776", - "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", - "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2", - "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3" - ], - "version": "==0.4.8" + "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c", + "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473" + ], + "markers": "python_version >= '3.8'", + "version": "==0.6.0" }, "pyasn1-modules": { "hashes": [ - "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8", - "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199", - "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811", - "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed", - "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4", - "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e", - "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74", - "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb", - "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45", - "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd", - "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0", - "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d", - "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405" - ], - "version": "==0.2.8" + "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6", + "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b" + ], + "markers": "python_version >= '3.8'", + "version": "==0.4.0" }, "pycparser": { "hashes": [ - "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", - "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" + "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", + "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc" ], - "version": "==2.21" + "markers": "python_version >= '3.8'", + "version": "==2.22" }, "pygments": { "hashes": [ - "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297", - "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717" + "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", + "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a" ], - "markers": "python_version >= '3.6'", - "version": "==2.14.0" + "markers": "python_version >= '3.8'", + "version": "==2.18.0" }, "pyopenssl": { "hashes": [ - "sha256:c1cc5f86bcacefc84dada7d31175cae1b1518d5f60d3d0bb595a67822a868a6f", - "sha256:df5fc28af899e74e19fccb5510df423581047e10ab6f1f4ba1763ff5fde844c0" + "sha256:17ed5be5936449c5418d1cd269a1a9e9081bc54c17aed272b45856a3d3dc86ad", + "sha256:cabed4bfaa5df9f1a16c0ef64a0cb65318b5cd077a7eda7d6970131ca2f41a6f" ], - "version": "==23.0.0" + "version": "==24.1.0" }, "pypng": { "hashes": [ @@ -731,91 +746,70 @@ ], "version": "==0.20220715.0" }, - "pyrsistent": { - "hashes": [ - "sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8", - "sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440", - "sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a", - "sha256:3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c", - "sha256:3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3", - "sha256:3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393", - "sha256:42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9", - "sha256:49c32f216c17148695ca0e02a5c521e28a4ee6c5089f97e34fe24163113722da", - "sha256:4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf", - "sha256:4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64", - "sha256:5a474fb80f5e0d6c9394d8db0fc19e90fa540b82ee52dba7d246a7791712f74a", - "sha256:64220c429e42a7150f4bfd280f6f4bb2850f95956bde93c6fda1b70507af6ef3", - "sha256:878433581fc23e906d947a6814336eee031a00e6defba224234169ae3d3d6a98", - "sha256:99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2", - "sha256:a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8", - "sha256:aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf", - "sha256:b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc", - "sha256:c147257a92374fde8498491f53ffa8f4822cd70c0d85037e09028e478cababb7", - "sha256:c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28", - "sha256:c74bed51f9b41c48366a286395c67f4e894374306b197e62810e0fdaf2364da2", - "sha256:c9bb60a40a0ab9aba40a59f68214eed5a29c6274c83b2cc206a359c4a89fa41b", - "sha256:cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a", - "sha256:ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64", - "sha256:e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19", - "sha256:e8f2b814a3dc6225964fa03d8582c6e0b6650d68a232df41e3cc1b66a5d2f8d1", - "sha256:f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9", - "sha256:f0e7c4b2f77593871e918be000b96c8107da48444d57005b6a6bc61fb4331b2c" - ], - "markers": "python_version >= '3.7'", - "version": "==0.19.3" - }, - "pytz": { + "python-ipware": { "hashes": [ - "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0", - "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a" + "sha256:9117b1c4dddcb5d5ca49e6a9617de2fc66aec2ef35394563ac4eecabdf58c062", + "sha256:fc936e6e7ec9fcc107f9315df40658f468ac72f739482a707181742882e36b60" ], - "version": "==2022.7.1" + "markers": "python_version >= '3.7'", + "version": "==3.0.0" }, "pyyaml": { "hashes": [ - "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf", - "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", - "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", - "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", - "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", - "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", - "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", - "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", - "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", - "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", - "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", - "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", - "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782", - "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", - "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", - "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", - "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", - "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", - "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1", - "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", - "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", - "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", - "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", - "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", - "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", - "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d", - "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", - "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", - "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7", - "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", - "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", - "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", - "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358", - "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", - "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", - "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", - "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", - "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f", - "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", - "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" + "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", + "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", + "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", + "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", + "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", + "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", + "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", + "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", + "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", + "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", + "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", + "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", + "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", + "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", + "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", + "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", + "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", + "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", + "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", + "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", + "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", + "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", + "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", + "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", + "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", + "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", + "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", + "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", + "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", + "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", + "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", + "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", + "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", + "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", + "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", + "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", + "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", + "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", + "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", + "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", + "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", + "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", + "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", + "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", + "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", + "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", + "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", + "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", + "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", + "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", + "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" ], "markers": "python_version >= '3.6'", - "version": "==6.0" + "version": "==6.0.1" }, "qrcode": { "hashes": [ @@ -823,38 +817,154 @@ "sha256:9dd969454827e127dbd93696b20747239e6d540e082937c90f14ac95b30f5845" ], "index": "pypi", + "markers": "python_version >= '3.7'", "version": "==7.4.2" }, "redis": { "hashes": [ - "sha256:1eec3741cda408d3a5f84b78d089c8b8d895f21b3b050988351e925faf202864", - "sha256:5deb072d26e67d2be1712603bfb7947ec3431fb0eec9c578994052e33035af6d" + "sha256:7adc2835c7a9b5033b7ad8f8918d09b7344188228809c98df07af226d39dec91", + "sha256:ec31f2ed9675cc54c21ba854cfe0462e6faf1d83c8ce5944709db8a4700b9c61" ], "index": "pypi", - "version": "==4.5.1" + "markers": "python_version >= '3.7'", + "version": "==5.0.4" + }, + "referencing": { + "hashes": [ + "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c", + "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de" + ], + "markers": "python_version >= '3.8'", + "version": "==0.35.1" + }, + "rpds-py": { + "hashes": [ + "sha256:01e36a39af54a30f28b73096dd39b6802eddd04c90dbe161c1b8dbe22353189f", + "sha256:044a3e61a7c2dafacae99d1e722cc2d4c05280790ec5a05031b3876809d89a5c", + "sha256:08231ac30a842bd04daabc4d71fddd7e6d26189406d5a69535638e4dcb88fe76", + "sha256:08f9ad53c3f31dfb4baa00da22f1e862900f45908383c062c27628754af2e88e", + "sha256:0ab39c1ba9023914297dd88ec3b3b3c3f33671baeb6acf82ad7ce883f6e8e157", + "sha256:0af039631b6de0397ab2ba16eaf2872e9f8fca391b44d3d8cac317860a700a3f", + "sha256:0b8612cd233543a3781bc659c731b9d607de65890085098986dfd573fc2befe5", + "sha256:11a8c85ef4a07a7638180bf04fe189d12757c696eb41f310d2426895356dcf05", + "sha256:1374f4129f9bcca53a1bba0bb86bf78325a0374577cf7e9e4cd046b1e6f20e24", + "sha256:1d4acf42190d449d5e89654d5c1ed3a4f17925eec71f05e2a41414689cda02d1", + "sha256:1d9a5be316c15ffb2b3c405c4ff14448c36b4435be062a7f578ccd8b01f0c4d8", + "sha256:1df3659d26f539ac74fb3b0c481cdf9d725386e3552c6fa2974f4d33d78e544b", + "sha256:22806714311a69fd0af9b35b7be97c18a0fc2826e6827dbb3a8c94eac6cf7eeb", + "sha256:2644e47de560eb7bd55c20fc59f6daa04682655c58d08185a9b95c1970fa1e07", + "sha256:2e6d75ab12b0bbab7215e5d40f1e5b738aa539598db27ef83b2ec46747df90e1", + "sha256:30f43887bbae0d49113cbaab729a112251a940e9b274536613097ab8b4899cf6", + "sha256:34b18ba135c687f4dac449aa5157d36e2cbb7c03cbea4ddbd88604e076aa836e", + "sha256:36b3ee798c58ace201289024b52788161e1ea133e4ac93fba7d49da5fec0ef9e", + "sha256:39514da80f971362f9267c600b6d459bfbbc549cffc2cef8e47474fddc9b45b1", + "sha256:39f5441553f1c2aed4de4377178ad8ff8f9d733723d6c66d983d75341de265ab", + "sha256:3a96e0c6a41dcdba3a0a581bbf6c44bb863f27c541547fb4b9711fd8cf0ffad4", + "sha256:3f26b5bd1079acdb0c7a5645e350fe54d16b17bfc5e71f371c449383d3342e17", + "sha256:41ef53e7c58aa4ef281da975f62c258950f54b76ec8e45941e93a3d1d8580594", + "sha256:42821446ee7a76f5d9f71f9e33a4fb2ffd724bb3e7f93386150b61a43115788d", + "sha256:43fbac5f22e25bee1d482c97474f930a353542855f05c1161fd804c9dc74a09d", + "sha256:4457a94da0d5c53dc4b3e4de1158bdab077db23c53232f37a3cb7afdb053a4e3", + "sha256:465a3eb5659338cf2a9243e50ad9b2296fa15061736d6e26240e713522b6235c", + "sha256:482103aed1dfe2f3b71a58eff35ba105289b8d862551ea576bd15479aba01f66", + "sha256:4832d7d380477521a8c1644bbab6588dfedea5e30a7d967b5fb75977c45fd77f", + "sha256:4901165d170a5fde6f589acb90a6b33629ad1ec976d4529e769c6f3d885e3e80", + "sha256:5307def11a35f5ae4581a0b658b0af8178c65c530e94893345bebf41cc139d33", + "sha256:5417558f6887e9b6b65b4527232553c139b57ec42c64570569b155262ac0754f", + "sha256:56a737287efecafc16f6d067c2ea0117abadcd078d58721f967952db329a3e5c", + "sha256:586f8204935b9ec884500498ccc91aa869fc652c40c093bd9e1471fbcc25c022", + "sha256:5b4e7d8d6c9b2e8ee2d55c90b59c707ca59bc30058269b3db7b1f8df5763557e", + "sha256:5ddcba87675b6d509139d1b521e0c8250e967e63b5909a7e8f8944d0f90ff36f", + "sha256:618a3d6cae6ef8ec88bb76dd80b83cfe415ad4f1d942ca2a903bf6b6ff97a2da", + "sha256:635dc434ff724b178cb192c70016cc0ad25a275228f749ee0daf0eddbc8183b1", + "sha256:661d25cbffaf8cc42e971dd570d87cb29a665f49f4abe1f9e76be9a5182c4688", + "sha256:66e6a3af5a75363d2c9a48b07cb27c4ea542938b1a2e93b15a503cdfa8490795", + "sha256:67071a6171e92b6da534b8ae326505f7c18022c6f19072a81dcf40db2638767c", + "sha256:685537e07897f173abcf67258bee3c05c374fa6fff89d4c7e42fb391b0605e98", + "sha256:69e64831e22a6b377772e7fb337533c365085b31619005802a79242fee620bc1", + "sha256:6b0817e34942b2ca527b0e9298373e7cc75f429e8da2055607f4931fded23e20", + "sha256:6c81e5f372cd0dc5dc4809553d34f832f60a46034a5f187756d9b90586c2c307", + "sha256:6d7faa6f14017c0b1e69f5e2c357b998731ea75a442ab3841c0dbbbfe902d2c4", + "sha256:6ef0befbb5d79cf32d0266f5cff01545602344eda89480e1dd88aca964260b18", + "sha256:6ef687afab047554a2d366e112dd187b62d261d49eb79b77e386f94644363294", + "sha256:7223a2a5fe0d217e60a60cdae28d6949140dde9c3bcc714063c5b463065e3d66", + "sha256:77f195baa60a54ef9d2de16fbbfd3ff8b04edc0c0140a761b56c267ac11aa467", + "sha256:793968759cd0d96cac1e367afd70c235867831983f876a53389ad869b043c948", + "sha256:7bd339195d84439cbe5771546fe8a4e8a7a045417d8f9de9a368c434e42a721e", + "sha256:7cd863afe7336c62ec78d7d1349a2f34c007a3cc6c2369d667c65aeec412a5b1", + "sha256:7f2facbd386dd60cbbf1a794181e6aa0bd429bd78bfdf775436020172e2a23f0", + "sha256:84ffab12db93b5f6bad84c712c92060a2d321b35c3c9960b43d08d0f639d60d7", + "sha256:8c8370641f1a7f0e0669ddccca22f1da893cef7628396431eb445d46d893e5cd", + "sha256:8db715ebe3bb7d86d77ac1826f7d67ec11a70dbd2376b7cc214199360517b641", + "sha256:8e8916ae4c720529e18afa0b879473049e95949bf97042e938530e072fde061d", + "sha256:8f03bccbd8586e9dd37219bce4d4e0d3ab492e6b3b533e973fa08a112cb2ffc9", + "sha256:8f2fc11e8fe034ee3c34d316d0ad8808f45bc3b9ce5857ff29d513f3ff2923a1", + "sha256:923d39efa3cfb7279a0327e337a7958bff00cc447fd07a25cddb0a1cc9a6d2da", + "sha256:93df1de2f7f7239dc9cc5a4a12408ee1598725036bd2dedadc14d94525192fc3", + "sha256:998e33ad22dc7ec7e030b3df701c43630b5bc0d8fbc2267653577e3fec279afa", + "sha256:99f70b740dc04d09e6b2699b675874367885217a2e9f782bdf5395632ac663b7", + "sha256:9a00312dea9310d4cb7dbd7787e722d2e86a95c2db92fbd7d0155f97127bcb40", + "sha256:9d54553c1136b50fd12cc17e5b11ad07374c316df307e4cfd6441bea5fb68496", + "sha256:9dbbeb27f4e70bfd9eec1be5477517365afe05a9b2c441a0b21929ee61048124", + "sha256:a1ce3ba137ed54f83e56fb983a5859a27d43a40188ba798993812fed73c70836", + "sha256:a34d557a42aa28bd5c48a023c570219ba2593bcbbb8dc1b98d8cf5d529ab1434", + "sha256:a5f446dd5055667aabaee78487f2b5ab72e244f9bc0b2ffebfeec79051679984", + "sha256:ad36cfb355e24f1bd37cac88c112cd7730873f20fb0bdaf8ba59eedf8216079f", + "sha256:aec493917dd45e3c69d00a8874e7cbed844efd935595ef78a0f25f14312e33c6", + "sha256:b316144e85316da2723f9d8dc75bada12fa58489a527091fa1d5a612643d1a0e", + "sha256:b34ae4636dfc4e76a438ab826a0d1eed2589ca7d9a1b2d5bb546978ac6485461", + "sha256:b34b7aa8b261c1dbf7720b5d6f01f38243e9b9daf7e6b8bc1fd4657000062f2c", + "sha256:bc362ee4e314870a70f4ae88772d72d877246537d9f8cb8f7eacf10884862432", + "sha256:bed88b9a458e354014d662d47e7a5baafd7ff81c780fd91584a10d6ec842cb73", + "sha256:c0013fe6b46aa496a6749c77e00a3eb07952832ad6166bd481c74bda0dcb6d58", + "sha256:c0b5dcf9193625afd8ecc92312d6ed78781c46ecbf39af9ad4681fc9f464af88", + "sha256:c4325ff0442a12113a6379af66978c3fe562f846763287ef66bdc1d57925d337", + "sha256:c463ed05f9dfb9baebef68048aed8dcdc94411e4bf3d33a39ba97e271624f8f7", + "sha256:c8362467a0fdeccd47935f22c256bec5e6abe543bf0d66e3d3d57a8fb5731863", + "sha256:cd5bf1af8efe569654bbef5a3e0a56eca45f87cfcffab31dd8dde70da5982475", + "sha256:cf1ea2e34868f6fbf070e1af291c8180480310173de0b0c43fc38a02929fc0e3", + "sha256:d62dec4976954a23d7f91f2f4530852b0c7608116c257833922a896101336c51", + "sha256:d68c93e381010662ab873fea609bf6c0f428b6d0bb00f2c6939782e0818d37bf", + "sha256:d7c36232a90d4755b720fbd76739d8891732b18cf240a9c645d75f00639a9024", + "sha256:dd18772815d5f008fa03d2b9a681ae38d5ae9f0e599f7dda233c439fcaa00d40", + "sha256:ddc2f4dfd396c7bfa18e6ce371cba60e4cf9d2e5cdb71376aa2da264605b60b9", + "sha256:e003b002ec72c8d5a3e3da2989c7d6065b47d9eaa70cd8808b5384fbb970f4ec", + "sha256:e32a92116d4f2a80b629778280103d2a510a5b3f6314ceccd6e38006b5e92dcb", + "sha256:e4461d0f003a0aa9be2bdd1b798a041f177189c1a0f7619fe8c95ad08d9a45d7", + "sha256:e541ec6f2ec456934fd279a3120f856cd0aedd209fc3852eca563f81738f6861", + "sha256:e546e768d08ad55b20b11dbb78a745151acbd938f8f00d0cfbabe8b0199b9880", + "sha256:ea7d4a99f3b38c37eac212dbd6ec42b7a5ec51e2c74b5d3223e43c811609e65f", + "sha256:ed4eb745efbff0a8e9587d22a84be94a5eb7d2d99c02dacf7bd0911713ed14dd", + "sha256:f8a2f084546cc59ea99fda8e070be2fd140c3092dc11524a71aa8f0f3d5a55ca", + "sha256:fcb25daa9219b4cf3a0ab24b0eb9a5cc8949ed4dc72acb8fa16b7e1681aa3c58", + "sha256:fdea4952db2793c4ad0bdccd27c1d8fdd1423a92f04598bc39425bcc2b8ee46e" + ], + "markers": "python_version >= '3.8'", + "version": "==0.18.0" }, "sentry-sdk": { "hashes": [ - "sha256:633edefead34d976ff22e7edc367cdf57768e24bc714615ccae746d9d91795ae", - "sha256:a900845bd78c263d49695d48ce78a4bce1030bbd917e0b6cc021fc000c901113" + "sha256:b54c54a2160f509cf2757260d0cf3885b608c6192c2555a3857e3a4d0f84bdb3", + "sha256:c278e0f523f6f0ee69dc43ad26dcdb1202dffe5ac326ae31472e012d941bee21" ], "index": "pypi", - "version": "==1.16.0" + "markers": "python_version >= '3.6'", + "version": "==2.0.1" }, "service-identity": { "hashes": [ - "sha256:6e6c6086ca271dc11b033d17c3a8bea9f24ebff920c587da090afc9519419d34", - "sha256:f0b0caac3d40627c3c04d7a51b6e06721857a0e10a8775f2d1d7e72901b3a7db" + "sha256:6829c9d62fb832c2e1c435629b0a8c476e1929881f28bee4d20bc24161009221", + "sha256:a28caf8130c8a5c1c7a6f5293faaf239bbfb7751e4862436920ee6f2616f568a" ], - "version": "==21.1.0" + "version": "==24.1.0" }, "setuptools": { "hashes": [ - "sha256:e5fd0a713141a4a105412233c63dc4e17ba0090c8e8334594ac790ec97792330", - "sha256:f106dee1b506dee5102cc3f3e9e68137bbad6d47b616be7991714b0c62204251" + "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987", + "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32" ], - "markers": "python_version >= '3.7'", - "version": "==67.4.0" + "markers": "python_version >= '3.8'", + "version": "==69.5.1" }, "six": { "hashes": [ @@ -866,30 +976,30 @@ }, "sqlparse": { "hashes": [ - "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34", - "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268" + "sha256:714d0a4932c059d16189f58ef5411ec2287a4360f17cdd0edd2d09d4c5087c93", + "sha256:c204494cd97479d0e39f28c93d46c0b2d5959c7b9ab904762ea6c7af211c8663" ], - "markers": "python_version >= '3.5'", - "version": "==0.4.3" + "markers": "python_version >= '3.8'", + "version": "==0.5.0" }, "traitlets": { "hashes": [ - "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8", - "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9" + "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", + "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f" ], - "markers": "python_version >= '3.7'", - "version": "==5.9.0" + "markers": "python_version >= '3.8'", + "version": "==5.14.3" }, "twisted": { "extras": [ "tls" ], "hashes": [ - "sha256:32acbd40a94f5f46e7b42c109bfae2b302250945561783a8b7a059048f2d4d31", - "sha256:86c55f712cc5ab6f6d64e02503352464f0400f66d4f079096d744080afcccbd0" + "sha256:039f2e6a49ab5108abd94de187fa92377abe5985c7a72d68d0ad266ba19eae63", + "sha256:6b38b6ece7296b5e122c9eb17da2eeab3d98a198f50ca9efd00fb03e5b4fd4ae" ], - "markers": "python_full_version >= '3.7.1'", - "version": "==22.10.0" + "markers": "python_full_version >= '3.8.0'", + "version": "==24.3.0" }, "txaio": { "hashes": [ @@ -901,11 +1011,11 @@ }, "typing-extensions": { "hashes": [ - "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb", - "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4" + "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0", + "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a" ], - "markers": "python_version >= '3.7'", - "version": "==4.5.0" + "markers": "python_version >= '3.8'", + "version": "==4.11.0" }, "uritemplate": { "hashes": [ @@ -917,18 +1027,18 @@ }, "urllib3": { "hashes": [ - "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72", - "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1" + "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", + "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19" ], - "markers": "python_version >= '3.6'", - "version": "==1.26.14" + "markers": "python_version >= '3.8'", + "version": "==2.2.1" }, "wcwidth": { "hashes": [ - "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e", - "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0" + "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", + "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5" ], - "version": "==0.2.6" + "version": "==0.2.13" }, "webencodings": { "hashes": [ @@ -939,197 +1049,209 @@ }, "whitenoise": { "hashes": [ - "sha256:599dc6ca57e48929dfeffb2e8e187879bfe2aed0d49ca419577005b7f2cc930b", - "sha256:a02d6660ad161ff17e3042653c8e3f5ecbb2a2481a006bde125b9efb9a30113a" + "sha256:8998f7370973447fac1e8ef6e8ded2c5209a7b1f67c1012866dbcd09681c3251", + "sha256:b1f9db9bf67dc183484d760b99f4080185633136a273a03f6436034a41064146" ], "index": "pypi", - "version": "==6.4.0" - }, - "zope.interface": { - "hashes": [ - "sha256:008b0b65c05993bb08912f644d140530e775cf1c62a072bf9340c2249e613c32", - "sha256:0217a9615531c83aeedb12e126611b1b1a3175013bbafe57c702ce40000eb9a0", - "sha256:0fb497c6b088818e3395e302e426850f8236d8d9f4ef5b2836feae812a8f699c", - "sha256:17ebf6e0b1d07ed009738016abf0d0a0f80388e009d0ac6e0ead26fc162b3b9c", - "sha256:311196634bb9333aa06f00fc94f59d3a9fddd2305c2c425d86e406ddc6f2260d", - "sha256:3218ab1a7748327e08ef83cca63eea7cf20ea7e2ebcb2522072896e5e2fceedf", - "sha256:404d1e284eda9e233c90128697c71acffd55e183d70628aa0bbb0e7a3084ed8b", - "sha256:4087e253bd3bbbc3e615ecd0b6dd03c4e6a1e46d152d3be6d2ad08fbad742dcc", - "sha256:40f4065745e2c2fa0dff0e7ccd7c166a8ac9748974f960cd39f63d2c19f9231f", - "sha256:5334e2ef60d3d9439c08baedaf8b84dc9bb9522d0dacbc10572ef5609ef8db6d", - "sha256:604cdba8f1983d0ab78edc29aa71c8df0ada06fb147cea436dc37093a0100a4e", - "sha256:6373d7eb813a143cb7795d3e42bd8ed857c82a90571567e681e1b3841a390d16", - "sha256:655796a906fa3ca67273011c9805c1e1baa047781fca80feeb710328cdbed87f", - "sha256:65c3c06afee96c654e590e046c4a24559e65b0a87dbff256cd4bd6f77e1a33f9", - "sha256:696f3d5493eae7359887da55c2afa05acc3db5fc625c49529e84bd9992313296", - "sha256:6e972493cdfe4ad0411fd9abfab7d4d800a7317a93928217f1a5de2bb0f0d87a", - "sha256:7579960be23d1fddecb53898035a0d112ac858c3554018ce615cefc03024e46d", - "sha256:765d703096ca47aa5d93044bf701b00bbce4d903a95b41fff7c3796e747b1f1d", - "sha256:7e66f60b0067a10dd289b29dceabd3d0e6d68be1504fc9d0bc209cf07f56d189", - "sha256:8a2ffadefd0e7206adc86e492ccc60395f7edb5680adedf17a7ee4205c530df4", - "sha256:959697ef2757406bff71467a09d940ca364e724c534efbf3786e86eee8591452", - "sha256:9d783213fab61832dbb10d385a319cb0e45451088abd45f95b5bb88ed0acca1a", - "sha256:a16025df73d24795a0bde05504911d306307c24a64187752685ff6ea23897cb0", - "sha256:a2ad597c8c9e038a5912ac3cf166f82926feff2f6e0dabdab956768de0a258f5", - "sha256:bfee1f3ff62143819499e348f5b8a7f3aa0259f9aca5e0ddae7391d059dce671", - "sha256:d169ccd0756c15bbb2f1acc012f5aab279dffc334d733ca0d9362c5beaebe88e", - "sha256:d514c269d1f9f5cd05ddfed15298d6c418129f3f064765295659798349c43e6f", - "sha256:d692374b578360d36568dd05efb8a5a67ab6d1878c29c582e37ddba80e66c396", - "sha256:dbaeb9cf0ea0b3bc4b36fae54a016933d64c6d52a94810a63c00f440ecb37dd7", - "sha256:dc26c8d44472e035d59d6f1177eb712888447f5799743da9c398b0339ed90b1b", - "sha256:e1574980b48c8c74f83578d1e77e701f8439a5d93f36a5a0af31337467c08fcf", - "sha256:e74a578172525c20d7223eac5f8ad187f10940dac06e40113d62f14f3adb1e8f", - "sha256:e945de62917acbf853ab968d8916290548df18dd62c739d862f359ecd25842a6", - "sha256:f0980d44b8aded808bec5059018d64692f0127f10510eca71f2f0ace8fb11188", - "sha256:f98d4bd7bbb15ca701d19b93263cc5edfd480c3475d163f137385f49e5b3a3a7", - "sha256:fb68d212efd057596dee9e6582daded9f8ef776538afdf5feceb3059df2d2e7b" + "markers": "python_version >= '3.8'", + "version": "==6.6.0" + }, + "zope-interface": { + "hashes": [ + "sha256:014bb94fe6bf1786da1aa044eadf65bc6437bcb81c451592987e5be91e70a91e", + "sha256:01a0b3dd012f584afcf03ed814bce0fc40ed10e47396578621509ac031be98bf", + "sha256:10cde8dc6b2fd6a1d0b5ca4be820063e46ddba417ab82bcf55afe2227337b130", + "sha256:187f7900b63845dcdef1be320a523dbbdba94d89cae570edc2781eb55f8c2f86", + "sha256:1b0c4c90e5eefca2c3e045d9f9ed9f1e2cdbe70eb906bff6b247e17119ad89a1", + "sha256:22e8a218e8e2d87d4d9342aa973b7915297a08efbebea5b25900c73e78ed468e", + "sha256:26c9a37fb395a703e39b11b00b9e921c48f82b6e32cc5851ad5d0618cd8876b5", + "sha256:2bb78c12c1ad3a20c0d981a043d133299117b6854f2e14893b156979ed4e1d2c", + "sha256:2c3cfb272bcb83650e6695d49ae0d14dd06dc694789a3d929f23758557a23d92", + "sha256:2f32010ffb87759c6a3ad1c65ed4d2e38e51f6b430a1ca11cee901ec2b42e021", + "sha256:3c8731596198198746f7ce2a4487a0edcbc9ea5e5918f0ab23c4859bce56055c", + "sha256:40aa8c8e964d47d713b226c5baf5f13cdf3a3169c7a2653163b17ff2e2334d10", + "sha256:4137025731e824eee8d263b20682b28a0bdc0508de9c11d6c6be54163e5b7c83", + "sha256:46034be614d1f75f06e7dcfefba21d609b16b38c21fc912b01a99cb29e58febb", + "sha256:483e118b1e075f1819b3c6ace082b9d7d3a6a5eb14b2b375f1b80a0868117920", + "sha256:4d6b229f5e1a6375f206455cc0a63a8e502ed190fe7eb15e94a312dc69d40299", + "sha256:567d54c06306f9c5b6826190628d66753b9f2b0422f4c02d7c6d2b97ebf0a24e", + "sha256:5683aa8f2639016fd2b421df44301f10820e28a9b96382a6e438e5c6427253af", + "sha256:600101f43a7582d5b9504a7c629a1185a849ce65e60fca0f6968dfc4b76b6d39", + "sha256:62e32f02b3f26204d9c02c3539c802afc3eefb19d601a0987836ed126efb1f21", + "sha256:69dedb790530c7ca5345899a1b4cb837cc53ba669051ea51e8c18f82f9389061", + "sha256:72d5efecad16c619a97744a4f0b67ce1bcc88115aa82fcf1dc5be9bb403bcc0b", + "sha256:8d407e0fd8015f6d5dfad481309638e1968d70e6644e0753f229154667dd6cd5", + "sha256:a058e6cf8d68a5a19cb5449f42a404f0d6c2778b897e6ce8fadda9cea308b1b0", + "sha256:a1adc14a2a9d5e95f76df625a9b39f4709267a483962a572e3f3001ef90ea6e6", + "sha256:a56fe1261230093bfeedc1c1a6cd6f3ec568f9b07f031c9a09f46b201f793a85", + "sha256:ad4524289d8dbd6fb5aa17aedb18f5643e7d48358f42c007a5ee51a2afc2a7c5", + "sha256:afa0491a9f154cf8519a02026dc85a416192f4cb1efbbf32db4a173ba28b289a", + "sha256:bf34840e102d1d0b2d39b1465918d90b312b1119552cebb61a242c42079817b9", + "sha256:c40df4aea777be321b7e68facb901bc67317e94b65d9ab20fb96e0eb3c0b60a1", + "sha256:d0e7321557c702bd92dac3c66a2f22b963155fdb4600133b6b29597f62b71b12", + "sha256:d165d7774d558ea971cb867739fb334faf68fc4756a784e689e11efa3becd59e", + "sha256:e78a183a3c2f555c2ad6aaa1ab572d1c435ba42f1dc3a7e8c82982306a19b785", + "sha256:e8fa0fb05083a1a4216b4b881fdefa71c5d9a106e9b094cd4399af6b52873e91", + "sha256:f83d6b4b22262d9a826c3bd4b2fbfafe1d0000f085ef8e44cd1328eea274ae6a", + "sha256:f95bebd0afe86b2adc074df29edb6848fc4d474ff24075e2c263d698774e108d" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==5.5.2" + "markers": "python_version >= '3.7'", + "version": "==6.3" } }, "develop": { "alabaster": { "hashes": [ - "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3", - "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2" + "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", + "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92" ], - "markers": "python_version >= '3.6'", - "version": "==0.7.13" + "markers": "python_version >= '3.9'", + "version": "==0.7.16" + }, + "anyio": { + "hashes": [ + "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8", + "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6" + ], + "markers": "python_version >= '3.8'", + "version": "==4.3.0" }, "asgiref": { "hashes": [ - "sha256:71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac", - "sha256:9567dfe7bd8d3c8c892227827c41cce860b368104c3431da67a0c5a65a949506" + "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", + "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590" ], "index": "pypi", - "version": "==3.6.0" + "markers": "python_version >= '3.8'", + "version": "==3.8.1" }, "babel": { "hashes": [ - "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610", - "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455" + "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363", + "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287" ], "markers": "python_version >= '3.7'", - "version": "==2.12.1" + "version": "==2.14.0" }, "certifi": { "hashes": [ - "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", - "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" + "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f", + "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1" ], "markers": "python_version >= '3.6'", - "version": "==2022.12.7" + "version": "==2024.2.2" }, "cfgv": { "hashes": [ - "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426", - "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736" + "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", + "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560" ], - "markers": "python_full_version >= '3.6.1'", - "version": "==3.3.1" + "markers": "python_version >= '3.8'", + "version": "==3.4.0" }, "charset-normalizer": { "hashes": [ - "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b", - "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42", - "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d", - "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b", - "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a", - "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59", - "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154", - "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1", - "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c", - "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a", - "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d", - "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6", - "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b", - "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b", - "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783", - "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5", - "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918", - "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555", - "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639", - "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786", - "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e", - "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed", - "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820", - "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8", - "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3", - "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541", - "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14", - "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be", - "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e", - "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76", - "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b", - "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c", - "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b", - "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3", - "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc", - "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6", - "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59", - "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4", - "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d", - "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d", - "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3", - "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a", - "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea", - "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6", - "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e", - "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603", - "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24", - "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a", - "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58", - "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678", - "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a", - "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c", - "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6", - "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18", - "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174", - "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317", - "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f", - "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc", - "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837", - "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41", - "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c", - "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579", - "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753", - "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8", - "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291", - "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087", - "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866", - "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3", - "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d", - "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1", - "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca", - "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e", - "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db", - "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72", - "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d", - "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc", - "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539", - "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d", - "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af", - "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b", - "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602", - "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f", - "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478", - "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c", - "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e", - "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479", - "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7", - "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8" - ], - "markers": "python_version >= '3.6'", - "version": "==3.0.1" + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" }, "click": { "hashes": [ - "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", - "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" + "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" ], "markers": "python_version >= '3.7'", - "version": "==8.1.3" + "version": "==8.1.7" }, "colorama": { "hashes": [ @@ -1141,57 +1263,67 @@ }, "distlib": { "hashes": [ - "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46", - "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e" + "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784", + "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64" ], - "version": "==0.3.6" + "version": "==0.3.8" }, "django": { "hashes": [ - "sha256:44f714b81c5f190d9d2ddad01a532fe502fa01c4cb8faf1d081f4264ed15dcd8", - "sha256:f2f431e75adc40039ace496ad3b9f17227022e8b11566f4b363da44c7e44761e" + "sha256:4bd01a8c830bb77a8a3b0e7d8b25b887e536ad17a81ba2dce5476135c73312bd", + "sha256:916423499d75d62da7aa038d19aef23d23498d8df229775eb0a6309ee1013775" ], "index": "pypi", - "version": "==4.1.7" + "markers": "python_version >= '3.10'", + "version": "==5.0.4" }, "django-debug-toolbar": { "hashes": [ - "sha256:24ef1a7d44d25e60d7951e378454c6509bf536dce7e7d9d36e7c387db499bc27", - "sha256:879f8a4672d41621c06a4d322dcffa630fc4df056cada6e417ed01db0e5e0478" + "sha256:0b0dddee5ea29b9cb678593bc0d7a6d76b21d7799cb68e091a2148341a80f3c4", + "sha256:e09b7dcb8417b743234dfc57c95a7c1d1d87a88844abd13b4c5387f807b31bf6" ], "index": "pypi", - "version": "==3.8.1" + "markers": "python_version >= '3.8'", + "version": "==4.3.0" }, "docutils": { "hashes": [ - "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c", - "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06" + "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6", + "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==0.18.1" + "markers": "python_version >= '3.7'", + "version": "==0.20.1" }, "filelock": { "hashes": [ - "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de", - "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d" + "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f", + "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a" + ], + "markers": "python_version >= '3.8'", + "version": "==3.14.0" + }, + "h11": { + "hashes": [ + "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", + "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761" ], "markers": "python_version >= '3.7'", - "version": "==3.9.0" + "version": "==0.14.0" }, "identify": { "hashes": [ - "sha256:89e144fa560cc4cffb6ef2ab5e9fb18ed9f9b3cb054384bab4b95c12f6c309fe", - "sha256:93aac7ecf2f6abf879b8f29a8002d3c6de7086b8c28d88e1ad15045a15ab63f9" + "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa", + "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d" ], - "markers": "python_version >= '3.7'", - "version": "==2.5.18" + "markers": "python_version >= '3.8'", + "version": "==2.5.36" }, "idna": { "hashes": [ - "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", - "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", + "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" ], - "version": "==3.4" + "version": "==3.7" }, "imagesize": { "hashes": [ @@ -1201,178 +1333,185 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.4.1" }, - "importlib-metadata": { - "hashes": [ - "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad", - "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d" - ], - "markers": "python_version < '3.10'", - "version": "==6.0.0" - }, "jinja2": { "hashes": [ - "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", - "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" + "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa", + "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90" ], "markers": "python_version >= '3.7'", - "version": "==3.1.2" - }, - "livereload": { - "hashes": [ - "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869", - "sha256:ad4ac6f53b2d62bb6ce1a5e6e96f1f00976a32348afedcb4b6d68df2a1d346e4" - ], - "version": "==2.6.3" + "version": "==3.1.3" }, "markupsafe": { "hashes": [ - "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed", - "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc", - "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2", - "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460", - "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7", - "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0", - "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1", - "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa", - "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03", - "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323", - "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65", - "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013", - "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036", - "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f", - "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4", - "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419", - "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2", - "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619", - "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a", - "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a", - "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd", - "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7", - "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666", - "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65", - "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859", - "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625", - "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff", - "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156", - "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd", - "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba", - "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f", - "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1", - "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094", - "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a", - "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513", - "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed", - "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d", - "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3", - "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147", - "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c", - "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603", - "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601", - "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a", - "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1", - "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d", - "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3", - "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54", - "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2", - "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6", - "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58" + "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf", + "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", + "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", + "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3", + "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532", + "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", + "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", + "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df", + "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4", + "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", + "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", + "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", + "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8", + "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371", + "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2", + "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465", + "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52", + "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6", + "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", + "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", + "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", + "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0", + "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029", + "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", + "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a", + "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", + "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", + "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", + "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf", + "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9", + "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", + "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", + "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3", + "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", + "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46", + "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", + "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a", + "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", + "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", + "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", + "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea", + "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", + "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", + "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e", + "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", + "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f", + "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50", + "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", + "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", + "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", + "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff", + "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2", + "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", + "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", + "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf", + "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", + "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5", + "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab", + "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", + "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68" ], "markers": "python_version >= '3.7'", - "version": "==2.1.2" + "version": "==2.1.5" }, "nodeenv": { "hashes": [ - "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e", - "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b" + "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2", + "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", - "version": "==1.7.0" + "version": "==1.8.0" }, "packaging": { "hashes": [ - "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2", - "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97" + "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", + "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9" ], "markers": "python_version >= '3.7'", - "version": "==23.0" + "version": "==24.0" }, "platformdirs": { "hashes": [ - "sha256:13b08a53ed71021350c9e300d4ea8668438fb0046ab3937ac9a29913a1a1350a", - "sha256:accc3665857288317f32c7bebb5a8e482ba717b474f3fc1d18ca7f9214be0cef" + "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf", + "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1" ], - "markers": "python_version >= '3.7'", - "version": "==3.1.0" + "markers": "python_version >= '3.8'", + "version": "==4.2.1" }, "pre-commit": { "hashes": [ - "sha256:b80254e60668e1dd1f5c03a1c9e0413941d61f568a57d745add265945f65bfe8", - "sha256:d63e6537f9252d99f65755ae5b79c989b462d511ebbc481b561db6a297e1e865" + "sha256:5eae9e10c2b5ac51577c3452ec0a490455c45a0533f7960f993a0d01e59decab", + "sha256:e209d61b8acdcf742404408531f0c37d49d2c734fd7cff2d6076083d191cb060" ], "index": "pypi", - "version": "==3.1.1" + "markers": "python_version >= '3.9'", + "version": "==3.7.0" }, "pygments": { "hashes": [ - "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297", - "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717" + "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", + "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a" ], - "markers": "python_version >= '3.6'", - "version": "==2.14.0" + "markers": "python_version >= '3.8'", + "version": "==2.18.0" }, "pyyaml": { "hashes": [ - "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf", - "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", - "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", - "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", - "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", - "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", - "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", - "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", - "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", - "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", - "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", - "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", - "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782", - "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", - "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", - "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", - "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", - "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", - "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1", - "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", - "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", - "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", - "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", - "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", - "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", - "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d", - "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", - "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", - "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7", - "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", - "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", - "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", - "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358", - "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", - "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", - "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", - "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", - "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f", - "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", - "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" + "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", + "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", + "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", + "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", + "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", + "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", + "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", + "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", + "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", + "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", + "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", + "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", + "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", + "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", + "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", + "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", + "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", + "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", + "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", + "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", + "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", + "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", + "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", + "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", + "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", + "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", + "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", + "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", + "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", + "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", + "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", + "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", + "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", + "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", + "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", + "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", + "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", + "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", + "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", + "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", + "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", + "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", + "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", + "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", + "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", + "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", + "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", + "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", + "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", + "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", + "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" ], "markers": "python_version >= '3.6'", - "version": "==6.0" + "version": "==6.0.1" }, "requests": { "hashes": [ - "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", - "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", + "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" ], - "markers": "python_version >= '3.7' and python_version < '4'", - "version": "==2.28.2" + "markers": "python_version >= '3.7'", + "version": "==2.31.0" }, "selenium": { "hashes": [ @@ -1384,19 +1523,19 @@ }, "setuptools": { "hashes": [ - "sha256:e5fd0a713141a4a105412233c63dc4e17ba0090c8e8334594ac790ec97792330", - "sha256:f106dee1b506dee5102cc3f3e9e68137bbad6d47b616be7991714b0c62204251" + "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987", + "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32" ], - "markers": "python_version >= '3.7'", - "version": "==67.4.0" + "markers": "python_version >= '3.8'", + "version": "==69.5.1" }, - "six": { + "sniffio": { "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", + "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" + "markers": "python_version >= '3.7'", + "version": "==1.3.1" }, "snowballstemmer": { "hashes": [ @@ -1407,67 +1546,71 @@ }, "sphinx": { "hashes": [ - "sha256:0dac3b698538ffef41716cf97ba26c1c7788dba73ce6f150c1ff5b4720786dd2", - "sha256:807d1cb3d6be87eb78a381c3e70ebd8d346b9a25f3753e9947e866b2786865fc" + "sha256:413f75440be4cacf328f580b4274ada4565fb2187d696a84970c23f77b64d8c3", + "sha256:a4a7db75ed37531c05002d56ed6948d4c42f473a36f46e1382b0bd76ca9627bc" ], "index": "pypi", - "version": "==6.1.3" + "markers": "python_version >= '3.9'", + "version": "==7.3.7" }, "sphinx-autobuild": { "hashes": [ - "sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac", - "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05" + "sha256:1c0ed37a1970eed197f9c5a66d65759e7c4e4cba7b5a5d77940752bf1a59f2c7", + "sha256:f2522779d30fcbf0253e09714f274ce8c608cb6ebcd67922b1c54de59faba702" ], "index": "pypi", - "version": "==2021.3.14" + "markers": "python_version >= '3.9'", + "version": "==2024.4.16" }, "sphinx-intl": { "hashes": [ - "sha256:9798946b995989de691387651d70c3fc191275b587e2e519655541edfd7bbd68", - "sha256:9d9849ae42515b39786824e99f1e30db0404c377b01bb022690fc932b0221c02" + "sha256:56ad5f360fae4aa1cb963448c802f141b55c87223bb32a7b29e936620bd1a381", + "sha256:66976a85d31624dfcb564059a6918f90b31669269bfe3f30b2d72e81f225ab20" ], "index": "pypi", - "version": "==2.1.0" + "markers": "python_version >= '3.7'", + "version": "==2.2.0" }, "sphinx-rtd-theme": { "hashes": [ - "sha256:a0d8bd1a2ed52e0b338cbe19c4b2eef3c5e7a048769753dac6a9f059c7b641b8", - "sha256:f823f7e71890abe0ac6aaa6013361ea2696fc8d3e1fa798f463e82bdb77eeff2" + "sha256:bd5d7b80622406762073a04ef8fadc5f9151261563d47027de09910ce03afe6b", + "sha256:ec93d0856dc280cf3aee9a4c9807c60e027c7f7b461b77aeffed682e68f0e586" ], "index": "pypi", - "version": "==1.2.0" + "markers": "python_version >= '3.6'", + "version": "==2.0.0" }, "sphinxcontrib-applehelp": { "hashes": [ - "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228", - "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e" + "sha256:c40a4f96f3776c4393d933412053962fac2b84f4c99a7982ba42e09576a70619", + "sha256:cb61eb0ec1b61f349e5cc36b2028e9e7ca765be05e49641c97241274753067b4" ], - "markers": "python_version >= '3.8'", - "version": "==1.0.4" + "markers": "python_version >= '3.9'", + "version": "==1.0.8" }, "sphinxcontrib-devhelp": { "hashes": [ - "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e", - "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4" + "sha256:6485d09629944511c893fa11355bda18b742b83a2b181f9a009f7e500595c90f", + "sha256:9893fd3f90506bc4b97bdb977ceb8fbd823989f4316b28c3841ec128544372d3" ], - "markers": "python_version >= '3.5'", - "version": "==1.0.2" + "markers": "python_version >= '3.9'", + "version": "==1.0.6" }, "sphinxcontrib-htmlhelp": { "hashes": [ - "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff", - "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903" + "sha256:0dc87637d5de53dd5eec3a6a01753b1ccf99494bd756aafecd74b4fa9e729015", + "sha256:393f04f112b4d2f53d93448d4bce35842f62b307ccdc549ec1585e950bc35e04" ], - "markers": "python_version >= '3.8'", - "version": "==2.0.1" + "markers": "python_version >= '3.9'", + "version": "==2.0.5" }, "sphinxcontrib-jquery": { "hashes": [ - "sha256:8fb65f6dba84bf7bcd1aea1f02ab3955ac34611d838bcc95d4983b805b234daa", - "sha256:ed47fa425c338ffebe3c37e1cdb56e30eb806116b85f01055b158c7057fdb995" + "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", + "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae" ], - "markers": "python_version >= '3.1'", - "version": "==2.0.0" + "markers": "python_version >= '2.7'", + "version": "==4.1" }, "sphinxcontrib-jsmath": { "hashes": [ @@ -1479,68 +1622,218 @@ }, "sphinxcontrib-qthelp": { "hashes": [ - "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72", - "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6" + "sha256:053dedc38823a80a7209a80860b16b722e9e0209e32fea98c90e4e6624588ed6", + "sha256:e2ae3b5c492d58fcbd73281fbd27e34b8393ec34a073c792642cd8e529288182" ], - "markers": "python_version >= '3.5'", - "version": "==1.0.3" + "markers": "python_version >= '3.9'", + "version": "==1.0.7" }, "sphinxcontrib-serializinghtml": { "hashes": [ - "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd", - "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952" + "sha256:326369b8df80a7d2d8d7f99aa5ac577f51ea51556ed974e7716cfd4fca3f6cb7", + "sha256:93f3f5dc458b91b192fe10c397e324f262cf163d79f3282c158e8436a2c4511f" ], - "markers": "python_version >= '3.5'", - "version": "==1.1.5" + "markers": "python_version >= '3.9'", + "version": "==1.1.10" }, "sqlparse": { "hashes": [ - "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34", - "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268" + "sha256:714d0a4932c059d16189f58ef5411ec2287a4360f17cdd0edd2d09d4c5087c93", + "sha256:c204494cd97479d0e39f28c93d46c0b2d5959c7b9ab904762ea6c7af211c8663" ], - "markers": "python_version >= '3.5'", - "version": "==0.4.3" + "markers": "python_version >= '3.8'", + "version": "==0.5.0" }, - "tornado": { + "starlette": { "hashes": [ - "sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca", - "sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72", - "sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23", - "sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8", - "sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b", - "sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9", - "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13", - "sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75", - "sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac", - "sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e", - "sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b" + "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee", + "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823" ], - "markers": "python_version > '2.7'", - "version": "==6.2" + "markers": "python_version >= '3.8'", + "version": "==0.37.2" }, "urllib3": { "hashes": [ - "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72", - "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1" + "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", + "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19" ], - "markers": "python_version >= '3.6'", - "version": "==1.26.14" + "markers": "python_version >= '3.8'", + "version": "==2.2.1" }, - "virtualenv": { + "uvicorn": { "hashes": [ - "sha256:3c22fa5a7c7aa106ced59934d2c20a2ecb7f49b4130b8bf444178a16b880fa45", - "sha256:a8a4b8ca1e28f864b7514a253f98c1d62b64e31e77325ba279248c65fb4fcef4" + "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de", + "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0" ], - "markers": "python_version >= '3.7'", - "version": "==20.20.0" + "markers": "python_version >= '3.8'", + "version": "==0.29.0" }, - "zipp": { + "virtualenv": { "hashes": [ - "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b", - "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556" + "sha256:604bfdceaeece392802e6ae48e69cec49168b9c5f4a44e483963f9242eb0e78b", + "sha256:7aa9982a728ae5892558bff6a2839c00b9ed145523ece2274fad6f414690ae75" ], "markers": "python_version >= '3.7'", - "version": "==3.15.0" + "version": "==20.26.1" + }, + "watchfiles": { + "hashes": [ + "sha256:02b73130687bc3f6bb79d8a170959042eb56eb3a42df3671c79b428cd73f17cc", + "sha256:02d91cbac553a3ad141db016e3350b03184deaafeba09b9d6439826ee594b365", + "sha256:06247538e8253975bdb328e7683f8515ff5ff041f43be6c40bff62d989b7d0b0", + "sha256:08dca260e85ffae975448e344834d765983237ad6dc308231aa16e7933db763e", + "sha256:0d9ac347653ebd95839a7c607608703b20bc07e577e870d824fa4801bc1cb124", + "sha256:0dd5fad9b9c0dd89904bbdea978ce89a2b692a7ee8a0ce19b940e538c88a809c", + "sha256:11cd0c3100e2233e9c53106265da31d574355c288e15259c0d40a4405cbae317", + "sha256:18722b50783b5e30a18a8a5db3006bab146d2b705c92eb9a94f78c72beb94094", + "sha256:18d5b4da8cf3e41895b34e8c37d13c9ed294954907929aacd95153508d5d89d7", + "sha256:1ad7247d79f9f55bb25ab1778fd47f32d70cf36053941f07de0b7c4e96b5d235", + "sha256:1b8d1eae0f65441963d805f766c7e9cd092f91e0c600c820c764a4ff71a0764c", + "sha256:1bd467213195e76f838caf2c28cd65e58302d0254e636e7c0fca81efa4a2e62c", + "sha256:1c9198c989f47898b2c22201756f73249de3748e0fc9de44adaf54a8b259cc0c", + "sha256:1fd9a5205139f3c6bb60d11f6072e0552f0a20b712c85f43d42342d162be1235", + "sha256:214cee7f9e09150d4fb42e24919a1e74d8c9b8a9306ed1474ecaddcd5479c293", + "sha256:27b4035013f1ea49c6c0b42d983133b136637a527e48c132d368eb19bf1ac6aa", + "sha256:3a23092a992e61c3a6a70f350a56db7197242f3490da9c87b500f389b2d01eef", + "sha256:3ad692bc7792be8c32918c699638b660c0de078a6cbe464c46e1340dadb94c19", + "sha256:3ccceb50c611c433145502735e0370877cced72a6c70fd2410238bcbc7fe51d8", + "sha256:3d0f32ebfaa9c6011f8454994f86108c2eb9c79b8b7de00b36d558cadcedaa3d", + "sha256:3f92944efc564867bbf841c823c8b71bb0be75e06b8ce45c084b46411475a915", + "sha256:40bca549fdc929b470dd1dbfcb47b3295cb46a6d2c90e50588b0a1b3bd98f429", + "sha256:43babacef21c519bc6631c5fce2a61eccdfc011b4bcb9047255e9620732c8097", + "sha256:4566006aa44cb0d21b8ab53baf4b9c667a0ed23efe4aaad8c227bfba0bf15cbe", + "sha256:49f56e6ecc2503e7dbe233fa328b2be1a7797d31548e7a193237dcdf1ad0eee0", + "sha256:4c48a10d17571d1275701e14a601e36959ffada3add8cdbc9e5061a6e3579a5d", + "sha256:4ea10a29aa5de67de02256a28d1bf53d21322295cb00bd2d57fcd19b850ebd99", + "sha256:511f0b034120cd1989932bf1e9081aa9fb00f1f949fbd2d9cab6264916ae89b1", + "sha256:51ddac60b96a42c15d24fbdc7a4bfcd02b5a29c047b7f8bf63d3f6f5a860949a", + "sha256:57d430f5fb63fea141ab71ca9c064e80de3a20b427ca2febcbfcef70ff0ce895", + "sha256:59137c0c6826bd56c710d1d2bda81553b5e6b7c84d5a676747d80caf0409ad94", + "sha256:5a03651352fc20975ee2a707cd2d74a386cd303cc688f407296064ad1e6d1562", + "sha256:5eb86c6acb498208e7663ca22dbe68ca2cf42ab5bf1c776670a50919a56e64ab", + "sha256:642d66b75eda909fd1112d35c53816d59789a4b38c141a96d62f50a3ef9b3360", + "sha256:6674b00b9756b0af620aa2a3346b01f8e2a3dc729d25617e1b89cf6af4a54eb1", + "sha256:668c265d90de8ae914f860d3eeb164534ba2e836811f91fecc7050416ee70aa7", + "sha256:66fac0c238ab9a2e72d026b5fb91cb902c146202bbd29a9a1a44e8db7b710b6f", + "sha256:6c107ea3cf2bd07199d66f156e3ea756d1b84dfd43b542b2d870b77868c98c03", + "sha256:6c889025f59884423428c261f212e04d438de865beda0b1e1babab85ef4c0f01", + "sha256:6cb8fdc044909e2078c248986f2fc76f911f72b51ea4a4fbbf472e01d14faa58", + "sha256:6e9be3ef84e2bb9710f3f777accce25556f4a71e15d2b73223788d528fcc2052", + "sha256:7f762a1a85a12cc3484f77eee7be87b10f8c50b0b787bb02f4e357403cad0c0e", + "sha256:83a696da8922314ff2aec02987eefb03784f473281d740bf9170181829133765", + "sha256:853853cbf7bf9408b404754b92512ebe3e3a83587503d766d23e6bf83d092ee6", + "sha256:8ad3fe0a3567c2f0f629d800409cd528cb6251da12e81a1f765e5c5345fd0137", + "sha256:8c6ed10c2497e5fedadf61e465b3ca12a19f96004c15dcffe4bd442ebadc2d85", + "sha256:8d5f400326840934e3507701f9f7269247f7c026d1b6cfd49477d2be0933cfca", + "sha256:927c589500f9f41e370b0125c12ac9e7d3a2fd166b89e9ee2828b3dda20bfe6f", + "sha256:9a0aa47f94ea9a0b39dd30850b0adf2e1cd32a8b4f9c7aa443d852aacf9ca214", + "sha256:9b37a7ba223b2f26122c148bb8d09a9ff312afca998c48c725ff5a0a632145f7", + "sha256:9c873345680c1b87f1e09e0eaf8cf6c891b9851d8b4d3645e7efe2ec20a20cc7", + "sha256:9d09869f2c5a6f2d9df50ce3064b3391d3ecb6dced708ad64467b9e4f2c9bef3", + "sha256:9d353c4cfda586db2a176ce42c88f2fc31ec25e50212650c89fdd0f560ee507b", + "sha256:a1e3014a625bcf107fbf38eece0e47fa0190e52e45dc6eee5a8265ddc6dc5ea7", + "sha256:a3b9bec9579a15fb3ca2d9878deae789df72f2b0fdaf90ad49ee389cad5edab6", + "sha256:ab03a90b305d2588e8352168e8c5a1520b721d2d367f31e9332c4235b30b8994", + "sha256:aff06b2cac3ef4616e26ba17a9c250c1fe9dd8a5d907d0193f84c499b1b6e6a9", + "sha256:b3cab0e06143768499384a8a5efb9c4dc53e19382952859e4802f294214f36ec", + "sha256:b4a21f71885aa2744719459951819e7bf5a906a6448a6b2bbce8e9cc9f2c8128", + "sha256:b6d45d9b699ecbac6c7bd8e0a2609767491540403610962968d258fd6405c17c", + "sha256:be6dd5d52b73018b21adc1c5d28ac0c68184a64769052dfeb0c5d9998e7f56a2", + "sha256:c550a56bf209a3d987d5a975cdf2063b3389a5d16caf29db4bdddeae49f22078", + "sha256:c76c635fabf542bb78524905718c39f736a98e5ab25b23ec6d4abede1a85a6a3", + "sha256:c81818595eff6e92535ff32825f31c116f867f64ff8cdf6562cd1d6b2e1e8f3e", + "sha256:cfb92d49dbb95ec7a07511bc9efb0faff8fe24ef3805662b8d6808ba8409a71a", + "sha256:d23bcd6c8eaa6324fe109d8cac01b41fe9a54b8c498af9ce464c1aeeb99903d6", + "sha256:d5b1dc0e708fad9f92c296ab2f948af403bf201db8fb2eb4c8179db143732e49", + "sha256:d78f30cbe8b2ce770160d3c08cff01b2ae9306fe66ce899b73f0409dc1846c1b", + "sha256:d8f57c4461cd24fda22493109c45b3980863c58a25b8bec885ca8bea6b8d4b28", + "sha256:d9792dff410f266051025ecfaa927078b94cc7478954b06796a9756ccc7e14a9", + "sha256:e7941bbcfdded9c26b0bf720cb7e6fd803d95a55d2c14b4bd1f6a2772230c586", + "sha256:ebe684d7d26239e23d102a2bad2a358dedf18e462e8808778703427d1f584400", + "sha256:ec8c8900dc5c83650a63dd48c4d1d245343f904c4b64b48798c67a3767d7e165", + "sha256:f564bf68404144ea6b87a78a3f910cc8de216c6b12a4cf0b27718bf4ec38d303", + "sha256:fd7ac678b92b29ba630d8c842d8ad6c555abda1b9ef044d6cc092dacbfc9719d" + ], + "markers": "python_version >= '3.8'", + "version": "==0.21.0" + }, + "websockets": { + "hashes": [ + "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b", + "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6", + "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df", + "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b", + "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205", + "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892", + "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53", + "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2", + "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed", + "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c", + "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd", + "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b", + "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931", + "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30", + "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370", + "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be", + "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec", + "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf", + "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62", + "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b", + "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402", + "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f", + "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123", + "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9", + "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603", + "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45", + "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558", + "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4", + "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438", + "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137", + "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480", + "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447", + "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8", + "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04", + "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c", + "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb", + "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967", + "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b", + "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d", + "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def", + "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c", + "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92", + "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2", + "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113", + "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b", + "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28", + "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7", + "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d", + "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f", + "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468", + "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8", + "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae", + "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611", + "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d", + "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9", + "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca", + "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f", + "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2", + "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077", + "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2", + "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6", + "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374", + "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc", + "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e", + "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53", + "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399", + "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547", + "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3", + "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870", + "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5", + "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8", + "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7" + ], + "markers": "python_version >= '3.8'", + "version": "==12.0" } } } diff --git a/render.yaml b/render.yaml index 53b2fbbd92c..b32157cd14c 100644 --- a/render.yaml +++ b/render.yaml @@ -30,7 +30,7 @@ services: - key: DJANGO_SECRET_KEY generateValue: true - key: PYTHON_VERSION - value: "3.9.9" + value: "3.12.3" - key: WEB_CONCURRENCY value: 4 - key: TAB_DIRECTOR_EMAIL diff --git a/runtime.txt b/runtime.txt index 5402961979a..546f3c8de17 100644 --- a/runtime.txt +++ b/runtime.txt @@ -1 +1 @@ -python-3.9.12 +python-3.11.9 diff --git a/tabbycat/actionlog/models.py b/tabbycat/actionlog/models.py index 8ef044c2e09..893ccc1cfa6 100644 --- a/tabbycat/actionlog/models.py +++ b/tabbycat/actionlog/models.py @@ -153,7 +153,7 @@ def clean(self): if self.user is None and self.ip_address is None: raise ValidationError(_("All log entries require at least one of a user and an IP address.")) - def get_content_object_display(self, omit_tournament=False): + def get_content_object_display(self, omit_tournament=False, user=None): obj = self.content_object if obj is None: @@ -162,12 +162,12 @@ def get_content_object_display(self, omit_tournament=False): model_name = self.content_type.model try: if model_name == 'ballotsubmission': - if use_team_code_names(self.tournament, True): + if use_team_code_names(self.tournament, True, user): return obj.debate.matchup_codes else: return obj.debate.matchup elif model_name == 'debate': - if use_team_code_names(self.tournament, True): + if use_team_code_names(self.tournament, True, user): return obj.debate.matchup_codes else: return obj.debate.matchup @@ -192,6 +192,8 @@ def serialize(self): 'id': self.id, 'user': self.user.username if self.user else self.ip_address or _("anonymous"), 'type': self.get_type_display(), - 'param': self.get_content_object_display(omit_tournament=True), + # As the team names are passed in the content of the message for all users, + # must assume they don't have permission for real names + 'param': self.get_content_object_display(omit_tournament=True, user=None), 'timestamp': badge_datetime_format(self.timestamp), } diff --git a/tabbycat/adjallocation/migrations/0010_alter_adjudicatoradjudicatorconflict_unique_together_and_more.py b/tabbycat/adjallocation/migrations/0010_alter_adjudicatoradjudicatorconflict_unique_together_and_more.py new file mode 100644 index 00000000000..a297ce889b4 --- /dev/null +++ b/tabbycat/adjallocation/migrations/0010_alter_adjudicatoradjudicatorconflict_unique_together_and_more.py @@ -0,0 +1,56 @@ +# Generated by Django 5.0.4 on 2024-05-04 13:21 + +import utils.models +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('adjallocation', '0009_auto_20200902_1208'), + ('draw', '0008_alter_debateteam_side_alter_teamsideallocation_side'), + ('participants', '0021_team_seed'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='adjudicatoradjudicatorconflict', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='adjudicatorinstitutionconflict', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='adjudicatorteamconflict', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='debateadjudicator', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='teaminstitutionconflict', + unique_together=set(), + ), + migrations.AddConstraint( + model_name='adjudicatoradjudicatorconflict', + constraint=utils.models.UniqueConstraint(fields=('adjudicator1', 'adjudicator2'), name='adjallo_adjudicatoradjudicatorconflict_adjudicator1__adjudicator2_uniq'), + ), + migrations.AddConstraint( + model_name='adjudicatorinstitutionconflict', + constraint=utils.models.UniqueConstraint(fields=('adjudicator', 'institution'), name='adjallo_adjudicatorinstitutionconflict_adjudicator__institution_uniq'), + ), + migrations.AddConstraint( + model_name='adjudicatorteamconflict', + constraint=utils.models.UniqueConstraint(fields=('adjudicator', 'team'), name='adjallo_adjudicatorteamconflict_adjudicator__team_uniq'), + ), + migrations.AddConstraint( + model_name='debateadjudicator', + constraint=utils.models.UniqueConstraint(fields=('debate', 'adjudicator'), name='adjallo_debateadjudicator_debate__adjudicator_uniq'), + ), + migrations.AddConstraint( + model_name='teaminstitutionconflict', + constraint=utils.models.UniqueConstraint(fields=('team', 'institution'), name='adjallo_teaminstitutionconflict_team__institution_uniq'), + ), + ] diff --git a/tabbycat/adjallocation/models.py b/tabbycat/adjallocation/models.py index 635503cc526..7dd0e65be68 100644 --- a/tabbycat/adjallocation/models.py +++ b/tabbycat/adjallocation/models.py @@ -1,6 +1,8 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from utils.models import UniqueConstraint + class DebateAdjudicatorManager(models.Manager): use_for_related_fields = True @@ -31,9 +33,9 @@ class DebateAdjudicator(models.Model): timing_confirmed = models.BooleanField(null=True, verbose_name=_("available?")) class Meta: + constraints = [UniqueConstraint(fields=['debate', 'adjudicator'])] verbose_name = _("debate adjudicator") verbose_name_plural = _("debate adjudicators") - unique_together = ('debate', 'adjudicator') def __str__(self): return '{} in {} ({})'.format(self.adjudicator, self.debate, self.get_type_display()) @@ -50,9 +52,9 @@ class AdjudicatorTeamConflict(models.Model): verbose_name=_("team")) class Meta: + constraints = [UniqueConstraint(fields=['adjudicator', 'team'])] verbose_name = _("adjudicator-team conflict") verbose_name_plural = _("adjudicator-team conflicts") - unique_together = ('adjudicator', 'team') def __str__(self): return '{} with {}'.format(self.adjudicator, self.team) @@ -67,9 +69,9 @@ class AdjudicatorAdjudicatorConflict(models.Model): verbose_name=_("adjudicator 2")) class Meta: + constraints = [UniqueConstraint(fields=['adjudicator1', 'adjudicator2'])] verbose_name = _("adjudicator-adjudicator conflict") verbose_name_plural = _("adjudicator-adjudicator conflicts") - unique_together = ('adjudicator1', 'adjudicator2') def __str__(self): return '{} with {}'.format(self.adjudicator1, self.adjudicator2) @@ -82,9 +84,9 @@ class AdjudicatorInstitutionConflict(models.Model): verbose_name=_("institution")) class Meta: + constraints = [UniqueConstraint(fields=['adjudicator', 'institution'])] verbose_name = _("adjudicator-institution conflict") verbose_name_plural = _("adjudicator-institution conflicts") - unique_together = ('adjudicator', 'institution') def __str__(self): return '{} with {}'.format(self.adjudicator, self.institution) @@ -97,9 +99,9 @@ class TeamInstitutionConflict(models.Model): verbose_name=_("institution")) class Meta: + constraints = [UniqueConstraint(fields=['team', 'institution'])] verbose_name = _("team-institution conflict") verbose_name_plural = _("team-institution conflicts") - unique_together = ('team', 'institution') def __str__(self): return '{} with {}'.format(self.team, self.institution) diff --git a/tabbycat/adjallocation/templates/edit_conflicts.html b/tabbycat/adjallocation/templates/edit_conflicts.html index 9d3c8211d06..2d6e13ec22d 100644 --- a/tabbycat/adjallocation/templates/edit_conflicts.html +++ b/tabbycat/adjallocation/templates/edit_conflicts.html @@ -1,5 +1,9 @@ {% extends "base.html" %} {% block content %} - {% include "components/formset.html" with triple=True %} + {% if can_edit %} + {% include "components/formset.html" with triple=True %} + {% else %} + {% include "components/formset.html" with double=True %} + {% endif %} {% endblock content %} diff --git a/tabbycat/adjallocation/views.py b/tabbycat/adjallocation/views.py index 1e2f372e414..7308c287d56 100644 --- a/tabbycat/adjallocation/views.py +++ b/tabbycat/adjallocation/views.py @@ -13,6 +13,7 @@ from participants.models import Adjudicator, Region from participants.prefetch import populate_feedback_scores from tournaments.mixins import DebateDragAndDropMixin, TournamentMixin +from users.permissions import has_permission, Permission from utils.misc import ranks_dictionary, redirect_tournament, reverse_tournament from utils.mixins import AdministratorMixin from utils.views import ModelFormSetView @@ -90,6 +91,8 @@ class EditDebateAdjudicatorsView(BaseEditDebateOrPanelAdjudicatorsView): page_title = gettext_lazy("Edit Allocation") prefetch_adjs = True # Fetched in full as get_serialised + view_permission = Permission.VIEW_DEBATEADJUDICATORS + def get_extra_info(self): info = super().get_extra_info() return info @@ -104,6 +107,8 @@ class EditPanelAdjudicatorsView(BaseEditDebateOrPanelAdjudicatorsView): template_name = "edit_panel_adjudicators.html" page_title = gettext_lazy("Edit Panels") + view_permission = Permission.VIEW_PREFORMEDPANELS + def get_extra_info(self): info = super().get_extra_info() info['backUrl'] = reverse_tournament('panel-adjudicators-index', @@ -123,9 +128,10 @@ def debates_or_panels_factory(self, panels): context={'round': self.round}) -class PanelAdjudicatorsIndexView(TemplateView, AdministratorMixin): +class PanelAdjudicatorsIndexView(AdministratorMixin, TournamentMixin, TemplateView): template_name = "preformed_index.html" page_title = gettext_lazy("Preformed Panels") + view_permission = True # ============================================================================== @@ -143,13 +149,26 @@ class BaseAdjudicatorConflictsView(LogActionMixin, AdministratorMixin, Tournamen template_name = 'edit_conflicts.html' page_emoji = "🔶" - formset_factory_kwargs = { - 'extra': 10, - 'can_delete': True, - } + formset_factory_kwargs = {} + + def get_formset_factory_kwargs(self): + can_edit = has_permission(self.request.user, self.get_edit_permission(), self.tournament) + kwargs = super().get_formset_factory_kwargs() + kwargs['extra'] = 10 * int(can_edit) + kwargs['can_delete'] = can_edit + return kwargs + + def get_formset(self): + formset = super().get_formset() + if not has_permission(self.request.user, self.get_edit_permission(), self.tournament): + for form in formset: + for field in form.fields.values(): + field.disabled = True + return formset def get_context_data(self, **kwargs): kwargs['save_text'] = self.save_text + kwargs['can_edit'] = has_permission(self.request.user, self.get_edit_permission(), self.tournament) return super().get_context_data(**kwargs) def get_success_url(self, *args, **kwargs): @@ -167,6 +186,9 @@ def formset_valid(self, formset): class AdjudicatorTeamConflictsView(BaseAdjudicatorConflictsView): + view_permission = Permission.VIEW_ADJ_TEAM_CONFLICTS + edit_permission = Permission.EDIT_ADJ_TEAM_CONFLICTS + action_log_type = ActionLogEntry.ActionType.CONFLICTS_ADJ_TEAM_EDIT formset_model = AdjudicatorTeamConflict page_title = gettext_lazy("Adjudicator-Team Conflicts") @@ -211,6 +233,9 @@ def add_message(self, nsaved, ndeleted): class AdjudicatorAdjudicatorConflictsView(BaseAdjudicatorConflictsView): + view_permission = Permission.VIEW_ADJ_ADJ_CONFLICTS + edit_permission = Permission.EDIT_ADJ_ADJ_CONFLICTS + action_log_type = ActionLogEntry.ActionType.CONFLICTS_ADJ_ADJ_EDIT formset_model = AdjudicatorAdjudicatorConflict page_title = gettext_lazy("Adjudicator-Adjudicator Conflicts") @@ -251,6 +276,9 @@ def add_message(self, nsaved, ndeleted): class AdjudicatorInstitutionConflictsView(BaseAdjudicatorConflictsView): + view_permission = Permission.VIEW_ADJ_INST_CONFLICTS + edit_permission = Permission.EDIT_ADJ_INST_CONFLICTS + action_log_type = ActionLogEntry.ActionType.CONFLICTS_ADJ_INST_EDIT formset_model = AdjudicatorInstitutionConflict page_title = gettext_lazy("Adjudicator-Institution Conflicts") @@ -290,6 +318,9 @@ def add_message(self, nsaved, ndeleted): class TeamInstitutionConflictsView(BaseAdjudicatorConflictsView): + view_permission = Permission.VIEW_TEAM_INST_CONFLICTS + edit_permission = Permission.EDIT_TEAM_INST_CONFLICTS + action_log_type = ActionLogEntry.ActionType.CONFLICTS_TEAM_INST_EDIT formset_model = TeamInstitutionConflict page_title = gettext_lazy("Team-Institution Conflicts") diff --git a/tabbycat/adjfeedback/migrations/0015_alter_adjudicatorfeedback_unique_together_and_more.py b/tabbycat/adjfeedback/migrations/0015_alter_adjudicatorfeedback_unique_together_and_more.py new file mode 100644 index 00000000000..3cfab4bcf8b --- /dev/null +++ b/tabbycat/adjfeedback/migrations/0015_alter_adjudicatorfeedback_unique_together_and_more.py @@ -0,0 +1,80 @@ +# Generated by Django 5.0.4 on 2024-05-04 13:21 + +import utils.models +from django.conf import settings +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('adjallocation', '0010_alter_adjudicatoradjudicatorconflict_unique_together_and_more'), + ('adjfeedback', '0014_alter_adjudicatorfeedback_submitter_type'), + ('draw', '0008_alter_debateteam_side_alter_teamsideallocation_side'), + ('participants', '0021_team_seed'), + ('tournaments', '0010_alter_round_draw_type'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='adjudicatorfeedback', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='adjudicatorfeedbackbooleananswer', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='adjudicatorfeedbackfloatanswer', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='adjudicatorfeedbackintegeranswer', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='adjudicatorfeedbackmanyanswer', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='adjudicatorfeedbackquestion', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='adjudicatorfeedbackstringanswer', + unique_together=set(), + ), + migrations.AddConstraint( + model_name='adjudicatorfeedback', + constraint=utils.models.UniqueConstraint(fields=('adjudicator', 'source_adjudicator', 'source_team', 'version'), name='adjfeed_adjudicatorfeedback_adjudicator__source_adjudicator__source_team__version_uniq'), + ), + migrations.AddConstraint( + model_name='adjudicatorfeedbackbooleananswer', + constraint=utils.models.UniqueConstraint(fields=('question', 'feedback'), name='adjfeed_adjudicatorfeedbackbooleananswer_question__feedback_uniq'), + ), + migrations.AddConstraint( + model_name='adjudicatorfeedbackfloatanswer', + constraint=utils.models.UniqueConstraint(fields=('question', 'feedback'), name='adjfeed_adjudicatorfeedbackfloatanswer_question__feedback_uniq'), + ), + migrations.AddConstraint( + model_name='adjudicatorfeedbackintegeranswer', + constraint=utils.models.UniqueConstraint(fields=('question', 'feedback'), name='adjfeed_adjudicatorfeedbackintegeranswer_question__feedback_uniq'), + ), + migrations.AddConstraint( + model_name='adjudicatorfeedbackmanyanswer', + constraint=utils.models.UniqueConstraint(fields=('question', 'feedback'), name='adjfeed_adjudicatorfeedbackmanyanswer_question__feedback_uniq'), + ), + migrations.AddConstraint( + model_name='adjudicatorfeedbackquestion', + constraint=utils.models.UniqueConstraint(fields=('tournament', 'reference'), name='adjfeed_adjudicatorfeedbackquestion_tournament__reference_uniq'), + ), + migrations.AddConstraint( + model_name='adjudicatorfeedbackquestion', + constraint=utils.models.UniqueConstraint(fields=('tournament', 'seq'), name='adjfeed_adjudicatorfeedbackquestion_tournament__seq_uniq'), + ), + migrations.AddConstraint( + model_name='adjudicatorfeedbackstringanswer', + constraint=utils.models.UniqueConstraint(fields=('question', 'feedback'), name='adjfeed_adjudicatorfeedbackstringanswer_question__feedback_uniq'), + ), + ] diff --git a/tabbycat/adjfeedback/models.py b/tabbycat/adjfeedback/models.py index b6524f7473b..d8613d073c7 100644 --- a/tabbycat/adjfeedback/models.py +++ b/tabbycat/adjfeedback/models.py @@ -7,6 +7,7 @@ from adjallocation.models import DebateAdjudicator from results.models import Submission +from utils.models import UniqueConstraint class AdjudicatorBaseScoreHistory(models.Model): @@ -34,7 +35,7 @@ class AdjudicatorFeedbackAnswer(models.Model): class Meta: abstract = True - unique_together = [('question', 'feedback')] + constraints = [UniqueConstraint(fields=['question', 'feedback'])] class AdjudicatorFeedbackBooleanAnswer(AdjudicatorFeedbackAnswer): @@ -179,7 +180,10 @@ class AdjudicatorFeedbackQuestion(models.Model): default=list) class Meta: - unique_together = [('tournament', 'reference'), ('tournament', 'seq')] + constraints = [ + UniqueConstraint(fields=['tournament', 'reference']), + UniqueConstraint(fields=['tournament', 'seq']), + ] verbose_name = _("adjudicator feedback question") verbose_name_plural = _("adjudicator feedback questions") @@ -242,7 +246,9 @@ class AdjudicatorFeedback(Submission): help_text=_("Whether the feedback should affect the adjudicator's score")) class Meta: - unique_together = [('adjudicator', 'source_adjudicator', 'source_team', 'version')] + constraints = [ + UniqueConstraint(fields=['adjudicator', 'source_adjudicator', 'source_team', 'version']), + ] verbose_name = _("adjudicator feedback") verbose_name_plural = _("adjudicator feedbacks") diff --git a/tabbycat/adjfeedback/views.py b/tabbycat/adjfeedback/views.py index 72b78a92871..a99b9495e22 100644 --- a/tabbycat/adjfeedback/views.py +++ b/tabbycat/adjfeedback/views.py @@ -23,6 +23,7 @@ from tournaments.mixins import (PersonalizablePublicTournamentPageMixin, PublicTournamentPageMixin, SingleObjectByRandomisedUrlMixin, SingleObjectFromTournamentMixin, TournamentMixin) from tournaments.models import Round +from users.permissions import Permission from utils.misc import reverse_tournament from utils.mixins import AdministratorMixin, AssistantMixin from utils.tables import TabbycatTableBuilder @@ -122,6 +123,7 @@ class FeedbackOverview(AdministratorMixin, BaseFeedbackOverview): sort_key = 'score' sort_order = 'desc' template_name = 'feedback_overview.html' + view_permission = Permission.VIEW_FEEDBACK_OVERVIEW def annotate_table(self, table, adjudicators): feedback_weight = self.tournament.current_round.feedback_weight @@ -142,6 +144,7 @@ class FeedbackByTargetView(AdministratorMixin, TournamentMixin, VueTableTemplate template_name = "feedback_base.html" page_title = gettext_lazy("Find Feedback on Adjudicator") page_emoji = '🔍' + view_permission = Permission.VIEW_FEEDBACK def get_table(self): adjudicators = self.tournament.adjudicator_set.annotate(feedback_count=Count('adjudicatorfeedback')) @@ -164,6 +167,7 @@ class FeedbackBySourceView(AdministratorMixin, TournamentMixin, VueTableTemplate template_name = "feedback_base.html" page_title = gettext_lazy("Find Feedback") page_emoji = '🔍' + view_permission = Permission.VIEW_FEEDBACK def get_tables(self): tournament = self.tournament @@ -252,6 +256,7 @@ def get_feedback_queryset(self): class FeedbackCardsView(FeedbackMixin, AdministratorMixin, TournamentMixin, TemplateView): """Base class for views displaying feedback as cards.""" template_name = "feedback_cards_list.html" + view_permission = Permission.VIEW_FEEDBACK def get_score_thresholds(self): tournament = self.tournament @@ -404,6 +409,7 @@ class AdminAddFeedbackIndexView(AdministratorMixin, BaseAddFeedbackIndexView): of the feedback.""" template_name = 'add_feedback.html' tabroom = True + view_permission = Permission.ADD_FEEDBACK def get_from_adj_link(self, adj): return reverse_tournament('adjfeedback-add-from-adjudicator', @@ -521,7 +527,7 @@ def get_success_url(self): class AdminAddFeedbackView(AdministratorMixin, BaseTabroomAddFeedbackView): - pass + edit_permission = Permission.ADD_FEEDBACK class AssistantAddFeedbackView(AssistantMixin, BaseTabroomAddFeedbackView): @@ -594,7 +600,7 @@ class PublicAddFeedbackByIdUrlView(PublicAddFeedbackView): tabroom = False def get_team_short_name(self, team): - use_code_names = use_team_code_names(self.tournament, admin=False) + use_code_names = use_team_code_names(self.tournament, admin=False, user=self.request.user) return team.code_name if use_code_names else team.short_name def is_page_enabled(self, tournament): @@ -644,6 +650,7 @@ class SetAdjudicatorBaseScoreView(BaseAdjudicatorActionView): action_log_type = ActionLogEntry.ActionType.TEST_SCORE_EDIT action_log_content_object_attr = 'atsh' + edit_permission = Permission.EDIT_BASEJUDGESCORES_IND def modify_adjudicator(self, request, adjudicator): try: @@ -663,6 +670,7 @@ def modify_adjudicator(self, request, adjudicator): class SetAdjudicatorBreakingStatusView(AdministratorMixin, TournamentMixin, LogActionMixin, View): + edit_permission = Permission.EDIT_ADJ_BREAK action_log_type = ActionLogEntry.ActionType.ADJUDICATOR_BREAK_SET def post(self, request, *args, **kwargs): @@ -722,6 +730,7 @@ def get_tables(self): class FeedbackProgress(AdministratorMixin, BaseFeedbackProgressView): template_name = 'feedback_base.html' + view_permission = Permission.VIEW_FEEDBACK_UNSUBMITTED class PublicFeedbackProgress(PublicTournamentPageMixin, BaseFeedbackProgressView): @@ -754,6 +763,7 @@ def get_redirect_url(self, *args, **kwargs): class ConfirmFeedbackView(BaseFeedbackToggleView): + edit_permission = Permission.EDIT_FEEDBACK_CONFIRM def feedback_result(self, feedback): return _("confirmed") if feedback.confirmed else _("un-confirmed") @@ -767,6 +777,7 @@ def modify_feedback(self, feedback): class IgnoreFeedbackView(BaseFeedbackToggleView): + edit_permission = Permission.EDIT_FEEDBACK_IGNORE def feedback_result(self, feedback): return _("ignored") if feedback.ignored else _("un-ignored") @@ -783,6 +794,7 @@ def modify_feedback(self, feedback): class UpdateAdjudicatorScoresView(AdministratorMixin, LogActionMixin, TournamentMixin, FormView): template_name = 'update_adjudicator_scores.html' form_class = UpdateAdjudicatorScoresForm + edit_permission = Permission.EDIT_JUDGESCORES_BULK action_log_type = ActionLogEntry.ActionType.UPDATE_ADJUDICATOR_SCORES def get_context_data(self, **kwargs): diff --git a/tabbycat/api/mixins.py b/tabbycat/api/mixins.py index 591642d3315..b3e3ed83fa2 100644 --- a/tabbycat/api/mixins.py +++ b/tabbycat/api/mixins.py @@ -7,7 +7,7 @@ from actionlog.models import ActionLogEntry from tournaments.models import Round, Tournament -from .permissions import APIEnabledPermission, IsAdminOrReadOnly, PublicIfReleasedPermission, PublicPreferencePermission +from .permissions import APIEnabledPermission, IsAdminOrReadOnly, PerTournamentPermissionRequired, PublicIfReleasedPermission, PublicPreferencePermission class APILogActionMixin(LogActionMixin): @@ -69,16 +69,16 @@ def get_serializer_context(self): class AdministratorAPIMixin: - permission_classes = [APIEnabledPermission, IsAdminUser] + permission_classes = [APIEnabledPermission, IsAdminUser | PerTournamentPermissionRequired] class TournamentPublicAPIMixin: - permission_classes = [APIEnabledPermission, PublicPreferencePermission] + permission_classes = [APIEnabledPermission, PublicPreferencePermission | PerTournamentPermissionRequired] class OnReleasePublicAPIMixin(TournamentPublicAPIMixin): - permission_classes = [APIEnabledPermission, PublicIfReleasedPermission] + permission_classes = [APIEnabledPermission, PublicIfReleasedPermission | PerTournamentPermissionRequired] class PublicAPIMixin: - permission_classes = [APIEnabledPermission, IsAdminOrReadOnly] + permission_classes = [APIEnabledPermission, IsAdminOrReadOnly | PerTournamentPermissionRequired] diff --git a/tabbycat/api/permissions.py b/tabbycat/api/permissions.py index 2021413391a..c0fc745184c 100644 --- a/tabbycat/api/permissions.py +++ b/tabbycat/api/permissions.py @@ -1,6 +1,8 @@ from dynamic_preferences.registries import global_preferences_registry from rest_framework.permissions import BasePermission, SAFE_METHODS +from users.permissions import has_permission + class APIEnabledPermission(BasePermission): message = "The API has been disabled on this site." @@ -30,3 +32,32 @@ def has_object_permission(self, request, view, obj): class IsAdminOrReadOnly(BasePermission): def has_permission(self, request, view): return request.method in SAFE_METHODS or (request.user and request.user.is_staff) + + +class PerTournamentPermissionRequired(BasePermission): + def get_required_permission(self, view): + """ + Given a model and an HTTP method, return the list of permission + codes that the user is required to have. + """ + return ({ + 'list': getattr(view, 'list_permission', False), + 'create': getattr(view, 'create_permission', False), + 'retrieve': getattr(view, 'list_permission', False), + 'update': getattr(view, 'update_permission', False), + 'partial_update': getattr(view, 'update_permission', False), + 'destroy': getattr(view, 'destroy_permission', False), + 'delete_all': getattr(view, 'destroy_permission', False), + 'add_blank': getattr(view, 'create_permission', False), + 'GET': getattr(view, 'list_permission', False), + 'POST': getattr(view, 'update_permission', False), + 'PUT': getattr(view, 'update_permission', False), + 'PATCH': getattr(view, 'update_permission', False), + 'DELETE': getattr(view, 'destroy_permission', False), + }).get(getattr(view, 'action', view.request.method), False) + + def has_permission(self, request, view): + if not hasattr(view, 'tournament'): + return True + perm = self.get_required_permission(view) + return has_permission(request.user, perm, view.tournament) diff --git a/tabbycat/api/serializers.py b/tabbycat/api/serializers.py index 3823ed3a9ca..bceb5a54ebf 100644 --- a/tabbycat/api/serializers.py +++ b/tabbycat/api/serializers.py @@ -1,6 +1,7 @@ from collections import OrderedDict from collections.abc import Mapping -from functools import partialmethod +from datetime import date, datetime, time +from functools import partial, partialmethod from django.contrib.auth import get_user_model from django.core.exceptions import ValidationError as DjangoValidationError @@ -27,6 +28,8 @@ from standings.speakers import SpeakerStandingsGenerator from standings.teams import TeamStandingsGenerator from tournaments.models import Round, Tournament +from users.models import Group +from users.permissions import has_permission, Permission from venues.models import Venue, VenueCategory, VenueConstraint from . import fields @@ -56,7 +59,7 @@ class V1RootSerializer(serializers.Serializer): class V1LinksSerializer(serializers.Serializer): tournaments = serializers.HyperlinkedIdentityField(view_name='api-tournament-list') institutions = serializers.HyperlinkedIdentityField(view_name='api-global-institution-list') - users = serializers.HyperlinkedIdentityField(view_name='api-users-list') + users = serializers.HyperlinkedIdentityField(view_name='api-user-list') _links = V1LinksSerializer(source='*', read_only=True) @@ -159,7 +162,12 @@ def create(self, validated_data): if isinstance(motion_data, Motion): # If passed in a URL - Becomes an object validated_data['motion'] = motion_data else: - validated_data['motion'] = Motion(text=motion_data['text'], reference=motion_data['reference'], info_slide=motion_data.get('info_slide', ''), tournament=self.context['tournament']) + validated_data['motion'] = Motion( + text=motion_data['text'], + reference=motion_data['reference'], + info_slide=motion_data.get('info_slide', ''), + tournament=self.context['tournament'], + ) validated_data['motion'].save() return super().create(validated_data) @@ -171,6 +179,16 @@ class RoundLinksSerializer(serializers.Serializer): availabilities = fields.TournamentHyperlinkedIdentityField(view_name='api-availability-list', lookup_field='seq', lookup_url_kwarg='round_seq') preformed_panels = fields.TournamentHyperlinkedIdentityField(view_name='api-preformedpanel-list', lookup_field='seq', lookup_url_kwarg='round_seq') + class TimeOrDateTimeField(serializers.DateTimeField): + def to_internal_value(self, value): + try: + value = time.fromisoformat(value) + except ValueError: + return super().to_internal_value(value) + + value = datetime.combine(date.today(), value) + return super().to_internal_value(value) + url = fields.TournamentHyperlinkedIdentityField( view_name='api-round-detail', lookup_field='seq', lookup_url_kwarg='round_seq') @@ -179,16 +197,19 @@ class RoundLinksSerializer(serializers.Serializer): queryset=BreakCategory.objects.all(), allow_null=True, required=False) motions = RoundMotionSerializer(many=True, source='roundmotion_set', required=False) + starts_at = TimeOrDateTimeField(required=False, allow_null=True) _links = RoundLinksSerializer(source='*', read_only=True) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if not is_staff(kwargs.get('context')): - self.fields.pop('feedback_weight') + with_permission = partial(has_permission, user=kwargs['context']['request'].user, tournament=kwargs['context']['tournament']) + if not with_permission(permission=Permission.VIEW_FEEDBACK_OVERVIEW): + self.fields.pop('feedback_weight') # Can't show in a ListSerializer - if isinstance(self.instance, QuerySet) or not self.instance.motions_released: + if not with_permission(permission=Permission.VIEW_MOTION) and (isinstance(self.instance, QuerySet) or not self.instance.motions_released): self.fields.pop('motions') class Meta: @@ -207,6 +228,9 @@ def validate(self, data): def create(self, validated_data): motions_data = validated_data.pop('roundmotion_set', []) + if len(motions_data) > 0 and not has_permission(self.context['request'].user, Permission.EDIT_MOTION, self.context['tournament']): + raise serializers.PermissionDenied('Editing motions disallowed') + round = super().create(validated_data) if len(motions_data) > 0: @@ -221,6 +245,8 @@ def create(self, validated_data): def update(self, instance, validated_data): motions_data = validated_data.pop('roundmotion_set', []) + if len(motions_data) > 0 and not has_permission(self.context['request'].user, Permission.EDIT_MOTION, self.context['tournament']): + raise serializers.PermissionDenied('Editing motions disallowed') for i, roundmotion in enumerate(motions_data, start=1): roundmotion['seq'] = i @@ -472,14 +498,20 @@ class SpeakerLinksSerializer(serializers.Serializer): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if not is_staff(kwargs.get('context')): - self.fields.pop('gender') - self.fields.pop('email') - self.fields.pop('phone') - self.fields.pop('pronoun') - self.fields.pop('url_key') - self.fields.pop('barcode') - - if kwargs['context']['tournament'].pref('participant_code_names') == 'everywhere': + t = kwargs['context']['tournament'] + with_permission = partial(has_permission, user=kwargs['context']['request'].user, tournament=t) + if not with_permission(permission=Permission.VIEW_PARTICIPANT_CONTACT): + self.fields.pop('email') + self.fields.pop('phone') + if not with_permission(permission=Permission.VIEW_PARTICIPANT_GENDER): + self.fields.pop('gender') + self.fields.pop('pronoun') + if not with_permission(permission=Permission.VIEW_PRIVATE_URLS): + self.fields.pop('url_key') + if not with_permission(permission=Permission.VIEW_CHECKIN): + self.fields.pop('barcode') + + if not with_permission(permission=Permission.VIEW_PARTICIPANT_DECODED) and t.pref('participant_code_names') == 'everywhere': self.fields.pop('name') class Meta: @@ -539,27 +571,39 @@ def __init__(self, *args, **kwargs): # Remove private fields in the public endpoint if needed if not is_staff(kwargs.get('context')): - self.fields.pop('institution_conflicts') - self.fields.pop('team_conflicts') - self.fields.pop('adjudicator_conflicts') - self.fields.pop('venue_constraints') - t = kwargs['context']['tournament'] - if not t.pref('show_adjudicator_institutions'): + with_permission = partial(has_permission, user=kwargs['context']['request'].user, tournament=t) + + if not with_permission(permission=Permission.VIEW_ADJ_INST_CONFLICTS): + self.fields.pop('institution_conflicts') + if not with_permission(permission=Permission.VIEW_ADJ_TEAM_CONFLICTS): + self.fields.pop('team_conflicts') + if not with_permission(permission=Permission.VIEW_ADJ_ADJ_CONFLICTS): + self.fields.pop('adjudicator_conflicts') + if not with_permission(permission=Permission.VIEW_ROOMCONSTRAINTS): + self.fields.pop('venue_constraints') + + if not with_permission(permission=Permission.VIEW_PARTICIPANT_INST) and not t.pref('show_adjudicator_institutions'): self.fields.pop('institution') - if not t.pref('public_breaking_adjs'): + if not with_permission(permission=Permission.VIEW_ADJ_BREAK) and not t.pref('public_breaking_adjs'): self.fields.pop('breaking') - if t.pref('participant_code_names') == 'everywhere': + if not with_permission(permission=Permission.VIEW_PARTICIPANT_DECODED) and not t.pref('participant_code_names') == 'everywhere': self.fields.pop('name') - self.fields.pop('base_score') - self.fields.pop('trainee') - self.fields.pop('gender') - self.fields.pop('email') - self.fields.pop('phone') - self.fields.pop('pronoun') - self.fields.pop('url_key') - self.fields.pop('barcode') + if not with_permission(permission=Permission.VIEW_FEEDBACK_OVERVIEW): + self.fields.pop('base_score') + self.fields.pop('trainee') + + if not with_permission(permission=Permission.VIEW_PARTICIPANT_CONTACT): + self.fields.pop('email') + self.fields.pop('phone') + if not with_permission(permission=Permission.VIEW_PARTICIPANT_GENDER): + self.fields.pop('gender') + self.fields.pop('pronoun') + if not with_permission(permission=Permission.VIEW_PRIVATE_URLS): + self.fields.pop('url_key') + if not with_permission(permission=Permission.VIEW_CHECKIN): + self.fields.pop('barcode') class Meta: model = Adjudicator @@ -650,20 +694,25 @@ def __init__(self, *args, **kwargs): # Remove private fields in the public endpoint if needed if not is_staff(kwargs.get('context')): - self.fields.pop('institution_conflicts') - self.fields.pop('venue_constraints') - t = kwargs['context']['tournament'] - if t.pref('team_code_names') in ('admin-tooltips-code', 'admin-tooltips-real', 'everywhere'): + with_permission = partial(has_permission, user=kwargs['context']['request'].user, tournament=t) + + if not with_permission(permission=Permission.VIEW_TEAM_INST_CONFLICTS): + self.fields.pop('institution_conflicts') + if not with_permission(permission=Permission.VIEW_ROOMCONSTRAINTS): + self.fields.pop('venue_constraints') + + if not with_permission(permission=Permission.VIEW_DECODED_TEAMS) and t.pref('team_code_names') in ('admin-tooltips-code', 'admin-tooltips-real', 'everywhere'): self.fields.pop('institution') self.fields.pop('use_institution_prefix') self.fields.pop('reference') self.fields.pop('short_reference') self.fields.pop('short_name') self.fields.pop('long_name') - elif not t.pref('show_team_institutions'): + elif not with_permission(permission=Permission.VIEW_PARTICIPANT_INST) and not t.pref('show_team_institutions'): self.fields.pop('institution') self.fields.pop('use_institution_prefix') + if not t.pref('public_break_categories'): self.fields.pop('break_categories') @@ -761,7 +810,9 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if not is_staff(kwargs.get('context')): - self.fields.pop('venue_constraints') + with_permission = partial(has_permission, user=kwargs['context']['request'].user, tournament=kwargs['context']['tournament']) + if not with_permission(permission=Permission.VIEW_ROOMCONSTRAINTS): + self.fields.pop('venue_constraints') def create(self, validated_data): venue_constraints = validated_data.pop('venue_constraints', []) @@ -803,8 +854,10 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if not is_staff(kwargs.get('context')): - self.fields.pop('teams') - self.fields.pop('adjudicators') + with_permission = partial(has_permission, user=kwargs['context']['request'].user, tournament=kwargs['context']['tournament']) + if not with_permission(permission=Permission.VIEW_PARTICIPANT_INST): + self.fields.pop('teams') + self.fields.pop('adjudicators') class VenueSerializer(serializers.ModelSerializer): @@ -931,10 +984,12 @@ class PairingLinksSerializer(serializers.Serializer): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if not is_staff(kwargs.get('context')): - self.fields.pop('bracket') - self.fields.pop('room_rank') - self.fields.pop('importance') - self.fields.pop('result_status') + with_permission = partial(has_permission, user=kwargs['context']['request'].user, tournament=kwargs['context']['tournament']) + if not with_permission(permission=Permission.VIEW_ADMIN_DRAW): + self.fields.pop('bracket') + self.fields.pop('room_rank') + self.fields.pop('importance') + self.fields.pop('result_status') class Meta: model = Debate @@ -1430,11 +1485,22 @@ class Meta: class UserSerializer(serializers.ModelSerializer): - url = serializers.HyperlinkedIdentityField(view_name='api-users-detail') + + class TournamentPermissionsSerializer(serializers.Serializer): + tournament = serializers.HyperlinkedIdentityField(view_name='api-tournament-detail', lookup_field='slug', lookup_url_kwarg='tournament_slug') + groups = fields.TournamentHyperlinkedRelatedField(many=True, view_name='api-group-detail', queryset=Group.objects.all(), default=[]) + permissions = serializers.ListField(child=serializers.ChoiceField(choices=Permission.choices), required=False) + + url = serializers.HyperlinkedIdentityField(view_name='api-user-detail') + tournaments = TournamentPermissionsSerializer(many=True, required=False) class Meta: model = get_user_model() - fields = ('url', 'id', 'username', 'password', 'email', 'is_staff', 'is_superuser', 'is_active') + fields = ('id', 'url', 'username', 'password', 'is_superuser', 'is_staff', 'email', 'is_active', 'date_joined', 'last_login', 'tournaments') + read_only_fields = ('date_joined', 'last_login') + extra_kwargs = { + 'password': {'write_only': True}, + } def create(self, validated_data): user = self.Meta.model(**validated_data) @@ -1442,3 +1508,11 @@ def create(self, validated_data): user.save() return user + + +class GroupSerializer(serializers.ModelSerializer): + url = fields.TournamentHyperlinkedIdentityField(view_name='api-group-detail') + + class Meta: + model = Group + exclude = ('tournament',) diff --git a/tabbycat/api/tests/test_serializers.py b/tabbycat/api/tests/test_serializers.py index 792f443e4d2..21162b1ad68 100644 --- a/tabbycat/api/tests/test_serializers.py +++ b/tabbycat/api/tests/test_serializers.py @@ -1,4 +1,6 @@ import logging +import zoneinfo +from datetime import date, datetime, time from django.contrib.auth import get_user_model from rest_framework.test import APIClient, APITestCase @@ -14,6 +16,7 @@ from utils.tests import CompletedTournamentTestMixin User = get_user_model() +tz = zoneinfo.ZoneInfo('Australia/Melbourne') class RoundSerializerTests(CompletedTournamentTestMixin, APITestCase): @@ -103,6 +106,37 @@ def test_can_update_round_motion(self): self.assertEqual(response.status_code, 200) self.assertEqual(response.data['name'], 'Round Five') + def test_can_give_start_datetime(self): + client = APIClient() + client.login(username="admin", password="admin") + self.tournament.round_set.get(seq=5).delete() + response = client.post(reverse_tournament('api-round-list', self.tournament), { + 'motions': [], + 'seq': 5, + 'name': 'Round 5', + 'abbreviation': 'R5', + 'draw_type': 'P', + 'starts_at': '2023-11-18T00:00:00Z', + }) + self.assertEqual(response.status_code, 201) + self.assertEqual(datetime.fromisoformat(response.data['starts_at']), datetime(2023, 11, 18, 11, 0, 0, tzinfo=tz)) + + def test_can_give_start_time(self): + client = APIClient() + client.login(username="admin", password="admin") + self.tournament.round_set.get(seq=5).delete() + response = client.post(reverse_tournament('api-round-list', self.tournament), { + 'motions': [], + 'seq': 5, + 'name': 'Round 5', + 'abbreviation': 'R5', + 'draw_type': 'P', + 'starts_at': '00:00:00', + }) + print(response.data) + self.assertEqual(response.status_code, 201) + self.assertEqual(datetime.fromisoformat(response.data['starts_at']), datetime.combine(date.today(), time(0, 0, 0, tzinfo=tz))) + class MotionSerializerTests(CompletedTournamentTestMixin, APITestCase): diff --git a/tabbycat/api/urls.py b/tabbycat/api/urls.py index 5fc195e9e0b..192e9107052 100644 --- a/tabbycat/api/urls.py +++ b/tabbycat/api/urls.py @@ -225,6 +225,15 @@ name='api-venuecategory-detail'), ])), + path('/user-groups', include([ + path('', + views.GroupViewSet.as_view(list_methods), + name='api-group-list'), + path('/', + views.GroupViewSet.as_view(detail_methods), + name='api-group-detail'), + ])), + path('/', include(pref_router.urls)), # Preferences ])), ])), @@ -236,13 +245,14 @@ views.GlobalInstitutionViewSet.as_view(detail_methods), name='api-global-institution-detail'), ])), + path('/users', include([ path('', views.UserViewSet.as_view(list_methods), - name='api-users-list'), + name='api-user-list'), path('/', - views.UserViewSet.as_view({'get': 'retrieve', 'delete': 'destroy'}), - name='api-users-detail'), + views.UserViewSet.as_view(detail_methods), + name='api-user-detail'), ])), ])), ] diff --git a/tabbycat/api/utils.py b/tabbycat/api/utils.py index acbad4278f0..95c753b07a9 100644 --- a/tabbycat/api/utils.py +++ b/tabbycat/api/utils.py @@ -1,4 +1,4 @@ def is_staff(context): # OpenAPI generation does not have a view (sometimes context is also None in that circumstance). # Avoid redacting fields. - return context is None or 'view' not in context or context['request'].user.is_staff + return context is None or 'view' not in context or not context['request'].user.is_anonymous diff --git a/tabbycat/api/views.py b/tabbycat/api/views.py index 45ab4d2c201..0a302cd23f2 100644 --- a/tabbycat/api/views.py +++ b/tabbycat/api/views.py @@ -4,6 +4,7 @@ from asgiref.sync import async_to_sync from channels.layers import get_channel_layer from django.conf import settings +from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType from django.db.models import Count, Prefetch, Q from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter @@ -13,6 +14,7 @@ from rest_framework.fields import DateTimeField from rest_framework.generics import GenericAPIView, get_object_or_404, RetrieveUpdateAPIView from rest_framework.mixins import ListModelMixin +from rest_framework.permissions import IsAdminUser from rest_framework.response import Response from rest_framework.reverse import reverse from rest_framework.views import APIView @@ -36,12 +38,13 @@ from standings.teams import TeamStandingsGenerator from tournaments.mixins import TournamentFromUrlMixin from tournaments.models import Round, Tournament +from users.permissions import get_permissions, Permission from venues.models import Venue, VenueCategory from . import serializers from .fields import ParticipantAvailabilityForeignKeyField from .mixins import AdministratorAPIMixin, APILogActionMixin, PublicAPIMixin, RoundAPIMixin, TournamentAPIMixin, TournamentPublicAPIMixin -from .permissions import APIEnabledPermission, PublicPreferencePermission +from .permissions import APIEnabledPermission, PerTournamentPermissionRequired, PublicPreferencePermission tournament_parameter = OpenApiParameter('tournament_slug', description="The tournament's slug", type=str, location="path") @@ -83,7 +86,7 @@ def get(self, request, format=None): """Entrypoint for version 1 of the API""" tournaments_create_url = reverse('api-tournament-list', request=request, format=format) institution_create_url = reverse('api-global-institution-list', request=request, format=format) - users_create_url = reverse('api-users-list', request=request, format=format) + users_create_url = reverse('api-user-list', request=request, format=format) return Response({ "_links": { "tournaments": tournaments_create_url, @@ -132,6 +135,9 @@ class TournamentPreferenceViewSet(TournamentFromUrlMixin, AdministratorAPIMixin, queryset = TournamentPreferenceModel.objects.all() serializer_class = PreferenceSerializer + list_permission = Permission.VIEW_TOURNAMENTPREFERENCEMODEL + update_permission = Permission.EDIT_TOURNAMENTPREFERENCEMODEL + action_log_content_object_attr = 'obj' action_log_type_updated = ActionLogEntry.ActionType.OPTIONS_EDIT @@ -155,6 +161,10 @@ class RoundViewSet(TournamentAPIMixin, PublicAPIMixin, ModelViewSet): action_log_type_created = ActionLogEntry.ActionType.ROUND_CREATE action_log_type_updated = ActionLogEntry.ActionType.ROUND_EDIT + create_permission = Permission.CREATE_ROUND + update_permission = Permission.EDIT_ROUND + destroy_permission = False + def get_queryset(self): return super().get_queryset().select_related( 'break_category', 'break_category__tournament', @@ -177,6 +187,11 @@ class MotionViewSet(TournamentAPIMixin, TournamentPublicAPIMixin, ModelViewSet): action_log_type_created = ActionLogEntry.ActionType.MOTION_EDIT action_log_type_updated = ActionLogEntry.ActionType.MOTION_EDIT + list_permission = Permission.VIEW_MOTION + create_permission = Permission.EDIT_MOTION + update_permission = Permission.EDIT_MOTION + destroy_permission = False + def get_queryset(self): filters = Q() if self.tournament.pref('public_motions') and not (self.tournament.pref('motion_tab_released') or self.request.user.is_staff): @@ -198,6 +213,11 @@ class BreakCategoryViewSet(TournamentAPIMixin, PublicAPIMixin, ModelViewSet): action_log_type_created = ActionLogEntry.ActionType.BREAK_CATEGORIES_EDIT action_log_type_updated = ActionLogEntry.ActionType.BREAK_CATEGORIES_EDIT + list_permission = Permission.VIEW_BREAK_CATEGORIES + create_permission = Permission.EDIT_BREAK_CATEGORIES + update_permission = Permission.EDIT_BREAK_CATEGORIES + destroy_permission = Permission.EDIT_BREAK_CATEGORIES + @extend_schema(tags=['speaker-categories'], parameters=[tournament_parameter]) @extend_schema_view( @@ -213,6 +233,11 @@ class SpeakerCategoryViewSet(TournamentAPIMixin, PublicAPIMixin, ModelViewSet): action_log_type_created = ActionLogEntry.ActionType.SPEAKER_CATEGORIES_EDIT action_log_type_updated = ActionLogEntry.ActionType.SPEAKER_CATEGORIES_EDIT + list_permission = Permission.VIEW_SPEAKER_CATEGORIES + create_permission = Permission.EDIT_SPEAKER_CATEGORIES + update_permission = Permission.EDIT_SPEAKER_CATEGORIES + destroy_permission = Permission.EDIT_SPEAKER_CATEGORIES + def get_queryset(self): if not self.request.user or not self.request.user.is_staff: return super().get_queryset().filter(public=True) @@ -230,6 +255,10 @@ class BreakEligibilityView(TournamentAPIMixin, TournamentPublicAPIMixin, Retriev access_preference = 'public_break_categories' action_log_type_updated = ActionLogEntry.ActionType.BREAK_ELIGIBILITY_EDIT + list_permission = Permission.VIEW_BREAK_ELIGIBILITY + create_permission = Permission.EDIT_BREAK_ELIGIBILITY + update_permission = Permission.EDIT_BREAK_ELIGIBILITY + def get_queryset(self): return super().get_queryset().prefetch_related('team_set') @@ -245,6 +274,10 @@ class SpeakerEligibilityView(TournamentAPIMixin, TournamentPublicAPIMixin, Retri access_preference = 'public_participants' action_log_type_updated = ActionLogEntry.ActionType.SPEAKER_ELIGIBILITY_EDIT + list_permission = Permission.VIEW_SPEAKER_ELIGIBILITY + create_permission = Permission.EDIT_SPEAKER_ELIGIBILITY + update_permission = Permission.EDIT_SPEAKER_ELIGIBILITY + def get_queryset(self): qs = super().get_queryset().prefetch_related('speaker_set') if not self.request.user or not self.request.user.is_staff: @@ -261,6 +294,11 @@ class BreakingTeamsView(TournamentAPIMixin, TournamentPublicAPIMixin, GenerateBr access_preference = 'public_breaking_teams' action_log_content_object_attr = 'break_category' + list_permission = Permission.VIEW_BREAK + create_permission = Permission.GENERATE_BREAK + update_permission = Permission.GENERATE_BREAK + destroy_permission = Permission.GENERATE_BREAK + @property def break_category(self): if not hasattr(self, "_break_category"): @@ -313,6 +351,11 @@ class InstitutionViewSet(TournamentAPIMixin, TournamentPublicAPIMixin, ModelView action_log_type_created = ActionLogEntry.ActionType.INSTITUTION_CREATE action_log_type_updated = ActionLogEntry.ActionType.INSTITUTION_EDIT + list_permission = Permission.VIEW_INSTITUTIONS + create_permission = Permission.ADD_INSTITUTIONS + update_permission = Permission.ADD_INSTITUTIONS + destroy_permission = Permission.ADD_INSTITUTIONS + def perform_create(self, serializer): serializer.save() self.log_action(type=self.action_log_type_created) @@ -347,6 +390,11 @@ class TeamViewSet(TournamentAPIMixin, TournamentPublicAPIMixin, ModelViewSet): action_log_type_created = ActionLogEntry.ActionType.TEAM_CREATE action_log_type_updated = ActionLogEntry.ActionType.TEAM_EDIT + list_permission = Permission.VIEW_TEAMS + create_permission = Permission.ADD_TEAMS + update_permission = Permission.ADD_TEAMS + destroy_permission = Permission.ADD_TEAMS + def get_queryset(self): category_prefetch = Prefetch('categories', queryset=SpeakerCategory.objects.all().select_related('tournament')) if not self.request.user or not self.request.user.is_staff: @@ -379,6 +427,11 @@ class AdjudicatorViewSet(TournamentAPIMixin, TournamentPublicAPIMixin, ModelView action_log_type_created = ActionLogEntry.ActionType.ADJUDICATOR_CREATE action_log_type_updated = ActionLogEntry.ActionType.ADJUDICATOR_EDIT + list_permission = Permission.VIEW_ADJUDICATORS + create_permission = Permission.ADD_ADJUDICATORS + update_permission = Permission.ADD_ADJUDICATORS + destroy_permission = Permission.ADD_ADJUDICATORS + def get_break_permission(self): return self.request.user.is_staff or self.tournament.pref('public_breaking_adjs') @@ -410,6 +463,11 @@ class GlobalInstitutionViewSet(AdministratorAPIMixin, ModelViewSet): action_log_type_created = ActionLogEntry.ActionType.INSTITUTION_CREATE action_log_type_updated = ActionLogEntry.ActionType.INSTITUTION_EDIT + list_permission = Permission.VIEW_INSTITUTIONS + create_permission = Permission.ADD_INSTITUTIONS + update_permission = Permission.ADD_INSTITUTIONS + destroy_permission = Permission.ADD_INSTITUTIONS + def get_queryset(self): filters = Q() if self.request.query_params.get('region'): @@ -433,6 +491,11 @@ class SpeakerViewSet(TournamentAPIMixin, TournamentPublicAPIMixin, ModelViewSet) action_log_type_created = ActionLogEntry.ActionType.SPEAKER_CREATE action_log_type_updated = ActionLogEntry.ActionType.SPEAKER_EDIT + list_permission = Permission.VIEW_TEAMS + create_permission = Permission.ADD_TEAMS + update_permission = Permission.ADD_TEAMS + destroy_permission = Permission.ADD_TEAMS + def perform_create(self, serializer): serializer.save() self.log_action(type=self.action_log_type_created) @@ -459,6 +522,11 @@ class VenueViewSet(TournamentAPIMixin, PublicAPIMixin, ModelViewSet): action_log_type_created = ActionLogEntry.ActionType.VENUE_CREATE action_log_type_updated = ActionLogEntry.ActionType.VENUE_EDIT + list_permission = Permission.VIEW_ROOMS + create_permission = Permission.ADD_ROOMS + update_permission = Permission.ADD_ROOMS + destroy_permission = Permission.ADD_ROOMS + def get_queryset(self): # Tournament must exist for URLs return super().get_queryset().select_related('tournament').prefetch_related( @@ -479,6 +547,11 @@ class VenueCategoryViewSet(TournamentAPIMixin, PublicAPIMixin, ModelViewSet): action_log_type_created = ActionLogEntry.ActionType.VENUE_CATEGORY_CREATE action_log_type_updated = ActionLogEntry.ActionType.VENUE_CATEGORIES_EDIT + list_permission = Permission.VIEW_ROOMCATEGORIES + create_permission = Permission.EDIT_ROOMCATEGORIES + update_permission = Permission.EDIT_ROOMCATEGORIES + destroy_permission = Permission.EDIT_ROOMCATEGORIES + def get_queryset(self): # Tournament must exist for URLs return super().get_queryset().select_related('tournament').prefetch_related( @@ -492,6 +565,11 @@ class BaseCheckinsView(AdministratorAPIMixin, TournamentAPIMixin, APIView): lookup_field = 'pk' lookup_url_kwarg = None + list_permission = Permission.VIEW_CHECKIN + create_permission = Permission.EDIT_PARTICIPANT_CHECKIN + update_permission = Permission.EDIT_PARTICIPANT_CHECKIN + destroy_permission = Permission.EDIT_PARTICIPANT_CHECKIN + def get_object_queryset(self): lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} @@ -628,6 +706,10 @@ class VenueCheckinsView(BaseCheckinsView): object_api_view = 'api-venue-detail' window_preference_pref = 'checkin_window_venues' + create_permission = Permission.EDIT_ROOM_CHECKIN + update_permission = Permission.EDIT_ROOM_CHECKIN + destroy_permission = Permission.EDIT_ROOM_CHECKIN + def get_metrics_params(generator): metrics = { @@ -697,6 +779,8 @@ class SubstantiveSpeakerStandingsView(BaseStandingsView): tournament_field = 'team__tournament' generator = SpeakerStandingsGenerator + list_permission = Permission.VIEW_SPEAKERSSTANDINGS + def get_queryset(self): category = self.request.query_params.get('category', None) if category is not None: @@ -726,6 +810,8 @@ class TeamStandingsView(BaseStandingsView): model = Team generator = TeamStandingsGenerator + list_permission = Permission.VIEW_TEAMSTANDINGS + def get_queryset(self): category = self.request.query_params.get('category', None) if category is not None: @@ -747,6 +833,8 @@ class SpeakerRoundStandingsRoundsView(TournamentAPIMixin, TournamentPublicAPIMix tournament_field = "team__tournament" access_preference = 'speaker_tab_released' + list_permission = Permission.VIEW_SPEAKERSSTANDINGS + def get_queryset(self): qs = super().get_queryset().prefetch_related(Prefetch('team__debateteam_set', queryset=DebateTeam.objects.all().select_related('debate__round__tournament'))) data = {s.id: s for s in qs.all()} @@ -786,6 +874,8 @@ class TeamRoundStandingsRoundsView(TournamentAPIMixin, TournamentPublicAPIMixin, serializer_class = serializers.TeamRoundScoresSerializer access_preference = 'team_tab_released' + list_permission = Permission.VIEW_TEAMSTANDINGS + def get_queryset(self): ts_pf = Prefetch('teamscore_set', queryset=TeamScore.objects.filter(ballot_submission__confirmed=True), to_attr='round_scores') qs = super().get_queryset().prefetch_related( @@ -838,7 +928,12 @@ def get_round_status(self, view): round_released_field = 'draw_status' round_released_value = Round.Status.RELEASED - permission_classes = [APIEnabledPermission, Permission] + """list_permission = Permission.VIEW_DEBATE + create_permission = Permission.GENERATE_DEBATE + update_permission = Permission.GENERATE_DEBATE + destroy_permission = Permission.GENERATE_DEBATE""" + + permission_classes = [APIEnabledPermission, Permission | PerTournamentPermissionRequired] action_log_type_created = ActionLogEntry.ActionType.DEBATE_CREATE action_log_type_updated = ActionLogEntry.ActionType.DEBATE_EDIT @@ -873,6 +968,11 @@ class BallotViewSet(RoundAPIMixin, TournamentPublicAPIMixin, ModelViewSet): tournament_field = 'debate__round__tournament' round_field = 'debate__round' + list_permission = Permission.VIEW_BALLOTSUBMISSIONS + create_permission = Permission.ADD_BALLOTSUBMISSIONS + update_permission = Permission.EDIT_BALLOTSUBMISSIONS + destroy_permission = Permission.MARK_BALLOTSUBMISSIONS + action_log_type_created = ActionLogEntry.ActionType.BALLOT_CREATE action_log_type_updated = ActionLogEntry.ActionType.BALLOT_EDIT @@ -930,6 +1030,11 @@ class FeedbackQuestionViewSet(TournamentAPIMixin, PublicAPIMixin, ModelViewSet): action_log_type_created = ActionLogEntry.ActionType.FEEDBACK_QUESTION_CREATE action_log_type_updated = ActionLogEntry.ActionType.FEEDBACK_QUESTION_EDIT + list_permission = True + create_permission = Permission.EDIT_FEEDBACKQUESTION + update_permission = Permission.EDIT_FEEDBACKQUESTION + destroy_permission = Permission.EDIT_FEEDBACKQUESTION + def get_queryset(self): filters = Q() if self.request.query_params.get('from_adj'): @@ -959,6 +1064,11 @@ class FeedbackViewSet(TournamentAPIMixin, AdministratorAPIMixin, ModelViewSet): action_log_type_created = ActionLogEntry.ActionType.FEEDBACK_SAVE action_log_type_updated = ActionLogEntry.ActionType.FEEDBACK_SAVE + list_permission = Permission.VIEW_FEEDBACK + create_permission = Permission.ADD_FEEDBACK + update_permission = Permission.EDIT_FEEDBACK_IGNORE + destroy_permission = Permission.EDIT_FEEDBACK_CONFIRM + def perform_create(self, serializer): serializer.save() @@ -1002,6 +1112,11 @@ class AvailabilitiesViewSet(RoundAPIMixin, AdministratorAPIMixin, APIView): serializer_class = serializers.AvailabilitiesSerializer # Isn't actually used action_log_type_updated = ActionLogEntry.ActionType.AVAIL_SAVE + list_permission = Permission.VIEW_ROUNDAVAILABILITIES + create_permission = Permission.EDIT_ROUNDAVAILABILITIES + update_permission = Permission.EDIT_ROUNDAVAILABILITIES + destroy_permission = Permission.EDIT_ROUNDAVAILABILITIES + extra_params = [ OpenApiParameter('adjudicators', description='Only include adjudicators', required=False, type=bool, default=False), OpenApiParameter('teams', description='Only include teams', required=False, type=bool, default=False), @@ -1096,6 +1211,11 @@ class PreformedPanelViewSet(RoundAPIMixin, AdministratorAPIMixin, ModelViewSet): action_log_type_created = ActionLogEntry.ActionType.PREFORMED_PANELS_CREATE action_log_type_updated = ActionLogEntry.ActionType.PREFORMED_PANELS_ADJUDICATOR_EDIT + list_permission = Permission.VIEW_PREFORMEDPANELS + create_permission = Permission.EDIT_PREFORMEDPANELS + update_permission = Permission.EDIT_PREFORMEDPANELS + destroy_permission = Permission.EDIT_PREFORMEDPANELS + @property def debate(self): if hasattr(self, '_debate'): @@ -1141,12 +1261,41 @@ def add_blank(self, request, *args, **kwargs): @extend_schema(tags=['users']) @extend_schema_view( - list=extend_schema(summary="Get users"), + list=extend_schema(summary="List all users"), create=extend_schema(summary="Create user"), retrieve=extend_schema(summary="Get user", parameters=[id_parameter]), + update=extend_schema(summary="Update user", parameters=[id_parameter]), + partial_update=extend_schema(summary="Patch user", parameters=[id_parameter]), + destroy=extend_schema(summary="Deactivate user", parameters=[id_parameter]), ) class UserViewSet(AdministratorAPIMixin, ModelViewSet): serializer_class = serializers.UserSerializer + permission_classes = [IsAdminUser] def get_queryset(self): - return self.get_serializer_class().Meta.model.objects.all() + qs = get_user_model().objects.prefetch_related('membership_set__group__tournament', 'userpermission_set__tournament') + for user in qs: + user.tournaments = get_permissions(user) + return qs + + def get_object(self): + obj = super().get_object() + obj.tournaments = get_permissions(obj) + return obj + + def perform_destroy(self, instance): + instance.is_active = False + instance.save() + + +@extend_schema(tags=['users'], parameters=[tournament_parameter]) +@extend_schema_view( + list=extend_schema(summary="List all permission groups in tournament"), + create=extend_schema(summary="Create group"), + retrieve=extend_schema(summary="Get group", parameters=[id_parameter]), + update=extend_schema(summary="Update group", parameters=[id_parameter]), + partial_update=extend_schema(summary="Patch group", parameters=[id_parameter]), + destroy=extend_schema(summary="Delete group", parameters=[id_parameter]), +) +class GroupViewSet(TournamentAPIMixin, AdministratorAPIMixin, ModelViewSet): + serializer_class = serializers.GroupSerializer diff --git a/tabbycat/availability/migrations/0006_alter_roundavailability_unique_together_and_more.py b/tabbycat/availability/migrations/0006_alter_roundavailability_unique_together_and_more.py new file mode 100644 index 00000000000..3a2af28baf9 --- /dev/null +++ b/tabbycat/availability/migrations/0006_alter_roundavailability_unique_together_and_more.py @@ -0,0 +1,24 @@ +# Generated by Django 5.0.4 on 2024-05-04 13:21 + +import utils.models +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('availability', '0005_auto_20180307_2217'), + ('contenttypes', '0002_remove_content_type_name'), + ('tournaments', '0010_alter_round_draw_type'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='roundavailability', + unique_together=set(), + ), + migrations.AddConstraint( + model_name='roundavailability', + constraint=utils.models.UniqueConstraint(fields=('round', 'content_type', 'object_id'), name='availab_roundavailability_round__content_type__object_id_uniq'), + ), + ] diff --git a/tabbycat/availability/models.py b/tabbycat/availability/models.py index d974d8bf43a..41bec62d974 100644 --- a/tabbycat/availability/models.py +++ b/tabbycat/availability/models.py @@ -3,6 +3,8 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from utils.models import UniqueConstraint + class RoundAvailability(models.Model): @@ -20,7 +22,9 @@ class RoundAvailability(models.Model): verbose_name=_("round")) class Meta: - unique_together = [('round', 'content_type', 'object_id')] + constraints = [ + UniqueConstraint(fields=['round', 'content_type', 'object_id']), + ] verbose_name = _("round availability") verbose_name_plural = _("round availabilities") diff --git a/tabbycat/availability/views.py b/tabbycat/availability/views.py index 7f058fb06e3..742438dd1fd 100644 --- a/tabbycat/availability/views.py +++ b/tabbycat/availability/views.py @@ -21,6 +21,7 @@ from draw.models import Debate from participants.models import Adjudicator, Team from tournaments.mixins import RoundMixin +from users.permissions import Permission from utils.misc import reverse_round from utils.mixins import AdministratorMixin from utils.tables import TabbycatTableBuilder @@ -36,6 +37,7 @@ class AvailabilityIndexView(RoundMixin, AdministratorMixin, TemplateView): template_name = 'availability_index.html' page_title = gettext_lazy("Availability") page_emoji = '📍' + view_permission = Permission.VIEW_ROUNDAVAILABILITIES def get_context_data(self, **kwargs): if self.round.prev: @@ -206,6 +208,10 @@ def get_table(self): class AvailabilityTypeTeamView(AvailabilityTypeBase): + + view_permission = Permission.VIEW_ROUNDAVAILABILITIES_TEAM + edit_permission = Permission.EDIT_ROUNDAVAILABILITIES_TEAM + page_title = gettext_lazy("Team Availability") page_emoji = '👂' model = Team @@ -225,6 +231,10 @@ def annotate_checkins(queryset, t): class AvailabilityTypeAdjudicatorView(AvailabilityTypeBase): + + view_permission = Permission.VIEW_ROUNDAVAILABILITIES_ADJ + edit_permission = Permission.EDIT_ROUNDAVAILABILITIES_ADJ + page_title = gettext_lazy("Adjudicator Availability") page_emoji = '👂' model = Adjudicator @@ -244,6 +254,10 @@ def annotate_checkins(queryset, t): class AvailabilityTypeVenueView(AvailabilityTypeBase): + + view_permission = Permission.VIEW_ROUNDAVAILABILITIES_VENUE + edit_permission = Permission.EDIT_ROUNDAVAILABILITIES_VENUE + page_title = gettext_lazy("Room Availability") page_emoji = '🎪' model = Venue @@ -278,6 +292,7 @@ def annotate_checkins(queryset, t): class BaseBulkActivationView(RoundMixin, AdministratorMixin, PostOnlyRedirectView): round_redirect_pattern_name = 'availability-index' + edit_permission = Permission.EDIT_ROUNDAVAILABILITIES def post(self, request, *args, **kwargs): try: @@ -330,6 +345,7 @@ def activate_function(self): # ============================================================================== class BaseAvailabilityUpdateView(RoundMixin, AdministratorMixin, LogActionMixin, View): + edit_permission = Permission.EDIT_ROUNDAVAILABILITIES def post(self, request, *args, **kwargs): body = self.request.body.decode('utf-8') diff --git a/tabbycat/breakqual/migrations/0006_alter_breakcategory_unique_together_and_more.py b/tabbycat/breakqual/migrations/0006_alter_breakcategory_unique_together_and_more.py new file mode 100644 index 00000000000..240b55757dd --- /dev/null +++ b/tabbycat/breakqual/migrations/0006_alter_breakcategory_unique_together_and_more.py @@ -0,0 +1,45 @@ +# Generated by Django 5.0.4 on 2024-05-04 13:21 + +import utils.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('breakqual', '0005_remove_league_fields'), + ('participants', '0021_team_seed'), + ('tournaments', '0010_alter_round_draw_type'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='breakcategory', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='breakingteam', + unique_together=set(), + ), + migrations.AlterIndexTogether( + name='breakcategory', + index_together=set(), + ), + migrations.AlterField( + model_name='breakcategory', + name='is_general', + field=models.BooleanField(help_text='Are teams eligible for this break by default', verbose_name='is general'), + ), + migrations.AddConstraint( + model_name='breakcategory', + constraint=utils.models.UniqueConstraint(fields=('tournament', 'seq'), name='breakqu_breakcategory_tournament__seq_uniq'), + ), + migrations.AddConstraint( + model_name='breakcategory', + constraint=utils.models.UniqueConstraint(fields=('tournament', 'slug'), name='breakqu_breakcategory_tournament__slug_uniq'), + ), + migrations.AddConstraint( + model_name='breakingteam', + constraint=utils.models.UniqueConstraint(fields=('break_category', 'team'), name='breakqu_breakingteam_break_category__team_uniq'), + ), + ] diff --git a/tabbycat/breakqual/models.py b/tabbycat/breakqual/models.py index a09b54efed9..7e7f9477143 100644 --- a/tabbycat/breakqual/models.py +++ b/tabbycat/breakqual/models.py @@ -4,6 +4,8 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from utils.models import UniqueConstraint + class BreakCategory(models.Model): tournament = models.ForeignKey('tournaments.Tournament', models.CASCADE, @@ -51,9 +53,11 @@ def __str__(self): return "[{}] {}".format(self.tournament.slug, self.name) class Meta: - unique_together = [('tournament', 'seq'), ('tournament', 'slug')] + constraints = [ + UniqueConstraint(fields=['tournament', 'seq']), + UniqueConstraint(fields=['tournament', 'slug']), + ] ordering = ['tournament', 'seq'] - index_together = ['tournament', 'seq'] verbose_name = _("break category") verbose_name_plural = _("break categories") @@ -100,6 +104,6 @@ class BreakingTeam(models.Model): help_text=_("Used to explain why an otherwise-qualified team didn't break")) class Meta: - unique_together = [('break_category', 'team')] + constraints = [UniqueConstraint(fields=['break_category', 'team'])] verbose_name = _("breaking team") verbose_name_plural = _("breaking teams") diff --git a/tabbycat/breakqual/views.py b/tabbycat/breakqual/views.py index bbddb985914..792c49dbea5 100644 --- a/tabbycat/breakqual/views.py +++ b/tabbycat/breakqual/views.py @@ -16,6 +16,7 @@ from participants.models import Team from participants.views import EditSpeakerCategoriesView, UpdateEligibilityEditView as BaseUpdateEligibilityEditView from tournaments.mixins import PublicTournamentPageMixin, SingleObjectFromTournamentMixin, TournamentMixin +from users.permissions import Permission from utils.misc import reverse_tournament from utils.mixins import AdministratorMixin from utils.tables import TabbycatTableBuilder @@ -39,6 +40,7 @@ class PublicBreakIndexView(PublicTournamentPageMixin, TemplateView): class AdminBreakIndexView(AdministratorMixin, TournamentMixin, TemplateView): template_name = 'breaking_index.html' + view_permission = Permission.VIEW_BREAK_OVERVIEW def get_context_data(self, **kwargs): tournament = self.tournament @@ -109,6 +111,8 @@ class BreakingTeamsFormView(GenerateBreakMixin, LogActionMixin, AdministratorMix form_class = forms.BreakingTeamsForm template_name = 'breaking_teams.html' action_log_content_object_attr = 'object' + view_permission = Permission.VIEW_BREAK + edit_permission = Permission.GENERATE_BREAK def get_action_log_type(self): if 'save_update_all' in self.request.POST: @@ -188,6 +192,7 @@ class GenerateAllBreaksView(GenerateBreakMixin, LogActionMixin, TournamentMixin, action_log_type = ActionLogEntry.ActionType.BREAK_GENERATE_ALL tournament_redirect_pattern_name = 'breakqual-teams' + edit_permission = Permission.GENERATE_BREAK def post(self, request, *args, **kwargs): BreakingTeam.objects.filter(break_category__tournament=self.tournament).delete() @@ -219,6 +224,7 @@ def get_table(self): class AdminBreakingAdjudicatorsView(AdministratorMixin, BaseBreakingAdjudicatorsView): template_name = 'breaking_adjs.html' + view_permission = Permission.VIEW_ADJ_BREAK class PublicBreakingAdjudicatorsView(PublicTournamentPageMixin, BaseBreakingAdjudicatorsView): @@ -253,6 +259,8 @@ class EditBreakCategoriesView(EditSpeakerCategoriesView): template_name = 'break_categories_edit.html' formset_model = BreakCategory + view_permission = Permission.VIEW_BREAK_CATEGORIES + edit_permission = Permission.EDIT_BREAK_CATEGORIES action_log_type = ActionLogEntry.ActionType.BREAK_CATEGORIES_EDIT url_name = 'break-categories-edit' @@ -283,6 +291,8 @@ class EditTeamEligibilityView(AdministratorMixin, TournamentMixin, VueTableTempl template_name = 'edit_break_eligibility.html' page_title = _("Break Eligibility") page_emoji = '🍯' + view_permission = Permission.VIEW_BREAK_ELIGIBILITY + edit_permission = Permission.EDIT_BREAK_ELIGIBILITY def get_table(self): t = self.tournament @@ -334,3 +344,4 @@ class UpdateEligibilityEditView(BaseUpdateEligibilityEditView): action_log_type = ActionLogEntry.ActionType.BREAK_ELIGIBILITY_EDIT participant_model = Team many_to_many_field = 'break_categories' + edit_permission = Permission.EDIT_BREAK_ELIGIBILITY diff --git a/tabbycat/checkins/views.py b/tabbycat/checkins/views.py index f81ca9e754f..c7ad269f185 100644 --- a/tabbycat/checkins/views.py +++ b/tabbycat/checkins/views.py @@ -15,6 +15,7 @@ from participants.models import Person, Speaker from participants.serializers import InstitutionSerializer from tournaments.mixins import PublicTournamentPageMixin, TournamentMixin +from users.permissions import Permission from utils.misc import reverse_tournament from utils.mixins import AdministratorMixin, AssistantMixin from utils.views import PostOnlyRedirectView @@ -37,6 +38,7 @@ def get_context_data(self, **kwargs): class AdminCheckInPreScanView(AdministratorMixin, CheckInPreScanView): scan_view = 'admin-checkin-scan' + edit_permission = Permission.EDIT_PARTICIPANT_CHECKIN class AssistantCheckInPreScanView(AssistantMixin, CheckInPreScanView): @@ -63,9 +65,11 @@ class CheckInPeopleStatusView(BaseCheckInStatusView): page_title = _("People's Check-In Statuses") window_preference = 'checkin_window_people' + edit_permission = Permission.EDIT_PARTICIPANT_CHECKIN + def get_context_data(self, **kwargs): - team_codes = use_team_code_names(self.tournament, admin=self.for_admin) + team_codes = use_team_code_names(self.tournament, admin=self.for_admin, user=self.request.user) kwargs["team_codes"] = json.dumps(team_codes) adjudicators = [] @@ -104,6 +108,8 @@ def get_context_data(self, **kwargs): class AdminCheckInPeopleStatusView(AdministratorMixin, CheckInPeopleStatusView): scan_view = 'admin-checkin-scan' + view_permission = Permission.VIEW_CHECKIN + edit_permission = Permission.EDIT_PARTICIPANT_CHECKIN class AssistantCheckInPeopleStatusView(AssistantMixin, CheckInPeopleStatusView): @@ -138,6 +144,8 @@ def get_context_data(self, **kwargs): class AdminCheckInVenuesStatusView(AdministratorMixin, CheckInVenuesStatusView): scan_view = 'admin-checkin-scan' + view_permission = Permission.VIEW_CHECKIN + edit_permission = Permission.EDIT_ROOM_CHECKIN class AssistantCheckInVenuesStatusView(AssistantMixin, CheckInVenuesStatusView): @@ -192,7 +200,7 @@ def get_context_data(self, **kwargs): class AdminCheckInIdentifiersView(AdministratorMixin, CheckInIdentifiersView): - pass + view_permission = Permission.VIEW_CHECKIN class AssistantCheckInIdentifiersView(AssistantMixin, CheckInIdentifiersView): @@ -201,6 +209,7 @@ class AssistantCheckInIdentifiersView(AssistantMixin, CheckInIdentifiersView): class AdminCheckInGenerateView(AdministratorMixin, LogActionMixin, TournamentMixin, PostOnlyRedirectView): + edit_permission = Permission.VIEW_CHECKIN def get_action_log_type(self): if self.kwargs["kind"] == "speakers": @@ -247,7 +256,7 @@ def get_context_data(self, **kwargs): class AdminCheckInPrintablesView(AdministratorMixin, CheckInPrintablesView): - pass + view_permission = Permission.VIEW_CHECKIN class AssistantCheckInPrintablesView(AssistantMixin, CheckInPrintablesView): diff --git a/tabbycat/draw/migrations/0009_alter_teamsideallocation_unique_together_and_more.py b/tabbycat/draw/migrations/0009_alter_teamsideallocation_unique_together_and_more.py new file mode 100644 index 00000000000..786321f8596 --- /dev/null +++ b/tabbycat/draw/migrations/0009_alter_teamsideallocation_unique_together_and_more.py @@ -0,0 +1,24 @@ +# Generated by Django 5.0.4 on 2024-05-04 13:21 + +import utils.models +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('draw', '0008_alter_debateteam_side_alter_teamsideallocation_side'), + ('participants', '0022_rename_team_tournament_institution_short_reference_participant_tournam_160efa_idx_and_more'), + ('tournaments', '0012_alter_round_unique_together_and_more'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='teamsideallocation', + unique_together=set(), + ), + migrations.AddConstraint( + model_name='teamsideallocation', + constraint=utils.models.UniqueConstraint(fields=('round', 'team'), name='draw_teamsideallocation_round__team_uniq'), + ), + ] diff --git a/tabbycat/draw/models.py b/tabbycat/draw/models.py index 11ffbed3afa..4c926a15b9a 100644 --- a/tabbycat/draw/models.py +++ b/tabbycat/draw/models.py @@ -8,6 +8,7 @@ from tournaments.utils import get_side_name from utils.fields import ChoiceArrayField +from utils.models import UniqueConstraint from .generator import DRAW_FLAG_DESCRIPTIONS from .types import DebateSide @@ -359,6 +360,6 @@ class TeamSideAllocation(models.Model): side = models.IntegerField(verbose_name=_("side sequence")) class Meta: - unique_together = [('round', 'team')] + constraints = [UniqueConstraint(fields=['round', 'team'])] verbose_name = _("team side allocation") verbose_name_plural = _("team side allocations") diff --git a/tabbycat/draw/templates/draw_display_admin.html b/tabbycat/draw/templates/draw_display_admin.html index 7904f726021..936c8524ed6 100644 --- a/tabbycat/draw/templates/draw_display_admin.html +++ b/tabbycat/draw/templates/draw_display_admin.html @@ -357,7 +357,7 @@
- +
diff --git a/tabbycat/draw/views.py b/tabbycat/draw/views.py index 99320d25fed..8d126af3112 100644 --- a/tabbycat/draw/views.py +++ b/tabbycat/draw/views.py @@ -34,6 +34,7 @@ TournamentMixin) from tournaments.models import Round from tournaments.utils import get_side_name +from users.permissions import Permission from utils.misc import reverse_round, reverse_tournament from utils.mixins import AdministratorMixin from utils.tables import TabbycatTableBuilder @@ -282,22 +283,22 @@ def populate_table(self, debates, table): class AdminDrawDisplayForSpecificRoundByVenueView(AdministratorMixin, BriefingRoomDrawByVenueTableMixin, BaseDisplayDrawForSpecificRoundTableView): - pass + view_permission = Permission.VIEW_BRIEFING_DRAW class AdminDrawDisplayForSpecificRoundByTeamView(AdministratorMixin, BriefingRoomDrawByTeamTableMixin, BaseDisplayDrawForSpecificRoundTableView): - pass + view_permission = Permission.VIEW_BRIEFING_DRAW class AdminDrawDisplayForCurrentRoundsByVenueView(AdministratorMixin, BriefingRoomDrawByVenueTableMixin, BaseDisplayDrawForCurrentRoundsTableView): - pass + view_permission = Permission.VIEW_BRIEFING_DRAW class AdminDrawDisplayForCurrentRoundsByTeamView(AdministratorMixin, BriefingRoomDrawByTeamTableMixin, BaseDisplayDrawForCurrentRoundsTableView): - pass + view_permission = Permission.VIEW_BRIEFING_DRAW class AssistantDrawDisplayForSpecificRoundByVenueView(OptionalAssistantTournamentPageMixin, @@ -376,6 +377,7 @@ class BaseDrawDisplayIndexView(AdminDrawUtilitiesMixin, RoundMixin, TemplateView class AdminDrawDisplayView(AdministratorMixin, BaseDrawDisplayIndexView): template_name = 'draw_display_admin.html' + view_permission = True class AssistantDrawDisplayView(CurrentRoundMixin, OptionalAssistantTournamentPageMixin, BaseDrawDisplayIndexView): @@ -445,6 +447,8 @@ def get_queryset(self): class AdminDrawView(RoundMixin, AdministratorMixin, AdminDrawUtilitiesMixin, VueTableTemplateView): detailed = False + view_permission = Permission.VIEW_ADMIN_DRAW + def get_page_title(self): round = self.round self.page_emoji = '👀' @@ -652,10 +656,11 @@ def get_template_names(self): class DrawStatusEdit(LogActionMixin, AdministratorMixin, RoundMixin, PostOnlyRedirectView): round_redirect_pattern_name = 'draw' + view_permission = Permission.GENERATE_DEBATE class CreateDrawView(DrawStatusEdit): - + edit_permission = Permission.GENERATE_DEBATE action_log_type = ActionLogEntry.ActionType.DRAW_CREATE def post(self, request, *args, **kwargs): @@ -738,9 +743,11 @@ def post(self, request, *args, **kwargs): class ConfirmDrawRegenerationView(AdministratorMixin, TemplateView): template_name = "draw_confirm_regeneration.html" + view_permission = Permission.GENERATE_DEBATE class DrawReleaseView(DrawStatusEdit): + edit_permission = Permission.RELEASE_DRAW action_log_type = ActionLogEntry.ActionType.DRAW_RELEASE round_redirect_pattern_name = 'draw-display' @@ -761,6 +768,7 @@ def post(self, request, *args, **kwargs): class DrawUnreleaseView(DrawStatusEdit): + edit_permission = Permission.UNRELEASE_DRAW action_log_type = ActionLogEntry.ActionType.DRAW_UNRELEASE round_redirect_pattern_name = 'draw-display' @@ -777,6 +785,7 @@ def post(self, request, *args, **kwargs): class SetRoundStartTimeView(DrawStatusEdit): + edit_permission = Permission.EDIT_STARTTIME action_log_type = ActionLogEntry.ActionType.ROUND_START_TIME_SET round_redirect_pattern_name = 'draw-display' @@ -828,7 +837,7 @@ def get_table(self): class SideAllocationsView(AdministratorMixin, BaseSideAllocationsView): - pass + view_permission = Permission.EDIT_ALLOCATESIDES class PublicSideAllocationsView(PublicTournamentPageMixin, BaseSideAllocationsView): @@ -839,6 +848,7 @@ class EditDebateTeamsView(DebateDragAndDropMixin, AdministratorMixin, TemplateVi template_name = "edit_debate_teams.html" page_title = gettext_lazy("Edit Matchups") prefetch_teams = False # Fetched in full as get_serialised + edit_permission = Permission.EDIT_DEBATETEAMS def get_serialised_allocatable_items(self): # TODO: account for shared teams diff --git a/tabbycat/importer/views.py b/tabbycat/importer/views.py index 88eba11e5dc..ae429b9b10f 100644 --- a/tabbycat/importer/views.py +++ b/tabbycat/importer/views.py @@ -22,6 +22,7 @@ from participants.utils import populate_code_names from tournaments.mixins import TournamentMixin from tournaments.models import Tournament +from users.permissions import Permission from utils.misc import redirect_tournament, reverse_tournament from utils.mixins import AdministratorMixin from utils.views import PostOnlyRedirectView @@ -40,6 +41,7 @@ class ImporterSimpleIndexView(AdministratorMixin, TournamentMixin, TemplateView): template_name = 'simple_import_index.html' + view_permission = True class BaseImportWizardView(AdministratorMixin, LogActionMixin, TournamentMixin, SessionWizardView): @@ -96,6 +98,7 @@ def done(self, form_list, form_dict, **kwargs): class ImportInstitutionsWizardView(BaseImportWizardView): model = Institution + edit_permission = Permission.ADD_INSTITUTIONS form_list = [ ('raw', ImportInstitutionsRawForm), ('details', modelformset_factory(Institution, fields=('name', 'code'), extra=0)), @@ -111,6 +114,7 @@ def get_message(self, count): class ImportVenuesWizardView(BaseImportWizardView): model = Venue + edit_permission = Permission.ADD_ROOMS form_list = [ ('raw', ImportVenuesRawForm), ('details', modelformset_factory(Venue, form=VenueDetailsForm, extra=0)), @@ -170,6 +174,7 @@ def get_details_instance_initial(self): class ImportTeamsWizardView(BaseImportByInstitutionWizardView): model = Team + edit_permission = Permission.ADD_TEAMS form_list = [ ('numbers', ImportTeamsNumbersForm), ('details', modelformset_factory(Team, form=TeamDetailsForm, formset=TeamDetailsFormSet, extra=0)), @@ -192,6 +197,7 @@ def get_message(self, count): class ImportAdjudicatorsWizardView(BaseImportByInstitutionWizardView): model = Adjudicator + edit_permission = Permission.ADD_ADJUDICATORS form_list = [ ('numbers', ImportAdjudicatorsNumbersForm), ('details', modelformset_factory(Adjudicator, form=AdjudicatorDetailsForm, extra=0)), @@ -271,9 +277,11 @@ def get_success_url(self): class ExportArchiveIndexView(AdministratorMixin, TournamentMixin, TemplateView): template_name = 'archive_export_index.html' + view_permission = Permission.EXPORT_XML class ExportArchiveAllView(AdministratorMixin, TournamentMixin, View): + view_permission = Permission.EXPORT_XML def get(self, request, *args, **kwargs): response = HttpResponse(self.get_xml(), content_type='text/xml; charset=utf-8') diff --git a/tabbycat/motions/migrations/0006_alter_debateteammotionpreference_unique_together_and_more.py b/tabbycat/motions/migrations/0006_alter_debateteammotionpreference_unique_together_and_more.py new file mode 100644 index 00000000000..9f8112c72fe --- /dev/null +++ b/tabbycat/motions/migrations/0006_alter_debateteammotionpreference_unique_together_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 5.0.4 on 2024-05-04 13:21 + +import utils.models +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('draw', '0009_alter_teamsideallocation_unique_together_and_more'), + ('motions', '0005_motions_mtm'), + ('results', '0015_alter_ballotsubmission_submitter_type'), + ('tournaments', '0012_alter_round_unique_together_and_more'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='debateteammotionpreference', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='roundmotion', + unique_together=set(), + ), + migrations.AddConstraint( + model_name='debateteammotionpreference', + constraint=utils.models.UniqueConstraint(fields=('debate_team', 'preference', 'ballot_submission'), name='motions_debateteammotionpreference_debate_team__preference__ballot_submission_uniq'), + ), + migrations.AddConstraint( + model_name='roundmotion', + constraint=utils.models.UniqueConstraint(fields=('round', 'seq'), name='motions_roundmotion_round__seq_uniq'), + ), + ] diff --git a/tabbycat/motions/models.py b/tabbycat/motions/models.py index f72be12ad5c..c1abc5a3198 100644 --- a/tabbycat/motions/models.py +++ b/tabbycat/motions/models.py @@ -2,6 +2,8 @@ from django.utils.translation import gettext_lazy as _ from html2text import html2text +from utils.models import UniqueConstraint + class Motion(models.Model): """Represents a single motion (not a set of motions).""" @@ -53,7 +55,7 @@ class DebateTeamMotionPreference(models.Model): verbose_name=_("ballot submission")) class Meta: - unique_together = [('debate_team', 'preference', 'ballot_submission')] + constraints = [UniqueConstraint(fields=['debate_team', 'preference', 'ballot_submission'])] verbose_name = _("debate team motion preference") verbose_name_plural = _("debate team motion preferences") @@ -80,8 +82,8 @@ class RoundMotion(models.Model): help_text=_("The order in which motions are displayed")) class Meta: + constraints = [UniqueConstraint(fields=['round', 'seq'])] ordering = ('round', 'seq') - unique_together = ('round', 'seq') verbose_name = _("round motion") verbose_name_plural = _("round motions") diff --git a/tabbycat/motions/views.py b/tabbycat/motions/views.py index 73e00f113eb..f96e5ba3198 100644 --- a/tabbycat/motions/views.py +++ b/tabbycat/motions/views.py @@ -14,6 +14,7 @@ from tournaments.mixins import (CurrentRoundMixin, OptionalAssistantTournamentPageMixin, PublicTournamentPageMixin, RoundMixin, TournamentMixin) from tournaments.models import Round +from users.permissions import Permission from utils.misc import redirect_round from utils.mixins import AdministratorMixin from utils.views import ModelFormSetView, PostOnlyRedirectView @@ -43,7 +44,8 @@ def get_context_data(self, **kwargs): class EditMotionsView(AdministratorMixin, LogActionMixin, RoundMixin, ModelFormSetView): # Django doesn't have a class-based view for formsets, so this implements # the form processing analogously to FormView, with less decomposition. - + view_permission = Permission.VIEW_MOTION + edit_permission = Permission.EDIT_MOTION template_name = 'motions_edit.html' action_log_type = ActionLogEntry.ActionType.MOTION_EDIT formset_model = Motion @@ -167,6 +169,7 @@ def post(self, request, *args, **kwargs): class ReleaseMotionsView(BaseReleaseMotionsView): + edit_permission = Permission.RELEASE_MOTION action_log_type = ActionLogEntry.ActionType.MOTIONS_RELEASE motions_released = True @@ -199,7 +202,7 @@ def get_context_data(self, **kwargs): class AdminDisplayMotionsView(AdministratorMixin, BaseDisplayMotionsView): - pass + view_permission = Permission.DISPLAY_MOTION class AssistantDisplayMotionsView(CurrentRoundMixin, OptionalAssistantTournamentPageMixin, BaseDisplayMotionsView): @@ -262,11 +265,11 @@ class BasePublicMotionStatisticsView(PublicTournamentPageMixin): class AdminRoundMotionStatisticsView(AdministratorMixin, RoundMotionStatisticsView): - pass + view_permission = Permission.VIEW_MOTIONSTAB class AdminGlobalMotionStatisticsView(AdministratorMixin, GlobalMotionStatisticsView): - pass + view_permission = Permission.VIEW_MOTIONSTAB class PublicRoundMotionStatisticsView(BasePublicMotionStatisticsView, RoundMotionStatisticsView): diff --git a/tabbycat/notifications/migrations/0013_alter_bulknotification_event.py b/tabbycat/notifications/migrations/0013_alter_bulknotification_event.py new file mode 100644 index 00000000000..e7f0c89077f --- /dev/null +++ b/tabbycat/notifications/migrations/0013_alter_bulknotification_event.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.4 on 2024-05-04 13:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notifications', '0012_auto_20201018_2128'), + ] + + operations = [ + migrations.AlterField( + model_name='bulknotification', + name='event', + field=models.CharField(choices=[('p', 'team points'), ('c', 'ballot confirmed'), ('f', 'feedback URL'), ('b', 'ballot URL'), ('u', 'landing page URL'), ('d', 'adjudicator draw released'), ('t', 'team registration'), ('a', 'adjudicator registration'), ('m', 'motion(s) released'), ('r', 'team draw released'), ('', 'custom message')], max_length=20, verbose_name='event'), + ), + ] diff --git a/tabbycat/notifications/views.py b/tabbycat/notifications/views.py index ca5e2ff28f0..4de07b97413 100644 --- a/tabbycat/notifications/views.py +++ b/tabbycat/notifications/views.py @@ -19,6 +19,7 @@ from participants.models import Person from tournaments.mixins import RoundMixin, TournamentMixin +from users.permissions import Permission from utils.mixins import AdministratorMixin, WarnAboutLegacySendgridConfigVarsMixin from utils.tables import TabbycatTableBuilder from utils.views import VueTableTemplateView @@ -82,6 +83,7 @@ class EmailStatusView(AdministratorMixin, TournamentMixin, VueTableTemplateView) page_title = gettext_lazy("Email Statuses") page_emoji = '📤' template_name = 'email_statuses.html' + view_permission = Permission.VIEW_EMAIL_STATUSES tables_orientation = 'rows' @@ -195,7 +197,7 @@ def post(self, request: 'HttpRequest', *args, **kwargs) -> HttpResponse: for obj in data: dt = datetime.fromtimestamp(obj['timestamp']) - timestamp = timezone.make_aware(dt, timezone.utc) + timestamp = timezone.make_aware(dt, datetime.timezone.utc) email_id = record_lookup.get(obj['hook-id'], None) if email_id is None: continue @@ -210,6 +212,7 @@ class BaseSelectPeopleEmailView(AdministratorMixin, TournamentMixin, VueTableTem template_name = "email_participants.html" page_title = gettext_lazy("Email Participants") page_emoji = '📤' + edit_permission = Permission.SEND_EMAILS form_class = BasicEmailForm diff --git a/tabbycat/options/tests/test_utils.py b/tabbycat/options/tests/test_utils.py index d6013454271..0f4286d458d 100644 --- a/tabbycat/options/tests/test_utils.py +++ b/tabbycat/options/tests/test_utils.py @@ -1,5 +1,7 @@ from itertools import product +from django.contrib.auth import get_user_model +from django.contrib.auth.models import AnonymousUser from django.forms import ValidationError from django.test import TestCase @@ -7,26 +9,32 @@ from options.utils import use_team_code_names, use_team_code_names_data_entry, validate_metric_duplicates from standings.teams import TeamStandingsGenerator from tournaments.models import Tournament +from users.permissions import Permission + +User = get_user_model() class UseTeamCodeNamesTests(TestCase): def setUp(self): - Tournament.objects.create(slug="unittest", name="Unit Testing") + t = Tournament.objects.create(slug="unittest", name="Unit Testing") + self.user = User.objects.create(username='admin1', password='admin', is_active=True) + self.user.userpermission_set.create(tournament=t, permission=Permission.VIEW_DECODED_TEAMS) def tearDown(self): Tournament.objects.filter(slug='unittest').delete() + self.user.delete() def test_use_codes_if_setting(self): tournament = Tournament.objects.get(slug='unittest') - for setting, admin in product([c[0] for c in TeamCodeNames.choices], (False, True)): - with self.subTest(setting=setting, admin=admin): + for setting, admin, user in product([c[0] for c in TeamCodeNames.choices], (False, True), (self.user, AnonymousUser())): + with self.subTest(setting=setting, admin=admin, user=user.is_anonymous): tournament._prefs['team_code_names'] = setting - if setting in ['admin-tooltips-real', 'everywhere'] or (setting == 'admin-tooltips-code' and not admin): - self.assertTrue(use_team_code_names(tournament, admin)) + if setting in ['admin-tooltips-real', 'everywhere'] or (setting == 'admin-tooltips-code' and not (admin and not user.is_anonymous)): + self.assertTrue(use_team_code_names(tournament, admin, user)) else: - self.assertFalse(use_team_code_names(tournament, admin)) + self.assertFalse(use_team_code_names(tournament, admin, user)) def test_use_codes_data_entry(self): tournament = Tournament.objects.get(slug='unittest') diff --git a/tabbycat/options/tests/test_views.py b/tabbycat/options/tests/test_views.py index ab03357af2d..799ecbe8e9a 100644 --- a/tabbycat/options/tests/test_views.py +++ b/tabbycat/options/tests/test_views.py @@ -25,8 +25,11 @@ class TestPreset(PreferencesPreset): class TournamentConfigIndexViewTests(TestCase): def test_order_presets(self): + tournament = Tournament.objects.create(slug="optionform", name="Option Form Testing") view = TournamentConfigIndexView() - view.setup(RequestFactory()) + request = RequestFactory() + request.user = get_user_model()(is_superuser=True) + view.setup(request, tournament_slug=tournament.slug) presets = view.get_context_data()['presets'] self.assertEqual(presets[0], PublicInformation) diff --git a/tabbycat/options/utils.py b/tabbycat/options/utils.py index 9d4744678e6..cf65dd2e27b 100644 --- a/tabbycat/options/utils.py +++ b/tabbycat/options/utils.py @@ -1,20 +1,23 @@ import logging +from django.contrib.auth.models import AnonymousUser from django.forms import ValidationError from django.utils.encoding import force_str from django.utils.translation import gettext_lazy as _ +from users.permissions import has_permission, Permission + logger = logging.getLogger(__name__) -def use_team_code_names(tournament, admin): +def use_team_code_names(tournament, admin, user=AnonymousUser()): """Returns True if team code names should be used, given the tournament preferences of `tournament` and whether the request is for an admin view. `admin` should be True if the request is for an admin view and False if not. """ if tournament.pref('team_code_names') in ['admin-tooltips-real', 'everywhere']: return True - if tournament.pref('team_code_names') == 'admin-tooltips-code' and not admin: + if tournament.pref('team_code_names') == 'admin-tooltips-code' and not (admin and has_permission(user, Permission.VIEW_DECODED_TEAMS, tournament)): return True return False diff --git a/tabbycat/options/views.py b/tabbycat/options/views.py index 90adf294a54..bf601778b30 100644 --- a/tabbycat/options/views.py +++ b/tabbycat/options/views.py @@ -12,6 +12,7 @@ from actionlog.mixins import LogActionMixin from actionlog.models import ActionLogEntry from tournaments.mixins import TournamentMixin +from users.permissions import Permission from utils.misc import reverse_tournament from utils.mixins import AdministratorMixin @@ -24,6 +25,7 @@ class TournamentConfigIndexView(AdministratorMixin, TournamentMixin, TemplateView): template_name = "preferences_index.html" + view_permission = True def get_preset_options(self): """Returns a list of all preset classes.""" @@ -59,6 +61,8 @@ class TournamentPreferenceFormView(AdministratorMixin, LogActionMixin, Tournamen possible_registries = [global_preferences_registry, tournament_preferences_registry] section = None template_name = "preferences_section_set.html" + view_permission = Permission.VIEW_SETTINGS + edit_permission = Permission.EDIT_SETTINGS action_log_type = ActionLogEntry.ActionType.OPTIONS_EDIT @@ -78,6 +82,8 @@ def get_form_class(self, *args, **kwargs): class SetPresetPreferencesView(AdministratorMixin, LogActionMixin, TournamentMixin, FormView): template_name = "preset_edit.html" page_emoji = '❔' + view_permission = Permission.VIEW_SETTINGS + edit_permission = Permission.EDIT_SETTINGS action_log_type = ActionLogEntry.ActionType.OPTIONS_EDIT diff --git a/tabbycat/participants/emoji.py b/tabbycat/participants/emoji.py index 8a6e6cab0c8..d212ad7b1f4 100644 --- a/tabbycat/participants/emoji.py +++ b/tabbycat/participants/emoji.py @@ -1693,6 +1693,87 @@ def populate_code_names_from_emoji(teams, overwrite=True): ("🪥", True , "Toothbrush"), ("🪦", False, "Headstone"), # potentially offensive ("🪧", False, "Placard"), # dull + + # Unicode Version 13.1 + ("😶‍🌫", True , "Cloudy Face"), + ("😮‍💨", True , "Hot Air"), + ("😵‍💫", True , "Hypnotised"), + ("❤‍🔥", True , "Fiery Heart"), + ("❤‍🩹", True , "Mending Heart"), + ("🧔‍♂", False, "Bearded Man"), # dull + ("🧔‍♀", False, "Bearded Woman"), # potentially offensive + + # Unicode Version 14.0 + ("🫠", True , "Melting Face"), + ("🫢", True , "Oops Face"), + ("🫣", True , "Peekaboo"), + ("🫡", False, "Saluting Face"), # potentially offensive + ("🫥", True , "Invisible Face"), + ("🫤", True , "Diagonal Mouth"), + ("🥹", True , "Grateful Face"), + ("🫱", False, "Rightwards Hand"), # dull + ("🫲", False, "Leftwards Hand"), # dull + ("🫳", False, "Palm Down Hand"), # dull + ("🫴", False, "Palm Up Hand"), # dull + ("🫰", True , "Love Gesture"), + ("🫵", True , "YOU"), + ("🫶", True , "Heart Hands"), + ("🫦", True , "Biting Lip"), + ("🫅", True , "Crowned"), + ("🫃", False, "Pregnant Man"), # potentially offensive + ("🫄", False, "Pregnant Person"), # potentially offensive + ("🧌", True , "Bridgekeeper"), + ("🪸", True , "Coral"), + ("🪷", True , "Lotus"), + ("🪹", False, "Empty Nest"), # dull + ("🪺", True , "Unladen Swallow"), + ("🫘", True , "Beans"), + ("🫗", False, "Leak"), # dull + ("🫙", False, "Jar"), # dull + ("🛝", True , "Slide"), + ("🛞", True , "Wheel"), + ("🛟", True , "Buoy"), + ("🪩", True , "Mirror Ball"), + ("🪫", True , "Low Battery"), + ("🩼", False, "Crutch"), # potentially offensive + ("🩻", True , "X-Ray"), + ("🫧", True , "Bubbles"), + ("🪬", False, "Hamsa"), # potentially offensive + ("🪪", True , "Identification Card"), + ("🟰", False, "Heavy Equals Sign"), # dull + + # Unicode Version 15.0 + ("🫨", True , "Car Sick Face"), + ("🩷", False, "Pink Heart"), # too similar to another + ("🩵", False, "Light Blue Heart"), # too similar to another + ("🩶", False, "Grey Heart"), # too similar to another + ("🫷", True , "No Thanks Hand"), + ("🫸", False, "Rightwards Pushing Hand"), # too similar to another + ("🫎", True , "Moose"), + ("🫏", True , "Donkey"), + ("🪽", True , "Wing"), + ("🪿", True , "Honking Bird"), + ("🪼", True , "Jellyfish"), + ("🪻", True , "Hyacinth"), + ("🫚", True , "Ginger"), + ("🫛", True , "Pea Pod"), + ("🪭", True , "Folding Hand Fan"), + ("🪮", True , "Hair Pick"), + ("🪇", True , "Maracas"), + ("🪈", True , "Flute"), + ("🪯", False, "Khanda"), # potentially offensive + ("🛜", True , "Wireless"), + + # Unicode Version 15.1 + ("🙂‍↔", True , "Headshake"), + ("🙂‍↕", True , "Nodding Face"), + ("🚶‍➡", False, "Walking"), # dull + ("🧎‍➡", False, "Person Kneeling"), # dull + ("🏃‍➡", False, "Person Running"), # dull + ("🐦‍🔥", True , "Phoenix"), + ("🍋‍🟩", True , "Lime"), + ("🍄‍🟫", True , "Brown Mushroom"), + ("⛓‍💥", True , "Broken Chain"), ) # The field choices are the permissible values diff --git a/tabbycat/participants/migrations/0022_rename_team_tournament_institution_short_reference_participant_tournam_160efa_idx_and_more.py b/tabbycat/participants/migrations/0022_rename_team_tournament_institution_short_reference_participant_tournam_160efa_idx_and_more.py new file mode 100644 index 00000000000..2fd5c802d9f --- /dev/null +++ b/tabbycat/participants/migrations/0022_rename_team_tournament_institution_short_reference_participant_tournam_160efa_idx_and_more.py @@ -0,0 +1,60 @@ +# Generated by Django 5.0.4 on 2024-05-04 13:21 + +import utils.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('adjallocation', '0010_alter_adjudicatoradjudicatorconflict_unique_together_and_more'), + ('breakqual', '0006_alter_breakcategory_unique_together_and_more'), + ('participants', '0021_team_seed'), + ('tournaments', '0012_alter_round_unique_together_and_more'), + ] + + operations = [ + migrations.RenameIndex( + model_name='team', + new_name='participant_tournam_160efa_idx', + old_fields=('tournament', 'institution', 'short_reference'), + ), + migrations.AlterUniqueTogether( + name='speakercategory', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='team', + unique_together=set(), + ), + migrations.AlterIndexTogether( + name='speakercategory', + index_together=set(), + ), + migrations.AlterField( + model_name='person', + name='code_name', + field=models.CharField(blank=True, help_text='Name used to obscure real name on public-facing pages', max_length=25, verbose_name='code name'), + ), + migrations.AlterField( + model_name='team', + name='emoji', + field=models.CharField(blank=True, choices=[('☺️', '☺️ White Smiling'), ('☹', '☹ White Frowning'), ('☝️', '☝️ White Up Pointing Index'), ('✌️', '✌️ Victory Hand'), ('✍', '✍ Writing Hand'), ('❤️', '❤️ Heavy Black Heart'), ('❣', '❣ Heart Exclamation Mark'), ('☠', '☠ Skull and Crossbones'), ('♨️', '♨️ Hot Springs'), ('✈️', '✈️ Airplane'), ('⌛', '⌛ Hourglass'), ('⌚', '⌚ Watch'), ('♈', '♈ Aries'), ('♉', '♉ Taurus'), ('♊', '♊ Gemini'), ('♋', '♋ Cancer'), ('♌', '♌ Leo'), ('♍', '♍ Virgo'), ('♎', '♎ Libra'), ('♏', '♏ Scorpius'), ('♐', '♐ Sagittarius'), ('♑', '♑ Capricorn'), ('♒', '♒ Aquarius'), ('♓', '♓ Pisces'), ('☀️', '☀️ Black Sun With Rays'), ('☁️', '☁️ Cloud'), ('☂', '☂ Umbrella'), ('❄️', '❄️ Snowflake'), ('☃', '☃ Snowman'), ('☄️', '☄️ Comet'), ('♠️', '♠️ Spade Suit'), ('♥️', '♥️ Heart Suit'), ('♦️', '♦️ Diamond Suit'), ('♣️', '♣️ Club Suit'), ('▶️', '▶️ Black Right-Pointing Triangle'), ('◀️', '◀️ Black Left-Pointing Triangle'), ('☎️', '☎️ Black Telephone'), ('⌨', '⌨ Keyboard'), ('✉️', '✉️ Envelope'), ('✏️', '✏️ Pencil'), ('✒️', '✒️ Black Nib'), ('✂️', '✂️ Scissors'), ('↗️', '↗️ North East Arrow'), ('➡️', '➡️ Black Rightwards Arrow'), ('↘️', '↘️ South East Arrow'), ('↙️', '↙️ South West Arrow'), ('↖️', '↖️ North West Arrow'), ('↕️', '↕️ Up Down Arrow'), ('↔️', '↔️ Left Right Arrow'), ('↩️', '↩️ Leftwards Arrow With Hook'), ('↪️', '↪️ Rightwards Arrow With Hook'), ('✡', '✡ Star of David'), ('☸', '☸ Wheel of Dharma'), ('☯', '☯ Yin Yang'), ('✝', '✝ Latin Cross'), ('☦', '☦ Orthodox Cross'), ('☪', '☪ Star and Crescent'), ('☮', '☮ Peace Symbol'), ('☢', '☢ Radioactive Sign'), ('☣', '☣ Biohazard Sign'), ('☑️', '☑️ Ballot Box With Check'), ('✔️', '✔️ Heavy Check Mark'), ('✖️', '✖️ Heavy Multiplication X'), ('✳️', '✳️ Eight Spoked Asterisk'), ('✴️', '✴️ Eight Pointed Black Star'), ('❇️', '❇️ Sparkle'), ('‼️', '‼️ Double Exclamation Mark'), ('〰️', '〰️ Wavy Dash'), ('©️', '©️ Copyright Sign'), ('®️', '®️ Registered Sign'), ('™️', '™️ Trade Mark Sign'), ('Ⓜ️', 'Ⓜ️ Capital M'), ('㊗️', '㊗️ Congratulations'), ('㊙️', '㊙️ Secret'), ('▪️', '▪️ Black Square'), ('▫️', '▫️ White Square'), ('#⃣️', '#⃣️ Keycap Number Sign'), ('*⃣', '*⃣ Keycap Asterisk'), ('0⃣️', '0⃣️ Keycap Digit Zero'), ('1⃣️', '1⃣️ Keycap Digit One'), ('2⃣️', '2⃣️ Keycap Digit Two'), ('3⃣️', '3⃣️ Keycap Digit Three'), ('4⃣️', '4⃣️ Keycap Digit Four'), ('5⃣️', '5⃣️ Keycap Digit Five'), ('6⃣️', '6⃣️ Keycap Digit Six'), ('7⃣️', '7⃣️ Keycap Digit Seven'), ('8⃣️', '8⃣️ Keycap Digit Eight'), ('9⃣️', '9⃣️ Keycap Digit Nine'), ('⁉️', '⁉️ Exclamation Question Mark'), ('ℹ️', 'ℹ️ Information Source'), ('⤴️', '⤴️ Right-Curve-Up'), ('⤵️', '⤵️ Right-Curve-Down'), ('♻️', '♻️ Recycling'), ('〽️', '〽️ Part Alternation Mark'), ('◻️', '◻️ White Medium Square'), ('◼️', '◼️ Black Medium Square'), ('◽', '◽ White Medium Small Square'), ('◾', '◾ Black Medium Small Square'), ('☕', '☕ Hot Beverage'), ('⚠️', '⚠️ Warning Sign'), ('☔', '☔ Umbrella With Rain Drops'), ('⏏', '⏏ Eject Symbol'), ('⬆️', '⬆️ Upwards Black Arrow'), ('⬇️', '⬇️ Downwards Black Arrow'), ('⬅️', '⬅️ Leftwards Black Arrow'), ('⚡', '⚡ High Voltage'), ('☘', '☘ Shamrock'), ('⚓', '⚓ Anchor'), ('♿', '♿ Wheelchair Symbol'), ('⚒', '⚒ Hammer and Pick'), ('⚙', '⚙ Gear'), ('⚗', '⚗ Alembic'), ('⚖', '⚖ Scales'), ('⚔', '⚔ Crossed Swords'), ('⚰', '⚰ Coffin'), ('⚱', '⚱ Funeral Urn'), ('⚜', '⚜ Fleur-De-Lis'), ('⚛', '⚛ Atom Symbol'), ('⚪', '⚪ Medium White Circle'), ('⚫', '⚫ Medium Black Circle'), ('🀄', '🀄 Mahjong Tile Red Dragon'), ('⭐', '⭐ White Medium Star'), ('⬛', '⬛ Black Square'), ('⬜', '⬜ White Square'), ('⛑', '⛑ Rescue Hat'), ('⛰', '⛰ Mountain'), ('⛪', '⛪ Church'), ('⛲', '⛲ Fountain'), ('⛺', '⛺ Tent'), ('⛽', '⛽ Fuel Pump'), ('⛵', '⛵ Sailboat'), ('⛴', '⛴ Ferry'), ('⛔', '⛔ No Entry'), ('⛅', '⛅ Overcast'), ('⛈', '⛈ Storm'), ('⛱', '⛱ Umbrella'), ('⛄', '⛄ Snowman'), ('⚽', '⚽ Soccer'), ('⚾', '⚾ Baseball'), ('⛳', '⛳ Hole in One'), ('⛸', '⛸ Ice Skate'), ('⛷', '⛷ Skier'), ('⛹', '⛹ Person With Ball'), ('⛏', '⛏ Pick'), ('⛓', '⛓ Chains'), ('⛩', '⛩ Shinto Shrine'), ('⭕', '⭕ Heavy Large Circle'), ('❗', '❗ Heavy Exclamation Mark'), ('🅿️', '🅿️ Squared P'), ('🈯', '🈯 Squared 指 (Finger)'), ('🈚', '🈚 Squared CJK Unified Ideograph-7121'), ('😁', '😁 Smiling Eyes'), ('😂', '😂 Joy Tears'), ('😃', '😃 Smiling Face With Open Mouth'), ('😄', '😄 Smiling Face With Open Mouth and Smiling Eyes'), ('😅', '😅 Cold Sweat'), ('😆', '😆 Closed Eyes'), ('😉', '😉 Winky'), ('😊', '😊 Smiling Eyes'), ('😋', '😋 Face Savouring Delicious Food'), ('😎', '😎 Shaded Eyes'), ('😍', '😍 Heart Eyes'), ('😘', '😘 Kissy'), ('😚', '😚 Kissing Face With Closed Eyes'), ('😇', '😇 Halo'), ('😐', '😐 Neutral'), ('😶', '😶 No Mouth'), ('😏', '😏 Smirking'), ('😣', '😣 Persevering'), ('😥', '😥 Disappointed'), ('😪', '😪 Sleepy'), ('😫', '😫 Tired'), ('😌', '😌 Relieved'), ('😜', '😜 Tongue Out'), ('😝', '😝 Tongue Out Closed Eyes'), ('😒', '😒 Unamused'), ('😓', '😓 Cold Sweat'), ('😔', '😔 Pensive'), ('😖', '😖 Confounded'), ('😷', '😷 Medical Mask'), ('😲', '😲 Astonished'), ('😞', '😞 Disappointed'), ('😤', '😤 Face With Look of Triumph'), ('😢', '😢 Crying'), ('😭', '😭 Sobbing'), ('😨', '😨 Fearful'), ('😩', '😩 Weary'), ('😰', '😰 Open Mouth Cold Sweat'), ('😱', '😱 Screaming'), ('😳', '😳 Flushed'), ('😵', '😵 Dizzy'), ('😡', '😡 Pouting'), ('😠', '😠 Angry'), ('👿', '👿 Imp'), ('😈', '😈 Smiling Face With Horns'), ('👦', '👦 Boy'), ('👧', '👧 Girl'), ('👨', '👨 Generic Man'), ('👩', '👩 Generic Woman'), ('👴', '👴 Older Man'), ('👵', '👵 Older Woman'), ('👶', '👶 Baby'), ('👱', '👱 Person With Blond Hair'), ('👮', '👮 Police Officer'), ('👲', '👲 Man With Gua Pi Mao'), ('👳', '👳 Man With Turban'), ('👷', '👷 Trade Worker'), ('👸', '👸 Princess'), ('💂', '💂 Guardsman'), ('🎅', '🎅 Santa Claus'), ('👼', '👼 Baby Angel'), ('👯', '👯 Bunny Women'), ('💆', '💆 Face Massage'), ('💇', '💇 Haircut'), ('👰', '👰 Bride'), ('🙍', '🙍 Person Frowning'), ('🙎', '🙎 Person With Pouting'), ('🙅', '🙅 Block Gesture'), ('🙆', '🙆 OK Gesture'), ('💁', '💁 Sass Gesture'), ('🙋', '🙋 Raised Hand'), ('🙇', '🙇 Deep Bow'), ('🙌', '🙌 Praise Hands'), ('🙏', '🙏 Prayer Hands'), ('👤', '👤 Bust in Silhouette'), ('👥', '👥 Busts in Silhouette'), ('🚶', '🚶 Pedestrian'), ('🏃', '🏃 Runner'), ('💃', '💃 Dancer'), ('💏', '💏 Kiss'), ('💑', '💑 Heteronormative Couple'), ('👪', '👪 Hetero Family'), ('👫', '👫 Man & Woman'), ('👬', '👬 Two Men'), ('👭', '👭 Two Women'), ('💪', '💪 Biceps'), ('👈', '👈 Left Pointing Backhand'), ('👉', '👉 Right Pointing Backhand'), ('👆', '👆 Pointing Hand'), ('👇', '👇 Down Pointing Backhand'), ('✊', '✊ Power Hand'), ('✋', '✋ Palm Hand'), ('👊', '👊 Fist Hand'), ('👌', '👌 OK Hand'), ('👍', '👍 Thumbs Up'), ('👎', '👎 Thumbs Down'), ('👋', '👋 Waving Hand Sign'), ('👏', '👏 Clappy Hands'), ('👐', '👐 Open Hands Sign'), ('💅', '💅 Nail Polish'), ('👣', '👣 Footprints'), ('👀', '👀 Eyes'), ('👂', '👂 Ear'), ('👃', '👃 Nose'), ('👅', '👅 Lick'), ('👄', '👄 Mouth'), ('💋', '💋 Kiss Mark'), ('💘', '💘 Cupid Arrow'), ('💓', '💓 Beating Heart'), ('💔', '💔 Broken Heart'), ('💕', '💕 Two Hearts'), ('💖', '💖 Sparkly Heart'), ('💗', '💗 Growing Heart'), ('💙', '💙 Blue Heart'), ('💚', '💚 Green Heart'), ('💛', '💛 Yellow Heart'), ('💜', '💜 Purple Heart'), ('💝', '💝 Heart With Ribbon'), ('💞', '💞 Revolving Hearts'), ('💟', '💟 Heart Decoration'), ('💌', '💌 Love Letter'), ('💧', '💧 Droplet'), ('💤', '💤 ZZZ'), ('💢', '💢 Anger'), ('💣', '💣 Bomb'), ('💥', '💥 Sparks'), ('💦', '💦 Splashing'), ('💨', '💨 Dash'), ('💫', '💫 Shooting Star'), ('💬', '💬 Speech Bubble'), ('💭', '💭 Thinky Cloud'), ('👓', '👓 Eyeglasses'), ('👔', '👔 Business Casual'), ('👕', '👕 T-Shirt'), ('👖', '👖 Jeans'), ('👗', '👗 Dress'), ('👘', '👘 Kimono'), ('👙', '👙 Bikini'), ('👚', '👚 Womans Clothes'), ('👛', '👛 Purse'), ('👜', '👜 Handbag'), ('👝', '👝 Pouch'), ('🎒', '🎒 Backpack'), ('👞', '👞 Mans Shoe'), ('👟', '👟 Running Shoe'), ('👠', '👠 Heels'), ('👡', '👡 Womans Sandal'), ('👢', '👢 Womans Boots'), ('👑', '👑 Crown'), ('👒', "👒 Lady's Hat"), ('🎩', '🎩 Top Hat'), ('💄', '💄 Lipstick'), ('💍', '💍 Proposal'), ('💎', '💎 Gem'), ('👹', '👹 Japanese Ogre'), ('👺', '👺 Japanese Goblin'), ('👻', '👻 Ghost'), ('💀', '💀 Skull'), ('👽', '👽 Alien'), ('👾', '👾 Space Invader'), ('💩', '💩 Pile of Poo'), ('🐵', '🐵 Monkey'), ('🙈', '🙈 See No Evil'), ('🙉', '🙉 Hear No Evil'), ('🙊', '🙊 Speak No Evil'), ('🐒', '🐒 Monkey'), ('🐶', '🐶 Dog'), ('🐕', '🐕 Dog'), ('🐩', '🐩 Poodle'), ('🐺', '🐺 Wolf'), ('🐱', '🐱 Cat'), ('😸', '😸 Grinning Cat with Smiling Eyes'), ('😹', '😹 Cat with Tears of Joy'), ('😺', '😺 Smiling Cat with Open Mouth'), ('😻', '😻 Smiling Cat with Heart Eyes'), ('😼', '😼 Cat with Wry Smile'), ('😽', '😽 Kissing Cat with Closed Eyes'), ('😾', '😾 Pouting Cat Face'), ('😿', '😿 Crying Cat Face'), ('🙀', '🙀 Weary Cat Face'), ('🐈', '🐈 Cat'), ('🐯', '🐯 Tiger'), ('🐅', '🐅 Tiger'), ('🐆', '🐆 Leopard'), ('🐴', '🐴 Horse'), ('🐎', '🐎 Horse'), ('🐮', '🐮 Cow'), ('🐂', '🐂 Ox'), ('🐃', '🐃 Water Buffalo'), ('🐄', '🐄 Cow'), ('🐷', '🐷 Pig'), ('🐖', '🐖 Pig'), ('🐗', '🐗 Boar'), ('🐽', '🐽 Pig Nose'), ('🐏', '🐏 Ram'), ('🐑', '🐑 Sheep'), ('🐐', '🐐 Goat'), ('🐪', '🐪 Dromedary Camel'), ('🐫', '🐫 Bactrian Camel'), ('🐘', '🐘 Elephant'), ('🐭', '🐭 Mouse'), ('🐁', '🐁 Mouse'), ('🐀', '🐀 Rat'), ('🐹', '🐹 Hamster'), ('🐰', '🐰 Rabbit'), ('🐇', '🐇 Rabbit'), ('🐻', '🐻 Bear'), ('🐨', '🐨 Koala'), ('🐼', '🐼 Panda'), ('🐾', '🐾 Paw Prints'), ('🐔', '🐔 Chicken'), ('🐓', '🐓 Rooster'), ('🐣', '🐣 Hatching'), ('🐤', '🐤 Chick'), ('🐥', '🐥 Front-Facing Baby Chick'), ('🐦', '🐦 Bird'), ('🐧', '🐧 Penguin'), ('🐸', '🐸 Frog'), ('🐊', '🐊 Croc'), ('🐢', '🐢 Turtle'), ('🐍', '🐍 Slithering'), ('🐲', '🐲 Dragon'), ('🐉', '🐉 Dragon'), ('🐳', '🐳 Whale'), ('🐋', '🐋 Whale'), ('🐬', '🐬 Dolphin'), ('🐟', '🐟 Fish'), ('🐠', '🐠 Fish'), ('🐡', '🐡 Blowfish'), ('🐙', '🐙 Octopus'), ('🐚', '🐚 Shell'), ('🐌', '🐌 Snail'), ('🐛', '🐛 Bug'), ('🐜', '🐜 Ant'), ('🐝', '🐝 Honeybee'), ('🐞', '🐞 Lady Beetle'), ('💐', '💐 Bouquet'), ('🌸', '🌸 Sakura'), ('💮', '💮 White Flower'), ('🌹', '🌹 Rose'), ('🌺', '🌺 Hibiscus'), ('🌻', '🌻 Sunflower'), ('🌼', '🌼 Blossom'), ('🌷', '🌷 Tulip'), ('🌱', '🌱 Seedling'), ('🌲', '🌲 Evergreen Tree'), ('🌳', '🌳 Deciduous Tree'), ('🌴', '🌴 Palm Tree'), ('🌵', '🌵 Cactus'), ('🌾', '🌾 Ear of Rice'), ('🌿', '🌿 Herb'), ('🍀', '🍀 Clover'), ('🍁', '🍁 Maple Leaf'), ('🍂', '🍂 Fallen Leaf'), ('🍃', '🍃 Blown Leaves'), ('🍇', '🍇 Grapes'), ('🍈', '🍈 Melon'), ('🍉', '🍉 Watermelon'), ('🍊', '🍊 Tangerine'), ('🍋', '🍋 Lemon'), ('🍌', '🍌 Banana'), ('🍍', '🍍 Pineapple'), ('🍎', '🍎 Red Apple'), ('🍏', '🍏 Green Apple'), ('🍐', '🍐 Pear'), ('🍑', '🍑 Peach'), ('🍒', '🍒 Cherries'), ('🍓', '🍓 Strawberry'), ('🍅', '🍅 Tomato'), ('🍆', '🍆 Eggplant'), ('🌽', '🌽 Corn'), ('🍄', '🍄 Mushroom'), ('🌰', '🌰 Chestnut'), ('🍞', '🍞 Bread'), ('🍖', '🍖 Meat on Bone'), ('🍗', '🍗 Poultry Leg'), ('🍔', '🍔 Hamburger'), ('🍟', '🍟 Fries'), ('🍕', '🍕 Pizza'), ('🍲', '🍲 Pot of Food'), ('🍱', '🍱 Bento Box'), ('🍘', '🍘 Rice Cracker'), ('🍙', '🍙 Rice Ball'), ('🍚', '🍚 Cooked Rice'), ('🍛', '🍛 Curry and Rice'), ('🍜', '🍜 Steaming Bowl'), ('🍝', '🍝 Spaghetti'), ('🍠', '🍠 Sweet Potato'), ('🍢', '🍢 Oden'), ('🍣', '🍣 Sushi'), ('🍤', '🍤 Fried Shrimp'), ('🍥', '🍥 Fish Cake With Swirl Design'), ('🍡', '🍡 Dango'), ('🍦', '🍦 Ice Cream'), ('🍧', '🍧 Shaved Ice'), ('🍨', '🍨 Ice Cream'), ('🍩', '🍩 Doughnut'), ('🍪', '🍪 Cookie'), ('🎂', '🎂 Birthday Cake'), ('🍰', '🍰 Shortcake'), ('🍫', '🍫 Chocolate Bar'), ('🍬', '🍬 Candy'), ('🍭', '🍭 Lollipop'), ('🍮', '🍮 Custard'), ('🍯', '🍯 Honey Pot'), ('🍼', '🍼 Baby Bottle'), ('🍵', '🍵 Teacup Without Handle'), ('🍶', '🍶 Sake Bottle and Cup'), ('🍷', '🍷 Wine Glass'), ('🍸', '🍸 Cocktail Glass'), ('🍹', '🍹 Tropical Drink'), ('🍺', '🍺 Beer'), ('🍻', '🍻 Clinking Beer Mugs'), ('🍴', '🍴 Fork & Knife'), ('🍳', '🍳 Cooking'), ('🌍', '🌍 Earth Globe Europe-Africa'), ('🌎', '🌎 Earth Globe Americas'), ('🌏', '🌏 Earth Globe Asia-Australia'), ('🌐', '🌐 Globe With Meridians'), ('🌋', '🌋 Volcano'), ('🗻', '🗻 Mount Fuji'), ('🏠', '🏠 House'), ('🏡', '🏡 House With Garden'), ('🏢', '🏢 Office'), ('🏣', '🏣 Japanese Post Office'), ('🏤', '🏤 European Post Office'), ('🏥', '🏥 Hospital'), ('🏦', '🏦 Bank'), ('🏨', '🏨 Hotel'), ('🏩', '🏩 Love Hotel'), ('🏪', '🏪 Convenience Store'), ('🏫', '🏫 School'), ('🏬', '🏬 Department Store'), ('🏭', '🏭 Factory'), ('🏯', '🏯 Japanese Castle'), ('🏰', '🏰 Castle'), ('💒', '💒 Wedding'), ('🗼', '🗼 Tokyo Tower'), ('🗽', '🗽 Liberty'), ('🗾', '🗾 Silhouette of Japan'), ('🌁', '🌁 Foggy'), ('🌃', '🌃 Night With Stars'), ('🌄', '🌄 Sunrise Over Mountains'), ('🌅', '🌅 Sunrise'), ('🌆', '🌆 Cityscape at Dusk'), ('🌇', '🌇 Sunset Over Buildings'), ('🌉', '🌉 Bridge at Night'), ('🌊', '🌊 Big Wave'), ('🗿', '🗿 Moyai'), ('🌌', '🌌 Milky Way'), ('🎠', '🎠 Carousel Horse'), ('🎡', '🎡 Ferris Wheel'), ('🎢', '🎢 Roller Coaster'), ('💈', '💈 Barber Pole'), ('🎪', '🎪 Circus Tent'), ('🎭', '🎭 Performing Arts'), ('🎨', '🎨 Palette'), ('🎰', '🎰 Slot Machine'), ('🚂', '🚂 Steam Locomotive'), ('🚃', '🚃 Railcar'), ('🚄', '🚄 Fast Train'), ('🚅', '🚅 Fast Train with Bullet Nose'), ('🚆', '🚆 Train'), ('🚇', '🚇 Metro'), ('🚈', '🚈 Light Rail'), ('🚉', '🚉 Station'), ('🚊', '🚊 Tram'), ('🚝', '🚝 Monorail'), ('🚞', '🚞 Mountain Railway'), ('🚋', '🚋 Tram Car'), ('🚌', '🚌 Bus'), ('🚍', '🚍 Bus'), ('🚎', '🚎 Trolleybus'), ('🚏', '🚏 Bus Stop'), ('🚐', '🚐 Minibus'), ('🚑', '🚑 Ambulance'), ('🚒', '🚒 Fire Engine'), ('🚓', '🚓 Police Car'), ('🚔', '🚔 Police Car'), ('🚕', '🚕 Taxi'), ('🚖', '🚖 Oncoming Taxi'), ('🚗', '🚗 Automobile'), ('🚘', '🚘 Automobile'), ('🚙', '🚙 Recreational Vehicle'), ('🚚', '🚚 Truck'), ('🚛', '🚛 Articulated Lorry'), ('🚜', '🚜 Tractor'), ('🚲', '🚲 Bicycle'), ('🚳', '🚳 No Bicycles'), ('🚨', '🚨 Alert Light'), ('🔱', '🔱 Trident'), ('🚣', '🚣 Rowboat'), ('🚤', '🚤 Speedboat'), ('🚢', '🚢 Ship'), ('💺', '💺 Seat'), ('🚁', '🚁 Helicopter'), ('🚟', '🚟 Suspension Railway'), ('🚠', '🚠 Sky Tram'), ('🚡', '🚡 Aerial Tramway'), ('🚀', '🚀 Rocket'), ('🏧', '🏧 ATM'), ('🚮', '🚮 Put Litter in Its Place'), ('🚥', '🚥 Horizontal Traffic Light'), ('🚦', '🚦 Traffic Light'), ('🚧', '🚧 Hazard Sign'), ('🚫', '🚫 Prohibited'), ('🚭', '🚭 No Smoking'), ('🚯', '🚯 Do Not Litter'), ('🚰', '🚰 Tap Water'), ('🚱', '🚱 Non-Potable Water'), ('🚷', '🚷 No Pedestrians'), ('🚸', '🚸 Children Crossing'), ('🚹', '🚹 Mens Symbol'), ('🚺', '🚺 Womens Symbol'), ('🚻', '🚻 Restroom'), ('🚼', '🚼 Baby Symbol'), ('🚾', '🚾 Water Closet'), ('🛂', '🛂 Passport Control'), ('🛃', '🛃 Customs'), ('🛄', '🛄 Baggage Claim'), ('🛅', '🛅 Left Luggage'), ('🚪', '🚪 Door'), ('🚽', '🚽 Toilet'), ('🚿', '🚿 Shower'), ('🛀', '🛀 Bath'), ('🛁', '🛁 Bathtub'), ('⏳', '⏳ Hourglass'), ('⏰', '⏰ Alarm Clock'), ('⏱', '⏱ Stopwatch'), ('⏲', '⏲ Timer Clock'), ('🕛', "🕛 Twelve O'Clock"), ('🕧', '🕧 Half Past Twelve'), ('🕐', "🕐 One O'Clock"), ('🕜', '🕜 Half Past One'), ('🕑', "🕑 Two O'Clock"), ('🕝', '🕝 Half Past Two'), ('🕒', "🕒 Three O'Clock"), ('🕞', '🕞 Half Past Three'), ('🕓', "🕓 Four O'Clock"), ('🕟', '🕟 Half Past Four'), ('🕔', "🕔 Five O'Clock"), ('🕠', '🕠 Half Past Five'), ('🕕', "🕕 Six O'Clock"), ('🕡', '🕡 Half Past Six'), ('🕖', "🕖 Seven O'Clock"), ('🕢', '🕢 Half Past Seven'), ('🕗', "🕗 Eight O'Clock"), ('🕣', '🕣 Half Past Eight'), ('🕘', "🕘 Nine O'Clock"), ('🕤', '🕤 Half Past Nine'), ('🕙', "🕙 Ten O'Clock"), ('🕥', '🕥 Half Past Ten'), ('🕚', "🕚 Eleven O'Clock"), ('🕦', '🕦 Half Past Eleven'), ('⛎', '⛎ Ophiuchus'), ('🌑', '🌑 New Moon'), ('🌒', '🌒 Waxing Crescent'), ('🌓', '🌓 First Quarter Moon Symbol'), ('🌔', '🌔 Waxing Gibbous'), ('🌕', '🌕 Full Moon'), ('🌖', '🌖 Waning Gibbous'), ('🌗', '🌗 Half Moon'), ('🌘', '🌘 Waning Crescent'), ('🌙', '🌙 Crescent Moon'), ('🌚', '🌚 New Moon With Face'), ('🌛', '🌛 First Quarter Moon With Face'), ('🌜', '🌜 Last Quarter Moon With Face'), ('🌝', '🌝 Full Moon With Face'), ('🌞', '🌞 Sun'), ('🌀', '🌀 Cyclone'), ('🌈', '🌈 Rainbow'), ('🌂', '🌂 Umbrella'), ('🌟', '🌟 Glowing Star'), ('🌠', '🌠 Shooting Star'), ('🔥', '🔥 Fire'), ('🎃', '🎃 Jack-O-Lantern'), ('🎄', '🎄 Presents Tree'), ('🎆', '🎆 Fireworks'), ('🎇', '🎇 Firework Sparkler'), ('✨', '✨ Sparkles'), ('🎈', '🎈 Balloon'), ('🎉', '🎉 Party Pop'), ('🎊', '🎊 Confetti Ball'), ('🎋', '🎋 Tanabata Tree'), ('🎌', '🎌 Crossed Flags'), ('🎍', '🎍 Pine Decoration'), ('🎎', '🎎 Japanese Dolls'), ('🎏', '🎏 Carp Streamer'), ('🎐', '🎐 Wind Chime'), ('🎑', '🎑 Moon Viewing Ceremony'), ('🎓', '🎓 Grad Cap'), ('🎯', '🎯 Bullseye'), ('🎴', '🎴 Flower Playing Cards'), ('🎀', '🎀 Ribbon'), ('🎁', '🎁 Wrapped Present'), ('🎫', '🎫 Ticket'), ('🏀', '🏀 Basketball'), ('🏈', '🏈 America Ball'), ('🏉', '🏉 Rugby Ball'), ('🎾', '🎾 Tennis'), ('🎱', '🎱 Billiards'), ('🎳', '🎳 Bowling'), ('🎣', '🎣 Fishing Pole and Fish'), ('🎽', '🎽 Running Shirt With Sash'), ('🎿', '🎿 Ski and Ski Boot'), ('🏂', '🏂 Snowboarder'), ('🏄', '🏄 Surfer'), ('🏇', '🏇 Horse Racing'), ('🏊', '🏊 Swimmer'), ('🚴', '🚴 Bicyclist'), ('🚵', '🚵 Mountain Bicyclist'), ('🏆', '🏆 Trophy'), ('🎮', '🎮 Video Game'), ('🎲', '🎲 Random Cube'), ('🃏', '🃏 Playing Card Black Joker'), ('🔇', '🔇 Speaker With Cancellation Stroke'), ('🔈', '🔈 Speaker'), ('🔉', '🔉 Speaker With One Sound Wave'), ('🔊', '🔊 Speaker With Three Sound Waves'), ('📢', '📢 Public Address Loudspeaker'), ('📣', '📣 Loud Phone'), ('📯', '📯 Horn'), ('🔔', '🔔 Bell'), ('🔕', '🔕 No Bells'), ('🔀', '🔀 Shuffle'), ('🔁', '🔁 Repeat'), ('🔂', '🔂 Repeat Once'), ('⏩', '⏩ Fast Forward'), ('⏭', '⏭ Next Track'), ('⏯', '⏯ Play/Pause'), ('⏪', '⏪ Rewind'), ('⏮', '⏮ Previous Track'), ('🔼', '🔼 Up-Pointing Small Red Triangle'), ('⏫', '⏫ Up to Top'), ('🔽', '🔽 Down-Pointing Small Red Triangle'), ('⏬', '⏬ Down to Bottom'), ('🎼', '🎼 Musical Score'), ('🎵', '🎵 Musical Note'), ('🎶', '🎶 Music Notes'), ('🎤', '🎤 Microphone'), ('🎧', '🎧 Headphone'), ('🎷', '🎷 Saxophone'), ('🎸', '🎸 Guitar'), ('🎹', '🎹 Keyboard'), ('🎺', '🎺 Trumpet'), ('🎻', '🎻 Violin'), ('📻', '📻 Boom Box'), ('📱', '📱 Internet Phone'), ('📳', '📳 Vibration Mode'), ('📴', '📴 Mobile Phone Off'), ('📲', '📲 Download to Phone'), ('📵', '📵 No Mobile Phones'), ('📞', '📞 Old Phone'), ('🔟', '🔟 Keycap Ten'), ('📶', '📶 Antenna With Bars'), ('📟', '📟 Pager'), ('📠', '📠 Fax Machine'), ('🔋', '🔋 Battery'), ('🔌', '🔌 Plug'), ('💻', '💻 Personal Computer'), ('💽', '💽 Minidisc'), ('💾', '💾 Floppy'), ('💿', '💿 Compact Disc'), ('📀', '📀 DVD'), ('🎥', '🎥 Movie Camera'), ('🎦', '🎦 Cinema'), ('🎬', '🎬 Clapper'), ('📺', '📺 Television'), ('📷', '📷 Camera'), ('📹', '📹 Video Camera'), ('📼', '📼 Videocassette'), ('🔅', '🔅 Low Brightness Symbol'), ('🔆', '🔆 High Brightness Symbol'), ('🔍', '🔍 Bigger Glass'), ('🔎', '🔎 Right-Pointing Magnifying Glass'), ('🔬', '🔬 Microscope'), ('🔭', '🔭 Telescope'), ('📡', '📡 Satellite Dish'), ('💡', '💡 Light Bulb'), ('🔦', '🔦 Electric Torch'), ('🏮', '🏮 Izakaya Lantern'), ('📔', '📔 Notebook With Decorative Cover'), ('📕', '📕 Closed Book'), ('📖', '📖 Open Book'), ('📗', '📗 Green Book'), ('📘', '📘 Blue Book'), ('📙', '📙 Orange Book'), ('📚', '📚 Books'), ('📓', '📓 Notebook'), ('📒', '📒 Ledger'), ('📃', '📃 Page With Curl'), ('📜', '📜 Scroll'), ('📄', '📄 Page Facing Up'), ('📰', '📰 Newspaper'), ('📑', '📑 Bookmark Tabs'), ('🔖', '🔖 Bookmark'), ('💰', '💰 Money Bag'), ('💴', '💴 Banknote With Yen Sign'), ('💵', '💵 Banknote With Dollar Sign'), ('💶', '💶 Banknote With Euro Sign'), ('💷', '💷 Banknote With Pound Sign'), ('💸', '💸 Flying Money'), ('💱', '💱 Currency Exchange'), ('💲', '💲 Heavy Dollar Sign'), ('💳', '💳 Credit Card'), ('💹', '💹 Upwards Trend in Yen'), ('📧', '📧 E-Mail Symbol'), ('📨', '📨 Incoming Envelope'), ('📩', '📩 Going Into Envelope'), ('📤', '📤 Outbox Tray'), ('📥', '📥 Inbox Tray'), ('📦', '📦 Package'), ('📫', '📫 Mailbox'), ('📪', '📪 Closed Mailbox With Lowered Flag'), ('📬', '📬 Open Mailbox With Raised Flag'), ('📭', '📭 Open Mailbox With Lowered Flag'), ('📮', '📮 Postbox'), ('📝', '📝 Memo'), ('💼', '💼 Briefcase'), ('📁', '📁 File Folder'), ('📂', '📂 Open File Folder'), ('📅', '📅 Dated'), ('📆', '📆 Tear-Off Calendar'), ('📇', '📇 Card Index'), ('📈', '📈 Up Trend'), ('📉', '📉 Down Trend'), ('📊', '📊 Bar Chart'), ('📋', '📋 Clipboard'), ('📌', '📌 Pushpin'), ('📍', '📍 Location'), ('📎', '📎 Paperclip'), ('📏', '📏 Straight Line'), ('📐', '📐 Three Sides'), ('📛', '📛 Name Badge'), ('🔒', '🔒 Lock'), ('🔓', '🔓 Open Lock'), ('🔏', '🔏 Lock With Ink Pen'), ('🔐', '🔐 Closed Lock With Key'), ('🔑', '🔑 Key'), ('🔨', '🔨 Hammer'), ('🔧', '🔧 Spanner'), ('🔩', '🔩 Calipers'), ('🔗', '🔗 Link Symbol'), ('💉', '💉 Syringe'), ('💊', '💊 Pill'), ('🔪', '🔪 Chef Knife'), ('🔫', '🔫 Pistol'), ('🚬', '🚬 Durry'), ('🏁', '🏁 Get Set Go'), ('🚩', '🚩 Triangular Flag on Post'), ('🇦🇫', '🇦🇫 Afghanistan'), ('🇦🇽', '🇦🇽 Åland Islands'), ('🇦🇱', '🇦🇱 Albania'), ('🇩🇿', '🇩🇿 Algeria'), ('🇦🇸', '🇦🇸 American Samoa'), ('🇦🇩', '🇦🇩 Andorra'), ('🇦🇴', '🇦🇴 Angola'), ('🇦🇮', '🇦🇮 Anguilla'), ('🇦🇶', '🇦🇶 Antarctica'), ('🇦🇬', '🇦🇬 Antigua & Barbuda'), ('🇦🇷', '🇦🇷 Argentina'), ('🇦🇲', '🇦🇲 Armenia'), ('🇦🇼', '🇦🇼 Aruba'), ('🇦🇨', '🇦🇨 Ascension Island'), ('🇦🇺', '🇦🇺 Australia'), ('🇦🇹', '🇦🇹 Austria'), ('🇦🇿', '🇦🇿 Azerbaijan'), ('🇧🇸', '🇧🇸 Bahamas'), ('🇧🇭', '🇧🇭 Bahrain'), ('🇧🇩', '🇧🇩 Bangladesh'), ('🇧🇧', '🇧🇧 Barbados'), ('🇧🇾', '🇧🇾 Belarus'), ('🇧🇪', '🇧🇪 Belgium'), ('🇧🇿', '🇧🇿 Belize'), ('🇧🇯', '🇧🇯 Benin'), ('🇧🇲', '🇧🇲 Bermuda'), ('🇧🇹', '🇧🇹 Bhutan'), ('🇧🇴', '🇧🇴 Bolivia'), ('🇧🇦', '🇧🇦 Bosnia & Herzegovina'), ('🇧🇼', '🇧🇼 Botswana'), ('🇧🇻', '🇧🇻 Bouvet Island'), ('🇧🇷', '🇧🇷 Brazil'), ('🇮🇴', '🇮🇴 British Indian Ocean Territory'), ('🇻🇬', '🇻🇬 British Virgin Islands'), ('🇧🇳', '🇧🇳 Brunei'), ('🇧🇬', '🇧🇬 Bulgaria'), ('🇧🇫', '🇧🇫 Burkina Faso'), ('🇧🇮', '🇧🇮 Burundi'), ('🇰🇭', '🇰🇭 Cambodia'), ('🇨🇲', '🇨🇲 Cameroon'), ('🇨🇦', '🇨🇦 Canada'), ('🇮🇨', '🇮🇨 Canary Islands'), ('🇨🇻', '🇨🇻 Cape Verde'), ('🇧🇶', '🇧🇶 Caribbean Netherlands'), ('🇰🇾', '🇰🇾 Cayman Islands'), ('🇨🇫', '🇨🇫 Central African Republic'), ('🇪🇦', '🇪🇦 Ceuta & Melilla'), ('🇹🇩', '🇹🇩 Chad'), ('🇨🇱', '🇨🇱 Chile'), ('🇨🇳', '🇨🇳 China'), ('🇨🇽', '🇨🇽 Christmas Island'), ('🇨🇵', '🇨🇵 Clipperton Island'), ('🇨🇨', '🇨🇨 Cocos Islands'), ('🇨🇴', '🇨🇴 Colombia'), ('🇰🇲', '🇰🇲 Comoros'), ('🇨🇬', '🇨🇬 Congo - Brazzaville'), ('🇨🇩', '🇨🇩 Congo - Kinshasa'), ('🇨🇰', '🇨🇰 Cook Islands'), ('🇨🇷', '🇨🇷 Costa Rica'), ('🇨🇮', '🇨🇮 Côte D’Ivoire'), ('🇭🇷', '🇭🇷 Croatia'), ('🇨🇺', '🇨🇺 Cuba'), ('🇨🇼', '🇨🇼 Curaçao'), ('🇨🇾', '🇨🇾 Cyprus'), ('🇨🇿', '🇨🇿 Czech Republic'), ('🇩🇰', '🇩🇰 Denmark'), ('🇩🇬', '🇩🇬 Diego Garcia'), ('🇩🇯', '🇩🇯 Djibouti'), ('🇩🇲', '🇩🇲 Dominica'), ('🇩🇴', '🇩🇴 Dominican Republic'), ('🇪🇨', '🇪🇨 Ecuador'), ('🇪🇬', '🇪🇬 Egypt'), ('🇸🇻', '🇸🇻 El Salvador'), ('🇬🇶', '🇬🇶 Equatorial Guinea'), ('🇪🇷', '🇪🇷 Eritrea'), ('🇪🇪', '🇪🇪 Estonia'), ('🇪🇹', '🇪🇹 Ethiopia'), ('🇪🇺', '🇪🇺 European Union'), ('🇫🇰', '🇫🇰 Falkland Islands'), ('🇫🇴', '🇫🇴 Faroe Islands'), ('🇫🇯', '🇫🇯 Fiji'), ('🇫🇮', '🇫🇮 Finland'), ('🇫🇷', '🇫🇷 France'), ('🇬🇫', '🇬🇫 French Guiana'), ('🇵🇫', '🇵🇫 French Polynesia'), ('🇹🇫', '🇹🇫 French Southern Territories'), ('🇬🇦', '🇬🇦 Gabon'), ('🇬🇲', '🇬🇲 Gambia'), ('🇬🇪', '🇬🇪 Georgia'), ('🇩🇪', '🇩🇪 Germany'), ('🇬🇭', '🇬🇭 Ghana'), ('🇬🇮', '🇬🇮 Gibraltar'), ('🇬🇷', '🇬🇷 Greece'), ('🇬🇱', '🇬🇱 Greenland'), ('🇬🇩', '🇬🇩 Grenada'), ('🇬🇵', '🇬🇵 Guadeloupe'), ('🇬🇺', '🇬🇺 Guam'), ('🇬🇹', '🇬🇹 Guatemala'), ('🇬🇬', '🇬🇬 Guernsey'), ('🇬🇳', '🇬🇳 Guinea'), ('🇬🇼', '🇬🇼 Guinea-Bissau'), ('🇬🇾', '🇬🇾 Guyana'), ('🇭🇹', '🇭🇹 Haiti'), ('🇭🇲', '🇭🇲 Heard & McDonald Islands'), ('🇭🇳', '🇭🇳 Honduras'), ('🇭🇰', '🇭🇰 Hong Kong'), ('🇭🇺', '🇭🇺 Hungary'), ('🇮🇸', '🇮🇸 Iceland'), ('🇮🇳', '🇮🇳 India'), ('🇮🇩', '🇮🇩 Indonesia'), ('🇮🇷', '🇮🇷 Iran'), ('🇮🇶', '🇮🇶 Iraq'), ('🇮🇪', '🇮🇪 Ireland'), ('🇮🇲', '🇮🇲 Isle of Man'), ('🇮🇱', '🇮🇱 Israel'), ('🇮🇹', '🇮🇹 Italy'), ('🇯🇲', '🇯🇲 Jamaica'), ('🇯🇵', '🇯🇵 Japan'), ('🇯🇪', '🇯🇪 Jersey'), ('🇯🇴', '🇯🇴 Jordan'), ('🇰🇿', '🇰🇿 Kazakhstan'), ('🇰🇪', '🇰🇪 Kenya'), ('🇰🇮', '🇰🇮 Kiribati'), ('🇽🇰', '🇽🇰 Kosovo'), ('🇰🇼', '🇰🇼 Kuwait'), ('🇰🇬', '🇰🇬 Kyrgyzstan'), ('🇱🇦', '🇱🇦 Laos'), ('🇱🇻', '🇱🇻 Latvia'), ('🇱🇧', '🇱🇧 Lebanon'), ('🇱🇸', '🇱🇸 Lesotho'), ('🇱🇷', '🇱🇷 Liberia'), ('🇱🇾', '🇱🇾 Libya'), ('🇱🇮', '🇱🇮 Liechtenstein'), ('🇱🇹', '🇱🇹 Lithuania'), ('🇱🇺', '🇱🇺 Luxembourg'), ('🇲🇴', '🇲🇴 Macau'), ('🇲🇰', '🇲🇰 Macedonia'), ('🇲🇬', '🇲🇬 Madagascar'), ('🇲🇼', '🇲🇼 Malawi'), ('🇲🇾', '🇲🇾 Malaysia'), ('🇲🇻', '🇲🇻 Maldives'), ('🇲🇱', '🇲🇱 Mali'), ('🇲🇹', '🇲🇹 Malta'), ('🇲🇭', '🇲🇭 Marshall Islands'), ('🇲🇶', '🇲🇶 Martinique'), ('🇲🇷', '🇲🇷 Mauritania'), ('🇲🇺', '🇲🇺 Mauritius'), ('🇾🇹', '🇾🇹 Mayotte'), ('🇲🇽', '🇲🇽 Mexico'), ('🇫🇲', '🇫🇲 Micronesia'), ('🇲🇩', '🇲🇩 Moldova'), ('🇲🇨', '🇲🇨 Monaco'), ('🇲🇳', '🇲🇳 Mongolia'), ('🇲🇪', '🇲🇪 Montenegro'), ('🇲🇸', '🇲🇸 Montserrat'), ('🇲🇦', '🇲🇦 Morocco'), ('🇲🇿', '🇲🇿 Mozambique'), ('🇲🇲', '🇲🇲 Myanmar'), ('🇳🇦', '🇳🇦 Namibia'), ('🇳🇷', '🇳🇷 Nauru'), ('🇳🇵', '🇳🇵 Nepal'), ('🇳🇱', '🇳🇱 Netherlands'), ('🇳🇨', '🇳🇨 New Caledonia'), ('🇳🇿', '🇳🇿 New Zealand'), ('🇳🇮', '🇳🇮 Nicaragua'), ('🇳🇪', '🇳🇪 Niger'), ('🇳🇬', '🇳🇬 Nigeria'), ('🇳🇺', '🇳🇺 Niue'), ('🇳🇫', '🇳🇫 Norfolk Island'), ('🇲🇵', '🇲🇵 Northern Mariana Islands'), ('🇰🇵', '🇰🇵 North Korea'), ('🇳🇴', '🇳🇴 Norway'), ('🇴🇲', '🇴🇲 Oman'), ('🇵🇰', '🇵🇰 Pakistan'), ('🇵🇼', '🇵🇼 Palau'), ('🇵🇸', '🇵🇸 Palestinian Territories'), ('🇵🇦', '🇵🇦 Panama'), ('🇵🇬', '🇵🇬 Papua New Guinea'), ('🇵🇾', '🇵🇾 Paraguay'), ('🇵🇪', '🇵🇪 Peru'), ('🇵🇭', '🇵🇭 Philippines'), ('🇵🇳', '🇵🇳 Pitcairn Islands'), ('🇵🇱', '🇵🇱 Poland'), ('🇵🇹', '🇵🇹 Portugal'), ('🇵🇷', '🇵🇷 Puerto Rico'), ('🇶🇦', '🇶🇦 Qatar'), ('🇷🇪', '🇷🇪 Réunion'), ('🇷🇴', '🇷🇴 Romania'), ('🇷🇺', '🇷🇺 Russia'), ('🇷🇼', '🇷🇼 Rwanda'), ('🇼🇸', '🇼🇸 Samoa'), ('🇸🇲', '🇸🇲 San Marino'), ('🇸🇹', '🇸🇹 São Tomé & Príncipe'), ('🇸🇦', '🇸🇦 Saudi Arabia'), ('🇸🇳', '🇸🇳 Senegal'), ('🇷🇸', '🇷🇸 Serbia'), ('🇸🇨', '🇸🇨 Seychelles'), ('🇸🇱', '🇸🇱 Sierra Leone'), ('🇸🇬', '🇸🇬 Singapore'), ('🇸🇽', '🇸🇽 Sint Maarten'), ('🇸🇰', '🇸🇰 Slovakia'), ('🇸🇮', '🇸🇮 Slovenia'), ('🇸🇧', '🇸🇧 Solomon Islands'), ('🇸🇴', '🇸🇴 Somalia'), ('🇿🇦', '🇿🇦 South Africa'), ('🇬🇸', '🇬🇸 South Georgia & South Sandwich Islands'), ('🇰🇷', '🇰🇷 South Korea'), ('🇸🇸', '🇸🇸 South Sudan'), ('🇪🇸', '🇪🇸 Spain'), ('🇱🇰', '🇱🇰 Sri Lanka'), ('🇧🇱', '🇧🇱 St. Barthélemy'), ('🇸🇭', '🇸🇭 St. Helena'), ('🇰🇳', '🇰🇳 St. Kitts & Nevis'), ('🇱🇨', '🇱🇨 St. Lucia'), ('🇲🇫', '🇲🇫 St. Martin'), ('🇵🇲', '🇵🇲 St. Pierre & Miquelon'), ('🇻🇨', '🇻🇨 St. Vincent & Grenadines'), ('🇸🇩', '🇸🇩 Sudan'), ('🇸🇷', '🇸🇷 Suriname'), ('🇸🇯', '🇸🇯 Svalbard & Jan Mayen'), ('🇸🇿', '🇸🇿 Swaziland'), ('🇸🇪', '🇸🇪 Sweden'), ('🇨🇭', '🇨🇭 Switzerland'), ('🇸🇾', '🇸🇾 Syria'), ('🇹🇼', '🇹🇼 Taiwan'), ('🇹🇯', '🇹🇯 Tajikistan'), ('🇹🇿', '🇹🇿 Tanzania'), ('🇹🇭', '🇹🇭 Thailand'), ('🇹🇱', '🇹🇱 Timor-Leste'), ('🇹🇬', '🇹🇬 Togo'), ('🇹🇰', '🇹🇰 Tokelau'), ('🇹🇴', '🇹🇴 Tonga'), ('🇹🇹', '🇹🇹 Trinidad & Tobago'), ('🇹🇦', '🇹🇦 Tristan Da Cunha'), ('🇹🇳', '🇹🇳 Tunisia'), ('🇹🇷', '🇹🇷 Turkey'), ('🇹🇲', '🇹🇲 Turkmenistan'), ('🇹🇨', '🇹🇨 Turks & Caicos Islands'), ('🇹🇻', '🇹🇻 Tuvalu'), ('🇺🇬', '🇺🇬 Uganda'), ('🇺🇦', '🇺🇦 Ukraine'), ('🇦🇪', '🇦🇪 United Arab Emirates'), ('🇬🇧', '🇬🇧 United Kingdom'), ('🇺🇸', '🇺🇸 United States'), ('🇺🇾', '🇺🇾 Uruguay'), ('🇺🇲', '🇺🇲 U.S. Outlying Islands'), ('🇻🇮', '🇻🇮 U.S. Virgin Islands'), ('🇺🇿', '🇺🇿 Uzbekistan'), ('🇻🇺', '🇻🇺 Vanuatu'), ('🇻🇦', '🇻🇦 Vatican City'), ('🇻🇪', '🇻🇪 Venezuela'), ('🇻🇳', '🇻🇳 Vietnam'), ('🇼🇫', '🇼🇫 Wallis & Futuna'), ('🇪🇭', '🇪🇭 Western Sahara'), ('🇾🇪', '🇾🇪 Yemen'), ('🇿🇲', '🇿🇲 Zambia'), ('🇿🇼', '🇿🇼 Zimbabwe'), ('🔃', '🔃 Clockwise Arrows'), ('🔄', '🔄 Anticlockwise Arrows'), ('🔙', '🔙 Back'), ('🔚', '🔚 End'), ('🔛', '🔛 On'), ('🔜', '🔜 Soon'), ('🔝', '🔝 Top'), ('🔰', '🔰 Beginner'), ('🔮', '🔮 Crystal Ball'), ('🔯', '🔯 Six Pointed Star With Middle Dot'), ('✅', '✅ White Heavy Check Mark'), ('❌', '❌ Cross'), ('❎', '❎ Negative Squared Cross Mark'), ('➕', '➕ Heavy Plus Sign'), ('➖', '➖ Heavy Minus Sign'), ('➗', '➗ Heavy Division Sign'), ('➰', '➰ Curly Loop'), ('➿', '➿ Double Curly Loop'), ('❓', '❓ Question'), ('❔', '❔ White Question Mark Ornament'), ('❕', '❕ White Exclamation Mark Ornament'), ('💯', '💯 Hundred Points'), ('🔞', '🔞 Over Eighteen'), ('🔠', '🔠 Latin Capital Letters'), ('🔡', '🔡 Latin Small Letters'), ('🔢', '🔢 Numbers'), ('🔣', '🔣 Symbols'), ('🔤', '🔤 Latin Letters'), ('🅰️', '🅰️ Squared A'), ('🆎', '🆎 Squared AB'), ('🅱️', '🅱️ Squared B'), ('🆑', '🆑 Squared CL'), ('🆒', '🆒 Cool Square'), ('🆓', '🆓 Squared Free'), ('🆔', '🆔 Squared ID'), ('🆕', '🆕 New Square'), ('🆖', '🆖 Squared NG'), ('🅾️', '🅾️ Squared O'), ('🆗', '🆗 OK Square'), ('🆘', '🆘 SOS Square'), ('🆙', '🆙 Squared Up!'), ('🆚', '🆚 Squared Vs'), ('🈁', '🈁 Squared Katakana Koko'), ('🈂️', '🈂️ Squared Katakana Sa'), ('🈷️', '🈷️ Squared 月 (Moon)'), ('🈶', '🈶 Squared 有 (Have)'), ('🉐', '🉐 Circled Ideograph Advantage'), ('🈹', '🈹 Squared CJK Unified Ideograph-5272'), ('🈲', '🈲 Squared CJK Unified Ideograph-7981'), ('🉑', '🉑 Circled 可 (Accept)'), ('🈸', '🈸 Squared CJK Unified Ideograph-7533'), ('🈴', '🈴 Squared CJK Unified Ideograph-5408'), ('🈳', '🈳 Squared CJK Unified Ideograph-7a7a'), ('🈺', '🈺 Squared CJK Unified Ideograph-55b6'), ('🈵', '🈵 Squared CJK Unified Ideograph-6e80'), ('🔶', '🔶 Large Orange Diamond'), ('🔷', '🔷 Large Blue Diamond'), ('🔸', '🔸 Small Orange Diamond'), ('🔹', '🔹 Small Blue Diamond'), ('🔺', '🔺 Up-Pointing Red Triangle'), ('🔻', '🔻 Down-Pointing Red Triangle'), ('💠', '💠 Diamond Shape With a Dot Inside'), ('🔘', '🔘 Radio Button'), ('🔲', '🔲 Black Square Button'), ('🔳', '🔳 White Square Button'), ('🔴', '🔴 Large Red Circle'), ('🔵', '🔵 Large Blue Circle'), ('😀', '😀 Grinning'), ('😗', '😗 Kissing'), ('😙', '😙 Smooch'), ('😑', '😑 True Neutral'), ('😮', '😮 Stunned'), ('😯', '😯 Hushed'), ('😴', '😴 Sleepy'), ('😛', '😛 Tongue'), ('😕', '😕 Confused'), ('😟', '😟 Worried'), ('😦', '😦 Frowning Face With Open Mouth'), ('😧', '😧 Anguish Face'), ('😬', '😬 Grimace'), ('🙂', '🙂 Slightly Smiling'), ('🙁', '🙁 Slightly Frowning'), ('🕵', '🕵 Spy'), ('🗣', '🗣 Speaking Head in Silhouette'), ('🕴', '🕴 Man in Business Suit Levitating'), ('🖕', '🖕 Middle Finger'), ('🖖', '🖖 Vulcan Hand'), ('🖐', '🖐 Raised Hand With Fingers Splayed'), ('👁', '👁 Eye'), ('🕳', '🕳 Hole'), ('🗯', '🗯 Right Anger Bubble'), ('🕶', '🕶 Sunglasses'), ('🛍', '🛍 Shopping'), ('🐿', '🐿 Chipmunk'), ('🕊', '🕊 Peace Dove'), ('🕷', '🕷 Spider'), ('🕸', '🕸 Spider Web'), ('🏵', '🏵 Rosette'), ('🌶', '🌶 Chilli'), ('🍽', '🍽 Fork and Knife With Plate'), ('🗺', '🗺 World Map'), ('🏔', '🏔 Snow Capped Mountain'), ('🏕', '🏕 Camping'), ('🏖', '🏖 Beach'), ('🏜', '🏜 Desert'), ('🏝', '🏝 Desert Island'), ('🏞', '🏞 National Park'), ('🏟', '🏟 Stadium'), ('🏛', '🏛 Architecture'), ('🏗', '🏗 Building Construction'), ('🏘', '🏘 House Buildings'), ('🏙', '🏙 Cityscape'), ('🏚', '🏚 Derelict House Building'), ('🖼', '🖼 Frame With Picture'), ('🛢', '🛢 Oil Drum'), ('🛣', '🛣 Motorway'), ('🛤', '🛤 Railway Track'), ('🛳', '🛳 Passenger Ship'), ('🛥', '🛥 Boat'), ('🛩', '🛩 Airplane'), ('🛫', '🛫 Airplane Departure'), ('🛬', '🛬 Airplane Arriving'), ('🛰', '🛰 Satellite'), ('🛎', '🛎 Service Bell'), ('🛌', '🛌 Bed'), ('🛏', '🛏 Bed'), ('🛋', '🛋 Couch and Lamp'), ('🕰', '🕰 Mantelpiece'), ('🌡', '🌡 Thermometer'), ('🌤', '🌤 Small Cloud'), ('🌥', '🌥 White Sun Behind Cloud'), ('🌦', '🌦 White Sun Behind Cloud With Rain'), ('🌧', '🌧 Cloud With Rain'), ('🌨', '🌨 Cloud With Snow'), ('🌩', '🌩 Lightning'), ('🌪', '🌪 Tornado'), ('🌫', '🌫 Fog'), ('🌬', '🌬 Blowing'), ('🎖', '🎖 Medal'), ('🎗', '🎗 Ribbon'), ('🎞', '🎞 Film'), ('🎟', '🎟 Admission Tickets'), ('🏷', '🏷 Label'), ('🏌', '🏌 Golfer'), ('🏋', '🏋 Lifting'), ('🏎', '🏎 Racing Car'), ('🏍', '🏍 Racing Motorcycle'), ('🏅', '🏅 Medal'), ('🕹', '🕹 Joystick'), ('⏸', '⏸ Double Vertical Bar'), ('⏹', '⏹ Black Square for Stop'), ('⏺', '⏺ Black Circle for Record'), ('🎙', '🎙 Microphone'), ('🎚', '🎚 Level Slider'), ('🎛', '🎛 Control Knobs'), ('🖥', '🖥 Desktop'), ('🖨', '🖨 Printer'), ('🖱', '🖱 Three Button Mouse'), ('🖲', '🖲 Trackball'), ('📽', '📽 Film Projector'), ('📸', '📸 Camera With Flash'), ('🕯', '🕯 Candle'), ('🗞', '🗞 Newspaper'), ('🗳', '🗳 Ballot Box With Ballot'), ('🖋', '🖋 Fancy Pen'), ('🖊', '🖊 Lower Left Ballpoint Pen'), ('🖌', '🖌 Lower Left Paintbrush'), ('🖍', '🖍 Lower Left Crayon'), ('🗂', '🗂 Card Index Dividers'), ('🗒', '🗒 Spiral Note Pad'), ('🗓', '🗓 Spiral Calendar Pad'), ('🖇', '🖇 Linked Paperclips'), ('🗃', '🗃 Card File Box'), ('🗄', '🗄 File Cabinet'), ('🗑', '🗑 Wastebasket'), ('🗝', '🗝 Old Key'), ('🛠', '🛠 Tools'), ('🗜', '🗜 Compression'), ('🗡', '🗡 Dagger'), ('🛡', '🛡 Shield'), ('🏳', '🏳 White Flag'), ('🏴', '🏴 Black Flag'), ('🕉', '🕉 Om Symbol'), ('🗨', '🗨 Left Speech Bubble'), ('🤗', '🤗 Hugging'), ('🤔', '🤔 Thinking'), ('🙄', '🙄 Rolling Eyes'), ('🤐', '🤐 Hushed'), ('🤓', '🤓 Nerd'), ('🙃', '🙃 Upside Down'), ('🤒', '🤒 Sick'), ('🤕', '🤕 Hurt Head'), ('🤑', '🤑 Money'), ('🏻', '🏻 Emoji Modifier 1-2'), ('🏼', '🏼 Emoji Modifier 3'), ('🏽', '🏽 Emoji Modifier 4'), ('🏾', '🏾 Emoji Modifier 5'), ('🏿', '🏿 Emoji Modifier 6'), ('🤘', '🤘 Rock On'), ('📿', '📿 Prayer Beads'), ('🤖', '🤖 Robot'), ('🦁', '🦁 Lion'), ('🦄', '🦄 Unicorn'), ('🦃', '🦃 Turkey'), ('🦀', '🦀 Crab'), ('🦂', '🦂 Scorpion'), ('🧀', '🧀 Cheese'), ('🌭', '🌭 Hot Dog'), ('🌮', '🌮 Taco'), ('🌯', '🌯 Burrito'), ('🍿', '🍿 Popcorn'), ('🍾', '🍾 Popping Cork'), ('🏺', '🏺 Amphora'), ('🛐', '🛐 Place of Worship'), ('🕋', '🕋 Kaaba'), ('🕌', '🕌 Mosque'), ('🕍', '🕍 Synagogue'), ('🕎', '🕎 Menorah'), ('🏏', '🏏 Bat and Ball'), ('🏐', '🏐 Volleyball'), ('🏑', '🏑 Field Hockey'), ('🏒', '🏒 Ice Hockey'), ('🏓', '🏓 Table Tennis'), ('🏸', '🏸 Badminton'), ('🏹', '🏹 Archer'), ('🤣', '🤣 ROFL Face'), ('🤤', '🤤 Drooling'), ('🤢', '🤢 Nauseated'), ('🤧', '🤧 Sneezing'), ('🤠', '🤠 Cowboy'), ('🤡', '🤡 Clown'), ('🤥', '🤥 Lying'), ('🤴', '🤴 Prince'), ('🤵', '🤵 Tuxedo Man'), ('🤰', '🤰 Pregnant'), ('🤶', '🤶 Mrs. Claus'), ('🤦', '🤦 Facepalm'), ('🤷', '🤷 Shrugging'), ('🕺', '🕺 Man Dancing'), ('🤺', '🤺 Fencing'), ('🤸', '🤸 Cartwheel'), ('🤼', '🤼 Wrestling'), ('🤽', '🤽 Water Polo'), ('🤾', '🤾 Handball'), ('🤹', '🤹 Juggling'), ('🤳', '🤳 Selfie'), ('🤞', '🤞 Luck Hand'), ('🤙', '🤙 Call Me Hand'), ('🤛', '🤛 Left-Facing Fist'), ('🤜', '🤜 Right-Facing Fist'), ('🤚', '🤚 Raised Back of Hand'), ('🤝', '🤝 Business Hi'), ('🖤', '🖤 Black Heart'), ('🦍', '🦍 Gorilla'), ('🦊', '🦊 Fox'), ('🦌', '🦌 Deer'), ('🦏', '🦏 Rhinoceros'), ('🦇', '🦇 Bat'), ('🦅', '🦅 Eagle'), ('🦆', '🦆 Duck'), ('🦉', '🦉 Owl'), ('🦎', '🦎 Lizard'), ('🦈', '🦈 Shark'), ('🦐', '🦐 Shrimp'), ('🦑', '🦑 Squid'), ('🦋', '🦋 Butterfly'), ('🥀', '🥀 Wilted'), ('🥝', '🥝 Kiwifruit'), ('🥑', '🥑 Pricey Fruit'), ('🥔', '🥔 Potato'), ('🥕', '🥕 Carrot'), ('🥒', '🥒 Cucumber'), ('🥜', '🥜 Peanuts'), ('🥐', '🥐 Croissant'), ('🥖', '🥖 Bread Sword'), ('🥞', '🥞 Pancakes'), ('🥓', '🥓 Bacon'), ('🥙', '🥙 Stuffed Flatbread'), ('🥚', '🥚 Chicken Rock'), ('🥘', '🥘 Shallow Pan'), ('🥗', '🥗 Salad'), ('🥛', '🥛 Cow Juice'), ('🥂', '🥂 Clinking Glasses'), ('🥃', '🥃 Tumbler'), ('🥄', '🥄 Spoon'), ('🛴', '🛴 Scoot Scoot'), ('🛵', '🛵 Motor Scooter'), ('🛑', '🛑 Stop Sign'), ('🛶', '🛶 Canoe'), ('🥇', '🥇 Gold Medal'), ('🥈', '🥈 Silver Medal'), ('🥉', '🥉 Participation'), ('🥊', '🥊 Boxing'), ('🥋', '🥋 Martial Arts'), ('🥅', '🥅 Hashtag Goals'), ('🥁', '🥁 Drum Roll'), ('🛒', '🛒 Food Ute'), ('🤩', '🤩 Star Struck'), ('🤨', '🤨 Unexpected Face'), ('🤯', '🤯 Mind Blown'), ('🤪', '🤪 Zany Face'), ('🤬', '🤬 Swear Face'), ('🤮', '🤮 Vomiting'), ('🤫', '🤫 Shushing'), ('🤭', '🤭 Hand Over Mouth'), ('🧐', '🧐 Monocle'), ('🧒', '🧒 Child Face'), ('🧑', '🧑 Adult'), ('🧓', '🧓 Older Adult'), ('🧕', '🧕 Headscarf'), ('🧔', '🧔 Bearded Person'), ('🤱', '🤱 Breast Feeding'), ('🧙', '🧙 Mage'), ('🧚', '🧚 Fairy'), ('🧛', '🧛 Vampire'), ('🧜', '🧜 Merperson'), ('🧝', '🧝 Cosplay'), ('🧞', '🧞 Genie'), ('🧟', '🧟 Unalive'), ('🧖', '🧖 Steamy Room'), ('🧗', '🧗 Person Climbing'), ('🧘', '🧘 Lotus Position'), ('🤟', '🤟 Love-You Gesture'), ('🤲', '🤲 Palms Up Together'), ('🧠', '🧠 Big Brain'), ('🧡', '🧡 Orange Heart'), ('🧣', '🧣 Neck Hider'), ('🧤', '🧤 Hand Socks'), ('🧥', '🧥 Coat'), ('🧦', '🧦 Feet Gloves'), ('🧢', '🧢 Billed Cap'), ('🦓', '🦓 Zebra'), ('🦒', '🦒 Giraffe'), ('🦔', '🦔 Spikehog'), ('🦕', '🦕 Long Neck'), ('🦖', '🦖 Big Roar'), ('🦗', '🦗 Cricket'), ('🥥', '🥥 Coconut'), ('🥦', '🥦 Tiny Tree'), ('🥨', '🥨 Twisty Bread'), ('🥩', '🥩 Cut of Meat'), ('🥪', '🥪 Sandwich'), ('🥣', '🥣 Bowl With Spoon'), ('🥫', '🥫 Canned Good'), ('🥟', '🥟 Dumpling'), ('🥠', '🥠 Tasty Future'), ('🥡', '🥡 Takeout Box'), ('🥧', '🥧 Pie'), ('🥤', '🥤 Cup With Straw'), ('🥢', '🥢 Chopsticks'), ('🛸', '🛸 Alien Plane'), ('🛷', '🛷 Sled'), ('🥌', '🥌 Curling'), ('🥰', '🥰 Smiling Face With 3 Hearts'), ('🥵', '🥵 Overheated'), ('🥶', '🥶 Freezing Face'), ('🥴', '🥴 Woozy Face'), ('🥳', '🥳 Party Face'), ('🥺', '🥺 Pleading Face'), ('🦵', '🦵 Leg'), ('🦶', '🦶 Foot'), ('🦷', '🦷 Tooth'), ('🦴', '🦴 Bone'), ('🦸', '🦸 Superhero'), ('🦹', '🦹 Supervillain'), ('🦝', '🦝 Trash Bandit'), ('🦙', '🦙 Llama'), ('🦛', '🦛 Hippopotamus'), ('🦘', '🦘 Kangaroo'), ('🦡', '🦡 Badger'), ('🦢', '🦢 Swan'), ('🦚', '🦚 Peacock'), ('🦜', '🦜 Parrot'), ('🦟', '🦟 Mosquito'), ('🦠', '🦠 Microbe'), ('🥭', '🥭 Mango'), ('🥬', '🥬 Leafy Green'), ('🥯', '🥯 Bagel'), ('🧂', '🧂 Salty'), ('🥮', '🥮 Moon Cake'), ('🦞', '🦞 Lobster'), ('🧁', '🧁 Cupcake'), ('🧭', '🧭 Compass'), ('🧱', '🧱 Brick'), ('🛹', '🛹 Skateboard'), ('🧳', '🧳 Baggage'), ('🧨', '🧨 Firework'), ('🧧', '🧧 Red Envelope'), ('🥎', '🥎 Softball'), ('🥏', '🥏 Throwing Disc'), ('🥍', '🥍 Lacrosse'), ('🧿', '🧿 Nazar Amulet'), ('🧩', '🧩 Puzzle Piece'), ('🧸', '🧸 Teddy Bear'), ('🧵', '🧵 Thread'), ('🧶', '🧶 Yarn Ball'), ('🥽', '🥽 The Goggles'), ('🥼', '🥼 Lab Coat'), ('🥾', '🥾 Hiking Boot'), ('🥿', '🥿 Flat Shoe'), ('🧮', '🧮 Abacus'), ('🧾', '🧾 Receipt'), ('🧰', '🧰 Toolbox'), ('🧲', '🧲 Magnet'), ('🧪', '🧪 Test Tube'), ('🧫', '🧫 Petri Dish'), ('🧬', '🧬 DNA'), ('🧴', '🧴 Lotion'), ('🧷', '🧷 Safety Pin'), ('🧹', '🧹 Broom'), ('🧺', '🧺 Basket'), ('🧻', '🧻 Roll of Paper'), ('🧼', '🧼 Soap'), ('🧽', '🧽 Fun sponge'), ('🧯', '🧯 Anti-fire Can'), ('🥱', '🥱 Yawning Face'), ('🤎', '🤎 Brown Heart'), ('🤍', '🤍 White Heart'), ('🤏', '🤏 Pinching Hand'), ('🦾', '🦾 Mechanical Arm'), ('🦿', '🦿 Mechanical Leg'), ('🦻', '🦻 Ear with Hearing Aid'), ('🧏', '🧏 Deaf Person'), ('🧍', '🧍 Person Standing'), ('🧎', '🧎 Person Kneeling'), ('🦧', '🦧 Orangutan'), ('🦮', '🦮 Guide Dog'), ('🦥', '🦥 Lazy Tree Dog'), ('🦦', '🦦 Water Dog'), ('🦨', '🦨 Stinky dog'), ('🦩', '🦩 Pink Dog'), ('🧄', '🧄 Garlic'), ('🧅', '🧅 Onion'), ('🧇', '🧇 Waffle'), ('🧆', '🧆 Falafel'), ('🧈', '🧈 Butter'), ('🦪', '🦪 Oyster'), ('🧃', '🧃 Beverage Box'), ('🧉', '🧉 Mate'), ('🧊', '🧊 Cold Cuboid'), ('🛕', '🛕 Hindu Temple'), ('🦽', '🦽 Manual Wheelchair'), ('🦼', '🦼 Motorized Wheelchair'), ('🛺', '🛺 Auto Rickshaw'), ('🪂', '🪂 Parachute'), ('🪐', '🪐 Ringed Planet'), ('🤿', '🤿 Diving Mask'), ('🪀', '🪀 Yo-Yo'), ('🪁', '🪁 Kite'), ('🦺', '🦺 Safety Vest'), ('🥻', '🥻 Sari'), ('🩱', '🩱 One-Piece Swimsuit'), ('🩲', '🩲 Briefs'), ('🩳', '🩳 Shorts'), ('🩰', '🩰 Ballet Shoes'), ('🪕', '🪕 Banjo'), ('🪔', '🪔 Diya Lamp'), ('🪓', '🪓 Axe'), ('🦯', '🦯 White Cane'), ('🩸', '🩸 Drop of Blood'), ('🩹', '🩹 Adhesive Bandage'), ('🩺', '🩺 Stethoscope'), ('🪑', '🪑 Chair'), ('🪒', '🪒 Razor'), ('🟠', '🟠 Orange Circle'), ('🟡', '🟡 Yellow Circle'), ('🟢', '🟢 Green Circle'), ('🟣', '🟣 Purple Circle'), ('🟤', '🟤 Brown Circle'), ('🟥', '🟥 Red Square'), ('🟧', '🟧 Orange Square'), ('🟨', '🟨 Yellow Square'), ('🟩', '🟩 Green Square'), ('🟦', '🟦 Blue Square'), ('🟪', '🟪 Purple Square'), ('🟫', '🟫 Brown Square'), ('🥲', '🥲 Smiling Face with Tear'), ('🥸', '🥸 Disguised Face'), ('🤌', '🤌 Pinched Fingers'), ('🫀', '🫀 Anatomical Heart'), ('🫁', '🫁 Lungs'), ('🥷', '🥷 Ninja'), ('🫂', '🫂 People Hugging'), ('🦬', '🦬 Bison'), ('🦣', '🦣 Mammoth'), ('🦫', '🦫 Beaver'), ('🦤', '🦤 Dodo'), ('🪶', '🪶 Feather'), ('🦭', '🦭 Seal'), ('🪲', '🪲 Beetle'), ('🪳', '🪳 Cockroach'), ('🪰', '🪰 Fly'), ('🪱', '🪱 Worm'), ('🪴', '🪴 Potted Plant'), ('🫐', '🫐 Blueberries'), ('🫒', '🫒 Olive'), ('🫑', '🫑 Bell Pepper'), ('🫓', '🫓 Flatbread'), ('🫔', '🫔 Tamale'), ('🫕', '🫕 Fondue'), ('🫖', '🫖 Teapot'), ('🧋', '🧋 Bubble Tea'), ('🪨', '🪨 Rock'), ('🪵', '🪵 Wood'), ('🛖', '🛖 Hut'), ('🛻', '🛻 Pickup Truck'), ('🛼', '🛼 Roller Skate'), ('🪄', '🪄 Magic Wand'), ('🪅', '🪅 Piñata'), ('🪆', '🪆 Nesting Dolls'), ('🪡', '🪡 Sewing Needle'), ('🪢', '🪢 Knot'), ('🩴', '🩴 Thong Sandal'), ('🪖', '🪖 Military Helmet'), ('🪗', '🪗 Accordion'), ('🪘', '🪘 Long Drum'), ('🪙', '🪙 Coin'), ('🪃', '🪃 Boomerang'), ('🪚', '🪚 Carpentry Saw'), ('🪛', '🪛 Screwdriver'), ('🪝', '🪝 Hook'), ('🪜', '🪜 Ladder'), ('🛗', '🛗 Elevator'), ('🪞', '🪞 Mirror'), ('🪟', '🪟 Window'), ('🪠', '🪠 Plunger'), ('🪤', '🪤 Mouse Trap'), ('🪣', '🪣 Bucket'), ('🪥', '🪥 Toothbrush'), ('🪦', '🪦 Headstone'), ('🪧', '🪧 Placard')], default=None, max_length=3, null=True, verbose_name='emoji'), + ), + migrations.AddConstraint( + model_name='speakercategory', + constraint=utils.models.UniqueConstraint(fields=('tournament', 'seq'), name='partici_speakercategory_tournament__seq_uniq'), + ), + migrations.AddConstraint( + model_name='speakercategory', + constraint=utils.models.UniqueConstraint(fields=('tournament', 'slug'), name='partici_speakercategory_tournament__slug_uniq'), + ), + migrations.AddConstraint( + model_name='team', + constraint=utils.models.UniqueConstraint(fields=('reference', 'institution', 'tournament'), name='partici_team_reference__institution__tournament_uniq'), + ), + migrations.AddConstraint( + model_name='team', + constraint=utils.models.UniqueConstraint(fields=('emoji', 'tournament'), name='partici_team_emoji__tournament_uniq'), + ), + ] diff --git a/tabbycat/participants/migrations/0023_alter_unique_together_and_team_emoji.py b/tabbycat/participants/migrations/0023_alter_unique_together_and_team_emoji.py new file mode 100644 index 00000000000..acc19597248 --- /dev/null +++ b/tabbycat/participants/migrations/0023_alter_unique_together_and_team_emoji.py @@ -0,0 +1,1701 @@ +# Generated by Django 5.0.4 on 2024-05-30 09:04 + +import utils.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "participants", + "0022_rename_team_tournament_institution_short_reference_participant_tournam_160efa_idx_and_more", + ), + ] + + operations = [ + migrations.AlterUniqueTogether( + name="institution", + unique_together=set(), + ), + migrations.AddConstraint( + model_name="institution", + constraint=utils.models.UniqueConstraint( + fields=("name", "code"), name="partici_institution_name__code_uniq" + ), + ), + migrations.AlterField( + model_name="team", + name="emoji", + field=models.CharField( + blank=True, + choices=[ + ("☺️", "☺️ White Smiling"), + ("☹", "☹ White Frowning"), + ("☝️", "☝️ White Up Pointing Index"), + ("✌️", "✌️ Victory Hand"), + ("✍", "✍ Writing Hand"), + ("❤️", "❤️ Heavy Black Heart"), + ("❣", "❣ Heart Exclamation Mark"), + ("☠", "☠ Skull and Crossbones"), + ("♨️", "♨️ Hot Springs"), + ("✈️", "✈️ Airplane"), + ("⌛", "⌛ Hourglass"), + ("⌚", "⌚ Watch"), + ("♈", "♈ Aries"), + ("♉", "♉ Taurus"), + ("♊", "♊ Gemini"), + ("♋", "♋ Cancer"), + ("♌", "♌ Leo"), + ("♍", "♍ Virgo"), + ("♎", "♎ Libra"), + ("♏", "♏ Scorpius"), + ("♐", "♐ Sagittarius"), + ("♑", "♑ Capricorn"), + ("♒", "♒ Aquarius"), + ("♓", "♓ Pisces"), + ("☀️", "☀️ Black Sun With Rays"), + ("☁️", "☁️ Cloud"), + ("☂", "☂ Umbrella"), + ("❄️", "❄️ Snowflake"), + ("☃", "☃ Snowman"), + ("☄️", "☄️ Comet"), + ("♠️", "♠️ Spade Suit"), + ("♥️", "♥️ Heart Suit"), + ("♦️", "♦️ Diamond Suit"), + ("♣️", "♣️ Club Suit"), + ("▶️", "▶️ Black Right-Pointing Triangle"), + ("◀️", "◀️ Black Left-Pointing Triangle"), + ("☎️", "☎️ Black Telephone"), + ("⌨", "⌨ Keyboard"), + ("✉️", "✉️ Envelope"), + ("✏️", "✏️ Pencil"), + ("✒️", "✒️ Black Nib"), + ("✂️", "✂️ Scissors"), + ("↗️", "↗️ North East Arrow"), + ("➡️", "➡️ Black Rightwards Arrow"), + ("↘️", "↘️ South East Arrow"), + ("↙️", "↙️ South West Arrow"), + ("↖️", "↖️ North West Arrow"), + ("↕️", "↕️ Up Down Arrow"), + ("↔️", "↔️ Left Right Arrow"), + ("↩️", "↩️ Leftwards Arrow With Hook"), + ("↪️", "↪️ Rightwards Arrow With Hook"), + ("✡", "✡ Star of David"), + ("☸", "☸ Wheel of Dharma"), + ("☯", "☯ Yin Yang"), + ("✝", "✝ Latin Cross"), + ("☦", "☦ Orthodox Cross"), + ("☪", "☪ Star and Crescent"), + ("☮", "☮ Peace Symbol"), + ("☢", "☢ Radioactive Sign"), + ("☣", "☣ Biohazard Sign"), + ("☑️", "☑️ Ballot Box With Check"), + ("✔️", "✔️ Heavy Check Mark"), + ("✖️", "✖️ Heavy Multiplication X"), + ("✳️", "✳️ Eight Spoked Asterisk"), + ("✴️", "✴️ Eight Pointed Black Star"), + ("❇️", "❇️ Sparkle"), + ("‼️", "‼️ Double Exclamation Mark"), + ("〰️", "〰️ Wavy Dash"), + ("©️", "©️ Copyright Sign"), + ("®️", "®️ Registered Sign"), + ("™️", "™️ Trade Mark Sign"), + ("Ⓜ️", "Ⓜ️ Capital M"), + ("㊗️", "㊗️ Congratulations"), + ("㊙️", "㊙️ Secret"), + ("▪️", "▪️ Black Square"), + ("▫️", "▫️ White Square"), + ("#⃣️", "#⃣️ Keycap Number Sign"), + ("*⃣", "*⃣ Keycap Asterisk"), + ("0⃣️", "0⃣️ Keycap Digit Zero"), + ("1⃣️", "1⃣️ Keycap Digit One"), + ("2⃣️", "2⃣️ Keycap Digit Two"), + ("3⃣️", "3⃣️ Keycap Digit Three"), + ("4⃣️", "4⃣️ Keycap Digit Four"), + ("5⃣️", "5⃣️ Keycap Digit Five"), + ("6⃣️", "6⃣️ Keycap Digit Six"), + ("7⃣️", "7⃣️ Keycap Digit Seven"), + ("8⃣️", "8⃣️ Keycap Digit Eight"), + ("9⃣️", "9⃣️ Keycap Digit Nine"), + ("⁉️", "⁉️ Exclamation Question Mark"), + ("ℹ️", "ℹ️ Information Source"), + ("⤴️", "⤴️ Right-Curve-Up"), + ("⤵️", "⤵️ Right-Curve-Down"), + ("♻️", "♻️ Recycling"), + ("〽️", "〽️ Part Alternation Mark"), + ("◻️", "◻️ White Medium Square"), + ("◼️", "◼️ Black Medium Square"), + ("◽", "◽ White Medium Small Square"), + ("◾", "◾ Black Medium Small Square"), + ("☕", "☕ Hot Beverage"), + ("⚠️", "⚠️ Warning Sign"), + ("☔", "☔ Umbrella With Rain Drops"), + ("⏏", "⏏ Eject Symbol"), + ("⬆️", "⬆️ Upwards Black Arrow"), + ("⬇️", "⬇️ Downwards Black Arrow"), + ("⬅️", "⬅️ Leftwards Black Arrow"), + ("⚡", "⚡ High Voltage"), + ("☘", "☘ Shamrock"), + ("⚓", "⚓ Anchor"), + ("♿", "♿ Wheelchair Symbol"), + ("⚒", "⚒ Hammer and Pick"), + ("⚙", "⚙ Gear"), + ("⚗", "⚗ Alembic"), + ("⚖", "⚖ Scales"), + ("⚔", "⚔ Crossed Swords"), + ("⚰", "⚰ Coffin"), + ("⚱", "⚱ Funeral Urn"), + ("⚜", "⚜ Fleur-De-Lis"), + ("⚛", "⚛ Atom Symbol"), + ("⚪", "⚪ Medium White Circle"), + ("⚫", "⚫ Medium Black Circle"), + ("🀄", "🀄 Mahjong Tile Red Dragon"), + ("⭐", "⭐ White Medium Star"), + ("⬛", "⬛ Black Square"), + ("⬜", "⬜ White Square"), + ("⛑", "⛑ Rescue Hat"), + ("⛰", "⛰ Mountain"), + ("⛪", "⛪ Church"), + ("⛲", "⛲ Fountain"), + ("⛺", "⛺ Tent"), + ("⛽", "⛽ Fuel Pump"), + ("⛵", "⛵ Sailboat"), + ("⛴", "⛴ Ferry"), + ("⛔", "⛔ No Entry"), + ("⛅", "⛅ Overcast"), + ("⛈", "⛈ Storm"), + ("⛱", "⛱ Umbrella"), + ("⛄", "⛄ Snowman"), + ("⚽", "⚽ Soccer"), + ("⚾", "⚾ Baseball"), + ("⛳", "⛳ Hole in One"), + ("⛸", "⛸ Ice Skate"), + ("⛷", "⛷ Skier"), + ("⛹", "⛹ Person With Ball"), + ("⛏", "⛏ Pick"), + ("⛓", "⛓ Chains"), + ("⛩", "⛩ Shinto Shrine"), + ("⭕", "⭕ Heavy Large Circle"), + ("❗", "❗ Heavy Exclamation Mark"), + ("🅿️", "🅿️ Squared P"), + ("🈯", "🈯 Squared 指 (Finger)"), + ("🈚", "🈚 Squared CJK Unified Ideograph-7121"), + ("😁", "😁 Smiling Eyes"), + ("😂", "😂 Joy Tears"), + ("😃", "😃 Smiling Face With Open Mouth"), + ("😄", "😄 Smiling Face With Open Mouth and Smiling Eyes"), + ("😅", "😅 Cold Sweat"), + ("😆", "😆 Closed Eyes"), + ("😉", "😉 Winky"), + ("😊", "😊 Smiling Eyes"), + ("😋", "😋 Face Savouring Delicious Food"), + ("😎", "😎 Shaded Eyes"), + ("😍", "😍 Heart Eyes"), + ("😘", "😘 Kissy"), + ("😚", "😚 Kissing Face With Closed Eyes"), + ("😇", "😇 Halo"), + ("😐", "😐 Neutral"), + ("😶", "😶 No Mouth"), + ("😏", "😏 Smirking"), + ("😣", "😣 Persevering"), + ("😥", "😥 Disappointed"), + ("😪", "😪 Sleepy"), + ("😫", "😫 Tired"), + ("😌", "😌 Relieved"), + ("😜", "😜 Tongue Out"), + ("😝", "😝 Tongue Out Closed Eyes"), + ("😒", "😒 Unamused"), + ("😓", "😓 Cold Sweat"), + ("😔", "😔 Pensive"), + ("😖", "😖 Confounded"), + ("😷", "😷 Medical Mask"), + ("😲", "😲 Astonished"), + ("😞", "😞 Disappointed"), + ("😤", "😤 Face With Look of Triumph"), + ("😢", "😢 Crying"), + ("😭", "😭 Sobbing"), + ("😨", "😨 Fearful"), + ("😩", "😩 Weary"), + ("😰", "😰 Open Mouth Cold Sweat"), + ("😱", "😱 Screaming"), + ("😳", "😳 Flushed"), + ("😵", "😵 Dizzy"), + ("😡", "😡 Pouting"), + ("😠", "😠 Angry"), + ("👿", "👿 Imp"), + ("😈", "😈 Smiling Face With Horns"), + ("👦", "👦 Boy"), + ("👧", "👧 Girl"), + ("👨", "👨 Generic Man"), + ("👩", "👩 Generic Woman"), + ("👴", "👴 Older Man"), + ("👵", "👵 Older Woman"), + ("👶", "👶 Baby"), + ("👱", "👱 Person With Blond Hair"), + ("👮", "👮 Police Officer"), + ("👲", "👲 Man With Gua Pi Mao"), + ("👳", "👳 Man With Turban"), + ("👷", "👷 Trade Worker"), + ("👸", "👸 Princess"), + ("💂", "💂 Guardsman"), + ("🎅", "🎅 Santa Claus"), + ("👼", "👼 Baby Angel"), + ("👯", "👯 Bunny Women"), + ("💆", "💆 Face Massage"), + ("💇", "💇 Haircut"), + ("👰", "👰 Bride"), + ("🙍", "🙍 Person Frowning"), + ("🙎", "🙎 Person With Pouting"), + ("🙅", "🙅 Block Gesture"), + ("🙆", "🙆 OK Gesture"), + ("💁", "💁 Sass Gesture"), + ("🙋", "🙋 Raised Hand"), + ("🙇", "🙇 Deep Bow"), + ("🙌", "🙌 Praise Hands"), + ("🙏", "🙏 Prayer Hands"), + ("👤", "👤 Bust in Silhouette"), + ("👥", "👥 Busts in Silhouette"), + ("🚶", "🚶 Pedestrian"), + ("🏃", "🏃 Runner"), + ("💃", "💃 Dancer"), + ("💏", "💏 Kiss"), + ("💑", "💑 Heteronormative Couple"), + ("👪", "👪 Hetero Family"), + ("👫", "👫 Man & Woman"), + ("👬", "👬 Two Men"), + ("👭", "👭 Two Women"), + ("💪", "💪 Biceps"), + ("👈", "👈 Left Pointing Backhand"), + ("👉", "👉 Right Pointing Backhand"), + ("👆", "👆 Pointing Hand"), + ("👇", "👇 Down Pointing Backhand"), + ("✊", "✊ Power Hand"), + ("✋", "✋ Palm Hand"), + ("👊", "👊 Fist Hand"), + ("👌", "👌 OK Hand"), + ("👍", "👍 Thumbs Up"), + ("👎", "👎 Thumbs Down"), + ("👋", "👋 Waving Hand Sign"), + ("👏", "👏 Clappy Hands"), + ("👐", "👐 Open Hands Sign"), + ("💅", "💅 Nail Polish"), + ("👣", "👣 Footprints"), + ("👀", "👀 Eyes"), + ("👂", "👂 Ear"), + ("👃", "👃 Nose"), + ("👅", "👅 Lick"), + ("👄", "👄 Mouth"), + ("💋", "💋 Kiss Mark"), + ("💘", "💘 Cupid Arrow"), + ("💓", "💓 Beating Heart"), + ("💔", "💔 Broken Heart"), + ("💕", "💕 Two Hearts"), + ("💖", "💖 Sparkly Heart"), + ("💗", "💗 Growing Heart"), + ("💙", "💙 Blue Heart"), + ("💚", "💚 Green Heart"), + ("💛", "💛 Yellow Heart"), + ("💜", "💜 Purple Heart"), + ("💝", "💝 Heart With Ribbon"), + ("💞", "💞 Revolving Hearts"), + ("💟", "💟 Heart Decoration"), + ("💌", "💌 Love Letter"), + ("💧", "💧 Droplet"), + ("💤", "💤 ZZZ"), + ("💢", "💢 Anger"), + ("💣", "💣 Bomb"), + ("💥", "💥 Sparks"), + ("💦", "💦 Splashing"), + ("💨", "💨 Dash"), + ("💫", "💫 Shooting Star"), + ("💬", "💬 Speech Bubble"), + ("💭", "💭 Thinky Cloud"), + ("👓", "👓 Eyeglasses"), + ("👔", "👔 Business Casual"), + ("👕", "👕 T-Shirt"), + ("👖", "👖 Jeans"), + ("👗", "👗 Dress"), + ("👘", "👘 Kimono"), + ("👙", "👙 Bikini"), + ("👚", "👚 Womans Clothes"), + ("👛", "👛 Purse"), + ("👜", "👜 Handbag"), + ("👝", "👝 Pouch"), + ("🎒", "🎒 Backpack"), + ("👞", "👞 Mans Shoe"), + ("👟", "👟 Running Shoe"), + ("👠", "👠 Heels"), + ("👡", "👡 Womans Sandal"), + ("👢", "👢 Womans Boots"), + ("👑", "👑 Crown"), + ("👒", "👒 Lady's Hat"), + ("🎩", "🎩 Top Hat"), + ("💄", "💄 Lipstick"), + ("💍", "💍 Proposal"), + ("💎", "💎 Gem"), + ("👹", "👹 Japanese Ogre"), + ("👺", "👺 Japanese Goblin"), + ("👻", "👻 Ghost"), + ("💀", "💀 Skull"), + ("👽", "👽 Alien"), + ("👾", "👾 Space Invader"), + ("💩", "💩 Pile of Poo"), + ("🐵", "🐵 Monkey"), + ("🙈", "🙈 See No Evil"), + ("🙉", "🙉 Hear No Evil"), + ("🙊", "🙊 Speak No Evil"), + ("🐒", "🐒 Monkey"), + ("🐶", "🐶 Dog"), + ("🐕", "🐕 Dog"), + ("🐩", "🐩 Poodle"), + ("🐺", "🐺 Wolf"), + ("🐱", "🐱 Cat"), + ("😸", "😸 Grinning Cat with Smiling Eyes"), + ("😹", "😹 Cat with Tears of Joy"), + ("😺", "😺 Smiling Cat with Open Mouth"), + ("😻", "😻 Smiling Cat with Heart Eyes"), + ("😼", "😼 Cat with Wry Smile"), + ("😽", "😽 Kissing Cat with Closed Eyes"), + ("😾", "😾 Pouting Cat Face"), + ("😿", "😿 Crying Cat Face"), + ("🙀", "🙀 Weary Cat Face"), + ("🐈", "🐈 Cat"), + ("🐯", "🐯 Tiger"), + ("🐅", "🐅 Tiger"), + ("🐆", "🐆 Leopard"), + ("🐴", "🐴 Horse"), + ("🐎", "🐎 Horse"), + ("🐮", "🐮 Cow"), + ("🐂", "🐂 Ox"), + ("🐃", "🐃 Water Buffalo"), + ("🐄", "🐄 Cow"), + ("🐷", "🐷 Pig"), + ("🐖", "🐖 Pig"), + ("🐗", "🐗 Boar"), + ("🐽", "🐽 Pig Nose"), + ("🐏", "🐏 Ram"), + ("🐑", "🐑 Sheep"), + ("🐐", "🐐 Goat"), + ("🐪", "🐪 Dromedary Camel"), + ("🐫", "🐫 Bactrian Camel"), + ("🐘", "🐘 Elephant"), + ("🐭", "🐭 Mouse"), + ("🐁", "🐁 Mouse"), + ("🐀", "🐀 Rat"), + ("🐹", "🐹 Hamster"), + ("🐰", "🐰 Rabbit"), + ("🐇", "🐇 Rabbit"), + ("🐻", "🐻 Bear"), + ("🐨", "🐨 Koala"), + ("🐼", "🐼 Panda"), + ("🐾", "🐾 Paw Prints"), + ("🐔", "🐔 Chicken"), + ("🐓", "🐓 Rooster"), + ("🐣", "🐣 Hatching"), + ("🐤", "🐤 Chick"), + ("🐥", "🐥 Front-Facing Baby Chick"), + ("🐦", "🐦 Bird"), + ("🐧", "🐧 Penguin"), + ("🐸", "🐸 Frog"), + ("🐊", "🐊 Croc"), + ("🐢", "🐢 Turtle"), + ("🐍", "🐍 Slithering"), + ("🐲", "🐲 Dragon"), + ("🐉", "🐉 Dragon"), + ("🐳", "🐳 Whale"), + ("🐋", "🐋 Whale"), + ("🐬", "🐬 Dolphin"), + ("🐟", "🐟 Fish"), + ("🐠", "🐠 Fish"), + ("🐡", "🐡 Blowfish"), + ("🐙", "🐙 Octopus"), + ("🐚", "🐚 Shell"), + ("🐌", "🐌 Snail"), + ("🐛", "🐛 Bug"), + ("🐜", "🐜 Ant"), + ("🐝", "🐝 Honeybee"), + ("🐞", "🐞 Lady Beetle"), + ("💐", "💐 Bouquet"), + ("🌸", "🌸 Sakura"), + ("💮", "💮 White Flower"), + ("🌹", "🌹 Rose"), + ("🌺", "🌺 Hibiscus"), + ("🌻", "🌻 Sunflower"), + ("🌼", "🌼 Blossom"), + ("🌷", "🌷 Tulip"), + ("🌱", "🌱 Seedling"), + ("🌲", "🌲 Evergreen Tree"), + ("🌳", "🌳 Deciduous Tree"), + ("🌴", "🌴 Palm Tree"), + ("🌵", "🌵 Cactus"), + ("🌾", "🌾 Ear of Rice"), + ("🌿", "🌿 Herb"), + ("🍀", "🍀 Clover"), + ("🍁", "🍁 Maple Leaf"), + ("🍂", "🍂 Fallen Leaf"), + ("🍃", "🍃 Blown Leaves"), + ("🍇", "🍇 Grapes"), + ("🍈", "🍈 Melon"), + ("🍉", "🍉 Watermelon"), + ("🍊", "🍊 Tangerine"), + ("🍋", "🍋 Lemon"), + ("🍌", "🍌 Banana"), + ("🍍", "🍍 Pineapple"), + ("🍎", "🍎 Red Apple"), + ("🍏", "🍏 Green Apple"), + ("🍐", "🍐 Pear"), + ("🍑", "🍑 Peach"), + ("🍒", "🍒 Cherries"), + ("🍓", "🍓 Strawberry"), + ("🍅", "🍅 Tomato"), + ("🍆", "🍆 Eggplant"), + ("🌽", "🌽 Corn"), + ("🍄", "🍄 Mushroom"), + ("🌰", "🌰 Chestnut"), + ("🍞", "🍞 Bread"), + ("🍖", "🍖 Meat on Bone"), + ("🍗", "🍗 Poultry Leg"), + ("🍔", "🍔 Hamburger"), + ("🍟", "🍟 Fries"), + ("🍕", "🍕 Pizza"), + ("🍲", "🍲 Pot of Food"), + ("🍱", "🍱 Bento Box"), + ("🍘", "🍘 Rice Cracker"), + ("🍙", "🍙 Rice Ball"), + ("🍚", "🍚 Cooked Rice"), + ("🍛", "🍛 Curry and Rice"), + ("🍜", "🍜 Steaming Bowl"), + ("🍝", "🍝 Spaghetti"), + ("🍠", "🍠 Sweet Potato"), + ("🍢", "🍢 Oden"), + ("🍣", "🍣 Sushi"), + ("🍤", "🍤 Fried Shrimp"), + ("🍥", "🍥 Fish Cake With Swirl Design"), + ("🍡", "🍡 Dango"), + ("🍦", "🍦 Ice Cream"), + ("🍧", "🍧 Shaved Ice"), + ("🍨", "🍨 Ice Cream"), + ("🍩", "🍩 Doughnut"), + ("🍪", "🍪 Cookie"), + ("🎂", "🎂 Birthday Cake"), + ("🍰", "🍰 Shortcake"), + ("🍫", "🍫 Chocolate Bar"), + ("🍬", "🍬 Candy"), + ("🍭", "🍭 Lollipop"), + ("🍮", "🍮 Custard"), + ("🍯", "🍯 Honey Pot"), + ("🍼", "🍼 Baby Bottle"), + ("🍵", "🍵 Teacup Without Handle"), + ("🍶", "🍶 Sake Bottle and Cup"), + ("🍷", "🍷 Wine Glass"), + ("🍸", "🍸 Cocktail Glass"), + ("🍹", "🍹 Tropical Drink"), + ("🍺", "🍺 Beer"), + ("🍻", "🍻 Clinking Beer Mugs"), + ("🍴", "🍴 Fork & Knife"), + ("🍳", "🍳 Cooking"), + ("🌍", "🌍 Earth Globe Europe-Africa"), + ("🌎", "🌎 Earth Globe Americas"), + ("🌏", "🌏 Earth Globe Asia-Australia"), + ("🌐", "🌐 Globe With Meridians"), + ("🌋", "🌋 Volcano"), + ("🗻", "🗻 Mount Fuji"), + ("🏠", "🏠 House"), + ("🏡", "🏡 House With Garden"), + ("🏢", "🏢 Office"), + ("🏣", "🏣 Japanese Post Office"), + ("🏤", "🏤 European Post Office"), + ("🏥", "🏥 Hospital"), + ("🏦", "🏦 Bank"), + ("🏨", "🏨 Hotel"), + ("🏩", "🏩 Love Hotel"), + ("🏪", "🏪 Convenience Store"), + ("🏫", "🏫 School"), + ("🏬", "🏬 Department Store"), + ("🏭", "🏭 Factory"), + ("🏯", "🏯 Japanese Castle"), + ("🏰", "🏰 Castle"), + ("💒", "💒 Wedding"), + ("🗼", "🗼 Tokyo Tower"), + ("🗽", "🗽 Liberty"), + ("🗾", "🗾 Silhouette of Japan"), + ("🌁", "🌁 Foggy"), + ("🌃", "🌃 Night With Stars"), + ("🌄", "🌄 Sunrise Over Mountains"), + ("🌅", "🌅 Sunrise"), + ("🌆", "🌆 Cityscape at Dusk"), + ("🌇", "🌇 Sunset Over Buildings"), + ("🌉", "🌉 Bridge at Night"), + ("🌊", "🌊 Big Wave"), + ("🗿", "🗿 Moyai"), + ("🌌", "🌌 Milky Way"), + ("🎠", "🎠 Carousel Horse"), + ("🎡", "🎡 Ferris Wheel"), + ("🎢", "🎢 Roller Coaster"), + ("💈", "💈 Barber Pole"), + ("🎪", "🎪 Circus Tent"), + ("🎭", "🎭 Performing Arts"), + ("🎨", "🎨 Palette"), + ("🎰", "🎰 Slot Machine"), + ("🚂", "🚂 Steam Locomotive"), + ("🚃", "🚃 Railcar"), + ("🚄", "🚄 Fast Train"), + ("🚅", "🚅 Fast Train with Bullet Nose"), + ("🚆", "🚆 Train"), + ("🚇", "🚇 Metro"), + ("🚈", "🚈 Light Rail"), + ("🚉", "🚉 Station"), + ("🚊", "🚊 Tram"), + ("🚝", "🚝 Monorail"), + ("🚞", "🚞 Mountain Railway"), + ("🚋", "🚋 Tram Car"), + ("🚌", "🚌 Bus"), + ("🚍", "🚍 Bus"), + ("🚎", "🚎 Trolleybus"), + ("🚏", "🚏 Bus Stop"), + ("🚐", "🚐 Minibus"), + ("🚑", "🚑 Ambulance"), + ("🚒", "🚒 Fire Engine"), + ("🚓", "🚓 Police Car"), + ("🚔", "🚔 Police Car"), + ("🚕", "🚕 Taxi"), + ("🚖", "🚖 Oncoming Taxi"), + ("🚗", "🚗 Automobile"), + ("🚘", "🚘 Automobile"), + ("🚙", "🚙 Recreational Vehicle"), + ("🚚", "🚚 Truck"), + ("🚛", "🚛 Articulated Lorry"), + ("🚜", "🚜 Tractor"), + ("🚲", "🚲 Bicycle"), + ("🚳", "🚳 No Bicycles"), + ("🚨", "🚨 Alert Light"), + ("🔱", "🔱 Trident"), + ("🚣", "🚣 Rowboat"), + ("🚤", "🚤 Speedboat"), + ("🚢", "🚢 Ship"), + ("💺", "💺 Seat"), + ("🚁", "🚁 Helicopter"), + ("🚟", "🚟 Suspension Railway"), + ("🚠", "🚠 Sky Tram"), + ("🚡", "🚡 Aerial Tramway"), + ("🚀", "🚀 Rocket"), + ("🏧", "🏧 ATM"), + ("🚮", "🚮 Put Litter in Its Place"), + ("🚥", "🚥 Horizontal Traffic Light"), + ("🚦", "🚦 Traffic Light"), + ("🚧", "🚧 Hazard Sign"), + ("🚫", "🚫 Prohibited"), + ("🚭", "🚭 No Smoking"), + ("🚯", "🚯 Do Not Litter"), + ("🚰", "🚰 Tap Water"), + ("🚱", "🚱 Non-Potable Water"), + ("🚷", "🚷 No Pedestrians"), + ("🚸", "🚸 Children Crossing"), + ("🚹", "🚹 Mens Symbol"), + ("🚺", "🚺 Womens Symbol"), + ("🚻", "🚻 Restroom"), + ("🚼", "🚼 Baby Symbol"), + ("🚾", "🚾 Water Closet"), + ("🛂", "🛂 Passport Control"), + ("🛃", "🛃 Customs"), + ("🛄", "🛄 Baggage Claim"), + ("🛅", "🛅 Left Luggage"), + ("🚪", "🚪 Door"), + ("🚽", "🚽 Toilet"), + ("🚿", "🚿 Shower"), + ("🛀", "🛀 Bath"), + ("🛁", "🛁 Bathtub"), + ("⏳", "⏳ Hourglass"), + ("⏰", "⏰ Alarm Clock"), + ("⏱", "⏱ Stopwatch"), + ("⏲", "⏲ Timer Clock"), + ("🕛", "🕛 Twelve O'Clock"), + ("🕧", "🕧 Half Past Twelve"), + ("🕐", "🕐 One O'Clock"), + ("🕜", "🕜 Half Past One"), + ("🕑", "🕑 Two O'Clock"), + ("🕝", "🕝 Half Past Two"), + ("🕒", "🕒 Three O'Clock"), + ("🕞", "🕞 Half Past Three"), + ("🕓", "🕓 Four O'Clock"), + ("🕟", "🕟 Half Past Four"), + ("🕔", "🕔 Five O'Clock"), + ("🕠", "🕠 Half Past Five"), + ("🕕", "🕕 Six O'Clock"), + ("🕡", "🕡 Half Past Six"), + ("🕖", "🕖 Seven O'Clock"), + ("🕢", "🕢 Half Past Seven"), + ("🕗", "🕗 Eight O'Clock"), + ("🕣", "🕣 Half Past Eight"), + ("🕘", "🕘 Nine O'Clock"), + ("🕤", "🕤 Half Past Nine"), + ("🕙", "🕙 Ten O'Clock"), + ("🕥", "🕥 Half Past Ten"), + ("🕚", "🕚 Eleven O'Clock"), + ("🕦", "🕦 Half Past Eleven"), + ("⛎", "⛎ Ophiuchus"), + ("🌑", "🌑 New Moon"), + ("🌒", "🌒 Waxing Crescent"), + ("🌓", "🌓 First Quarter Moon Symbol"), + ("🌔", "🌔 Waxing Gibbous"), + ("🌕", "🌕 Full Moon"), + ("🌖", "🌖 Waning Gibbous"), + ("🌗", "🌗 Half Moon"), + ("🌘", "🌘 Waning Crescent"), + ("🌙", "🌙 Crescent Moon"), + ("🌚", "🌚 New Moon With Face"), + ("🌛", "🌛 First Quarter Moon With Face"), + ("🌜", "🌜 Last Quarter Moon With Face"), + ("🌝", "🌝 Full Moon With Face"), + ("🌞", "🌞 Sun"), + ("🌀", "🌀 Cyclone"), + ("🌈", "🌈 Rainbow"), + ("🌂", "🌂 Umbrella"), + ("🌟", "🌟 Glowing Star"), + ("🌠", "🌠 Shooting Star"), + ("🔥", "🔥 Fire"), + ("🎃", "🎃 Jack-O-Lantern"), + ("🎄", "🎄 Presents Tree"), + ("🎆", "🎆 Fireworks"), + ("🎇", "🎇 Firework Sparkler"), + ("✨", "✨ Sparkles"), + ("🎈", "🎈 Balloon"), + ("🎉", "🎉 Party Pop"), + ("🎊", "🎊 Confetti Ball"), + ("🎋", "🎋 Tanabata Tree"), + ("🎌", "🎌 Crossed Flags"), + ("🎍", "🎍 Pine Decoration"), + ("🎎", "🎎 Japanese Dolls"), + ("🎏", "🎏 Carp Streamer"), + ("🎐", "🎐 Wind Chime"), + ("🎑", "🎑 Moon Viewing Ceremony"), + ("🎓", "🎓 Grad Cap"), + ("🎯", "🎯 Bullseye"), + ("🎴", "🎴 Flower Playing Cards"), + ("🎀", "🎀 Ribbon"), + ("🎁", "🎁 Wrapped Present"), + ("🎫", "🎫 Ticket"), + ("🏀", "🏀 Basketball"), + ("🏈", "🏈 America Ball"), + ("🏉", "🏉 Rugby Ball"), + ("🎾", "🎾 Tennis"), + ("🎱", "🎱 Billiards"), + ("🎳", "🎳 Bowling"), + ("🎣", "🎣 Fishing Pole and Fish"), + ("🎽", "🎽 Running Shirt With Sash"), + ("🎿", "🎿 Ski and Ski Boot"), + ("🏂", "🏂 Snowboarder"), + ("🏄", "🏄 Surfer"), + ("🏇", "🏇 Horse Racing"), + ("🏊", "🏊 Swimmer"), + ("🚴", "🚴 Bicyclist"), + ("🚵", "🚵 Mountain Bicyclist"), + ("🏆", "🏆 Trophy"), + ("🎮", "🎮 Video Game"), + ("🎲", "🎲 Random Cube"), + ("🃏", "🃏 Playing Card Black Joker"), + ("🔇", "🔇 Speaker With Cancellation Stroke"), + ("🔈", "🔈 Speaker"), + ("🔉", "🔉 Speaker With One Sound Wave"), + ("🔊", "🔊 Speaker With Three Sound Waves"), + ("📢", "📢 Public Address Loudspeaker"), + ("📣", "📣 Loud Phone"), + ("📯", "📯 Horn"), + ("🔔", "🔔 Bell"), + ("🔕", "🔕 No Bells"), + ("🔀", "🔀 Shuffle"), + ("🔁", "🔁 Repeat"), + ("🔂", "🔂 Repeat Once"), + ("⏩", "⏩ Fast Forward"), + ("⏭", "⏭ Next Track"), + ("⏯", "⏯ Play/Pause"), + ("⏪", "⏪ Rewind"), + ("⏮", "⏮ Previous Track"), + ("🔼", "🔼 Up-Pointing Small Red Triangle"), + ("⏫", "⏫ Up to Top"), + ("🔽", "🔽 Down-Pointing Small Red Triangle"), + ("⏬", "⏬ Down to Bottom"), + ("🎼", "🎼 Musical Score"), + ("🎵", "🎵 Musical Note"), + ("🎶", "🎶 Music Notes"), + ("🎤", "🎤 Microphone"), + ("🎧", "🎧 Headphone"), + ("🎷", "🎷 Saxophone"), + ("🎸", "🎸 Guitar"), + ("🎹", "🎹 Keyboard"), + ("🎺", "🎺 Trumpet"), + ("🎻", "🎻 Violin"), + ("📻", "📻 Boom Box"), + ("📱", "📱 Internet Phone"), + ("📳", "📳 Vibration Mode"), + ("📴", "📴 Mobile Phone Off"), + ("📲", "📲 Download to Phone"), + ("📵", "📵 No Mobile Phones"), + ("📞", "📞 Old Phone"), + ("🔟", "🔟 Keycap Ten"), + ("📶", "📶 Antenna With Bars"), + ("📟", "📟 Pager"), + ("📠", "📠 Fax Machine"), + ("🔋", "🔋 Battery"), + ("🔌", "🔌 Plug"), + ("💻", "💻 Personal Computer"), + ("💽", "💽 Minidisc"), + ("💾", "💾 Floppy"), + ("💿", "💿 Compact Disc"), + ("📀", "📀 DVD"), + ("🎥", "🎥 Movie Camera"), + ("🎦", "🎦 Cinema"), + ("🎬", "🎬 Clapper"), + ("📺", "📺 Television"), + ("📷", "📷 Camera"), + ("📹", "📹 Video Camera"), + ("📼", "📼 Videocassette"), + ("🔅", "🔅 Low Brightness Symbol"), + ("🔆", "🔆 High Brightness Symbol"), + ("🔍", "🔍 Bigger Glass"), + ("🔎", "🔎 Right-Pointing Magnifying Glass"), + ("🔬", "🔬 Microscope"), + ("🔭", "🔭 Telescope"), + ("📡", "📡 Satellite Dish"), + ("💡", "💡 Light Bulb"), + ("🔦", "🔦 Electric Torch"), + ("🏮", "🏮 Izakaya Lantern"), + ("📔", "📔 Notebook With Decorative Cover"), + ("📕", "📕 Closed Book"), + ("📖", "📖 Open Book"), + ("📗", "📗 Green Book"), + ("📘", "📘 Blue Book"), + ("📙", "📙 Orange Book"), + ("📚", "📚 Books"), + ("📓", "📓 Notebook"), + ("📒", "📒 Ledger"), + ("📃", "📃 Page With Curl"), + ("📜", "📜 Scroll"), + ("📄", "📄 Page Facing Up"), + ("📰", "📰 Newspaper"), + ("📑", "📑 Bookmark Tabs"), + ("🔖", "🔖 Bookmark"), + ("💰", "💰 Money Bag"), + ("💴", "💴 Banknote With Yen Sign"), + ("💵", "💵 Banknote With Dollar Sign"), + ("💶", "💶 Banknote With Euro Sign"), + ("💷", "💷 Banknote With Pound Sign"), + ("💸", "💸 Flying Money"), + ("💱", "💱 Currency Exchange"), + ("💲", "💲 Heavy Dollar Sign"), + ("💳", "💳 Credit Card"), + ("💹", "💹 Upwards Trend in Yen"), + ("📧", "📧 E-Mail Symbol"), + ("📨", "📨 Incoming Envelope"), + ("📩", "📩 Going Into Envelope"), + ("📤", "📤 Outbox Tray"), + ("📥", "📥 Inbox Tray"), + ("📦", "📦 Package"), + ("📫", "📫 Mailbox"), + ("📪", "📪 Closed Mailbox With Lowered Flag"), + ("📬", "📬 Open Mailbox With Raised Flag"), + ("📭", "📭 Open Mailbox With Lowered Flag"), + ("📮", "📮 Postbox"), + ("📝", "📝 Memo"), + ("💼", "💼 Briefcase"), + ("📁", "📁 File Folder"), + ("📂", "📂 Open File Folder"), + ("📅", "📅 Dated"), + ("📆", "📆 Tear-Off Calendar"), + ("📇", "📇 Card Index"), + ("📈", "📈 Up Trend"), + ("📉", "📉 Down Trend"), + ("📊", "📊 Bar Chart"), + ("📋", "📋 Clipboard"), + ("📌", "📌 Pushpin"), + ("📍", "📍 Location"), + ("📎", "📎 Paperclip"), + ("📏", "📏 Straight Line"), + ("📐", "📐 Three Sides"), + ("📛", "📛 Name Badge"), + ("🔒", "🔒 Lock"), + ("🔓", "🔓 Open Lock"), + ("🔏", "🔏 Lock With Ink Pen"), + ("🔐", "🔐 Closed Lock With Key"), + ("🔑", "🔑 Key"), + ("🔨", "🔨 Hammer"), + ("🔧", "🔧 Spanner"), + ("🔩", "🔩 Calipers"), + ("🔗", "🔗 Link Symbol"), + ("💉", "💉 Syringe"), + ("💊", "💊 Pill"), + ("🔪", "🔪 Chef Knife"), + ("🔫", "🔫 Pistol"), + ("🚬", "🚬 Durry"), + ("🏁", "🏁 Get Set Go"), + ("🚩", "🚩 Triangular Flag on Post"), + ("🇦🇫", "🇦🇫 Afghanistan"), + ("🇦🇽", "🇦🇽 Åland Islands"), + ("🇦🇱", "🇦🇱 Albania"), + ("🇩🇿", "🇩🇿 Algeria"), + ("🇦🇸", "🇦🇸 American Samoa"), + ("🇦🇩", "🇦🇩 Andorra"), + ("🇦🇴", "🇦🇴 Angola"), + ("🇦🇮", "🇦🇮 Anguilla"), + ("🇦🇶", "🇦🇶 Antarctica"), + ("🇦🇬", "🇦🇬 Antigua & Barbuda"), + ("🇦🇷", "🇦🇷 Argentina"), + ("🇦🇲", "🇦🇲 Armenia"), + ("🇦🇼", "🇦🇼 Aruba"), + ("🇦🇨", "🇦🇨 Ascension Island"), + ("🇦🇺", "🇦🇺 Australia"), + ("🇦🇹", "🇦🇹 Austria"), + ("🇦🇿", "🇦🇿 Azerbaijan"), + ("🇧🇸", "🇧🇸 Bahamas"), + ("🇧🇭", "🇧🇭 Bahrain"), + ("🇧🇩", "🇧🇩 Bangladesh"), + ("🇧🇧", "🇧🇧 Barbados"), + ("🇧🇾", "🇧🇾 Belarus"), + ("🇧🇪", "🇧🇪 Belgium"), + ("🇧🇿", "🇧🇿 Belize"), + ("🇧🇯", "🇧🇯 Benin"), + ("🇧🇲", "🇧🇲 Bermuda"), + ("🇧🇹", "🇧🇹 Bhutan"), + ("🇧🇴", "🇧🇴 Bolivia"), + ("🇧🇦", "🇧🇦 Bosnia & Herzegovina"), + ("🇧🇼", "🇧🇼 Botswana"), + ("🇧🇻", "🇧🇻 Bouvet Island"), + ("🇧🇷", "🇧🇷 Brazil"), + ("🇮🇴", "🇮🇴 British Indian Ocean Territory"), + ("🇻🇬", "🇻🇬 British Virgin Islands"), + ("🇧🇳", "🇧🇳 Brunei"), + ("🇧🇬", "🇧🇬 Bulgaria"), + ("🇧🇫", "🇧🇫 Burkina Faso"), + ("🇧🇮", "🇧🇮 Burundi"), + ("🇰🇭", "🇰🇭 Cambodia"), + ("🇨🇲", "🇨🇲 Cameroon"), + ("🇨🇦", "🇨🇦 Canada"), + ("🇮🇨", "🇮🇨 Canary Islands"), + ("🇨🇻", "🇨🇻 Cape Verde"), + ("🇧🇶", "🇧🇶 Caribbean Netherlands"), + ("🇰🇾", "🇰🇾 Cayman Islands"), + ("🇨🇫", "🇨🇫 Central African Republic"), + ("🇪🇦", "🇪🇦 Ceuta & Melilla"), + ("🇹🇩", "🇹🇩 Chad"), + ("🇨🇱", "🇨🇱 Chile"), + ("🇨🇳", "🇨🇳 China"), + ("🇨🇽", "🇨🇽 Christmas Island"), + ("🇨🇵", "🇨🇵 Clipperton Island"), + ("🇨🇨", "🇨🇨 Cocos Islands"), + ("🇨🇴", "🇨🇴 Colombia"), + ("🇰🇲", "🇰🇲 Comoros"), + ("🇨🇬", "🇨🇬 Congo - Brazzaville"), + ("🇨🇩", "🇨🇩 Congo - Kinshasa"), + ("🇨🇰", "🇨🇰 Cook Islands"), + ("🇨🇷", "🇨🇷 Costa Rica"), + ("🇨🇮", "🇨🇮 Côte D’Ivoire"), + ("🇭🇷", "🇭🇷 Croatia"), + ("🇨🇺", "🇨🇺 Cuba"), + ("🇨🇼", "🇨🇼 Curaçao"), + ("🇨🇾", "🇨🇾 Cyprus"), + ("🇨🇿", "🇨🇿 Czech Republic"), + ("🇩🇰", "🇩🇰 Denmark"), + ("🇩🇬", "🇩🇬 Diego Garcia"), + ("🇩🇯", "🇩🇯 Djibouti"), + ("🇩🇲", "🇩🇲 Dominica"), + ("🇩🇴", "🇩🇴 Dominican Republic"), + ("🇪🇨", "🇪🇨 Ecuador"), + ("🇪🇬", "🇪🇬 Egypt"), + ("🇸🇻", "🇸🇻 El Salvador"), + ("🇬🇶", "🇬🇶 Equatorial Guinea"), + ("🇪🇷", "🇪🇷 Eritrea"), + ("🇪🇪", "🇪🇪 Estonia"), + ("🇪🇹", "🇪🇹 Ethiopia"), + ("🇪🇺", "🇪🇺 European Union"), + ("🇫🇰", "🇫🇰 Falkland Islands"), + ("🇫🇴", "🇫🇴 Faroe Islands"), + ("🇫🇯", "🇫🇯 Fiji"), + ("🇫🇮", "🇫🇮 Finland"), + ("🇫🇷", "🇫🇷 France"), + ("🇬🇫", "🇬🇫 French Guiana"), + ("🇵🇫", "🇵🇫 French Polynesia"), + ("🇹🇫", "🇹🇫 French Southern Territories"), + ("🇬🇦", "🇬🇦 Gabon"), + ("🇬🇲", "🇬🇲 Gambia"), + ("🇬🇪", "🇬🇪 Georgia"), + ("🇩🇪", "🇩🇪 Germany"), + ("🇬🇭", "🇬🇭 Ghana"), + ("🇬🇮", "🇬🇮 Gibraltar"), + ("🇬🇷", "🇬🇷 Greece"), + ("🇬🇱", "🇬🇱 Greenland"), + ("🇬🇩", "🇬🇩 Grenada"), + ("🇬🇵", "🇬🇵 Guadeloupe"), + ("🇬🇺", "🇬🇺 Guam"), + ("🇬🇹", "🇬🇹 Guatemala"), + ("🇬🇬", "🇬🇬 Guernsey"), + ("🇬🇳", "🇬🇳 Guinea"), + ("🇬🇼", "🇬🇼 Guinea-Bissau"), + ("🇬🇾", "🇬🇾 Guyana"), + ("🇭🇹", "🇭🇹 Haiti"), + ("🇭🇲", "🇭🇲 Heard & McDonald Islands"), + ("🇭🇳", "🇭🇳 Honduras"), + ("🇭🇰", "🇭🇰 Hong Kong"), + ("🇭🇺", "🇭🇺 Hungary"), + ("🇮🇸", "🇮🇸 Iceland"), + ("🇮🇳", "🇮🇳 India"), + ("🇮🇩", "🇮🇩 Indonesia"), + ("🇮🇷", "🇮🇷 Iran"), + ("🇮🇶", "🇮🇶 Iraq"), + ("🇮🇪", "🇮🇪 Ireland"), + ("🇮🇲", "🇮🇲 Isle of Man"), + ("🇮🇱", "🇮🇱 Israel"), + ("🇮🇹", "🇮🇹 Italy"), + ("🇯🇲", "🇯🇲 Jamaica"), + ("🇯🇵", "🇯🇵 Japan"), + ("🇯🇪", "🇯🇪 Jersey"), + ("🇯🇴", "🇯🇴 Jordan"), + ("🇰🇿", "🇰🇿 Kazakhstan"), + ("🇰🇪", "🇰🇪 Kenya"), + ("🇰🇮", "🇰🇮 Kiribati"), + ("🇽🇰", "🇽🇰 Kosovo"), + ("🇰🇼", "🇰🇼 Kuwait"), + ("🇰🇬", "🇰🇬 Kyrgyzstan"), + ("🇱🇦", "🇱🇦 Laos"), + ("🇱🇻", "🇱🇻 Latvia"), + ("🇱🇧", "🇱🇧 Lebanon"), + ("🇱🇸", "🇱🇸 Lesotho"), + ("🇱🇷", "🇱🇷 Liberia"), + ("🇱🇾", "🇱🇾 Libya"), + ("🇱🇮", "🇱🇮 Liechtenstein"), + ("🇱🇹", "🇱🇹 Lithuania"), + ("🇱🇺", "🇱🇺 Luxembourg"), + ("🇲🇴", "🇲🇴 Macau"), + ("🇲🇰", "🇲🇰 Macedonia"), + ("🇲🇬", "🇲🇬 Madagascar"), + ("🇲🇼", "🇲🇼 Malawi"), + ("🇲🇾", "🇲🇾 Malaysia"), + ("🇲🇻", "🇲🇻 Maldives"), + ("🇲🇱", "🇲🇱 Mali"), + ("🇲🇹", "🇲🇹 Malta"), + ("🇲🇭", "🇲🇭 Marshall Islands"), + ("🇲🇶", "🇲🇶 Martinique"), + ("🇲🇷", "🇲🇷 Mauritania"), + ("🇲🇺", "🇲🇺 Mauritius"), + ("🇾🇹", "🇾🇹 Mayotte"), + ("🇲🇽", "🇲🇽 Mexico"), + ("🇫🇲", "🇫🇲 Micronesia"), + ("🇲🇩", "🇲🇩 Moldova"), + ("🇲🇨", "🇲🇨 Monaco"), + ("🇲🇳", "🇲🇳 Mongolia"), + ("🇲🇪", "🇲🇪 Montenegro"), + ("🇲🇸", "🇲🇸 Montserrat"), + ("🇲🇦", "🇲🇦 Morocco"), + ("🇲🇿", "🇲🇿 Mozambique"), + ("🇲🇲", "🇲🇲 Myanmar"), + ("🇳🇦", "🇳🇦 Namibia"), + ("🇳🇷", "🇳🇷 Nauru"), + ("🇳🇵", "🇳🇵 Nepal"), + ("🇳🇱", "🇳🇱 Netherlands"), + ("🇳🇨", "🇳🇨 New Caledonia"), + ("🇳🇿", "🇳🇿 New Zealand"), + ("🇳🇮", "🇳🇮 Nicaragua"), + ("🇳🇪", "🇳🇪 Niger"), + ("🇳🇬", "🇳🇬 Nigeria"), + ("🇳🇺", "🇳🇺 Niue"), + ("🇳🇫", "🇳🇫 Norfolk Island"), + ("🇲🇵", "🇲🇵 Northern Mariana Islands"), + ("🇰🇵", "🇰🇵 North Korea"), + ("🇳🇴", "🇳🇴 Norway"), + ("🇴🇲", "🇴🇲 Oman"), + ("🇵🇰", "🇵🇰 Pakistan"), + ("🇵🇼", "🇵🇼 Palau"), + ("🇵🇸", "🇵🇸 Palestinian Territories"), + ("🇵🇦", "🇵🇦 Panama"), + ("🇵🇬", "🇵🇬 Papua New Guinea"), + ("🇵🇾", "🇵🇾 Paraguay"), + ("🇵🇪", "🇵🇪 Peru"), + ("🇵🇭", "🇵🇭 Philippines"), + ("🇵🇳", "🇵🇳 Pitcairn Islands"), + ("🇵🇱", "🇵🇱 Poland"), + ("🇵🇹", "🇵🇹 Portugal"), + ("🇵🇷", "🇵🇷 Puerto Rico"), + ("🇶🇦", "🇶🇦 Qatar"), + ("🇷🇪", "🇷🇪 Réunion"), + ("🇷🇴", "🇷🇴 Romania"), + ("🇷🇺", "🇷🇺 Russia"), + ("🇷🇼", "🇷🇼 Rwanda"), + ("🇼🇸", "🇼🇸 Samoa"), + ("🇸🇲", "🇸🇲 San Marino"), + ("🇸🇹", "🇸🇹 São Tomé & Príncipe"), + ("🇸🇦", "🇸🇦 Saudi Arabia"), + ("🇸🇳", "🇸🇳 Senegal"), + ("🇷🇸", "🇷🇸 Serbia"), + ("🇸🇨", "🇸🇨 Seychelles"), + ("🇸🇱", "🇸🇱 Sierra Leone"), + ("🇸🇬", "🇸🇬 Singapore"), + ("🇸🇽", "🇸🇽 Sint Maarten"), + ("🇸🇰", "🇸🇰 Slovakia"), + ("🇸🇮", "🇸🇮 Slovenia"), + ("🇸🇧", "🇸🇧 Solomon Islands"), + ("🇸🇴", "🇸🇴 Somalia"), + ("🇿🇦", "🇿🇦 South Africa"), + ("🇬🇸", "🇬🇸 South Georgia & South Sandwich Islands"), + ("🇰🇷", "🇰🇷 South Korea"), + ("🇸🇸", "🇸🇸 South Sudan"), + ("🇪🇸", "🇪🇸 Spain"), + ("🇱🇰", "🇱🇰 Sri Lanka"), + ("🇧🇱", "🇧🇱 St. Barthélemy"), + ("🇸🇭", "🇸🇭 St. Helena"), + ("🇰🇳", "🇰🇳 St. Kitts & Nevis"), + ("🇱🇨", "🇱🇨 St. Lucia"), + ("🇲🇫", "🇲🇫 St. Martin"), + ("🇵🇲", "🇵🇲 St. Pierre & Miquelon"), + ("🇻🇨", "🇻🇨 St. Vincent & Grenadines"), + ("🇸🇩", "🇸🇩 Sudan"), + ("🇸🇷", "🇸🇷 Suriname"), + ("🇸🇯", "🇸🇯 Svalbard & Jan Mayen"), + ("🇸🇿", "🇸🇿 Swaziland"), + ("🇸🇪", "🇸🇪 Sweden"), + ("🇨🇭", "🇨🇭 Switzerland"), + ("🇸🇾", "🇸🇾 Syria"), + ("🇹🇼", "🇹🇼 Taiwan"), + ("🇹🇯", "🇹🇯 Tajikistan"), + ("🇹🇿", "🇹🇿 Tanzania"), + ("🇹🇭", "🇹🇭 Thailand"), + ("🇹🇱", "🇹🇱 Timor-Leste"), + ("🇹🇬", "🇹🇬 Togo"), + ("🇹🇰", "🇹🇰 Tokelau"), + ("🇹🇴", "🇹🇴 Tonga"), + ("🇹🇹", "🇹🇹 Trinidad & Tobago"), + ("🇹🇦", "🇹🇦 Tristan Da Cunha"), + ("🇹🇳", "🇹🇳 Tunisia"), + ("🇹🇷", "🇹🇷 Turkey"), + ("🇹🇲", "🇹🇲 Turkmenistan"), + ("🇹🇨", "🇹🇨 Turks & Caicos Islands"), + ("🇹🇻", "🇹🇻 Tuvalu"), + ("🇺🇬", "🇺🇬 Uganda"), + ("🇺🇦", "🇺🇦 Ukraine"), + ("🇦🇪", "🇦🇪 United Arab Emirates"), + ("🇬🇧", "🇬🇧 United Kingdom"), + ("🇺🇸", "🇺🇸 United States"), + ("🇺🇾", "🇺🇾 Uruguay"), + ("🇺🇲", "🇺🇲 U.S. Outlying Islands"), + ("🇻🇮", "🇻🇮 U.S. Virgin Islands"), + ("🇺🇿", "🇺🇿 Uzbekistan"), + ("🇻🇺", "🇻🇺 Vanuatu"), + ("🇻🇦", "🇻🇦 Vatican City"), + ("🇻🇪", "🇻🇪 Venezuela"), + ("🇻🇳", "🇻🇳 Vietnam"), + ("🇼🇫", "🇼🇫 Wallis & Futuna"), + ("🇪🇭", "🇪🇭 Western Sahara"), + ("🇾🇪", "🇾🇪 Yemen"), + ("🇿🇲", "🇿🇲 Zambia"), + ("🇿🇼", "🇿🇼 Zimbabwe"), + ("🔃", "🔃 Clockwise Arrows"), + ("🔄", "🔄 Anticlockwise Arrows"), + ("🔙", "🔙 Back"), + ("🔚", "🔚 End"), + ("🔛", "🔛 On"), + ("🔜", "🔜 Soon"), + ("🔝", "🔝 Top"), + ("🔰", "🔰 Beginner"), + ("🔮", "🔮 Crystal Ball"), + ("🔯", "🔯 Six Pointed Star With Middle Dot"), + ("✅", "✅ White Heavy Check Mark"), + ("❌", "❌ Cross"), + ("❎", "❎ Negative Squared Cross Mark"), + ("➕", "➕ Heavy Plus Sign"), + ("➖", "➖ Heavy Minus Sign"), + ("➗", "➗ Heavy Division Sign"), + ("➰", "➰ Curly Loop"), + ("➿", "➿ Double Curly Loop"), + ("❓", "❓ Question"), + ("❔", "❔ White Question Mark Ornament"), + ("❕", "❕ White Exclamation Mark Ornament"), + ("💯", "💯 Hundred Points"), + ("🔞", "🔞 Over Eighteen"), + ("🔠", "🔠 Latin Capital Letters"), + ("🔡", "🔡 Latin Small Letters"), + ("🔢", "🔢 Numbers"), + ("🔣", "🔣 Symbols"), + ("🔤", "🔤 Latin Letters"), + ("🅰️", "🅰️ Squared A"), + ("🆎", "🆎 Squared AB"), + ("🅱️", "🅱️ Squared B"), + ("🆑", "🆑 Squared CL"), + ("🆒", "🆒 Cool Square"), + ("🆓", "🆓 Squared Free"), + ("🆔", "🆔 Squared ID"), + ("🆕", "🆕 New Square"), + ("🆖", "🆖 Squared NG"), + ("🅾️", "🅾️ Squared O"), + ("🆗", "🆗 OK Square"), + ("🆘", "🆘 SOS Square"), + ("🆙", "🆙 Squared Up!"), + ("🆚", "🆚 Squared Vs"), + ("🈁", "🈁 Squared Katakana Koko"), + ("🈂️", "🈂️ Squared Katakana Sa"), + ("🈷️", "🈷️ Squared 月 (Moon)"), + ("🈶", "🈶 Squared 有 (Have)"), + ("🉐", "🉐 Circled Ideograph Advantage"), + ("🈹", "🈹 Squared CJK Unified Ideograph-5272"), + ("🈲", "🈲 Squared CJK Unified Ideograph-7981"), + ("🉑", "🉑 Circled 可 (Accept)"), + ("🈸", "🈸 Squared CJK Unified Ideograph-7533"), + ("🈴", "🈴 Squared CJK Unified Ideograph-5408"), + ("🈳", "🈳 Squared CJK Unified Ideograph-7a7a"), + ("🈺", "🈺 Squared CJK Unified Ideograph-55b6"), + ("🈵", "🈵 Squared CJK Unified Ideograph-6e80"), + ("🔶", "🔶 Large Orange Diamond"), + ("🔷", "🔷 Large Blue Diamond"), + ("🔸", "🔸 Small Orange Diamond"), + ("🔹", "🔹 Small Blue Diamond"), + ("🔺", "🔺 Up-Pointing Red Triangle"), + ("🔻", "🔻 Down-Pointing Red Triangle"), + ("💠", "💠 Diamond Shape With a Dot Inside"), + ("🔘", "🔘 Radio Button"), + ("🔲", "🔲 Black Square Button"), + ("🔳", "🔳 White Square Button"), + ("🔴", "🔴 Large Red Circle"), + ("🔵", "🔵 Large Blue Circle"), + ("😀", "😀 Grinning"), + ("😗", "😗 Kissing"), + ("😙", "😙 Smooch"), + ("😑", "😑 True Neutral"), + ("😮", "😮 Stunned"), + ("😯", "😯 Hushed"), + ("😴", "😴 Sleepy"), + ("😛", "😛 Tongue"), + ("😕", "😕 Confused"), + ("😟", "😟 Worried"), + ("😦", "😦 Frowning Face With Open Mouth"), + ("😧", "😧 Anguish Face"), + ("😬", "😬 Grimace"), + ("🙂", "🙂 Slightly Smiling"), + ("🙁", "🙁 Slightly Frowning"), + ("🕵", "🕵 Spy"), + ("🗣", "🗣 Speaking Head in Silhouette"), + ("🕴", "🕴 Man in Business Suit Levitating"), + ("🖕", "🖕 Middle Finger"), + ("🖖", "🖖 Vulcan Hand"), + ("🖐", "🖐 Raised Hand With Fingers Splayed"), + ("👁", "👁 Eye"), + ("🕳", "🕳 Hole"), + ("🗯", "🗯 Right Anger Bubble"), + ("🕶", "🕶 Sunglasses"), + ("🛍", "🛍 Shopping"), + ("🐿", "🐿 Chipmunk"), + ("🕊", "🕊 Peace Dove"), + ("🕷", "🕷 Spider"), + ("🕸", "🕸 Spider Web"), + ("🏵", "🏵 Rosette"), + ("🌶", "🌶 Chilli"), + ("🍽", "🍽 Fork and Knife With Plate"), + ("🗺", "🗺 World Map"), + ("🏔", "🏔 Snow Capped Mountain"), + ("🏕", "🏕 Camping"), + ("🏖", "🏖 Beach"), + ("🏜", "🏜 Desert"), + ("🏝", "🏝 Desert Island"), + ("🏞", "🏞 National Park"), + ("🏟", "🏟 Stadium"), + ("🏛", "🏛 Architecture"), + ("🏗", "🏗 Building Construction"), + ("🏘", "🏘 House Buildings"), + ("🏙", "🏙 Cityscape"), + ("🏚", "🏚 Derelict House Building"), + ("🖼", "🖼 Frame With Picture"), + ("🛢", "🛢 Oil Drum"), + ("🛣", "🛣 Motorway"), + ("🛤", "🛤 Railway Track"), + ("🛳", "🛳 Passenger Ship"), + ("🛥", "🛥 Boat"), + ("🛩", "🛩 Airplane"), + ("🛫", "🛫 Airplane Departure"), + ("🛬", "🛬 Airplane Arriving"), + ("🛰", "🛰 Satellite"), + ("🛎", "🛎 Service Bell"), + ("🛌", "🛌 Bed"), + ("🛏", "🛏 Bed"), + ("🛋", "🛋 Couch and Lamp"), + ("🕰", "🕰 Mantelpiece"), + ("🌡", "🌡 Thermometer"), + ("🌤", "🌤 Small Cloud"), + ("🌥", "🌥 White Sun Behind Cloud"), + ("🌦", "🌦 White Sun Behind Cloud With Rain"), + ("🌧", "🌧 Cloud With Rain"), + ("🌨", "🌨 Cloud With Snow"), + ("🌩", "🌩 Lightning"), + ("🌪", "🌪 Tornado"), + ("🌫", "🌫 Fog"), + ("🌬", "🌬 Blowing"), + ("🎖", "🎖 Medal"), + ("🎗", "🎗 Ribbon"), + ("🎞", "🎞 Film"), + ("🎟", "🎟 Admission Tickets"), + ("🏷", "🏷 Label"), + ("🏌", "🏌 Golfer"), + ("🏋", "🏋 Lifting"), + ("🏎", "🏎 Racing Car"), + ("🏍", "🏍 Racing Motorcycle"), + ("🏅", "🏅 Medal"), + ("🕹", "🕹 Joystick"), + ("⏸", "⏸ Double Vertical Bar"), + ("⏹", "⏹ Black Square for Stop"), + ("⏺", "⏺ Black Circle for Record"), + ("🎙", "🎙 Microphone"), + ("🎚", "🎚 Level Slider"), + ("🎛", "🎛 Control Knobs"), + ("🖥", "🖥 Desktop"), + ("🖨", "🖨 Printer"), + ("🖱", "🖱 Three Button Mouse"), + ("🖲", "🖲 Trackball"), + ("📽", "📽 Film Projector"), + ("📸", "📸 Camera With Flash"), + ("🕯", "🕯 Candle"), + ("🗞", "🗞 Newspaper"), + ("🗳", "🗳 Ballot Box With Ballot"), + ("🖋", "🖋 Fancy Pen"), + ("🖊", "🖊 Lower Left Ballpoint Pen"), + ("🖌", "🖌 Lower Left Paintbrush"), + ("🖍", "🖍 Lower Left Crayon"), + ("🗂", "🗂 Card Index Dividers"), + ("🗒", "🗒 Spiral Note Pad"), + ("🗓", "🗓 Spiral Calendar Pad"), + ("🖇", "🖇 Linked Paperclips"), + ("🗃", "🗃 Card File Box"), + ("🗄", "🗄 File Cabinet"), + ("🗑", "🗑 Wastebasket"), + ("🗝", "🗝 Old Key"), + ("🛠", "🛠 Tools"), + ("🗜", "🗜 Compression"), + ("🗡", "🗡 Dagger"), + ("🛡", "🛡 Shield"), + ("🏳", "🏳 White Flag"), + ("🏴", "🏴 Black Flag"), + ("🕉", "🕉 Om Symbol"), + ("🗨", "🗨 Left Speech Bubble"), + ("🤗", "🤗 Hugging"), + ("🤔", "🤔 Thinking"), + ("🙄", "🙄 Rolling Eyes"), + ("🤐", "🤐 Hushed"), + ("🤓", "🤓 Nerd"), + ("🙃", "🙃 Upside Down"), + ("🤒", "🤒 Sick"), + ("🤕", "🤕 Hurt Head"), + ("🤑", "🤑 Money"), + ("🏻", "🏻 Emoji Modifier 1-2"), + ("🏼", "🏼 Emoji Modifier 3"), + ("🏽", "🏽 Emoji Modifier 4"), + ("🏾", "🏾 Emoji Modifier 5"), + ("🏿", "🏿 Emoji Modifier 6"), + ("🤘", "🤘 Rock On"), + ("📿", "📿 Prayer Beads"), + ("🤖", "🤖 Robot"), + ("🦁", "🦁 Lion"), + ("🦄", "🦄 Unicorn"), + ("🦃", "🦃 Turkey"), + ("🦀", "🦀 Crab"), + ("🦂", "🦂 Scorpion"), + ("🧀", "🧀 Cheese"), + ("🌭", "🌭 Hot Dog"), + ("🌮", "🌮 Taco"), + ("🌯", "🌯 Burrito"), + ("🍿", "🍿 Popcorn"), + ("🍾", "🍾 Popping Cork"), + ("🏺", "🏺 Amphora"), + ("🛐", "🛐 Place of Worship"), + ("🕋", "🕋 Kaaba"), + ("🕌", "🕌 Mosque"), + ("🕍", "🕍 Synagogue"), + ("🕎", "🕎 Menorah"), + ("🏏", "🏏 Bat and Ball"), + ("🏐", "🏐 Volleyball"), + ("🏑", "🏑 Field Hockey"), + ("🏒", "🏒 Ice Hockey"), + ("🏓", "🏓 Table Tennis"), + ("🏸", "🏸 Badminton"), + ("🏹", "🏹 Archer"), + ("🤣", "🤣 ROFL Face"), + ("🤤", "🤤 Drooling"), + ("🤢", "🤢 Nauseated"), + ("🤧", "🤧 Sneezing"), + ("🤠", "🤠 Cowboy"), + ("🤡", "🤡 Clown"), + ("🤥", "🤥 Lying"), + ("🤴", "🤴 Prince"), + ("🤵", "🤵 Tuxedo Man"), + ("🤰", "🤰 Pregnant"), + ("🤶", "🤶 Mrs. Claus"), + ("🤦", "🤦 Facepalm"), + ("🤷", "🤷 Shrugging"), + ("🕺", "🕺 Man Dancing"), + ("🤺", "🤺 Fencing"), + ("🤸", "🤸 Cartwheel"), + ("🤼", "🤼 Wrestling"), + ("🤽", "🤽 Water Polo"), + ("🤾", "🤾 Handball"), + ("🤹", "🤹 Juggling"), + ("🤳", "🤳 Selfie"), + ("🤞", "🤞 Luck Hand"), + ("🤙", "🤙 Call Me Hand"), + ("🤛", "🤛 Left-Facing Fist"), + ("🤜", "🤜 Right-Facing Fist"), + ("🤚", "🤚 Raised Back of Hand"), + ("🤝", "🤝 Business Hi"), + ("🖤", "🖤 Black Heart"), + ("🦍", "🦍 Gorilla"), + ("🦊", "🦊 Fox"), + ("🦌", "🦌 Deer"), + ("🦏", "🦏 Rhinoceros"), + ("🦇", "🦇 Bat"), + ("🦅", "🦅 Eagle"), + ("🦆", "🦆 Duck"), + ("🦉", "🦉 Owl"), + ("🦎", "🦎 Lizard"), + ("🦈", "🦈 Shark"), + ("🦐", "🦐 Shrimp"), + ("🦑", "🦑 Squid"), + ("🦋", "🦋 Butterfly"), + ("🥀", "🥀 Wilted"), + ("🥝", "🥝 Kiwifruit"), + ("🥑", "🥑 Pricey Fruit"), + ("🥔", "🥔 Potato"), + ("🥕", "🥕 Carrot"), + ("🥒", "🥒 Cucumber"), + ("🥜", "🥜 Peanuts"), + ("🥐", "🥐 Croissant"), + ("🥖", "🥖 Bread Sword"), + ("🥞", "🥞 Pancakes"), + ("🥓", "🥓 Bacon"), + ("🥙", "🥙 Stuffed Flatbread"), + ("🥚", "🥚 Chicken Rock"), + ("🥘", "🥘 Shallow Pan"), + ("🥗", "🥗 Salad"), + ("🥛", "🥛 Cow Juice"), + ("🥂", "🥂 Clinking Glasses"), + ("🥃", "🥃 Tumbler"), + ("🥄", "🥄 Spoon"), + ("🛴", "🛴 Scoot Scoot"), + ("🛵", "🛵 Motor Scooter"), + ("🛑", "🛑 Stop Sign"), + ("🛶", "🛶 Canoe"), + ("🥇", "🥇 Gold Medal"), + ("🥈", "🥈 Silver Medal"), + ("🥉", "🥉 Participation"), + ("🥊", "🥊 Boxing"), + ("🥋", "🥋 Martial Arts"), + ("🥅", "🥅 Hashtag Goals"), + ("🥁", "🥁 Drum Roll"), + ("🛒", "🛒 Food Ute"), + ("🤩", "🤩 Star Struck"), + ("🤨", "🤨 Unexpected Face"), + ("🤯", "🤯 Mind Blown"), + ("🤪", "🤪 Zany Face"), + ("🤬", "🤬 Swear Face"), + ("🤮", "🤮 Vomiting"), + ("🤫", "🤫 Shushing"), + ("🤭", "🤭 Hand Over Mouth"), + ("🧐", "🧐 Monocle"), + ("🧒", "🧒 Child Face"), + ("🧑", "🧑 Adult"), + ("🧓", "🧓 Older Adult"), + ("🧕", "🧕 Headscarf"), + ("🧔", "🧔 Bearded Person"), + ("🤱", "🤱 Breast Feeding"), + ("🧙", "🧙 Mage"), + ("🧚", "🧚 Fairy"), + ("🧛", "🧛 Vampire"), + ("🧜", "🧜 Merperson"), + ("🧝", "🧝 Cosplay"), + ("🧞", "🧞 Genie"), + ("🧟", "🧟 Unalive"), + ("🧖", "🧖 Steamy Room"), + ("🧗", "🧗 Person Climbing"), + ("🧘", "🧘 Lotus Position"), + ("🤟", "🤟 Love-You Gesture"), + ("🤲", "🤲 Palms Up Together"), + ("🧠", "🧠 Big Brain"), + ("🧡", "🧡 Orange Heart"), + ("🧣", "🧣 Neck Hider"), + ("🧤", "🧤 Hand Socks"), + ("🧥", "🧥 Coat"), + ("🧦", "🧦 Feet Gloves"), + ("🧢", "🧢 Billed Cap"), + ("🦓", "🦓 Zebra"), + ("🦒", "🦒 Giraffe"), + ("🦔", "🦔 Spikehog"), + ("🦕", "🦕 Long Neck"), + ("🦖", "🦖 Big Roar"), + ("🦗", "🦗 Cricket"), + ("🥥", "🥥 Coconut"), + ("🥦", "🥦 Tiny Tree"), + ("🥨", "🥨 Twisty Bread"), + ("🥩", "🥩 Cut of Meat"), + ("🥪", "🥪 Sandwich"), + ("🥣", "🥣 Bowl With Spoon"), + ("🥫", "🥫 Canned Good"), + ("🥟", "🥟 Dumpling"), + ("🥠", "🥠 Tasty Future"), + ("🥡", "🥡 Takeout Box"), + ("🥧", "🥧 Pie"), + ("🥤", "🥤 Cup With Straw"), + ("🥢", "🥢 Chopsticks"), + ("🛸", "🛸 Alien Plane"), + ("🛷", "🛷 Sled"), + ("🥌", "🥌 Curling"), + ("🥰", "🥰 Smiling Face With 3 Hearts"), + ("🥵", "🥵 Overheated"), + ("🥶", "🥶 Freezing Face"), + ("🥴", "🥴 Woozy Face"), + ("🥳", "🥳 Party Face"), + ("🥺", "🥺 Pleading Face"), + ("🦵", "🦵 Leg"), + ("🦶", "🦶 Foot"), + ("🦷", "🦷 Tooth"), + ("🦴", "🦴 Bone"), + ("🦸", "🦸 Superhero"), + ("🦹", "🦹 Supervillain"), + ("🦝", "🦝 Trash Bandit"), + ("🦙", "🦙 Llama"), + ("🦛", "🦛 Hippopotamus"), + ("🦘", "🦘 Kangaroo"), + ("🦡", "🦡 Badger"), + ("🦢", "🦢 Swan"), + ("🦚", "🦚 Peacock"), + ("🦜", "🦜 Parrot"), + ("🦟", "🦟 Mosquito"), + ("🦠", "🦠 Microbe"), + ("🥭", "🥭 Mango"), + ("🥬", "🥬 Leafy Green"), + ("🥯", "🥯 Bagel"), + ("🧂", "🧂 Salty"), + ("🥮", "🥮 Moon Cake"), + ("🦞", "🦞 Lobster"), + ("🧁", "🧁 Cupcake"), + ("🧭", "🧭 Compass"), + ("🧱", "🧱 Brick"), + ("🛹", "🛹 Skateboard"), + ("🧳", "🧳 Baggage"), + ("🧨", "🧨 Firework"), + ("🧧", "🧧 Red Envelope"), + ("🥎", "🥎 Softball"), + ("🥏", "🥏 Throwing Disc"), + ("🥍", "🥍 Lacrosse"), + ("🧿", "🧿 Nazar Amulet"), + ("🧩", "🧩 Puzzle Piece"), + ("🧸", "🧸 Teddy Bear"), + ("🧵", "🧵 Thread"), + ("🧶", "🧶 Yarn Ball"), + ("🥽", "🥽 The Goggles"), + ("🥼", "🥼 Lab Coat"), + ("🥾", "🥾 Hiking Boot"), + ("🥿", "🥿 Flat Shoe"), + ("🧮", "🧮 Abacus"), + ("🧾", "🧾 Receipt"), + ("🧰", "🧰 Toolbox"), + ("🧲", "🧲 Magnet"), + ("🧪", "🧪 Test Tube"), + ("🧫", "🧫 Petri Dish"), + ("🧬", "🧬 DNA"), + ("🧴", "🧴 Lotion"), + ("🧷", "🧷 Safety Pin"), + ("🧹", "🧹 Broom"), + ("🧺", "🧺 Basket"), + ("🧻", "🧻 Roll of Paper"), + ("🧼", "🧼 Soap"), + ("🧽", "🧽 Fun sponge"), + ("🧯", "🧯 Anti-fire Can"), + ("🥱", "🥱 Yawning Face"), + ("🤎", "🤎 Brown Heart"), + ("🤍", "🤍 White Heart"), + ("🤏", "🤏 Pinching Hand"), + ("🦾", "🦾 Mechanical Arm"), + ("🦿", "🦿 Mechanical Leg"), + ("🦻", "🦻 Ear with Hearing Aid"), + ("🧏", "🧏 Deaf Person"), + ("🧍", "🧍 Person Standing"), + ("🧎", "🧎 Person Kneeling"), + ("🦧", "🦧 Orangutan"), + ("🦮", "🦮 Guide Dog"), + ("🦥", "🦥 Lazy Tree Dog"), + ("🦦", "🦦 Water Dog"), + ("🦨", "🦨 Stinky dog"), + ("🦩", "🦩 Pink Dog"), + ("🧄", "🧄 Garlic"), + ("🧅", "🧅 Onion"), + ("🧇", "🧇 Waffle"), + ("🧆", "🧆 Falafel"), + ("🧈", "🧈 Butter"), + ("🦪", "🦪 Oyster"), + ("🧃", "🧃 Beverage Box"), + ("🧉", "🧉 Mate"), + ("🧊", "🧊 Cold Cuboid"), + ("🛕", "🛕 Hindu Temple"), + ("🦽", "🦽 Manual Wheelchair"), + ("🦼", "🦼 Motorized Wheelchair"), + ("🛺", "🛺 Auto Rickshaw"), + ("🪂", "🪂 Parachute"), + ("🪐", "🪐 Ringed Planet"), + ("🤿", "🤿 Diving Mask"), + ("🪀", "🪀 Yo-Yo"), + ("🪁", "🪁 Kite"), + ("🦺", "🦺 Safety Vest"), + ("🥻", "🥻 Sari"), + ("🩱", "🩱 One-Piece Swimsuit"), + ("🩲", "🩲 Briefs"), + ("🩳", "🩳 Shorts"), + ("🩰", "🩰 Ballet Shoes"), + ("🪕", "🪕 Banjo"), + ("🪔", "🪔 Diya Lamp"), + ("🪓", "🪓 Axe"), + ("🦯", "🦯 White Cane"), + ("🩸", "🩸 Drop of Blood"), + ("🩹", "🩹 Adhesive Bandage"), + ("🩺", "🩺 Stethoscope"), + ("🪑", "🪑 Chair"), + ("🪒", "🪒 Razor"), + ("🟠", "🟠 Orange Circle"), + ("🟡", "🟡 Yellow Circle"), + ("🟢", "🟢 Green Circle"), + ("🟣", "🟣 Purple Circle"), + ("🟤", "🟤 Brown Circle"), + ("🟥", "🟥 Red Square"), + ("🟧", "🟧 Orange Square"), + ("🟨", "🟨 Yellow Square"), + ("🟩", "🟩 Green Square"), + ("🟦", "🟦 Blue Square"), + ("🟪", "🟪 Purple Square"), + ("🟫", "🟫 Brown Square"), + ("🥲", "🥲 Smiling Face with Tear"), + ("🥸", "🥸 Disguised Face"), + ("🤌", "🤌 Pinched Fingers"), + ("🫀", "🫀 Anatomical Heart"), + ("🫁", "🫁 Lungs"), + ("🥷", "🥷 Ninja"), + ("🫂", "🫂 People Hugging"), + ("🦬", "🦬 Bison"), + ("🦣", "🦣 Mammoth"), + ("🦫", "🦫 Beaver"), + ("🦤", "🦤 Dodo"), + ("🪶", "🪶 Feather"), + ("🦭", "🦭 Seal"), + ("🪲", "🪲 Beetle"), + ("🪳", "🪳 Cockroach"), + ("🪰", "🪰 Fly"), + ("🪱", "🪱 Worm"), + ("🪴", "🪴 Potted Plant"), + ("🫐", "🫐 Blueberries"), + ("🫒", "🫒 Olive"), + ("🫑", "🫑 Bell Pepper"), + ("🫓", "🫓 Flatbread"), + ("🫔", "🫔 Tamale"), + ("🫕", "🫕 Fondue"), + ("🫖", "🫖 Teapot"), + ("🧋", "🧋 Bubble Tea"), + ("🪨", "🪨 Rock"), + ("🪵", "🪵 Wood"), + ("🛖", "🛖 Hut"), + ("🛻", "🛻 Pickup Truck"), + ("🛼", "🛼 Roller Skate"), + ("🪄", "🪄 Magic Wand"), + ("🪅", "🪅 Piñata"), + ("🪆", "🪆 Nesting Dolls"), + ("🪡", "🪡 Sewing Needle"), + ("🪢", "🪢 Knot"), + ("🩴", "🩴 Thong Sandal"), + ("🪖", "🪖 Military Helmet"), + ("🪗", "🪗 Accordion"), + ("🪘", "🪘 Long Drum"), + ("🪙", "🪙 Coin"), + ("🪃", "🪃 Boomerang"), + ("🪚", "🪚 Carpentry Saw"), + ("🪛", "🪛 Screwdriver"), + ("🪝", "🪝 Hook"), + ("🪜", "🪜 Ladder"), + ("🛗", "🛗 Elevator"), + ("🪞", "🪞 Mirror"), + ("🪟", "🪟 Window"), + ("🪠", "🪠 Plunger"), + ("🪤", "🪤 Mouse Trap"), + ("🪣", "🪣 Bucket"), + ("🪥", "🪥 Toothbrush"), + ("🪦", "🪦 Headstone"), + ("🪧", "🪧 Placard"), + ("😶\u200d🌫", "😶\u200d🌫 Cloudy Face"), + ("😮\u200d💨", "😮\u200d💨 Hot Air"), + ("😵\u200d💫", "😵\u200d💫 Hypnotised"), + ("❤\u200d🔥", "❤\u200d🔥 Fiery Heart"), + ("❤\u200d🩹", "❤\u200d🩹 Mending Heart"), + ("🧔\u200d♂", "🧔\u200d♂ Bearded Man"), + ("🧔\u200d♀", "🧔\u200d♀ Bearded Woman"), + ("🫠", "🫠 Melting Face"), + ("🫢", "🫢 Oops Face"), + ("🫣", "🫣 Peekaboo"), + ("🫡", "🫡 Saluting Face"), + ("🫥", "🫥 Invisible Face"), + ("🫤", "🫤 Diagonal Mouth"), + ("🥹", "🥹 Grateful Face"), + ("🫱", "🫱 Rightwards Hand"), + ("🫲", "🫲 Leftwards Hand"), + ("🫳", "🫳 Palm Down Hand"), + ("🫴", "🫴 Palm Up Hand"), + ("🫰", "🫰 Love Gesture"), + ("🫵", "🫵 YOU"), + ("🫶", "🫶 Heart Hands"), + ("🫦", "🫦 Biting Lip"), + ("🫅", "🫅 Crowned"), + ("🫃", "🫃 Pregnant Man"), + ("🫄", "🫄 Pregnant Person"), + ("🧌", "🧌 Bridgekeeper"), + ("🪸", "🪸 Coral"), + ("🪷", "🪷 Lotus"), + ("🪹", "🪹 Empty Nest"), + ("🪺", "🪺 Unladen Swallow"), + ("🫘", "🫘 Beans"), + ("🫗", "🫗 Leak"), + ("🫙", "🫙 Jar"), + ("🛝", "🛝 Slide"), + ("🛞", "🛞 Wheel"), + ("🛟", "🛟 Buoy"), + ("🪩", "🪩 Mirror Ball"), + ("🪫", "🪫 Low Battery"), + ("🩼", "🩼 Crutch"), + ("🩻", "🩻 X-Ray"), + ("🫧", "🫧 Bubbles"), + ("🪬", "🪬 Hamsa"), + ("🪪", "🪪 Identification Card"), + ("🟰", "🟰 Heavy Equals Sign"), + ("\U0001fae8", "\U0001fae8 Car Sick Face"), + ("\U0001fa77", "\U0001fa77 Pink Heart"), + ("\U0001fa75", "\U0001fa75 Light Blue Heart"), + ("\U0001fa76", "\U0001fa76 Grey Heart"), + ("\U0001faf7", "\U0001faf7 No Thanks Hand"), + ("\U0001faf8", "\U0001faf8 Rightwards Pushing Hand"), + ("\U0001face", "\U0001face Moose"), + ("\U0001facf", "\U0001facf Donkey"), + ("\U0001fabd", "\U0001fabd Wing"), + ("\U0001fabf", "\U0001fabf Honking Bird"), + ("\U0001fabc", "\U0001fabc Jellyfish"), + ("\U0001fabb", "\U0001fabb Hyacinth"), + ("\U0001fada", "\U0001fada Ginger"), + ("\U0001fadb", "\U0001fadb Pea Pod"), + ("\U0001faad", "\U0001faad Folding Hand Fan"), + ("\U0001faae", "\U0001faae Hair Pick"), + ("\U0001fa87", "\U0001fa87 Maracas"), + ("\U0001fa88", "\U0001fa88 Flute"), + ("\U0001faaf", "\U0001faaf Khanda"), + ("\U0001f6dc", "\U0001f6dc Wireless"), + ("🙂\u200d↔", "🙂\u200d↔ Headshake"), + ("🙂\u200d↕", "🙂\u200d↕ Nodding Face"), + ("🚶\u200d➡", "🚶\u200d➡ Walking"), + ("🧎\u200d➡", "🧎\u200d➡ Person Kneeling"), + ("🏃\u200d➡", "🏃\u200d➡ Person Running"), + ("🐦\u200d🔥", "🐦\u200d🔥 Phoenix"), + ("🍋\u200d🟩", "🍋\u200d🟩 Lime"), + ("🍄\u200d🟫", "🍄\u200d🟫 Brown Mushroom"), + ("⛓\u200d💥", "⛓\u200d💥 Broken Chain"), + ], + default=None, + max_length=3, + null=True, + verbose_name="emoji", + ), + ), + ] diff --git a/tabbycat/participants/models.py b/tabbycat/participants/models.py index 2cf1f245a61..5d70e909dc8 100644 --- a/tabbycat/participants/models.py +++ b/tabbycat/participants/models.py @@ -10,6 +10,7 @@ from django.utils.translation import gettext_lazy as _ from utils.managers import LookupByNameFieldsMixin +from utils.models import UniqueConstraint from .emoji import EMOJI_FIELD_CHOICES @@ -50,7 +51,9 @@ class Institution(models.Model): objects = InstitutionManager() class Meta: - unique_together = [('name', 'code')] + constraints = [ + UniqueConstraint(fields=['name', 'code']), + ] ordering = ['name'] verbose_name = _("institution") verbose_name_plural = _("institutions") @@ -81,9 +84,11 @@ class SpeakerCategory(models.Model): help_text=_("If checked, this category will be included in the speaker category tabs shown to the public")) class Meta: - unique_together = [('tournament', 'seq'), ('tournament', 'slug')] + constraints = [ + UniqueConstraint(fields=['tournament', 'seq']), + UniqueConstraint(fields=['tournament', 'slug']), + ] ordering = ['tournament', 'seq'] - index_together = ['tournament', 'seq'] verbose_name = _("speaker category") verbose_name_plural = _("speaker categories") @@ -199,17 +204,17 @@ class Team(models.Model): verbose_name=_("emoji")) class Meta: - unique_together = [ + constraints = [ # Enforce for blank references also - two teams from the same # institution can't both be unlabelled. However, Django won't # enforce this for null institutions. - ('reference', 'institution', 'tournament'), + UniqueConstraint(fields=['reference', 'institution', 'tournament']), # Not enforced for blank emoji (null=True is set on emoji) - ('emoji', 'tournament'), + UniqueConstraint(fields=['emoji', 'tournament']), ] + indexes = [models.Index(fields=['tournament', 'institution', 'short_reference'])] ordering = ['tournament', 'institution', 'short_reference'] - index_together = ['tournament', 'institution', 'short_reference'] verbose_name = _("team") verbose_name_plural = _("teams") diff --git a/tabbycat/participants/templates/current_round/common.html b/tabbycat/participants/templates/current_round/common.html index 1a5be34615d..869e1d733ee 100644 --- a/tabbycat/participants/templates/current_round/common.html +++ b/tabbycat/participants/templates/current_round/common.html @@ -3,7 +3,7 @@ {# Round start time #} {% if current_round.starts_at %}
- {% blocktrans trimmed with start_time=current_round.starts_at %} + {% blocktrans trimmed with start_time=current_round.starts_at|time:'H:i' %} The round begins at {{ start_time }} {% endblocktrans %}
diff --git a/tabbycat/participants/templatetags/participant_link.py b/tabbycat/participants/templatetags/participant_link.py index f9d838fe232..29793ae1861 100644 --- a/tabbycat/participants/templatetags/participant_link.py +++ b/tabbycat/participants/templatetags/participant_link.py @@ -15,7 +15,7 @@ def team_record_link(context, team, admin, style=True): if not team or not context['tournament']: return "" - if use_team_code_names(context['tournament'], admin): + if use_team_code_names(context['tournament'], admin, user=context['user']): name = team.code_name else: name = team.short_name diff --git a/tabbycat/participants/views.py b/tabbycat/participants/views.py index b853ad9b8ac..2426bcffd05 100644 --- a/tabbycat/participants/views.py +++ b/tabbycat/participants/views.py @@ -22,6 +22,7 @@ from tournaments.mixins import (PublicTournamentPageMixin, SingleObjectFromTournamentMixin, TournamentMixin) from tournaments.models import Round +from users.permissions import Permission from utils.misc import redirect_tournament, reverse_tournament from utils.mixins import AdministratorMixin, AssistantMixin from utils.tables import TabbycatTableBuilder @@ -50,7 +51,7 @@ def get_tables(self): speakers = Speaker.objects.filter(team__tournament=self.tournament).select_related( 'team', 'team__institution').prefetch_related('team__speaker_set', 'categories') - if use_team_code_names(self.tournament, self.admin): + if use_team_code_names(self.tournament, self.admin, user=self.request.user): speakers = speakers.order_by('team__code_name') else: speakers = speakers.order_by('team__short_name') @@ -69,6 +70,7 @@ def get_context_data(self, **kwargs): class AdminParticipantsListView(AdministratorMixin, BaseParticipantsListView): + view_permission = Permission.VIEW_PARTICIPANTS template_name = 'participants_list.html' admin = True @@ -118,6 +120,7 @@ def get_table(self): class AdminInstitutionsListView(AdministratorMixin, BaseInstitutionsListView): + view_permission = Permission.VIEW_INSTITUTIONS template_name = 'participants_list.html' admin = True @@ -151,6 +154,7 @@ def get_table(self): class AdminCodeNamesListView(AdministratorMixin, BaseCodeNamesListView): template_name = 'participants_list.html' + view_permission = Permission.VIEW_DECODED_TEAMS class AssistantCodeNamesListView(AssistantMixin, BaseCodeNamesListView): @@ -192,7 +196,7 @@ def get_queryset(self): return super().get_queryset().select_related('institution__region') def use_team_code_names(self): - return use_team_code_names(self.tournament, self.admin) + return use_team_code_names(self.tournament, self.admin, user=self.request.user) @staticmethod def allocations_set(obj, admin, tournament): @@ -284,6 +288,7 @@ def get_table(self): class TeamRecordView(AdministratorMixin, BaseTeamRecordView): admin = True + view_permission = Permission.VIEW_TEAMS def get_queryset(self): return super().get_queryset().prefetch_related( @@ -295,6 +300,7 @@ def get_queryset(self): class AdjudicatorRecordView(AdministratorMixin, BaseAdjudicatorRecordView): admin = True + view_permission = Permission.VIEW_ADJUDICATORS def get_queryset(self): return super().get_queryset().prefetch_related( @@ -323,7 +329,7 @@ class EditSpeakerCategoriesView(LogActionMixin, AdministratorMixin, TournamentMi # uniqueness checks will work. Since this is a superuser form, they can # access all tournaments anyway, so tournament forgery wouldn't be a # security risk. - + view_permission = Permission.VIEW_SPEAKER_CATEGORIES template_name = 'speaker_categories_edit.html' formset_model = SpeakerCategory action_log_type = ActionLogEntry.ActionType.SPEAKER_CATEGORIES_EDIT @@ -386,6 +392,7 @@ class EditSpeakerCategoryEligibilityView(AdministratorMixin, TournamentMixin, Vu template_name = 'edit_speaker_eligibility.html' page_title = _("Speaker Category Eligibility") page_emoji = '🍯' + edit_permission = Permission.EDIT_SPEAKER_CATEGORIES def get_table(self): table = TabbycatTableBuilder(view=self, sort_key='team') diff --git a/tabbycat/printing/views.py b/tabbycat/printing/views.py index 7509d0af257..76a2fb5343d 100644 --- a/tabbycat/printing/views.py +++ b/tabbycat/printing/views.py @@ -17,6 +17,7 @@ from results.utils import side_and_position_names from tournaments.mixins import (CurrentRoundMixin, OptionalAssistantTournamentPageMixin, RoundMixin, TournamentMixin) +from users.permissions import Permission from utils.misc import reverse_tournament from utils.mixins import AdministratorMixin from venues.serializers import VenueSerializer @@ -120,7 +121,7 @@ def get_context_data(self, **kwargs): class AdminPrintFeedbackFormsView(AdministratorMixin, BasePrintFeedbackFormsView): - pass + view_permission = Permission.VIEW_DEBATE class AssistantPrintFeedbackFormsView(CurrentRoundMixin, OptionalAssistantTournamentPageMixin, BasePrintFeedbackFormsView): @@ -221,7 +222,7 @@ def get_context_data(self, **kwargs): class AdminPrintScoresheetsView(AdministratorMixin, BasePrintScoresheetsView): - pass + view_permission = Permission.VIEW_DEBATE class AssistantPrintScoresheetsView(CurrentRoundMixin, OptionalAssistantTournamentPageMixin, BasePrintScoresheetsView): @@ -229,6 +230,7 @@ class AssistantPrintScoresheetsView(CurrentRoundMixin, OptionalAssistantTourname class BasePrintableRandomisedURLs(TournamentMixin, AdministratorMixin, TemplateView): + view_permission = Permission.VIEW_PRIVATE_URLS template_name = 'randomised_url_sheets.html' diff --git a/tabbycat/privateurls/views.py b/tabbycat/privateurls/views.py index f283c4b5490..6d7469c7d88 100644 --- a/tabbycat/privateurls/views.py +++ b/tabbycat/privateurls/views.py @@ -17,6 +17,7 @@ from participants.views import BaseRecordView from tournaments.mixins import PersonalizablePublicTournamentPageMixin, SingleObjectByRandomisedUrlMixin, TournamentMixin from tournaments.models import Round +from users.permissions import Permission from utils.misc import reverse_tournament from utils.mixins import AdministratorMixin from utils.tables import TabbycatTableBuilder @@ -60,6 +61,7 @@ def get_participants_to_email(self, already_sent: bool = False) -> 'QuerySet[Per class RandomisedUrlsView(RandomisedUrlsMixin, VueTableTemplateView): + view_permission = Permission.VIEW_PRIVATE_URLS template_name = 'private_urls.html' tables_orientation = 'columns' @@ -113,6 +115,7 @@ def get_tables(self) -> List[TabbycatTableBuilder]: class GenerateRandomisedUrlsView(AdministratorMixin, TournamentMixin, PostOnlyRedirectView): tournament_redirect_pattern_name = 'privateurls-list' + edit_permission = Permission.GENERATE_PRIVATE_URLS def post(self, request: 'HttpRequest', *args, **kwargs) -> 'HttpResponseRedirect': tournament = self.tournament @@ -149,7 +152,8 @@ def post(self, request: 'HttpRequest', *args, **kwargs) -> 'HttpResponseRedirect class EmailRandomisedUrlsView(RoleColumnMixin, TournamentTemplateEmailCreateView): page_subtitle = _("Private URLs") - + view_permission = Permission.VIEW_PRIVATE_URLS_EMAIL_LIST + edit_permission = Permission.SEND_PRIVATE_URLS event = BulkNotification.EventType.URL subject_template = 'url_email_subject' message_template = 'url_email_message' diff --git a/tabbycat/results/forms.py b/tabbycat/results/forms.py index 24141775779..b644b90d2a7 100644 --- a/tabbycat/results/forms.py +++ b/tabbycat/results/forms.py @@ -1,5 +1,6 @@ import logging from itertools import product +from typing import TYPE_CHECKING from asgiref.sync import async_to_sync from channels.layers import get_channel_layer @@ -20,6 +21,9 @@ DebateResultByAdjudicator, DebateResultByAdjudicatorWithScores) from .utils import get_status_meta, side_and_position_names +if TYPE_CHECKING: + from .models import BallotSubmission + logger = logging.getLogger(__name__) @@ -97,6 +101,33 @@ class ReplyScoreField(BaseScoreField): DEFAULT_STEP_VALUE = 0.5 +def broadcast_results(ballotsub: 'BallotSubmission', debate: Debate): + t = debate.round.tournament + + # 5. Notify the Latest Results consumer (for results/overview) + if ballotsub.confirmed and debate.result_status == Debate.STATUS_CONFIRMED: + group_name = BallotResultConsumer.group_prefix + "_" + t.slug + async_to_sync(get_channel_layer().group_send)(group_name, { + "type": "send_json", + "data": ballotsub.serialize_like_actionlog, + }) + + # 6. Notify the Results Page/Ballots Status Graph + group_name = BallotStatusConsumer.group_prefix + "_" + t.slug + meta = get_status_meta(debate) + async_to_sync(get_channel_layer().group_send)(group_name, { + "type": "send_json", + "data": { + 'status': debate.result_status, + 'icon': meta[0], + 'class': meta[1], + 'sort': meta[2], + 'ballot': ballotsub.serialize(t), + 'round': debate.round_id, + }, + }) + + # ============================================================================== # Result/ballot forms # ============================================================================== @@ -180,34 +211,12 @@ def save(self): self.debate.result_status = self.cleaned_data['debate_result_status'] self.debate.save() - t = self.debate.round.tournament # Need to provide a timestamp immediately for BallotStatusConsumer # as it will broadcast before the view finishes assigning one if self.ballotsub.confirmed: self.ballotsub.confirm_timestamp = timezone.now() - # 5. Notify the Latest Results consumer (for results/overview) - if self.debate.result_status == Debate.STATUS_CONFIRMED: - group_name = BallotResultConsumer.group_prefix + "_" + t.slug - async_to_sync(get_channel_layer().group_send)(group_name, { - "type": "send_json", - "data": self.ballotsub.serialize_like_actionlog, - }) - - # 6. Notify the Results Page/Ballots Status Graph - group_name = BallotStatusConsumer.group_prefix + "_" + t.slug - meta = get_status_meta(self.debate) - async_to_sync(get_channel_layer().group_send)(group_name, { - "type": "send_json", - "data": { - 'status': self.debate.result_status, - 'icon': meta[0], - 'class': meta[1], - 'sort': meta[2], - 'ballot': self.ballotsub.serialize(t), - 'round': self.debate.round_id, - }, - }) + broadcast_results(self.ballotsub, self.debate) return self.ballotsub diff --git a/tabbycat/results/migrations/0016_rename_speakerscorebyadj_ballot_submission_debate_adjudicator_results_spe_ballot__667598_idx_and_mor.py b/tabbycat/results/migrations/0016_rename_speakerscorebyadj_ballot_submission_debate_adjudicator_results_spe_ballot__667598_idx_and_mor.py new file mode 100644 index 00000000000..08a2a41b815 --- /dev/null +++ b/tabbycat/results/migrations/0016_rename_speakerscorebyadj_ballot_submission_debate_adjudicator_results_spe_ballot__667598_idx_and_mor.py @@ -0,0 +1,70 @@ +# Generated by Django 5.0.4 on 2024-05-04 13:21 + +import utils.models +from django.conf import settings +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('adjallocation', '0010_alter_adjudicatoradjudicatorconflict_unique_together_and_more'), + ('draw', '0009_alter_teamsideallocation_unique_together_and_more'), + ('motions', '0006_alter_debateteammotionpreference_unique_together_and_more'), + ('participants', '0022_rename_team_tournament_institution_short_reference_participant_tournam_160efa_idx_and_more'), + ('results', '0015_alter_ballotsubmission_submitter_type'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.RenameIndex( + model_name='speakerscorebyadj', + new_name='results_spe_ballot__667598_idx', + old_fields=('ballot_submission', 'debate_adjudicator'), + ), + migrations.RenameIndex( + model_name='teamscorebyadj', + new_name='results_tea_ballot__a296a6_idx', + old_fields=('ballot_submission', 'debate_adjudicator'), + ), + migrations.AlterUniqueTogether( + name='ballotsubmission', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='speakerscore', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='speakerscorebyadj', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='teamscore', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='teamscorebyadj', + unique_together=set(), + ), + migrations.AddConstraint( + model_name='ballotsubmission', + constraint=utils.models.UniqueConstraint(fields=('debate', 'version'), name='results_ballotsubmission_debate__version_uniq'), + ), + migrations.AddConstraint( + model_name='speakerscore', + constraint=utils.models.UniqueConstraint(fields=('debate_team', 'position', 'ballot_submission'), name='results_speakerscore_debate_team__position__ballot_submission_uniq'), + ), + migrations.AddConstraint( + model_name='speakerscorebyadj', + constraint=utils.models.UniqueConstraint(fields=('debate_adjudicator', 'debate_team', 'position', 'ballot_submission'), name='results_speakerscorebyadj_debate_adjudicator__debate_team__position__ballot_submission_uniq'), + ), + migrations.AddConstraint( + model_name='teamscore', + constraint=utils.models.UniqueConstraint(fields=('debate_team', 'ballot_submission'), name='results_teamscore_debate_team__ballot_submission_uniq'), + ), + migrations.AddConstraint( + model_name='teamscorebyadj', + constraint=utils.models.UniqueConstraint(fields=('debate_adjudicator', 'debate_team', 'ballot_submission'), name='results_teamscorebyadj_debate_adjudicator__debate_team__ballot_submission_uniq'), + ), + ] diff --git a/tabbycat/results/models.py b/tabbycat/results/models.py index 92032fd5640..a11ce1a23e4 100644 --- a/tabbycat/results/models.py +++ b/tabbycat/results/models.py @@ -9,6 +9,7 @@ from motions.models import RoundMotion from utils.misc import badge_datetime_format, reverse_tournament +from utils.models import UniqueConstraint from .result import DebateResult from .utils import readable_ballotsub_result @@ -67,7 +68,7 @@ class Meta: @property def _unique_filter_args(self): - return dict((arg, getattr(self, arg)) for arg in self._meta.unique_together[0] + return dict((arg, getattr(self, arg)) for arg in self._meta.constraints[0].fields if arg != 'version') def _unique_unconfirm_args(self): @@ -117,7 +118,7 @@ class BallotSubmission(Submission): "when individual adjudicator ballots are enabled.")) class Meta: - unique_together = [('debate', 'version')] + constraints = [UniqueConstraint(fields=['debate', 'version'])] verbose_name = _("ballot submission") verbose_name_plural = _("ballot submissions") @@ -235,8 +236,10 @@ class TeamScoreByAdj(models.Model): verbose_name=_("score")) class Meta: - unique_together = [('debate_adjudicator', 'debate_team', 'ballot_submission')] - index_together = ['ballot_submission', 'debate_adjudicator'] + constraints = [ + UniqueConstraint(fields=['debate_adjudicator', 'debate_team', 'ballot_submission']), + ] + indexes = [models.Index(fields=['ballot_submission', 'debate_adjudicator'])] verbose_name = _("team score by adjudicator") verbose_name_plural = _("team scores by adjudicator") @@ -269,9 +272,10 @@ class SpeakerScoreByAdj(models.Model): position = models.IntegerField(verbose_name=_("position")) class Meta: - unique_together = [('debate_adjudicator', 'debate_team', 'position', - 'ballot_submission')] - index_together = ['ballot_submission', 'debate_adjudicator'] + constraints = [ + UniqueConstraint(fields=['debate_adjudicator', 'debate_team', 'position', 'ballot_submission']), + ] + indexes = [models.Index(fields=['ballot_submission', 'debate_adjudicator'])] verbose_name = _("speaker score by adjudicator") verbose_name_plural = _("speaker scores by adjudicator") @@ -317,7 +321,7 @@ class TeamScore(models.Model): has_ghost = models.BooleanField(null=True, blank=True, verbose_name=_("has ghost score")) class Meta: - unique_together = [('debate_team', 'ballot_submission')] + constraints = [UniqueConstraint(fields=['debate_team', 'ballot_submission'])] verbose_name = _("team score") verbose_name_plural = _("team scores") @@ -360,7 +364,7 @@ class SpeakerScore(models.Model): objects = SpeakerScoreManager() class Meta: - unique_together = [('debate_team', 'position', 'ballot_submission')] + constraints = [UniqueConstraint(fields=['debate_team', 'position', 'ballot_submission'])] verbose_name = _("speaker score") verbose_name_plural = _("speaker scores") diff --git a/tabbycat/results/views.py b/tabbycat/results/views.py index 57b7400f156..9e3815302da 100644 --- a/tabbycat/results/views.py +++ b/tabbycat/results/views.py @@ -6,7 +6,8 @@ from django.contrib import messages from django.core.exceptions import ValidationError from django.db import ProgrammingError -from django.db.models import Count, Max, Q +from django.db.models import Count, Max, Q, Window +from django.db.models.functions import Rank from django.http import HttpResponseRedirect from django.shortcuts import render from django.utils import timezone @@ -21,7 +22,7 @@ from draw.models import Debate from draw.prefetch import populate_opponents from draw.types import DebateSide -from motions.models import RoundMotion +from motions.models import DebateTeamMotionPreference, RoundMotion from motions.utils import merge_motion_vetos, merge_motions from notifications.models import BulkNotification from options.utils import use_team_code_names, use_team_code_names_data_entry @@ -31,14 +32,15 @@ RoundMixin, SingleObjectByRandomisedUrlMixin, SingleObjectFromTournamentMixin, TournamentMixin) from tournaments.models import Round +from users.permissions import Permission from utils.misc import get_ip_address, reverse_round, reverse_tournament from utils.mixins import AdministratorMixin, AssistantMixin from utils.tables import TabbycatTableBuilder from utils.views import PostOnlyRedirectView, VueTableTemplateView from .consumers import BallotStatusConsumer -from .forms import (PerAdjudicatorBallotSetForm, PerAdjudicatorEliminationBallotSetForm, SingleBallotSetForm, - SingleEliminationBallotSetForm) +from .forms import (broadcast_results, PerAdjudicatorBallotSetForm, PerAdjudicatorEliminationBallotSetForm, + SingleBallotSetForm, SingleEliminationBallotSetForm) from .models import BallotSubmission, TeamScore from .prefetch import populate_confirmed_ballots, populate_results from .result import DebateResult, get_class_name @@ -113,6 +115,7 @@ class AssistantResultsEntryView(AssistantMixin, CurrentRoundMixin, BaseResultsEn class AdminResultsEntryForRoundView(AdministratorMixin, BaseResultsEntryForRoundView): template_name = 'admin_results.html' + view_permission = Permission.VIEW_RESULTS def get_context_data(self, **kwargs): # Stopgap to warn user about potential database inconsistency, when @@ -324,6 +327,9 @@ def should_send_email_receipts(self): return self.tournament.pref('enable_ballot_receipts') and not (self.debate.round.stage == Round.Stage.ELIMINATION and self.tournament.pref('teams_in_debate') == 4) + def postprocess_result(self): + pass + def matchup_description(self): """This is primarily shown in messages, some of which are public. This is slightly different to its use in templates, but should match given @@ -355,6 +361,8 @@ def form_valid(self, form): self.add_success_message() self.round = form.debate.round # for LogActionMixin + self.postprocess_result() + return super().form_valid(form) def populate_objects(self, prefill=True): @@ -379,6 +387,8 @@ def post(self, request, *args, **kwargs): class AdministratorBallotSetMixin(AdministratorMixin): template_name = 'ballot_entry.html' + view_permission = Permission.VIEW_BALLOTSUBMISSIONS + edit_permission = Permission.ADD_BALLOTSUBMISSIONS tabroom = True def get_success_url(self): @@ -387,6 +397,8 @@ def get_success_url(self): class OldAdministratorBallotSetMixin(AdministratorMixin): template_name = 'enter_results.html' + view_permission = Permission.VIEW_BALLOTSUBMISSIONS + edit_permission = Permission.ADD_BALLOTSUBMISSIONS tabroom = True def get_success_url(self): @@ -539,6 +551,7 @@ def get_form_kwargs(self): kwargs['password'] = True kwargs['result'] = self.result kwargs['vetos'] = self.vetos + kwargs['filled'] = self.prefilled return kwargs def add_success_message(self): @@ -597,10 +610,10 @@ def populate_objects(self, prefill=True): "so you can't enter results for it. Please contact a tab room official.")) def set_speakers(self, former_ballot): - if former_ballot.speakerscore_set.exists(): - for ss in former_ballot.speakerscore_set.all(): - self.result.set_speaker(ss.debate_team.side, ss.position, ss.speaker) - self.result.set_ghost(ss.debate_team.side, ss.position, ss.ghost) + for ss in former_ballot.speakerscore_set.all(): + self.result.set_speaker(ss.debate_team.side, ss.position, ss.speaker) + self.result.set_ghost(ss.debate_team.side, ss.position, ss.ghost) + else: self.prefilled = True def set_motions(self, former_ballot): @@ -609,7 +622,7 @@ def set_motions(self, former_ballot): self.prefilled = True if self.tournament.pref('motion_vetoes_enabled'): self.vetos = {} - for dtmp in former_ballot.debateteammotionpreference_set.all(): + for dtmp in former_ballot.debateteammotionpreference_set.filter(preference=3): self.vetos[dtmp.debate_team.side] = dtmp self.vetos[dtmp.debate_team.side]._roundmotion = self.round_motions[dtmp.motion_id] self.prefilled = True @@ -632,6 +645,51 @@ def get_all_ballotsubs(self): return q.filter(participant_submitter=self.ballotsub.participant_submitter) return q + def postprocess_result(self): + if self.ballotsub.single_adj and self.tournament.pref('disable_ballot_confirms'): + merged_bs = BallotSubmission( + debate=self.debate, + submitter=None, + submitter_type=BallotSubmission.Submitter.AUTOMATION, + ip_address=get_ip_address(self.request), + confirmed=True, + confirm_timestamp=timezone.now(), + ) + bses = BallotSubmission.objects.filter( + debate=self.debate, participant_submitter__isnull=False, discarded=False, single_adj=True, + ).select_related('participant_submitter').annotate(ordering=Window(Rank(), partition_by="participant_submitter", order_by="-version")).filter(ordering=1) + if len(bses) != DebateAdjudicator.objects.filter(debate=self.debate).exclude(type=DebateAdjudicator.TYPE_TRAINEE).count(): + return + populate_results(bses, self.tournament) + + # Handle result conflicts + merged_result = DebateResult(merged_bs, tournament=self.tournament) + errors = merged_result.populate_from_merge(*[b.result for b in bses]) + + if len(errors) == 0: + has_errors = False + merged_bs.save() + merged_result.save() + + bs_motions = BallotSubmission.objects.filter( + id__in=[b.id for b in bses], motion__isnull=False, + ).prefetch_related('debateteammotionpreference_set__debate_team') + try: + merge_motions(merged_bs, bs_motions) + merged_bs.save() + except ValidationError: + has_errors = True + + try: + vetos = merge_motion_vetos(merged_bs, bs_motions) + DebateTeamMotionPreference.objects.bulk_create(list(vetos.values())) + except ValidationError: + has_errors = True + + self.debate.result_status = Debate.STATUS_POSTPONED if has_errors else Debate.STATUS_CONFIRMED + self.debate.save() + broadcast_results(merged_bs, self.debate) + class OldPublicNewBallotSetByIdUrlView(SingleObjectFromTournamentMixin, BasePublicNewBallotSetView): model = Adjudicator @@ -873,7 +931,7 @@ def get_form_kwargs(self): def get_form(self): form = super().get_form() for error in self.errors: - msg, t, side, pos, values = error.args + msg, t, side, pos = error.args if t == 'speaker': field = form._fieldname_speaker(side, pos) elif t == 'ghost': @@ -894,7 +952,7 @@ def populate_objects(self, prefill=True): bses = BallotSubmission.objects.filter( debate=self.debate, participant_submitter__isnull=False, discarded=False, single_adj=True, - ).distinct('participant_submitter').select_related('participant_submitter').order_by('participant_submitter', '-version') + ).annotate(ordering=Window(Rank(), partition_by="participant_submitter", order_by="-version")).filter(ordering=1).select_related('participant_submitter') populate_results(bses, self.tournament) self.merged_ballots = bses diff --git a/tabbycat/settings/core.py b/tabbycat/settings/core.py index a7ccbec4926..2f691567e48 100644 --- a/tabbycat/settings/core.py +++ b/tabbycat/settings/core.py @@ -32,7 +32,6 @@ USE_I18N = True USE_TZ = True -USE_L10N = True LANGUAGE_CODE = 'en' TIME_ZONE = os.environ.get('TIME_ZONE', 'Australia/Melbourne') @@ -231,7 +230,14 @@ 'django.contrib.staticfiles.finders.AppDirectoriesFinder', ) -STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' +STORAGES = { + "default": { + "BACKEND": "django.core.files.storage.FileSystemStorage", + }, + "staticfiles": { + "BACKEND": 'whitenoise.storage.CompressedManifestStaticFilesStorage', + }, +} # ============================================================================== # Logging diff --git a/tabbycat/standings/teams.py b/tabbycat/standings/teams.py index 019b65287c5..5958e5eea46 100644 --- a/tabbycat/standings/teams.py +++ b/tabbycat/standings/teams.py @@ -190,7 +190,7 @@ def annotate(self, queryset, standings, round=None): filter=opponents_filter) logger.info("Opponents annotation: %s", str(opponents_annotation)) teams_with_opponents = queryset.model.objects.annotate(opponent_ids=opponents_annotation) - opponents_by_team = {team.id: team.opponent_ids for team in teams_with_opponents} + opponents_by_team = {team.id: team.opponent_ids or [] for team in teams_with_opponents} opp_metric_queryset = self.opponent_annotator().get_annotated_queryset( queryset[0].tournament.team_set.all(), round) diff --git a/tabbycat/standings/views.py b/tabbycat/standings/views.py index 4321d434e83..2e16eb01333 100644 --- a/tabbycat/standings/views.py +++ b/tabbycat/standings/views.py @@ -18,6 +18,7 @@ from results.models import SpeakerScore, TeamScore from tournaments.mixins import PublicTournamentPageMixin, RoundMixin, SingleObjectFromTournamentMixin, TournamentMixin from tournaments.models import Round +from users.permissions import Permission from utils.misc import reverse_tournament from utils.mixins import AdministratorMixin from utils.tables import TabbycatTableBuilder @@ -36,6 +37,7 @@ class StandingsIndexView(AdministratorMixin, RoundMixin, TemplateView): template_name = 'standings_index.html' + view_permission = Permission.VIEW_STANDINGS_OVERVIEW def get_context_data(self, **kwargs): speaks = SpeakerScore.objects.filter( @@ -321,6 +323,7 @@ def add_round_results(self, standings, rounds): class SpeakerStandingsView(AdministratorMixin, BaseSubstantiveSpeakerStandingsView): template_name = 'speaker_standings.html' # add info alerts + view_permission = Permission.VIEW_SPEAKERSSTANDINGS class PublicSpeakerTabView(PublicTabMixin, BaseSubstantiveSpeakerStandingsView): @@ -347,7 +350,7 @@ def get(self, request, *args, **kwargs): class SpeakerCategoryStandingsView(AdministratorMixin, BaseSpeakerCategoryStandingsView): - pass + view_permission = Permission.VIEW_SPEAKERSSTANDINGS class PublicSpeakerCategoryTabView(PublicTabMixin, BaseSpeakerCategoryStandingsView): @@ -403,6 +406,7 @@ def populate_result_missing(self, standings): class ReplyStandingsView(AdministratorMixin, BaseReplyStandingsView): template_name = 'reply_standings.html' # add an info alert + view_permission = Permission.VIEW_REPLIESSTANDINGS class PublicReplyTabView(PublicTabMixin, BaseReplyStandingsView): @@ -486,6 +490,7 @@ class TeamStandingsView(AdministratorMixin, BaseTeamStandingsView): """Superuser team standings view.""" template_name = 'team_standings.html' # add info alerts rankings = ('rank',) + view_permission = Permission.VIEW_TEAMSTANDINGS def show_ballots(self): return True @@ -526,6 +531,7 @@ def get(self, request, *args, **kwargs): class BreakCategoryStandingsView(AdministratorMixin, BaseBreakCategoryStandingsView): """Superuser team standings view for a break category.""" rankings = ('rank',) + view_permission = Permission.VIEW_TEAMSTANDINGS def show_ballots(self): return True @@ -621,6 +627,7 @@ def get_context_data(self, **kwargs): class DiversityStandingsView(AdministratorMixin, BaseDiversityStandingsView): for_public = False + view_permission = Permission.VIEW_DIVERSITYTAB class PublicDiversityStandingsView(PublicTournamentPageMixin, BaseDiversityStandingsView): diff --git a/tabbycat/templates/components/formset.html b/tabbycat/templates/components/formset.html index d5169de47ad..0c19b37543e 100644 --- a/tabbycat/templates/components/formset.html +++ b/tabbycat/templates/components/formset.html @@ -17,10 +17,10 @@
- +
- +
diff --git a/tabbycat/tournaments/forms.py b/tabbycat/tournaments/forms.py index d8798e85aa1..223f50b27b5 100644 --- a/tabbycat/tournaments/forms.py +++ b/tabbycat/tournaments/forms.py @@ -12,6 +12,8 @@ from breakqual.utils import auto_make_break_rounds from options.preferences import TournamentStaff from options.presets import all_presets, data_entry_presets_for_form, presets_for_form, PrivateURLs, public_presets_for_form, PublicForms, PublicInformation +from users.groups import all_groups +from users.models import Group from .models import Round, Tournament from .signals import update_tournament_cache @@ -49,6 +51,11 @@ def add_default_feedback_questions(tournament): answer_type=AdjudicatorFeedbackQuestion.ANSWER_TYPE_LONGTEXT) comments.save() + @staticmethod + def add_default_permission_groups(tournament: Tournament): + for group in all_groups(): + Group.objects.create(name=group.name, permissions=group.permissions, tournament=tournament) + def save(self): tournament = super(TournamentStartForm, self).save() auto_make_rounds(tournament, self.cleaned_data["num_prelim_rounds"]) @@ -68,6 +75,7 @@ def save(self): open_break.full_clean() open_break.save() + self.add_default_permission_groups(tournament) self.add_default_feedback_questions(tournament) tournament.current_round = tournament.round_set.order_by('seq').first() tournament.save() diff --git a/tabbycat/tournaments/migrations/0011_alter_round_starts_at.py b/tabbycat/tournaments/migrations/0011_alter_round_starts_at.py new file mode 100644 index 00000000000..0e7e04f35bd --- /dev/null +++ b/tabbycat/tournaments/migrations/0011_alter_round_starts_at.py @@ -0,0 +1,31 @@ +# Generated by Django 4.2.5 on 2023-11-18 16:17 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("tournaments", "0010_alter_round_draw_type"), + ] + + operations = [ + migrations.AddField( + model_name="round", + name="starts_at_", + field=models.DateTimeField(blank=True, null=True, verbose_name="starts at"), + ), + migrations.RunSQL( + "WITH first_ballot AS (SELECT DISTINCT ON (round_id) d.round_id, bs.timestamp FROM results_ballotsubmission bs INNER JOIN draw_debate d ON d.id=bs.debate_id ORDER BY round_id, timestamp) UPDATE tournaments_round r SET starts_at_=fb.timestamp::date + r.starts_at AT TIME ZONE '" + settings.TIME_ZONE + "' FROM first_ballot fb WHERE r.id=fb.round_id;", + migrations.RunSQL.noop, + ), + migrations.RemoveField( + model_name="round", + name="starts_at", + ), + migrations.RenameField( + model_name="round", + old_name="starts_at_", + new_name="starts_at", + ), + ] diff --git a/tabbycat/tournaments/migrations/0012_alter_round_unique_together_and_more.py b/tabbycat/tournaments/migrations/0012_alter_round_unique_together_and_more.py new file mode 100644 index 00000000000..28843826ede --- /dev/null +++ b/tabbycat/tournaments/migrations/0012_alter_round_unique_together_and_more.py @@ -0,0 +1,27 @@ +# Generated by Django 5.0.4 on 2024-05-04 13:21 + +import utils.models +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('breakqual', '0006_alter_breakcategory_unique_together_and_more'), + ('tournaments', '0011_alter_round_starts_at'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='round', + unique_together=set(), + ), + migrations.AlterIndexTogether( + name='round', + index_together=set(), + ), + migrations.AddConstraint( + model_name='round', + constraint=utils.models.UniqueConstraint(fields=('tournament', 'seq'), name='tournam_round_tournament__seq_uniq'), + ), + ] diff --git a/tabbycat/tournaments/models.py b/tabbycat/tournaments/models.py index 8c42b5d1368..a17747f5503 100644 --- a/tabbycat/tournaments/models.py +++ b/tabbycat/tournaments/models.py @@ -11,6 +11,7 @@ from draw.types import DebateSide from participants.models import Person from utils.managers import LookupByNameFieldsMixin +from utils.models import UniqueConstraint logger = logging.getLogger(__name__) @@ -332,18 +333,17 @@ class Status(models.TextChoices): motions_released = models.BooleanField(default=False, verbose_name=_("motions released"), help_text=_("Whether motions will appear on the public website, assuming that feature is turned on")) - starts_at = models.TimeField(verbose_name=_("starts at"), blank=True, null=True) + starts_at = models.DateTimeField(verbose_name=_("starts at"), blank=True, null=True) weight = models.IntegerField(default=1, verbose_name=_("weight"), help_text=_("A factor for the points received in the round. For example, if 2, all points are doubled.")) class Meta: + constraints = [UniqueConstraint(fields=['tournament', 'seq'])] verbose_name = _('round') verbose_name_plural = _('rounds') - unique_together = [('tournament', 'seq')] ordering = ['tournament', 'seq'] - index_together = ['tournament', 'seq'] def __str__(self): return "[%s] %s" % (self.tournament, self.name) diff --git a/tabbycat/tournaments/templates/TournamentOverviewContainer.vue b/tabbycat/tournaments/templates/TournamentOverviewContainer.vue index 449b775cece..307bdc11313 100644 --- a/tabbycat/tournaments/templates/TournamentOverviewContainer.vue +++ b/tabbycat/tournaments/templates/TournamentOverviewContainer.vue @@ -9,11 +9,12 @@
@@ -27,12 +28,13 @@
-